svg-path-simplify 0.4.4 → 0.4.6

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +9 -1
  3. package/dist/svg-path-simplify.esm.js +1649 -1205
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +1649 -1204
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +301 -637
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +8 -9
  10. package/drawing.svg +62 -0
  11. package/index.html +13 -8
  12. package/package.json +1 -1
  13. package/src/index.js +3 -1
  14. package/src/pathData_get_intersections.js +777 -0
  15. package/src/pathData_offset.js +201 -0
  16. package/src/pathData_simplify_cubic.js +19 -21
  17. package/src/pathData_simplify_cubic_extrapolate.js +147 -8
  18. package/src/pathData_simplify_cubicsToArcs.js +65 -21
  19. package/src/pathSimplify-main.js +267 -64
  20. package/src/pathSimplify-presets.js +1 -1
  21. package/src/poly-fit-curve-schneider.js +171 -132
  22. package/src/poly-fit-curve-schneider_check_bulge.js +131 -0
  23. package/src/poly-offset.js +133 -0
  24. package/src/simplify_poly_RC.js +29 -7
  25. package/src/svg-getAttributes.js +1 -1
  26. package/src/svgii/geometry.js +130 -27
  27. package/src/svgii/geometry_bbox.js +1 -1
  28. package/src/svgii/geometry_length.js +1 -1
  29. package/src/svgii/pathData_analyze.js +6 -28
  30. package/src/svgii/pathData_convert.js +63 -15
  31. package/src/svgii/pathData_remove_collinear.js +32 -36
  32. package/src/svgii/pathData_reorder.js +3 -2
  33. package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
  34. package/src/svgii/pathData_stringify.js +132 -6
  35. package/src/svgii/pathData_toPolygon.js +33 -23
  36. package/src/svgii/poly_analyze.js +272 -125
  37. package/src/svgii/poly_analyze_cleanup.js +311 -0
  38. package/src/svgii/poly_analyze_getTangents.js +125 -0
  39. package/src/svgii/poly_analyze_get_chunks.js +3 -1
  40. package/src/svgii/poly_normalize.js +21 -8
  41. package/src/svgii/poly_to_pathdata.js +477 -8
  42. package/src/svgii/rounding.js +35 -14
  43. package/src/svgii/svg_cleanup.js +44 -51
  44. package/src/svgii/svg_cleanup_convertPathLength.js +5 -0
  45. package/src/svgii/svg_cleanup_normalize_transforms.js +2 -2
  46. package/src/svgii/visualize.js +33 -2
  47. package/v/0.4.5/index.html +1040 -0
  48. package/v/0.4.5/svg-path-simplify.js +13227 -0
@@ -18,6 +18,8 @@ import { simplifyRC } from "../simplify_poly_RC";
18
18
  import { getPolygonArea } from "./geometry_area";
19
19
  import { pathDataToD } from "./pathData_stringify";
20
20
  import { fixIntersectingCpts } from "../pathData_simplify_harmonize_cpts";
21
+ import { convertPathData } from "./pathData_convert";
22
+ import { combineCubicPairs } from "../pathData_simplify_cubic";
21
23
 
22
24
 
23
25
 
