abstract-geometry-tool
v1.2.0
Published
A lightweight TypeScript library for 2D geometry operations with rotation and reflection support
Maintainers
Readme
Abstract Geometry Tool
A lightweight TypeScript library for 2D geometry operations with rotation and reflection support. Perfect for graphics applications, CAD tools, and geometric transformations.
Why Use Abstract Geometry Tool?
- 🚀 Zero dependencies — lightweight and fast
- 🔒 Fully immutable — predictable and safe transformations
- 📐 Comprehensive — points, segments, polylines, rectangles, and groups
- 🔄 Rotation support — all shapes can be rotated around any point
- 🪞 Reflection support — reflect over arbitrary axes or use convenient horizontal/vertical helpers
- 📦 TypeScript first — complete type safety and IntelliSense support
- ✅ Battle-tested — 100% code coverage with 161 tests
- 📚 Well documented — clear API with examples
Features
- Point — immutable 2D point with rotation and reflection operations
- Segment — line segment defined by two points with length and center calculations
- Polyline — sequence of connected points with transformations
- Rectangle — rectangle with position, size, and rotation support
- ShapeGroup — container for multiple shapes with group transformations
- BoundingBox — axis-aligned bounding box with rotation support
- Shape — common interface for all transformable shapes
- Equatable / Cloneable — cross-cutting contracts for value-object equality and independent copying
- Fully immutable — all operations return new instances
- TypeScript support with full type definitions
- Comprehensive test coverage (161 tests, 100% code coverage)
Installation
npm install abstract-geometry-toolCompatibility
- Node.js: >= 18.0.0
- TypeScript: >= 4.5
- Module systems: CommonJS (ESM support planned)
- Browsers: All modern browsers (when bundled)
Usage
Basic Shapes
import { Point, Segment, Polyline, Rectangle, ShapeGroup } from "abstract-geometry-tool";
const point = new Point(10, 20);
console.log(point.x(), point.y());
const segment = new Segment(new Point(0, 0), new Point(10, 0));
console.log(segment.length());
console.log(segment.center());
const polyline = new Polyline([
new Point(0, 0),
new Point(10, 10),
new Point(20, 0)
]);
console.log(polyline.boundingBox());Rectangle Operations
const rect = new Rectangle(new Point(0, 0), new Point(100, 50));
const center = rect.center();
console.log(center.x(), center.y());
const rotated = rect.rotated(45);
const rotatedAroundOrigin = rect.rotated(90, new Point(0, 0));Rectangle AABB Operations
const a = new Rectangle(new Point(0, 0), new Point(10, 10));
const b = new Rectangle(new Point(5, 5), new Point(20, 20));
const bounding = a.union(b);
const overlap = a.intersected(b);
if (overlap !== undefined) {
console.log(overlap.width(), overlap.height());
}
const padded = a.grow(5);
const inset = a.grow(-2);
const copy = a.clone();
console.log(a.isEqualWith(copy));Reflections
const rect = new Rectangle(new Point(0, 0), new Point(100, 50));
const axis = new Segment(new Point(0, 0), new Point(100, 0));
const reflected = rect.reflected(axis);
const flippedH = rect.reflectedByHorizontal();
const flippedV = rect.reflectedByVertical();
const flippedHOrigin = rect.reflectedByHorizontal(false);
const flippedVOrigin = rect.reflectedByVertical(false);Working with Groups
const rect = new Rectangle(new Point(0, 0), new Point(50, 50));
const polyline = new Polyline([
new Point(60, 0),
new Point(70, 10),
new Point(80, 0)
]);
const group = new ShapeGroup([rect, polyline]);
const rotatedGroup = group.rotated(180);
const reflectedGroup = group.reflected(axis);
const box = group.boundingBox();
console.log(box.topLeft(), box.width(), box.height());Point Transformations
const point = new Point(10, 0);
const rotated = point.rotated(90, new Point(0, 0));
console.log(rotated.x(), rotated.y());
const reflected = point.reflected(new Point(0, 0), new Point(1, 1));
console.log(reflected.x(), reflected.y());Using Utility Functions
import { degreesToRadians, radiansToDegrees } from "abstract-geometry-tool";
const radians = degreesToRadians(90);
const degrees = radiansToDegrees(Math.PI / 2);
console.log(radians);
console.log(degrees);API
Point
new Point(x, y)— create a pointx(),y()— get coordinatesrotated(angleDegrees, center)— rotate around center (degrees)reflected(axisStart, axisEnd)— reflect over a line defined by two pointsisEqualWith(other)— exact coordinate equality (===)clone()— return a newPointwith the same coordinates
Segment
new Segment(start, end)— create a segment from two pointsstart(),end()— get endpointslength()— get segment lengthcenter()— get center point (midpoint)boundingBox()— get bounding boxrotated(angleDegrees, center?)— rotate around center (degrees)reflected(axis)— reflect over a line segmentreflectedByHorizontal(centered?)— reflect over horizontal axis (y=0 if centered=false)reflectedByVertical(centered?)— reflect over vertical axis (x=0 if centered=false)
Polyline
new Polyline(points)— create from array of pointspoints()— get all pointscenter()— get center point (midpoint between first and last point)boundingBox()— get bounding boxrotated(angleDegrees, center?)— rotate all points around center (degrees)reflected(axis)— reflect all points over a line segmentreflectedByHorizontal(centered?)— reflect over horizontal axis (y=0 if centered=false)reflectedByVertical(centered?)— reflect over vertical axis (x=0 if centered=false)
Rectangle
new Rectangle(topLeft, bottomRight, rotation?)— create from corners with optional rotationRectangle.fromTopLeftPointAndSize(topLeft, width, height, rotation?)— create from position and sizetopLeft(),bottomRight()— get corner pointscenter()— get center pointwidth(),height()— get dimensionsboundingBox()— get bounding box with rotationrotated(angleDegrees, center?)— rotate by angle in degreesreflected(axis)— reflect over a line segmentreflectedByHorizontal(centered?)— reflect over horizontal axis (y=0 if centered=false)reflectedByVertical(centered?)— reflect over vertical axis (x=0 if centered=false)union(other)— axis-aligned bounding rectangle covering both rectangles; result hasrotation = 0intersected(other)— axis-aligned intersection of the two rectangles, orundefinedwhen they do not overlap (touching sides count as no overlap); result hasrotation = 0grow(amount)— expand (or shrink, with a negative amount) the rectangle byamounton every side while keeping the center and rotation; no validation — an overly negative amount yields an inverted rectangleisEqualWith(other)— exact equality across corners and rotationclone()— return an independentRectanglewith freshly cloned corner points
ShapeGroup
new ShapeGroup(shapes)— create from array of shapesshapes()— get all shapes (returns a copy)center()— get center point of bounding boxboundingBox()— get combined bounding box of all shapes (handles rotation)rotated(angleDegrees, center?)— rotate all shapes around group center (degrees)reflected(axis)— reflect all shapes over a line segmentreflectedByHorizontal(centered?)— reflect over horizontal axisreflectedByVertical(centered?)— reflect over vertical axis
BoundingBox
new BoundingBox(topLeft, width, height, rotation?)— create bounding box with optional rotationtopLeft()— get top-left cornerwidth(),height()— get dimensionsrotation()— get rotation angle in degreescenter()— get center point
Shape (interface)
All shapes implement this interface:
rotated(angleDegrees, center?)— rotate shape (degrees)reflected(axis)— reflect shape over a linereflectedByHorizontal(centered?)— reflect over horizontal axisreflectedByVertical(centered?)— reflect over vertical axisboundingBox()— get bounding box
Equatable<T> (interface)
isEqualWith(other: T): boolean— value-object equality check
Cloneable<T> (interface)
clone(): T— return an independent copy of the object
Currently implemented by Point and Rectangle; other shapes will adopt these contracts in upcoming releases.
Utility Functions
degreesToRadians(degrees)— convert degrees to radiansradiansToDegrees(radians)— convert radians to degrees
Development
Setup
npm installRunning Tests
npm test
npm run test:coverageBuilding
npm run buildLinting and Formatting
npm run lint
npm run lint:fix
npm run formatReal-World Examples
Building a CAD Tool
import { Rectangle, ShapeGroup, Point } from "abstract-geometry-tool";
const wall = new Rectangle(new Point(0, 0), new Point(100, 10));
const door = new Rectangle(new Point(30, 0), new Point(50, 10));
const building = new ShapeGroup([wall, door]);
const rotatedBuilding = building.rotated(45);Graphics Transformation Pipeline
import { Polyline, Point } from "abstract-geometry-tool";
const path = new Polyline([
new Point(0, 0),
new Point(50, 25),
new Point(100, 0)
]);
const mirrored = path.reflectedByVertical();
const rotated = mirrored.rotated(180);
const final = rotated.reflectedByHorizontal(false);Coordinate System Transformations
import { Point, degreesToRadians } from "abstract-geometry-tool";
const screenPoint = new Point(100, 200);
const worldCenter = new Point(0, 0);
const rotatedToWorld = screenPoint.rotated(90, worldCenter);Key Concepts
Immutability
All shapes are immutable. Transformation methods return new instances rather than modifying the original:
const rect = new Rectangle(new Point(0, 0), new Point(10, 10));
const rotated = rect.rotated(45);Rotation
All rotation methods use degrees for consistency:
Point.rotated(angleDegrees, center)— rotate point by degreesSegment.rotated(angleDegrees, center?)— rotate segment by degreesPolyline.rotated(angleDegrees, center?)— rotate polyline by degreesRectangle.rotated(angleDegrees, center?)— rotate rectangle by degreesShapeGroup.rotated(angleDegrees, center?)— rotate all shapes by degrees
If you need to work with radians, use the provided utility functions:
import { degreesToRadians, radiansToDegrees } from "abstract-geometry-tool";
const angleInDegrees = radiansToDegrees(Math.PI / 2);
const point = new Point(10, 0).rotated(angleInDegrees, new Point(0, 0));Reflection
All shapes support reflection over arbitrary axes, as well as convenient horizontal and vertical reflections:
centered=true(default): reflects over axis through shape's centercentered=false: reflects over axis through origin (x=0 or y=0)
Bounding Boxes
Bounding boxes support rotation and are calculated considering rotated shapes. For ShapeGroup, the bounding box encompasses all rotated shapes.
Benchmarks
Performance benchmarks live in benchmarks/ and are powered by tinybench. They are not shipped to npm and not part of prepublishOnly.
npm run bench # run every suite
npm run bench ShapeGroup # filter suites by substring (case-insensitive)Parameterized suites (Polyline, ShapeGroup) exercise sizes N ∈ {10, 100, 1000} to surface scaling behavior.
License
ISC
