@thi.ng/geom-axidraw 0.5.45 → 0.5.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-09T19:12:03Z
3
+ - **Last updated**: 2023-12-11T10:07:09Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
package/README.md CHANGED
@@ -304,7 +304,7 @@ For Node.js REPL:
304
304
  const geomAxidraw = await import("@thi.ng/geom-axidraw");
305
305
  ```
306
306
 
307
- Package sizes (brotli'd, pre-treeshake): ESM: 1.50 KB
307
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.49 KB
308
308
 
309
309
  ## Dependencies
310
310
 
package/api.js CHANGED
@@ -1 +0,0 @@
1
- export {};
package/as-axidraw.js CHANGED
@@ -9,46 +9,9 @@ import { __dispatch } from "@thi.ng/geom/internal/dispatch";
9
9
  import { __sampleAttribs } from "@thi.ng/geom/internal/vertices";
10
10
  import { takeNth } from "@thi.ng/transducers/take-nth";
11
11
  import { pointsByNearestNeighbor } from "./sort.js";
12
- /**
13
- * Lazily converts given shape (or group) into an iterable of thi.ng/axidraw
14
- * drawing commands, using optionally provided config options.
15
- *
16
- * @remarks
17
- * The provided conversion options can (and will) be overridden by a shape's
18
- * `__axi` attribute. See {@link AxiDrawAttribs} for details.
19
- *
20
- * Currently supported shape types (at least all types which are supported by
21
- * the
22
- * [`asPolyline()`](https://docs.thi.ng/umbrella/geom/functions/asPolyline.html)
23
- * function):
24
- *
25
- * - arc
26
- * - circle
27
- * - cubic
28
- * - ellipse
29
- * - group
30
- * - line
31
- * - path
32
- * - points
33
- * - polygon
34
- * - polyline
35
- * - quad
36
- * - quadratic
37
- * - rect
38
- * - triangle
39
- *
40
- * @example
41
- * ```ts
42
- * [...asAxiDraw(circle(100), { samples: 100 })]
43
- * [
44
- * [ 'm', [ 10, 0 ] ],
45
- * [ 'd' ],
46
- * [ 'm', [ 9.980267284282716, 0.6279051952931337 ], undefined ],
47
- * ...
48
- * ]
49
- * ```
50
- */
51
- export const asAxiDraw = defmulti(__dispatch, {
12
+ const asAxiDraw = defmulti(
13
+ __dispatch,
14
+ {
52
15
  arc: "circle",
53
16
  cubic: "circle",
54
17
  ellipse: "circle",
@@ -58,105 +21,111 @@ export const asAxiDraw = defmulti(__dispatch, {
58
21
  quad: "polyline",
59
22
  quadratic: "circle",
60
23
  rect: "polyline",
61
- tri: "polyline",
62
- }, {
24
+ tri: "polyline"
25
+ },
26
+ {
63
27
  points: ($, opts) => __points(applyTransforms($).points, $.attribs, opts),
64
28
  // used for all shapes which need to be sampled
65
- circle: ($, opts) => __polyline(asPolyline(applyTransforms($), opts?.samples).points, $.attribs, opts),
29
+ circle: ($, opts) => __polyline(
30
+ asPolyline(applyTransforms($), opts?.samples).points,
31
+ $.attribs,
32
+ opts
33
+ ),
66
34
  // ignore sample opts for polyline & other polygonal shapes
67
35
  // i.e. use points verbatim
68
36
  polyline: ($, opts) => __polyline(asPolyline(applyTransforms($)).points, $.attribs, opts),
69
- group: ($, opts) => __group($, opts),
70
- });
37
+ group: ($, opts) => __group($, opts)
38
+ }
39
+ );
71
40
  function* __group($, opts) {
72
- const $sampleOpts = __sampleAttribs(opts?.samples, $.attribs);
73
- const { skip, sort, interleave } = __axiAttribs($.attribs);
74
- const children = skip ? [...takeNth(skip + 1, $.children)] : $.children;
75
- function* emitChunk(chunk) {
76
- const iter = sort ? sort(chunk) : chunk;
77
- for (let child of iter) {
78
- const shape = applyTransforms(child);
79
- shape.attribs = {
80
- ...$.attribs,
81
- ...shape.attribs,
82
- __samples: __sampleAttribs($sampleOpts, shape.attribs),
83
- };
84
- yield* asAxiDraw(shape, opts);
85
- }
41
+ const $sampleOpts = __sampleAttribs(opts?.samples, $.attribs);
42
+ const { skip, sort, interleave } = __axiAttribs($.attribs);
43
+ const children = skip ? [...takeNth(skip + 1, $.children)] : $.children;
44
+ function* emitChunk(chunk) {
45
+ const iter = sort ? sort(chunk) : chunk;
46
+ for (let child of iter) {
47
+ const shape = applyTransforms(child);
48
+ shape.attribs = {
49
+ ...$.attribs,
50
+ ...shape.attribs,
51
+ __samples: __sampleAttribs($sampleOpts, shape.attribs)
52
+ };
53
+ yield* asAxiDraw(shape, opts);
86
54
  }
87
- if (interleave) {
88
- const { num, commands } = interleave;
89
- if (interleave.start !== false)
90
- yield* commands(0);
91
- for (let i = 0, n = children.length; i < n;) {
92
- yield* emitChunk(children.slice(i, i + num));
93
- i += num;
94
- if (i < n)
95
- yield* commands(i);
96
- }
97
- if (interleave.end)
98
- yield* interleave.commands(children.length);
99
- }
100
- else {
101
- yield* emitChunk(children);
55
+ }
56
+ if (interleave) {
57
+ const { num, commands } = interleave;
58
+ if (interleave.start !== false)
59
+ yield* commands(0);
60
+ for (let i = 0, n = children.length; i < n; ) {
61
+ yield* emitChunk(children.slice(i, i + num));
62
+ i += num;
63
+ if (i < n)
64
+ yield* commands(i);
102
65
  }
66
+ if (interleave.end)
67
+ yield* interleave.commands(children.length);
68
+ } else {
69
+ yield* emitChunk(children);
70
+ }
103
71
  }
104
72
  function* __points(pts, attribs, opts) {
73
+ if (!pts.length)
74
+ return;
75
+ const { clip, delayDown, delayUp, down, skip, speed, sort, interleave } = {
76
+ sort: pointsByNearestNeighbor(),
77
+ ...__axiAttribs(attribs)
78
+ };
79
+ const clipPts = clip || opts?.clip;
80
+ if (clipPts) {
81
+ pts = pts.filter((p) => !!pointInPolygon2(p, clipPts));
105
82
  if (!pts.length)
106
- return;
107
- const { clip, delayDown, delayUp, down, skip, speed, sort, interleave } = {
108
- sort: pointsByNearestNeighbor(),
109
- ...__axiAttribs(attribs),
110
- };
111
- const clipPts = clip || opts?.clip;
112
- if (clipPts) {
113
- pts = pts.filter((p) => !!pointInPolygon2(p, clipPts));
114
- if (!pts.length)
115
- return;
116
- }
117
- if (skip) {
118
- pts = [...takeNth(skip + 1, pts)];
119
- }
120
- function* emitChunk($pts) {
121
- if (down != undefined)
122
- yield ["pen", down];
123
- for (let p of sort ? sort($pts) : $pts) {
124
- yield MOVE(p, speed);
125
- yield DOWN(delayDown);
126
- yield UP(delayUp);
127
- }
128
- if (down != undefined)
129
- yield ["pen"];
83
+ return;
84
+ }
85
+ if (skip) {
86
+ pts = [...takeNth(skip + 1, pts)];
87
+ }
88
+ function* emitChunk($pts) {
89
+ if (down != void 0)
90
+ yield ["pen", down];
91
+ for (let p of sort ? sort($pts) : $pts) {
92
+ yield MOVE(p, speed);
93
+ yield DOWN(delayDown);
94
+ yield UP(delayUp);
130
95
  }
131
- yield UP();
132
- if (interleave) {
133
- const { num, commands } = interleave;
134
- if (interleave.start !== false)
135
- yield* commands(0);
136
- for (let i = 0, n = pts.length; i < n;) {
137
- yield* emitChunk(pts.slice(i, i + num));
138
- i += num;
139
- if (i < n)
140
- yield* commands(i);
141
- }
142
- if (interleave.end)
143
- yield* interleave.commands(pts.length);
144
- }
145
- else {
146
- yield* emitChunk(pts);
96
+ if (down != void 0)
97
+ yield ["pen"];
98
+ }
99
+ yield UP();
100
+ if (interleave) {
101
+ const { num, commands } = interleave;
102
+ if (interleave.start !== false)
103
+ yield* commands(0);
104
+ for (let i = 0, n = pts.length; i < n; ) {
105
+ yield* emitChunk(pts.slice(i, i + num));
106
+ i += num;
107
+ if (i < n)
108
+ yield* commands(i);
147
109
  }
110
+ if (interleave.end)
111
+ yield* interleave.commands(pts.length);
112
+ } else {
113
+ yield* emitChunk(pts);
114
+ }
148
115
  }
149
116
  function* __polyline(pts, attribs, opts) {
150
- if (!pts.length)
151
- return;
152
- const { clip, down, delayDown, delayUp, speed } = __axiAttribs(attribs);
153
- const clipPts = clip || opts?.clip;
154
- const chunks = clipPts ? clipPolylinePoly(pts, clipPts) : [pts];
155
- if (!chunks.length)
156
- return;
157
- for (let chunk of chunks) {
158
- yield* polyline(chunk, { down, delayDown, delayUp, speed });
159
- }
117
+ if (!pts.length)
118
+ return;
119
+ const { clip, down, delayDown, delayUp, speed } = __axiAttribs(attribs);
120
+ const clipPts = clip || opts?.clip;
121
+ const chunks = clipPts ? clipPolylinePoly(pts, clipPts) : [pts];
122
+ if (!chunks.length)
123
+ return;
124
+ for (let chunk of chunks) {
125
+ yield* polyline(chunk, { down, delayDown, delayUp, speed });
126
+ }
160
127
  }
161
- /** @internal */
162
128
  const __axiAttribs = (attribs) => attribs ? attribs.__axi || {} : {};
129
+ export {
130
+ asAxiDraw
131
+ };
package/as-geometry.js CHANGED
@@ -4,109 +4,97 @@ import { polyline } from "@thi.ng/geom/polyline";
4
4
  import { add2 } from "@thi.ng/vectors/add";
5
5
  import { copy } from "@thi.ng/vectors/copy";
6
6
  const DEFAULT_ATTRIBS = {
7
- paths: { stroke: "#000" },
8
- rapids: { stroke: "#0ff" },
9
- ups: { fill: "#0f0", stroke: "none" },
10
- downs: { fill: "#f00", stroke: "none" },
7
+ paths: { stroke: "#000" },
8
+ rapids: { stroke: "#0ff" },
9
+ ups: { fill: "#0f0", stroke: "none" },
10
+ downs: { fill: "#f00", stroke: "none" }
11
11
  };
12
- /**
13
- * Converts a sequence of
14
- * [DrawCommands](https://docs.thi.ng/umbrella/axidraw/types/DrawCommand.html)
15
- * into thi.ng/geom geometry. Returns an object of shapes. The conversion can be
16
- * controlled via given options.
17
- *
18
- * @remarks
19
- * The returned object contains groups & shapes which are being color coded by
20
- * default:
21
- *
22
- * - `paths`: a group of polylines for which pen is down (#000)
23
- * - `rapids`: a group of polylines for which pen is up (#0ff)
24
- * - `ups`: a point cloud of positions where pen is being lifted (#0f0)
25
- * - `downs`: a point cloud of positions where pen is being placed down (#f00)
26
- *
27
- * @param src
28
- * @param opts
29
- */
30
- export const asGeometry = (src, opts = {}) => {
31
- opts = {
32
- rapids: true,
33
- pen: true,
34
- ...opts,
35
- attribs: { ...DEFAULT_ATTRIBS, ...opts.attribs },
36
- };
37
- const rapids = [];
38
- const paths = [];
39
- const downs = [];
40
- const ups = [];
41
- let penDown = false;
42
- let pts = null;
43
- let currPos = [0, 0];
44
- const $move = (newPos) => {
45
- if (penDown || opts.rapids) {
46
- if (!pts)
47
- pts = [copy(currPos), newPos];
48
- else
49
- pts.push(newPos);
50
- }
51
- currPos = newPos;
52
- };
53
- for (let cmd of src) {
54
- switch (cmd[0]) {
55
- // absolute
56
- case "M":
57
- $move(copy(cmd[1]));
58
- break;
59
- // relative
60
- case "m":
61
- $move(add2([], currPos, cmd[1]));
62
- break;
63
- case "u":
64
- if (pts) {
65
- if (penDown)
66
- paths.push(pts);
67
- else if (opts.rapids)
68
- rapids.push(pts);
69
- pts = null;
70
- }
71
- if (opts.pen)
72
- ups.push(copy(currPos));
73
- penDown = false;
74
- break;
75
- case "d":
76
- if (pts) {
77
- if (!penDown) {
78
- if (opts.rapids)
79
- rapids.push(pts);
80
- }
81
- else
82
- paths.push(pts);
83
- pts = null;
84
- }
85
- if (opts.pen)
86
- downs.push(copy(currPos));
87
- penDown = true;
88
- break;
89
- case "home":
90
- currPos = [0, 0];
91
- if (!pts)
92
- pts = [currPos];
93
- else
94
- pts.push(currPos);
95
- break;
96
- default:
97
- console.log("skipping command", cmd);
98
- }
12
+ const asGeometry = (src, opts = {}) => {
13
+ opts = {
14
+ rapids: true,
15
+ pen: true,
16
+ ...opts,
17
+ attribs: { ...DEFAULT_ATTRIBS, ...opts.attribs }
18
+ };
19
+ const rapids = [];
20
+ const paths = [];
21
+ const downs = [];
22
+ const ups = [];
23
+ let penDown = false;
24
+ let pts = null;
25
+ let currPos = [0, 0];
26
+ const $move = (newPos) => {
27
+ if (penDown || opts.rapids) {
28
+ if (!pts)
29
+ pts = [copy(currPos), newPos];
30
+ else
31
+ pts.push(newPos);
99
32
  }
100
- if (pts) {
101
- if (penDown)
33
+ currPos = newPos;
34
+ };
35
+ for (let cmd of src) {
36
+ switch (cmd[0]) {
37
+ case "M":
38
+ $move(copy(cmd[1]));
39
+ break;
40
+ case "m":
41
+ $move(add2([], currPos, cmd[1]));
42
+ break;
43
+ case "u":
44
+ if (pts) {
45
+ if (penDown)
102
46
  paths.push(pts);
103
- else
47
+ else if (opts.rapids)
104
48
  rapids.push(pts);
49
+ pts = null;
50
+ }
51
+ if (opts.pen)
52
+ ups.push(copy(currPos));
53
+ penDown = false;
54
+ break;
55
+ case "d":
56
+ if (pts) {
57
+ if (!penDown) {
58
+ if (opts.rapids)
59
+ rapids.push(pts);
60
+ } else
61
+ paths.push(pts);
62
+ pts = null;
63
+ }
64
+ if (opts.pen)
65
+ downs.push(copy(currPos));
66
+ penDown = true;
67
+ break;
68
+ case "home":
69
+ currPos = [0, 0];
70
+ if (!pts)
71
+ pts = [currPos];
72
+ else
73
+ pts.push(currPos);
74
+ break;
75
+ default:
76
+ console.log("skipping command", cmd);
105
77
  }
106
- return {
107
- paths: group(opts.attribs.paths, paths.map((pts) => polyline(pts))),
108
- rapids: group(opts.attribs.rapids, rapids.map((pts) => polyline(pts))),
109
- ups: points(ups, opts.attribs.ups),
110
- downs: points(downs, opts.attribs.downs),
111
- };
78
+ }
79
+ if (pts) {
80
+ if (penDown)
81
+ paths.push(pts);
82
+ else
83
+ rapids.push(pts);
84
+ }
85
+ return {
86
+ paths: group(
87
+ opts.attribs.paths,
88
+ paths.map((pts2) => polyline(pts2))
89
+ ),
90
+ rapids: group(
91
+ opts.attribs.rapids,
92
+ rapids.map((pts2) => polyline(pts2))
93
+ ),
94
+ ups: points(ups, opts.attribs.ups),
95
+ downs: points(downs, opts.attribs.downs)
96
+ };
97
+ };
98
+ export {
99
+ asGeometry
112
100
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/geom-axidraw",
3
- "version": "0.5.45",
3
+ "version": "0.5.47",
4
4
  "description": "Conversion and preparation of thi.ng/geom shapes & shape groups to/from AxiDraw pen plotter draw commands",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -33,21 +35,22 @@
33
35
  "test": "bun test"
34
36
  },
35
37
  "dependencies": {
36
- "@thi.ng/api": "^8.9.11",
37
- "@thi.ng/arrays": "^2.7.7",
38
- "@thi.ng/axidraw": "^1.1.39",
39
- "@thi.ng/compare": "^2.2.7",
40
- "@thi.ng/defmulti": "^3.0.9",
41
- "@thi.ng/geom": "^6.0.7",
42
- "@thi.ng/geom-accel": "^3.5.33",
43
- "@thi.ng/geom-api": "^3.4.49",
44
- "@thi.ng/geom-clip-line": "^2.3.49",
45
- "@thi.ng/geom-isec": "^2.1.91",
46
- "@thi.ng/transducers": "^8.8.14",
47
- "@thi.ng/vectors": "^7.8.8"
38
+ "@thi.ng/api": "^8.9.12",
39
+ "@thi.ng/arrays": "^2.7.8",
40
+ "@thi.ng/axidraw": "^1.1.41",
41
+ "@thi.ng/compare": "^2.2.8",
42
+ "@thi.ng/defmulti": "^3.0.10",
43
+ "@thi.ng/geom": "^6.0.9",
44
+ "@thi.ng/geom-accel": "^3.5.35",
45
+ "@thi.ng/geom-api": "^3.4.51",
46
+ "@thi.ng/geom-clip-line": "^2.3.51",
47
+ "@thi.ng/geom-isec": "^2.1.93",
48
+ "@thi.ng/transducers": "^8.8.15",
49
+ "@thi.ng/vectors": "^7.8.10"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@microsoft/api-extractor": "^7.38.3",
53
+ "esbuild": "^0.19.8",
51
54
  "rimraf": "^5.0.5",
52
55
  "tools": "^0.0.1",
53
56
  "typedoc": "^0.25.4",
@@ -119,5 +122,5 @@
119
122
  "status": "alpha",
120
123
  "year": 2022
121
124
  },
122
- "gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
125
+ "gitHead": "22e36fa838e5431d40165384918b395603bbd92f\n"
123
126
  }
package/sort.js CHANGED
@@ -4,68 +4,42 @@ import { KdTreeSet } from "@thi.ng/geom-accel/kd-tree-set";
4
4
  import { centroid } from "@thi.ng/geom/centroid";
5
5
  import { ZERO2 } from "@thi.ng/vectors/api";
6
6
  import { distSq2 } from "@thi.ng/vectors/distsq";
7
- /**
8
- * Higher order point ordering fn. Adds points to given spatial
9
- * index/acceleration structure and then lazily sorts them by nearest neighbor
10
- * distance, starting selection of first point based on given `ref` point
11
- * (default: [0, 0]).
12
- *
13
- * @remarks
14
- * By default is using a
15
- * [`KdTreeSet`](https://docs.thi.ng/umbrella/geom-accel/classes/KdTreeSet.html)
16
- * to index all points and then successively perform efficient nearest neighbor
17
- * searches (always w.r.t the most recent result point).
18
- *
19
- * @param accel
20
- * @param ref
21
- */
22
- export const pointsByNearestNeighbor = (accel = new KdTreeSet(2), ref = ZERO2) => function* (pts) {
23
- accel.into(pts);
24
- // const index = new KdTreeSet(2, pts);
25
- while (accel.size) {
26
- ref = accel.queryKeys(ref, 1e4, 1)[0];
27
- accel.remove(ref);
28
- yield ref;
29
- }
7
+ const pointsByNearestNeighbor = (accel = new KdTreeSet(2), ref = ZERO2) => function* (pts) {
8
+ accel.into(pts);
9
+ while (accel.size) {
10
+ ref = accel.queryKeys(ref, 1e4, 1)[0];
11
+ accel.remove(ref);
12
+ yield ref;
13
+ }
30
14
  };
31
- /**
32
- * Higher order point ordering fn. Sorts points by proximity to given `ref` point
33
- * (default: [0, 0]).
34
- *
35
- * @param ref
36
- */
37
- export const pointsByProximity = (ref = ZERO2) => (pts) => {
38
- return sortByCachedKey(pts.slice(), (p) => distSq2(p, ref), compareNumAsc);
15
+ const pointsByProximity = (ref = ZERO2) => (pts) => {
16
+ return sortByCachedKey(
17
+ pts.slice(),
18
+ (p) => distSq2(p, ref),
19
+ compareNumAsc
20
+ );
39
21
  };
40
- /**
41
- * Higher order shape sorting fn. Sorts shapes by their centroid's proximity to
42
- * given `ref` point (default: [0, 0]).
43
- *
44
- * @param ref
45
- */
46
- export const shapesByProximity = (ref = ZERO2) => (shapes) => {
47
- return sortByCachedKey(shapes.slice(), (s) => distSq2(centroid(s) || ZERO2, ref), compareNumAsc);
22
+ const shapesByProximity = (ref = ZERO2) => (shapes) => {
23
+ return sortByCachedKey(
24
+ shapes.slice(),
25
+ (s) => distSq2(centroid(s) || ZERO2, ref),
26
+ compareNumAsc
27
+ );
48
28
  };
49
- /**
50
- * Similar to {@link pointsByNearestNeighbor}, however for shapes and requiring
51
- * an
52
- * [`ISpatialMap`](https://docs.thi.ng/umbrella/geom-api/interfaces/ISpatialMap.html)
53
- * implementation and is using shape centroid (auto-computed) to perform
54
- * indexing and nearest neighbor queries.
55
- *
56
- * @remarks
57
- * Currently recommended to use
58
- * [`NdQuadtreeMap`](https://docs.thi.ng/umbrella/geom-accel/classes/NdQuadtreeMap.html).
59
- *
60
- * @param accel
61
- * @param ref
62
- */
63
- export const shapesByNearestNeighbor = (accel, ref = ZERO2) => function* (shapes) {
64
- accel.into(shapes.map((s) => [centroid(s) || [0, 0], s]));
65
- while (accel.size) {
66
- const pair = accel.query(ref, 1e4, 1)[0];
67
- ref = pair[0];
68
- accel.remove(ref);
69
- yield pair[1];
70
- }
29
+ const shapesByNearestNeighbor = (accel, ref = ZERO2) => function* (shapes) {
30
+ accel.into(
31
+ shapes.map((s) => [centroid(s) || [0, 0], s])
32
+ );
33
+ while (accel.size) {
34
+ const pair = accel.query(ref, 1e4, 1)[0];
35
+ ref = pair[0];
36
+ accel.remove(ref);
37
+ yield pair[1];
38
+ }
39
+ };
40
+ export {
41
+ pointsByNearestNeighbor,
42
+ pointsByProximity,
43
+ shapesByNearestNeighbor,
44
+ shapesByProximity
71
45
  };