Skip to content

Commit

Permalink
Add polyline; refactor LineSegment
Browse files Browse the repository at this point in the history
  • Loading branch information
ericyd committed Jan 9, 2024
1 parent 81dee29 commit a7656a2
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 47 deletions.
2 changes: 1 addition & 1 deletion lib/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ export * from './svg.js'
export * from './circle.js'
export * from './path.js'
export * from './defs.js'
export * from './line-segment.js'
export * from './rectangle.js'
export * from './polygon.js'
export * from './polyline.js'
29 changes: 0 additions & 29 deletions lib/components/line-segment.js

This file was deleted.

24 changes: 11 additions & 13 deletions lib/components/polygon.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Tag } from './tag.js'

/**
* @typedef {object} PolygonAttributes
* @property {Vector2[]} points
* @property {Vector2[]} [points=[]]
*/

export class Polygon extends Tag {
Expand All @@ -19,13 +19,9 @@ export class Polygon extends Tag {
/**
* @param {PolygonAttributes} attributes
*/
constructor(attributes = { points: [] }) {
if (!Array.isArray(attributes.points) || attributes.points.length === 0) {
// Maybe this will change in the future if I make a builder
throw new Error('Cannot construct a Polygon without points')
}
constructor({ points = [], ...attributes } = { points: [] }) {
super('polygon', attributes)
this.points = attributes.points
this.points = points
}

/** @returns {Rectangle} */
Expand All @@ -48,14 +44,16 @@ export class Polygon extends Tag {
}

render() {
if (!Array.isArray(this.points) || this.points.length === 0) {
throw new Error('Cannot render a Polygon without points')
}
this.setAttributes({
points: this.points
.map(
(vec) =>
`${toFixedPrecision(
vec.x,
this.numericPrecision,
)},${toFixedPrecision(vec.y, this.numericPrecision)}`,
.map((vec) =>
[
toFixedPrecision(vec.x, this.numericPrecision),
toFixedPrecision(vec.y, this.numericPrecision),
].join(','),
)
.join(' '),
})
Expand Down
19 changes: 18 additions & 1 deletion lib/components/polygon.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,25 @@ describe('Polygon', () => {
const actual = poly.render()
assert.strictEqual(
actual,
'<polygon points="0,0 100,100" fill="#000" stroke="#432"></polygon>',
'<polygon fill="#000" stroke="#432" points="0,0 100,100"></polygon>',
)
})

it('uses correct precision', () => {
const poly = new Polygon({
points: [vec2(0.1234, 0.1234), vec2(100.1234, 100.1234)],
})
poly.numericPrecision = 2
const actual = poly.render()
assert.strictEqual(
actual,
'<polygon points="0.12,0.12 100.12,100.12"></polygon>',
)
})

it('throws if points is empty', () => {
const poly = new Polygon()
assert.throws(() => poly.render())
})
})
})
107 changes: 107 additions & 0 deletions lib/components/polyline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { toFixedPrecision } from '../math.js'
import { Vector2 } from '../vector2.js'
import { Rectangle } from './rectangle.js'
import { Tag } from './tag.js'

/**
* @typedef {object} PolylineAttributes
* @property {Vector2[]} [points=[]]
*/

export class Polyline extends Tag {
/** @type {Vector2[]} */
points = []
/**
* Initialize to "empty" rectangle
* @type {Rectangle}
*/
#boundingBox = new Rectangle({ x: 0, y: 0, width: 0, height: 0 })
/**
* @param {PolylineAttributes} attributes
*/
constructor({ points = [], ...attributes } = { points: [] }) {
super('polyline', attributes)
this.points = points
}

/** @returns {Rectangle} */
get boundingBox() {
if (this.#boundingBox.empty()) {
const xs = this.points.map(({ x }) => x)
const ys = this.points.map(({ y }) => y)
const minX = Math.min(...xs)
const maxX = Math.max(...xs)
const minY = Math.min(...ys)
const maxY = Math.max(...ys)
this.#boundingBox = new Rectangle({
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
})
}
return this.#boundingBox
}

render() {
if (!Array.isArray(this.points) || this.points.length === 0) {
throw new Error('Cannot render a Polyline without points')
}
this.setAttributes({
points: this.points
.map((vec) =>
[
toFixedPrecision(vec.x, this.numericPrecision),
toFixedPrecision(vec.y, this.numericPrecision),
].join(','),
)
.join(' '),
})
return super.render()
}
}

