Skip to content

Commit f5f4049

Browse files
committed
feat(Cover): Added new cover config property
The cover property provides control over when vector points will stop being generated. - When cover is true it will attempt to fill the bounds with as many vectors as possible. - When false it will stop generating vectors as soon as one falls outside of the boudns given. BREAKING CHANGE: The arguments given to algorithms is now a single object. See some of the provided algorithms for an example. This only affects SysPlot when used with custom alogrithms.
1 parent bc04502 commit f5f4049

File tree

8 files changed

+185
-24
lines changed

8 files changed

+185
-24
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const sysPlot = new SysPlot();
2222

2323
sysPlot.setConfig({
2424
algorithm: VogelSpiral,
25+
cover: true,
2526
padding: 10,
2627
proportional: true,
2728
spread: 0.25,
@@ -86,24 +87,40 @@ const shapesWithValidPositions = zip(positions, shapes).filter(([, p]) => p);
8687
const config = {
8788
/**
8889
* One of the exported algorithm functions mentioned above.
90+
*
91+
* Defaults: ArchimedesSpiral
8992
*/
9093
algorithm: Function,
9194

95+
/**
96+
* Specifies to generate as many vector points needed to cover the entire
97+
* area give.
98+
*
99+
* Defaults: true
100+
*/
101+
cover: Boolean
102+
92103
/**
93104
* The amount of padding to be used around the shapes when
94105
* positioning.
106+
*
107+
* Defaults: 10
95108
*/
96109
padding: Number,
97110

98111
/**
99112
* Retains the aspect ratio for plotting the vector points.
113+
*
114+
* Defaults: false
100115
*/
101116
proportional: Boolean,
102117

103118
/**
104119
* A number between 0.1 and 1 that affects the density of the
105120
* vector points. 0.1 being very dense and 1 being very spread
106121
* apart.
122+
*
123+
* Defaults: 0.25
107124
*/
108125
spread: Number,
109126
}
@@ -162,7 +179,6 @@ const positions = sysPlot.getPositions();
162179
const shapesWithValidPositions = zip(positions, shapes).filter(([, p]) => p);
163180
```
164181

165-
166182
#### SysPlot.getVectors()
167183

168184
Gets the vector points used for positioning (as XY coordinates) that the algorithm generated, radiating out from the center.

src/SysPlot.js

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,44 @@
1+
import ArchimedesSpiral from './algorithms/ArchimedesSpiral';
2+
import getMaxShapeSize from './positioning/getMaxShapeSize';
13
import position from './positioning/position';
24

5+
const defaultConfig = {
6+
algorithm: ArchimedesSpiral,
7+
cover: true,
8+
padding: 10,
9+
proportional: false,
10+
spread: 0.25,
11+
};
12+
313
export default class SysPlot {
414
constructor() {
5-
this.config = {};
15+
this.config = Object.assign({}, defaultConfig);
616
}
717

818
setConfig(config = {}) {
19+
const {
20+
algorithm = defaultConfig.algorithm,
21+
cover = defaultConfig.cover,
22+
padding = defaultConfig.padding,
23+
proportional = defaultConfig.proportional,
24+
spread = defaultConfig.spread,
25+
} = config;
26+
927
const updateVectors =
10-
this.config.algorithm !== config.algorithm ||
11-
this.config.proportional !== config.proportional ||
12-
this.config.spread !== config.spread;
28+
this.config.algorithm !== algorithm ||
29+
this.config.cover !== cover ||
30+
this.config.proportional !== proportional ||
31+
this.config.spread !== spread;
1332

1433
const updatePositions = updateVectors ||
15-
this.config.padding !== config.padding;
34+
this.config.padding !== padding;
1635

1736
this.config = {
18-
algorithm: config.algorithm,
19-
padding: config.padding,
20-
proportional: config.proportional,
21-
spread: config.spread,
37+
algorithm,
38+
cover,
39+
padding,
40+
proportional,
41+
spread,
2242
};
2343

2444
if (updateVectors) this.vectors = null;
@@ -39,28 +59,38 @@ export default class SysPlot {
3959
}
4060

4161
setShapes(shapes) {
42-
this.shapes = shapes;
62+
const shapesBoundaryOffset = this.config.cover ? 0 : (getMaxShapeSize(shapes) / 2);
63+
4364
this.positions = null;
65+
this.shapes = shapes;
66+
67+
if (this.shapesBoundaryOffset !== shapesBoundaryOffset) {
68+
this.shapesBoundaryOffset = shapesBoundaryOffset;
69+
this.vectors = null;
70+
}
4471

4572
return this;
4673
}
4774

4875
getVectors() {
4976
if (!this.vectors) {
50-
const { config, width: w, height: h } = this;
77+
const { config, width, height } = this;
78+
const h = height - this.shapesBoundaryOffset;
79+
const w = width - this.shapesBoundaryOffset;
5180
const [xDim, yDim] = config.proportional ? [w, h] : (w > h ? [w, w] : [h, h]);
5281
const spread = Math.max(0.1, Math.min(1, config.spread)) / 10;
5382

54-
this.vectors = this.config.algorithm(
55-
this.width,
56-
this.height,
57-
this.width / 2,
58-
this.height / 2,
59-
(xDim * (spread * (xDim / yDim)))
83+
this.vectors = this.config.algorithm({
84+
cover: config.cover,
85+
height: h,
86+
width: w,
87+
xCenter: (w / 2) + (this.shapesBoundaryOffset / 2),
88+
xDistance: (xDim * (spread * (xDim / yDim)))
6089
* this.config.algorithm.NORMALISATION_FACTOR,
61-
(yDim * (spread * (yDim / xDim)))
90+
yCenter: (h / 2) + (this.shapesBoundaryOffset / 2),
91+
yDistance: (yDim * (spread * (yDim / xDim)))
6292
* this.config.algorithm.NORMALISATION_FACTOR,
63-
);
93+
});
6494
}
6595

6696
return this.vectors;

src/algorithms/ArchimedesSpiral.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
const ArchimedesSpiral = (w, h, cx, cy, xDist, yDist) => {
1+
const ArchimedesSpiral = ({
2+
cover: cover,
3+
height: h,
4+
width: w,
5+
xCenter: cx,
6+
xDistance: xDist,
7+
yDistance: yDist,
8+
yCenter: cy,
9+
}) => {
210
const vectors = [];
311
const hxDist = xDist / 2;
412
const hyDist = yDist / 2;
@@ -16,6 +24,8 @@ const ArchimedesSpiral = (w, h, cx, cy, xDist, yDist) => {
1624

1725
if (vx > 0 && vy > 0 && vx < w && vy < h) {
1826
vectors.push([vx, vy]);
27+
} else if (!cover) {
28+
return vectors;
1929
}
2030
}
2131

src/algorithms/ConcentricCircles.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
const pi2 = Math.PI * 2;
22

3-
const ConcentricCircles = (w, h, cx, cy, xDist, yDist) => {
3+
const ConcentricCircles = ({
4+
cover: cover,
5+
height: h,
6+
width: w,
7+
xCenter: cx,
8+
xDistance: xDist,
9+
yDistance: yDist,
10+
yCenter: cy,
11+
}) => {
412
const vectors = [[cx, cy]];
513
const max = Math.hypot(cx, cy);
614
let rx = 0;
@@ -22,6 +30,8 @@ const ConcentricCircles = (w, h, cx, cy, xDist, yDist) => {
2230

2331
if (vx > 0 && vy > 0 && vx < w && vy < h) {
2432
vectors.push([vx, vy]);
33+
} else if (!cover) {
34+
return vectors;
2535
}
2636
}
2737
}

src/algorithms/FermatSpiral.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
const FermatSpiral = (w, h, cx, cy, xDist, yDist, c = 1.5) => {
1+
const FermatSpiral = ({
2+
cover: cover,
3+
height: h,
4+
width: w,
5+
xCenter: cx,
6+
xDistance: xDist,
7+
yDistance: yDist,
8+
yCenter: cy,
9+
}, c = 1.5) => {
210
const vectors = [];
311
let rx, ry, tx = 0, ty = 0, vx = 1, vy = 1;
412

@@ -10,6 +18,8 @@ const FermatSpiral = (w, h, cx, cy, xDist, yDist, c = 1.5) => {
1018

1119
if (vx > 0 && vy > 0 && vx < w && vy < h) {
1220
vectors.push([vx, vy]);
21+
} else if (!cover) {
22+
return vectors;
1323
}
1424
}
1525

src/algorithms/UlamSpiral.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@ const isPrimeNumber = (n) => {
1313
return n !== 1;
1414
};
1515

16-
const UlamSpiral = (w, h, cx, cy, xDist, yDist) => {
16+
const UlamSpiral = ({
17+
cover: cover,
18+
height: h,
19+
width: w,
20+
xCenter: cx,
21+
xDistance: xDist,
22+
yDistance: yDist,
23+
yCenter: cy,
24+
}) => {
1725
const vectors = [];
1826
let n = 0;
1927
let d = R;
@@ -25,6 +33,8 @@ const UlamSpiral = (w, h, cx, cy, xDist, yDist) => {
2533
if (isPrimeNumber(n++)) {
2634
if (vx > 0 && vy > 0 && vx < w && vy < h) {
2735
vectors.push([vx, vy]);
36+
} else if (!cover) {
37+
return vectors;
2838
}
2939
}
3040

src/positioning/getMaxShapeSize.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export default (shapes) => {
2+
let max;
3+
4+
for (let i = 0; i < shapes.length; i++) {
5+
const { height, radius, width } = shapes[i];
6+
7+
if (radius !== undefined) {
8+
if (!max || radius > max) max = radius * 2;
9+
} else if (width !== undefined && height !== undefined) {
10+
if (!max || width > max) max = width;
11+
if (height > max) max = height;
12+
}
13+
}
14+
15+
return max || 0;
16+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import getMaxShapeSize from './getMaxShapeSize';
2+
3+
describe('getMaxShapeSize', () => {
4+
test('no shapes', () => {
5+
expect(getMaxShapeSize([])).toBe(0);
6+
});
7+
8+
test('one shape with radius', () => {
9+
expect(getMaxShapeSize([{ radius: 10 }])).toBe(20);
10+
});
11+
12+
test('one shape with same width and height', () => {
13+
expect(getMaxShapeSize([{ height: 10, width: 10 }])).toBe(10);
14+
});
15+
16+
test('one shape with greater width and height', () => {
17+
expect(getMaxShapeSize([{ height: 10, width: 20 }])).toBe(20);
18+
});
19+
20+
test('one shape with width and greater height', () => {
21+
expect(getMaxShapeSize([{ height: 20, width: 10 }])).toBe(20);
22+
});
23+
24+
test('multiple shapes with radius', () => {
25+
expect(getMaxShapeSize([
26+
{ radius: 20 },
27+
{ radius: 40 },
28+
{ radius: 30 },
29+
])).toBe(40);
30+
});
31+
32+
test('multiple shapes with width and height', () => {
33+
expect(getMaxShapeSize([
34+
{ height: 20, width: 10 },
35+
{ height: 30, width: 10 },
36+
{ height: 20, width: 40 },
37+
])).toBe(40);
38+
});
39+
40+
test('multiple mixed shapes with greater radius and width and height', () => {
41+
expect(getMaxShapeSize([
42+
{ radius: 20 },
43+
{ radius: 40 },
44+
{ radius: 30 },
45+
{ height: 20, width: 10 },
46+
{ height: 30, width: 10 },
47+
])).toBe(40);
48+
});
49+
50+
test('multiple mixed shapes with radius and greater width and height', () => {
51+
expect(getMaxShapeSize([
52+
{ radius: 20 },
53+
{ radius: 30 },
54+
{ radius: 30 },
55+
{ height: 20, width: 10 },
56+
{ height: 30, width: 40 },
57+
])).toBe(40);
58+
});
59+
});

0 commit comments

Comments
 (0)