@@ -35,6 +37,7 @@ export function simplifyPolygonToPathData(pts, {
35
37
  tolerance = 1,
36
38
  simplifyRD = 1,
37
39
  simplifyRDP = 1,
40
+ isClosed = true,
38
41
  } = {}) {
39
42
 
40
43
 
@@ -121,6 +124,28 @@ export function simplifyPolygonToPathData(pts, {
121
124
  // remove colinear
122
125
  //pts = simplifyRC(pts)
123
126
 
127
+ keepExtremes = false;
128
+ keepCorners = false;
129
+
130
+ keepExtremes = true;
131
+ keepCorners = true;
132
+
133
+ // check if closed
134
+ /*
135
+ let bb = getPolyBBox(pts)
136
+ let thresh = (bb.width+bb.height)*0.25
137
+ let dist0 = getDistManhattan(pts[0], pts[pts.length-1])
138
+
139
+ //let isClosed = dist0<=thresh;
140
+ //isClosed = false;
141
+ //console.log('isClosed', isClosed, 'dist0', dist0, thresh);
142
+ */
143
+
144
+ // copy 1st first to end
145
+ if (isClosed) {
146
+ pts.push(pts[0])
147
+ }
148
+
124
149
  // get topology of poly
125
150
  let polyAnalyzed = !keepExtremes && !keepCorners ? pts : analyzePoly(pts, {
126
151
  debug: false
@@ -131,27 +156,471 @@ export function simplifyPolygonToPathData(pts, {
131
156
  //console.log(polyAnalyzed, polyAnalyzed2);
132
157
 
133
158
  // split into segment chunks
134
- let chunks = !keepExtremes && !keepCorners ? [polyAnalyzed] : getPolyChunks(polyAnalyzed, { keepCorners, keepExtremes, keepInflections });
159
+ //let chunks = !keepExtremes && !keepCorners ? [polyAnalyzed] : getPolyChunks(polyAnalyzed, { keepCorners, keepExtremes, keepInflections });
160
+
161
+ let chunks = getPolyChunks(polyAnalyzed, { keepCorners, keepExtremes, keepInflections: true });
135
162
 
136
163
 
137
164
  // Schneider curve fit
138
165
  let threshold = width && height ? (width + height) / 2 * 0.004 * tolerance : 2.5
166
+ threshold = width && height ? (width + height) / 2 * 0.004 * tolerance : 2.5
167
+ // console.log('tolerance', tolerance, threshold);
168
+
139
169
 
140
170
  //threshold = 2
171
+ let useToplogy = true
172
+
173
+ if (useToplogy) {
174
+
175
+ polyPath = simplifyPolyChunksTopology(chunks, {
176
+ closed,
177
+ tolerance: threshold,
178
+ keepCorners,
179
+ keepExtremes: true,
180
+ });
181
+ } else {
182
+
183
+ polyPath = simplifyPolyChunks(chunks, {
184
+ closed,
185
+ tolerance: threshold,
186
+ keepCorners,
187
+ keepExtremes: true,
188
+ });
189
+ }
190
+
141
191
 
142
- polyPath = simplifyPolyChunks(chunks, {
143
- closed,
144
- tolerance: threshold,
145
- keepCorners,
146
- keepExtremes: true,
147
- });
148
192
 
149
- polyPath = fixIntersectingCpts(polyPath);
193
+
194
+ //polyPath = fixIntersectingCpts(polyPath);
150
195
 
151
196
  return polyPath;
152
197
  }
153
198
 
154
199
 
200
+ /**
201
+ * topology based curve fit
202
+ */
203
+ export function simplifyPolyChunksTopology(chunks = [], {
204
+ closed = true,
205
+ keepCorners = true,
206
+ tolerance = 1,
207
+ } = {}) {
208
+
209
+ console.log(chunks);
210
+
211
+
212
+ let l = chunks.length;
213
+
214
+ // new pathData
215
+ //let M = {x:chunks[0][0].x}
216
+ let pathData = [{ type: 'M', values: [chunks[0][0].x, chunks[0][0].y] }]
217
+
218
+ // loop chunks
219
+ for (let i = 0; i < l; i++) {
220
+
221
+ let chunkPrev = i > 0 ? chunks[i - 1] : (closed ? chunks[l - 1] : null);
222
+ let chunk = chunks[i];
223
+ let chunkN = chunks[i + 1] ? chunks[i + 1] : (closed ? chunks[0] : null)
224
+ let segments = []
225
+
226
+ // add from next command
227
+ if (chunkN) {
228
+ chunk.push(chunkN[0])
229
+ }
230
+
231
+ let chunklen = chunk.length
232
+ let hasInflection = false;
233
+ let segments_1 = [], segments_2 = [], segments_3 = [];
234
+ let segsRequired = 3;
235
+
236
+ // 1st point
237
+ let p1 = chunk[0]
238
+ // last point in chunk
239
+ let p2 = chunk[chunklen - 1]
240
+ let isLinetoSeg = false
241
+
242
+ // nothing to simplify - lineto
243
+
244
+ // if (chunklen < 2 || (chunklen === 2 && (chunk[1].isExtreme || i===l-1 && !closed) )) {
245
+
246
+
247
+
248
+ if (chunklen < 2 || (chunklen === 2 && (chunk[1].isExtreme))) {
249
+ //segments_1 = chunk.map(com => { return { type: 'L', values: [com.x, com.y] } })
250
+
251
+ isLinetoSeg = true
252
+
253
+ if (chunklen === 2) {
254
+ segsRequired = 2;
255
+ segments_2 = [
256
+ {
257
+ type: 'L',
258
+ values: [p1.x, p1.y],
259
+ p0: chunkPrev,
260
+ p: p1,
261
+ },
262
+ {
263
+ type: 'L',
264
+ values: [chunk[1].x, chunk[1].y],
265
+ p0: p1,
266
+ p: p2,
267
+ }
268
+ ]
269
+ } else {
270
+ segsRequired = 1;
271
+ segments_1 = [
272
+ {
273
+ type: 'L',
274
+ values: [p1.x, p1.y],
275
+ p0: chunkPrev,
276
+ p: p2,
277
+ },
278
+ ]
279
+ }
280
+
281
+ } else {
282
+ //renderPoint(markers, chunk[0], 'green')
283
+
284
+ // point before inflection
285
+ let p3 = chunk[chunklen - 2];
286
+
287
+
288
+ let extremes = chunk.filter(pt => pt.isExtreme)
289
+ let semiExtremes = chunk.filter(pt => pt.isSemiExtreme)
290
+ let corners = chunk.filter(pt => pt.isCorner)
291
+ let inflections = chunk.filter(pt => pt.isDirChange && !pt.isCorner && !pt.isExtreme)
292
+ hasInflection = inflections.length && inflections[0] !== p1
293
+
294
+
295
+ let idxMid = Math.floor(chunklen * 0.5)
296
+ let pMid = semiExtremes.length ? semiExtremes[Math.floor(semiExtremes.length * 0.5)] : chunk[idxMid]
297
+
298
+ //check if mid is close to end
299
+ let dist0 = getDistManhattan(p1, p3)
300
+ let dist1 = getDistManhattan(pMid, p3)
301
+ let dist2 = getDistManhattan(pMid, p1)
302
+ let thresh = dist0 * 0.25
303
+ let shortMidSegment = dist1 < thresh || dist2 < thresh
304
+
305
+
306
+ /**
307
+ * we have 3 modes
308
+ * 1 segment: only 1 segment between extremes/corners
309
+ * 2 segments: semiextreme/mid in between
310
+ * 3 segments: inflection
311
+ */
312
+
313
+ segsRequired = shortMidSegment ? (!hasInflection ? 1 : 2) : (hasInflection ? 3 : 2)
314
+
315
+ //if(shortMidSegment){renderPoint(markers, pMid, 'red', '2%')}
316
+ let cp1_1 = p1.tangentR;
317
+ let cp2_1 = pMid.tangentL;
318
+ let p_1 = pMid
319
+
320
+ // renderPoint(markers, pMid, 'orange', '2%')
321
+
322
+ let cp2_2 = p2.tangentL;
323
+ let cp1_2 = pMid.tangentR;
324
+ let p_2 = p2
325
+
326
+ let cp1_3 = null
327
+ let cp2_3 = null
328
+ let p_3 = null
329
+
330
+ // general extrapolation
331
+ let t = 0.666;
332
+ let d = ''
333
+ let ptI_1 = null, ptI_2 = null, ptI_3 = null;
334
+
335
+
336
+ // 1 segment
337
+ ptI_1 = checkLineIntersection(p1, p1.tangentR, p2, p2.tangentL, false, true)
338
+ if (ptI_1) {
339
+ cp1_1 = interpolate(p1, ptI_1, t)
340
+ cp2_1 = interpolate(p2, ptI_1, t)
341
+ p_1 = p2;
342
+
343
+ segments_1 = [
344
+ //{type: 'M', values: [p1.x, p1.y]},
345
+ {
346
+ type: 'C',
347
+ values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, p_1.x, p_1.y],
348
+ p0: p1,
349
+ cp1: cp1_1,
350
+ cp2: cp2_1,
351
+ p: p_1,
352
+ }
353
+ ];
354
+
355
+ //d = pathDataToD( [{ type: 'M', values: [p1.x, p1.y] },...segments_1])
356
+ //renderPath(markers, d)
357
+ //console.log(d);
358
+ }
359
+
360
+
361
+ // 2 segments
362
+ ptI_1 = checkLineIntersection(p1, p1.tangentR, pMid, pMid.tangentL, false, true)
363
+ ptI_2 = checkLineIntersection(p2, p2.tangentL, pMid, pMid.tangentR, false, true)
364
+
365
+ if (ptI_1 && ptI_2) {
366
+ cp1_1 = interpolate(p1, ptI_1, t)
367
+ cp2_1 = interpolate(pMid, ptI_1, t)
368
+ p_1 = pMid
369
+
370
+ cp1_2 = interpolate(pMid, ptI_2, t)
371
+ cp2_2 = interpolate(p2, ptI_2, t)
372
+ p_2 = p2
373
+
374
+ segments_2 = [
375
+ //{type: 'M', values: [p1.x, p1.y]},
376
+ {
377
+ type: 'C',
378
+ values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, p_1.x, p_1.y],
379
+ p0: p1,
380
+ cp1: cp1_1,
381
+ cp2: cp2_1,
382
+ p: p_1,
383
+ isExtreme: p_1.isExtreme
384
+ },
385
+ {
386
+ type: 'C',
387
+ values: [cp1_2.x, cp1_2.y, cp2_2.x, cp2_2.y, p_2.x, p_2.y],
388
+ p0: p_1,
389
+ cp1: cp1_2,
390
+ cp2: cp2_2,
391
+ p: p_2,
392
+ isExtreme: p_2.isExtreme
393
+
394
+ //isExtreme: true
395
+ },
396
+ ];
397
+
398
+ //d = pathDataToD( [{ type: 'M', values: [p1.x, p1.y] },...segments_2])
399
+ //renderPath(markers, d)
400
+
401
+ }
402
+
403
+
404
+ // 3 segments
405
+ //hasInflection=true
406
+ if (hasInflection) {
407
+
408
+ // get pt between dir change and mid
409
+ let idx_3_4 = Math.floor(chunklen * 0.75)
410
+ p3 = chunk[idx_3_4]
411
+ ptI_3 = checkLineIntersection(p3, p3.tangentR, p2, p2.tangentL, false, false)
412
+
413
+ if (ptI_3) {
414
+ let tangentR_beforeDirChange = interpolate(p3, ptI_3, t)
415
+
416
+ // extend right tangent
417
+ p3.tangentR.x = tangentR_beforeDirChange.x
418
+ p3.tangentR.y = tangentR_beforeDirChange.y
419
+
420
+ // extend dir change tangent
421
+ let tangentL_dirChange = interpolate(p2, ptI_3, t)
422
+ p2.tangentL.x = tangentL_dirChange.x
423
+ p2.tangentL.y = tangentL_dirChange.y
424
+ } else {
425
+
426
+ if (p3 === p2) {
427
+
428
+ idx_3_4 = Math.floor(chunklen * 0.3)
429
+ p3 = chunk[idx_3_4]
430
+
431
+ //renderPoint(markers, p2, 'purple', '3%')
432
+ //renderPoint(markers, p3, 'orange', '2%')
433
+
434
+ }
435
+ let ptI_4 = checkLineIntersection(p3, p3.tangentR, p2, p2.tangentL, false, false)
436
+ //renderPoint(markers, ptI_4, 'orange', '2%')
437
+ //renderPoint(markers, p3, 'blue', '1%')
438
+ //renderPoint(markers, p1, 'magenta', '2%')
439
+
440
+ //let ptI_4 = checkLineIntersection(p3, p3.tangentL, p1, p1.tangentR, false, false)
441
+ //renderPoint(markers, ptI_4, 'blue', '1%')
442
+
443
+
444
+ //cp1_1 = interpolate(p1, ptI_4, t )
445
+ //cp2_1 = interpolate(p1, ptI_4, t )
446
+
447
+ cp1_1 = interpolate(p1, p1.tangentR, 1.333)
448
+ cp2_1 = interpolate(p2, p2.tangentL, 1.333)
449
+
450
+
451
+ segments_3 = [
452
+ {
453
+ type: 'C',
454
+ values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, p2.x, p2.y],
455
+ p0: p1,
456
+ cp1: cp1_1,
457
+ cp2: cp2_1,
458
+ p: p2,
459
+ isExtreme: p2.isExtreme
460
+ //isExtreme: true
461
+ },
462
+ ];
463
+
464
+
465
+ //let ptI_4 = checkLineIntersection(p2, p2.tangentL, p2, p2.tangentL, false, false)
466
+ //renderPoint(markers, ptI_3, 'orange', '2%')
467
+
468
+ /*
469
+ let tangentR_beforeDirChange = interpolate(p3, ptI_3, t)
470
+
471
+ // extend right tangent
472
+ p3.tangentR.x = tangentR_beforeDirChange.x
473
+ p3.tangentR.y = tangentR_beforeDirChange.y
474
+
475
+ let tangentL_dirChange = interpolate(p2, ptI_3, t)
476
+ p2.tangentL.x = tangentL_dirChange.x
477
+ p2.tangentL.y = tangentL_dirChange.y
478
+ */
479
+
480
+ d = pathDataToD([{ type: 'M', values: [p1.x, p1.y] }, ...segments_3])
481
+ //renderPath(markers, d)
482
+
483
+ }
484
+
485
+
486
+
487
+ cp1_3 = p3.tangentR
488
+ cp2_3 = p2.tangentL
489
+ p_3 = p2
490
+
491
+ ptI_1 = checkLineIntersection(p1, p1.tangentR, pMid, pMid.tangentL, false, true)
492
+ ptI_2 = checkLineIntersection(pMid, pMid.tangentR, p3, p3.tangentL, false, true)
493
+
494
+ if (!ptI_3) {
495
+ //console.log(chunk, pMid, pMid.tangentR, 'p3', p3, p3.tangentL);
496
+ }
497
+
498
+ if (ptI_1 && ptI_2 && ptI_3) {
499
+
500
+ cp1_1 = interpolate(p1, ptI_1, t)
501
+ cp2_1 = interpolate(pMid, ptI_1, t)
502
+ p_1 = pMid
503
+
504
+ cp1_2 = interpolate(pMid, ptI_2, t)
505
+ cp2_2 = interpolate(p3, ptI_2, t)
506
+ p_2 = p3
507
+
508
+ segments_3 = [
509
+ {
510
+ type: 'C',
511
+ values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, p_1.x, p_1.y],
512
+ p0: p1,
513
+ cp1: cp1_1,
514
+ cp2: cp2_1,
515
+ p: p_1,
516
+ isExtreme: p_1.isExtreme
517
+ //isExtreme: true
518
+ },
519
+ {
520
+ type: 'C',
521
+ values: [cp1_2.x, cp1_2.y, cp2_2.x, cp2_2.y, p_2.x, p_2.y],
522
+ p0: p_1,
523
+ cp1: cp1_2,
524
+ cp2: cp2_2,
525
+ p: p_2,
526
+ },
527
+ {
528
+ type: 'C',
529
+ values: [cp1_3.x, cp1_3.y, cp2_3.x, cp2_3.y, p_3.x, p_3.y],
530
+ p0: p_2,
531
+ cp1: cp1_3,
532
+ cp2: cp2_3,
533
+ p: p_3,
534
+ isExtreme: p_3.isExtreme
535
+ //isExtreme: true
536
+ }
537
+ ];
538
+
539
+ d = pathDataToD([{ type: 'M', values: [p1.x, p1.y] }, ...segments_3])
540
+ //renderPath(markers, d)
541
+
542
+
543
+ } else {
544
+ //console.log('incomplete', ptI_1, ptI_2, ptI_3);
545
+ }
546
+
547
+ }
548
+
549
+ //console.log('chunk', chunk, extremes, semiExtremes, corners, inflections);
550
+
551
+ }
552
+
553
+ if (segsRequired === 1) {
554
+ segments = segments_1
555
+ } else if (segsRequired === 2 && segments_2.length) {
556
+ segments = segments_2
557
+ }
558
+ else {
559
+ }
560
+
561
+ segments = segments_3.length ? segments_3 : segments_2
562
+ //segments = segments_2.length ? segments_2 : segments_3
563
+ //segments = segments_3
564
+
565
+
566
+ /**
567
+ * try simplification
568
+ */
569
+ let simplify = false
570
+ /*
571
+ if (simplify && !isLinetoSeg && segments.length > 1) {
572
+
573
+ let com1 = segments[0]
574
+ let com2 = segments[1]
575
+
576
+ tolerance = 1.1
577
+ let combined = combineCubicPairs(com1, com2, { tolerance })
578
+ //let error = combined[0].error;
579
+ let error = 0;
580
+ let comsSimp =[]
581
+
582
+ console.log('!!!combined', segments.length, combined);
583
+
584
+ // success
585
+ if (combined.length === 1) {
586
+
587
+ if(segments.length === 2){
588
+ segments = combined
589
+ }
590
+
591
+ let com = combined[0]
592
+ }
593
+
594
+ }
595
+ */
596
+
597
+ // remove first segment to connect to last segment
598
+ pathData.push(...segments)
599
+
600
+ }
601
+
602
+
603
+
604
+ if (closed) {
605
+ pathData.push({ type: 'Z', values: [] })
606
+ }
607
+ //console.log('!!!pathData from poly', pathData);
608
+
609
+
610
+ //pathData2 = convertPathData(pathData2, { toRelative: true, decimals: 5 });
611
+ //let d = pathDataToD(pathData2);
612
+ //renderPath(markers, d, 'green', '0.75%', '0.5')
613
+ //console.log(d);
614
+
615
+
616
+ // refine extremes
617
+ return pathData
618
+
619
+ }
620
+
621
+
622
+
623
+
155
624
  /**
156
625
  * convert polygon chunks
157
626
  * to cubic beziers
@@ -19,7 +19,7 @@ export function roundPathData(pathData, decimalsGlobal = -1) {
19
19
 
20
20
  let len = pathData.length;
21
21
  let decimals = decimalsGlobal
22
- let decimalsArc = decimals < 3 ? decimals+2 : decimals
22
+ let decimalsArc = decimals < 3 ? decimals + 1 : decimals
23
23
  //decimalsArc = decimals
24
24
  //console.log(decimalsArc);
25
25
 
@@ -64,7 +64,7 @@ export function detectAccuracyPoly(pts) {
64
64
  }
65
65
  }
66
66
 
67
- let dim_min = dims.sort()
67
+ let dim_min = dims.sort((a,b)=>a-b)
68
68
  let sliceIdx = Math.ceil(dim_min.length / 8);
69
69
  dim_min = dim_min.slice(0, sliceIdx);
70
70
  let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
@@ -90,36 +90,57 @@ export function detectAccuracy(pathData) {
90
90
  let com = pathData[i];
91
91
  let { type, values, p0 = null, p = null, dimA = 0 } = com;
92
92
 
93
- // use existing averave dimension value or calculate
93
+ // use existing average dimension value or calculate
94
94
  if (values.length && p && p0) {
95
- //console.log(com);
96
95
  dimA = dimA ? dimA : getDistManhattan(p0, p);
97
96
 
98
- //let dimA = +getDistAv(p0, p).toFixed(8)
99
- //console.log('dimA', dimA, com.dimA, type);
97
+ //who would have thought we need an exception for arcs?
98
+ if (type === 'A') dimA *= 0.5
100
99
 
101
- if (dimA) dims.push(dimA);
100
+ if (dimA) dims.push(+dimA.toFixed(8));
102
101
  if (dimA && dimA < minDim) minDim = dimA;
103
- //if (dimA && dimA > maxDim) maxDim = dimA;
104
102
  }
105
103
 
106
104
  }
107
105
 
108
- let dim_min = dims.sort()
109
- //console.log('dim_min', dim_min);
110
106
 
111
- let sliceIdx = Math.ceil(dim_min.length / 6);
112
- dim_min = dim_min.slice(0, sliceIdx);
113
- let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
107
+ dims = dims.sort((a,b)=>a-b)
108
+ let len = dims.length;
109
+ let dim_mid = dims[Math.floor(len * 0.5)]
110
+
111
+ // smallest 25% of values
112
+ let idx_q = Math.ceil(len * 0.25);
113
+ let dims_min = dims.slice(0, idx_q);
114
+
115
+ // average smallest values with mid value
116
+ let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
117
+
114
118
 
115
119
  let threshold = 75
116
- let decimalsAuto = minVal > threshold * 1.5 ? 0 : Math.floor(threshold / minVal).toString().length
120
+ let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length
117
121
 
118
122
  // clamp
119
123
  return Math.min(Math.max(0, decimalsAuto), 8)
120
124
 
121
125
  }
122
126
 
127
+
128
+
129
+ export function roundPoly(poly = [], decimals = 3) {
130
+ if (!poly.length || decimals < 0) return poly;
131
+ poly = poly.map(pt => roundPoint(pt, decimals));
132
+ return poly
133
+ }
134
+
135
+
136
+ export function roundPoint(pt = {}, decimals = 3) {
137
+ if (pt.x === undefined || pt.y === undefined || decimals < 0) return pt;
138
+ pt.x=roundTo(pt.x, decimals)
139
+ pt.y=roundTo(pt.y, decimals)
140
+ return pt
141
+ //return { x: roundTo(pt.x, decimals), y: roundTo(pt.y, decimals) }
142
+ }
143
+
123
144
  /**
124
145
  * rounding helper
125
146
  * allows for quantized rounding