/**
* @overload
* @param {PolylineAttributes} attrsOrBuilder
* @returns {Polyline}
*/
/**
* @overload
* @param {(Polyline: Polyline) => void} attrsOrBuilder
* @returns {Polyline}
*/
/**
* @param {PolylineAttributes | ((Polyline: Polyline) => void)} attrsOrBuilder
* @returns {Polyline}
*/
export function polyline(attrsOrBuilder) {
if (typeof attrsOrBuilder === 'function') {
const poly = new Polyline()
attrsOrBuilder(poly)
return poly
}
return new Polyline(attrsOrBuilder)
}

// I would prefer this to live in it's own file but it creates a circular dependency. oh well.
export class LineSegment extends Polyline {
/**
* @param {Vector2} start
* @param {Vector2} end
*/
constructor(start, end) {
super({
points: [start, end],
})
}
}

/**
* @param {Vector2} start
* @param {Vector2} end
* @returns {LineSegment}
*/
export function lineSegment(start, end) {
return new LineSegment(start, end)
}
51 changes: 51 additions & 0 deletions lib/components/polyline.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Polyline, lineSegment } from './polyline.js'
import { vec2 } from '../vector2.js'

describe('Polyline', () => {
describe('render', () => {
it('formats `points` correctly', () => {
const poly = new Polyline({ points: [vec2(0, 0), vec2(100, 100)] })
const actual = poly.render()
assert.strictEqual(actual, '<polyline points="0,0 100,100"></polyline>')
})

it('includes other properties', () => {
const poly = new Polyline({
points: [vec2(0, 0), vec2(100, 100)],
fill: '#000',
stroke: '#432',
})
const actual = poly.render()
assert.strictEqual(
actual,
'<polyline fill="#000" stroke="#432" points="0,0 100,100"></polyline>',
)
})

it('uses correct precision', () => {
const poly = new Polyline({
points: [vec2(0.1234, 0.1234), vec2(100.1234, 100.1234)],
})
poly.numericPrecision = 2
const actual = poly.render()
assert.strictEqual(
actual,
'<polyline points="0.12,0.12 100.12,100.12"></polyline>',
)
})

it('throws if points is empty', () => {
const poly = new Polyline()
assert.throws(() => poly.render())
})
})
})

describe('LineSegment', () => {
it('renders a polyline tag', () => {
const actual = lineSegment(vec2(0, 0), vec2(100, 100)).render()
assert.strictEqual(actual, '<polyline points="0,0 100,100"></polyline>')
})
})
2 changes: 1 addition & 1 deletion lib/components/rectangle.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { pickBy } from '../util.js'
import { Vector2, vec2 } from '../vector2.js'
import { LineSegment } from './line-segment.js'
import { LineSegment } from './polyline.js'
import { Tag } from './tag.js'

/**
Expand Down
11 changes: 10 additions & 1 deletion lib/components/svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Tag } from './tag.js'
import { Circle, circle } from './circle.js'
import { Path, path } from './path.js'
import { Rectangle, rect } from './rectangle.js'
import { LineSegment } from './line-segment.js'
import { Polyline, LineSegment, polyline } from './polyline.js'
import { ColorRgb } from '../color/rgb.js'
import { Polygon } from './polygon.js'

Expand Down Expand Up @@ -107,6 +107,15 @@ export class Svg extends Tag {
: this.addChild(new Polygon(instanceOrBuilder))
}

/**
* @param {Polyline | ((poly: Polyline) => void)} instanceOrBuilder
*/
polyline(instanceOrBuilder) {
return instanceOrBuilder instanceof Polyline
? this.addChild(instanceOrBuilder)
: this.addChild(polyline(instanceOrBuilder))
}

/**
* Generates filename metadata when running in a render loop
* @returns {string}
Expand Down
2 changes: 1 addition & 1 deletion lib/data-structures/fractalized-line.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* That said, I could potentially implement a custom iterator if I wanted to use a linked list in the future.
*/

import { LineSegment } from '../components/line-segment.js'
import { LineSegment } from '../components/polyline.js'
import { Path } from '../components/path.js'
import { random } from '../random.js'
import { Vector2 } from '../vector2.js'
Expand Down

0 comments on commit a7656a2

Please sign in to comment.