@opendata-ai/openchart-engine 8.0.0-rc.9 → 8.1.0
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/dist/index.d.ts +175 -12
- package/dist/index.js +917 -168
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/__test-fixtures__/specs.ts +0 -182
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +0 -2401
- package/src/__tests__/annotation-behavior.test.ts +0 -852
- package/src/__tests__/axes.test.ts +0 -1579
- package/src/__tests__/compile-animation.test.ts +0 -161
- package/src/__tests__/compile-chart.test.ts +0 -1303
- package/src/__tests__/compile-footnotes.test.ts +0 -277
- package/src/__tests__/compile-layer.test.ts +0 -969
- package/src/__tests__/compile-snapshot.test.ts +0 -156
- package/src/__tests__/compound-labels.test.ts +0 -147
- package/src/__tests__/dimensions.test.ts +0 -918
- package/src/__tests__/encoding-sugar.test.ts +0 -390
- package/src/__tests__/facet.test.ts +0 -215
- package/src/__tests__/helpers/invariants.ts +0 -299
- package/src/__tests__/known-bugs.test.ts +0 -566
- package/src/__tests__/layout-fuzz.test.ts +0 -358
- package/src/__tests__/layout-invariants.test.ts +0 -535
- package/src/__tests__/legend.test.ts +0 -721
- package/src/__tests__/mark-keys.test.ts +0 -294
- package/src/__tests__/ranking-recipes.test.ts +0 -260
- package/src/__tests__/rotation.test.ts +0 -83
- package/src/__tests__/scales.test.ts +0 -323
- package/src/__tests__/series-search.test.ts +0 -92
- package/src/__tests__/spec-sugar.test.ts +0 -801
- package/src/__tests__/you-draw-it.test.ts +0 -160
- package/src/annotations/__tests__/compute.test.ts +0 -1672
- package/src/annotations/__tests__/geometry.test.ts +0 -66
- package/src/annotations/__tests__/placement-fuzz.test.ts +0 -256
- package/src/annotations/__tests__/placement-invariants.test.ts +0 -345
- package/src/annotations/__tests__/placement.test.ts +0 -316
- package/src/annotations/__tests__/resolve-text.test.ts +0 -788
- package/src/annotations/__tests__/rich-text.test.ts +0 -91
- package/src/annotations/__tests__/thinning.test.ts +0 -285
- package/src/annotations/collisions.ts +0 -326
- package/src/annotations/compute.ts +0 -333
- package/src/annotations/constants.ts +0 -162
- package/src/annotations/geometry.ts +0 -376
- package/src/annotations/move.ts +0 -135
- package/src/annotations/placement.ts +0 -636
- package/src/annotations/position.ts +0 -131
- package/src/annotations/resolve-range.ts +0 -117
- package/src/annotations/resolve-refline.ts +0 -157
- package/src/annotations/resolve-text.ts +0 -545
- package/src/annotations/rich-text.ts +0 -65
- package/src/annotations/thinning.ts +0 -190
- package/src/barlist/__tests__/compile-barlist.test.ts +0 -200
- package/src/barlist/compile-barlist.ts +0 -391
- package/src/barlist/types.ts +0 -28
- package/src/charts/__tests__/post-process.test.ts +0 -237
- package/src/charts/__tests__/registry.test.ts +0 -116
- package/src/charts/__tests__/utils.test.ts +0 -195
- package/src/charts/_shared/__tests__/density-filter.test.ts +0 -32
- package/src/charts/_shared/density-filter.ts +0 -26
- package/src/charts/_shared/format-label-value.ts +0 -16
- package/src/charts/bar/__tests__/compute.test.ts +0 -934
- package/src/charts/bar/__tests__/gradient-orient.test.ts +0 -127
- package/src/charts/bar/__tests__/labels.test.ts +0 -292
- package/src/charts/bar/compute.ts +0 -539
- package/src/charts/bar/index.ts +0 -60
- package/src/charts/bar/labels.ts +0 -311
- package/src/charts/beeswarm/__tests__/compute.test.ts +0 -280
- package/src/charts/beeswarm/__tests__/dodge.test.ts +0 -148
- package/src/charts/beeswarm/compute.ts +0 -203
- package/src/charts/beeswarm/dodge.ts +0 -155
- package/src/charts/beeswarm/index.ts +0 -32
- package/src/charts/builtin.ts +0 -75
- package/src/charts/calendar/__tests__/compute.test.ts +0 -417
- package/src/charts/calendar/compute.ts +0 -319
- package/src/charts/calendar/index.ts +0 -32
- package/src/charts/column/__tests__/compute.test.ts +0 -540
- package/src/charts/column/__tests__/labels.test.ts +0 -179
- package/src/charts/column/compute.ts +0 -493
- package/src/charts/column/index.ts +0 -53
- package/src/charts/column/labels.ts +0 -229
- package/src/charts/dot/__tests__/compute.test.ts +0 -380
- package/src/charts/dot/__tests__/labels.test.ts +0 -98
- package/src/charts/dot/compute.ts +0 -285
- package/src/charts/dot/index.ts +0 -62
- package/src/charts/dot/labels.ts +0 -121
- package/src/charts/line/__tests__/compute.test.ts +0 -1194
- package/src/charts/line/__tests__/curves.test.ts +0 -51
- package/src/charts/line/__tests__/labels.test.ts +0 -94
- package/src/charts/line/__tests__/mark-options.test.ts +0 -175
- package/src/charts/line/area.ts +0 -484
- package/src/charts/line/compute.ts +0 -356
- package/src/charts/line/curves.ts +0 -85
- package/src/charts/line/index.ts +0 -176
- package/src/charts/line/labels.ts +0 -177
- package/src/charts/parliament/__tests__/compute.test.ts +0 -411
- package/src/charts/parliament/compute.ts +0 -313
- package/src/charts/parliament/index.ts +0 -32
- package/src/charts/pie/__tests__/compute.test.ts +0 -384
- package/src/charts/pie/__tests__/labels.test.ts +0 -132
- package/src/charts/pie/compute.ts +0 -324
- package/src/charts/pie/index.ts +0 -49
- package/src/charts/pie/labels.ts +0 -143
- package/src/charts/post-process.ts +0 -229
- package/src/charts/range/__tests__/compute.test.ts +0 -486
- package/src/charts/range/compute.ts +0 -273
- package/src/charts/range/index.ts +0 -82
- package/src/charts/range/labels.ts +0 -169
- package/src/charts/rect/__tests__/compute.test.ts +0 -120
- package/src/charts/rect/index.ts +0 -127
- package/src/charts/registry.ts +0 -72
- package/src/charts/rule/index.ts +0 -139
- package/src/charts/scatter/__tests__/compute.test.ts +0 -488
- package/src/charts/scatter/__tests__/trendline.test.ts +0 -191
- package/src/charts/scatter/compute.ts +0 -192
- package/src/charts/scatter/index.ts +0 -46
- package/src/charts/scatter/trendline.ts +0 -100
- package/src/charts/text/__tests__/compute.test.ts +0 -307
- package/src/charts/text/index.ts +0 -141
- package/src/charts/tick/__tests__/compute.test.ts +0 -75
- package/src/charts/tick/index.ts +0 -99
- package/src/charts/utils.ts +0 -170
- package/src/charts/waffle/__tests__/compute.test.ts +0 -305
- package/src/charts/waffle/compute.ts +0 -197
- package/src/charts/waffle/index.ts +0 -31
- package/src/compile/__tests__/color-scale-range.test.ts +0 -158
- package/src/compile/__tests__/color-scale-reverse.test.ts +0 -89
- package/src/compile/__tests__/data-clip.test.ts +0 -127
- package/src/compile/__tests__/fill-patterns.test.ts +0 -154
- package/src/compile/__tests__/size-scale.test.ts +0 -108
- package/src/compile/__tests__/watermark-obstacle.test.ts +0 -93
- package/src/compile/color-scale-range.ts +0 -101
- package/src/compile/data-clip.ts +0 -48
- package/src/compile/fill-patterns.ts +0 -74
- package/src/compile/layer.ts +0 -630
- package/src/compile/size-scale.ts +0 -141
- package/src/compile/spec-sugar.ts +0 -758
- package/src/compile/watermark-obstacle.ts +0 -54
- package/src/compile/you-draw-it.ts +0 -123
- package/src/compile.ts +0 -1503
- package/src/compiler/__tests__/a11y-warnings.test.ts +0 -148
- package/src/compiler/__tests__/animation.test.ts +0 -177
- package/src/compiler/__tests__/compile.test.ts +0 -87
- package/src/compiler/__tests__/fixtures/vl-parity/axis-null.json +0 -19
- package/src/compiler/__tests__/fixtures/vl-parity/color-value.json +0 -20
- package/src/compiler/__tests__/fixtures/vl-parity/count-aggregate.json +0 -22
- package/src/compiler/__tests__/fixtures/vl-parity/data-values.json +0 -19
- package/src/compiler/__tests__/fixtures/vl-parity/layer-axis-null.json +0 -14
- package/src/compiler/__tests__/fixtures/vl-parity/layer-count.json +0 -34
- package/src/compiler/__tests__/fixtures/vl-parity/layer-data-values.json +0 -22
- package/src/compiler/__tests__/fixtures/vl-parity/layer-legend-null.json +0 -41
- package/src/compiler/__tests__/fixtures/vl-parity/layer-scheme.json +0 -38
- package/src/compiler/__tests__/fixtures/vl-parity/layer-sort.json +0 -28
- package/src/compiler/__tests__/fixtures/vl-parity/layer-theta.json +0 -28
- package/src/compiler/__tests__/fixtures/vl-parity/layer-title.json +0 -30
- package/src/compiler/__tests__/fixtures/vl-parity/layer-value-def.json +0 -29
- package/src/compiler/__tests__/fixtures/vl-parity/legend-null.json +0 -32
- package/src/compiler/__tests__/fixtures/vl-parity/scheme.json +0 -25
- package/src/compiler/__tests__/fixtures/vl-parity/sort-by-value.json +0 -19
- package/src/compiler/__tests__/fixtures/vl-parity/theta-arc.json +0 -19
- package/src/compiler/__tests__/fixtures/vl-parity/title-subtitle.json +0 -21
- package/src/compiler/__tests__/fixtures/vl-parity/width-height.json +0 -23
- package/src/compiler/__tests__/keys.test.ts +0 -83
- package/src/compiler/__tests__/normalize-highlight.test.ts +0 -103
- package/src/compiler/__tests__/normalize.test.ts +0 -399
- package/src/compiler/__tests__/sparkline-defaults.test.ts +0 -153
- package/src/compiler/__tests__/validate-did-you-mean.test.ts +0 -170
- package/src/compiler/__tests__/validate-highlight.test.ts +0 -112
- package/src/compiler/__tests__/validate-near-miss.test.ts +0 -251
- package/src/compiler/__tests__/validate.test.ts +0 -629
- package/src/compiler/__tests__/vl-parity.test.ts +0 -68
- package/src/compiler/a11y-warnings.ts +0 -110
- package/src/compiler/animation.ts +0 -142
- package/src/compiler/index.ts +0 -47
- package/src/compiler/keys.ts +0 -55
- package/src/compiler/normalize.ts +0 -734
- package/src/compiler/sparkline-defaults.ts +0 -138
- package/src/compiler/types.ts +0 -241
- package/src/compiler/validate.ts +0 -2041
- package/src/endpoint-labels/__tests__/compute.test.ts +0 -587
- package/src/endpoint-labels/compute.ts +0 -621
- package/src/endpoint-labels/constants.ts +0 -54
- package/src/endpoint-labels/format.ts +0 -60
- package/src/endpoint-labels/predict.ts +0 -139
- package/src/format/__tests__/field-format.test.ts +0 -164
- package/src/format/field-format.ts +0 -27
- package/src/geo/__tests__/compile-map.test.ts +0 -1128
- package/src/geo/compile-map.ts +0 -1376
- package/src/geo/join.ts +0 -115
- package/src/geo/projections.ts +0 -65
- package/src/geo/types.ts +0 -26
- package/src/graphs/__tests__/community.test.ts +0 -228
- package/src/graphs/__tests__/compile-graph.test.ts +0 -396
- package/src/graphs/__tests__/encoding.test.ts +0 -415
- package/src/graphs/community.ts +0 -92
- package/src/graphs/compile-graph.ts +0 -329
- package/src/graphs/encoding.ts +0 -356
- package/src/graphs/types.ts +0 -106
- package/src/index.ts +0 -177
- package/src/layout/__tests__/metrics.test.ts +0 -97
- package/src/layout/__tests__/scales-highlight.test.ts +0 -192
- package/src/layout/axes/rotation.ts +0 -152
- package/src/layout/axes/thinning.ts +0 -98
- package/src/layout/axes/ticks.ts +0 -409
- package/src/layout/axes.ts +0 -757
- package/src/layout/dimensions.ts +0 -912
- package/src/layout/facet.ts +0 -91
- package/src/layout/gridlines.ts +0 -84
- package/src/layout/metrics.ts +0 -132
- package/src/layout/plan.ts +0 -631
- package/src/layout/scales.ts +0 -1053
- package/src/layout/shared.ts +0 -44
- package/src/legend/__tests__/continuous.test.ts +0 -318
- package/src/legend/__tests__/redundancy.test.ts +0 -108
- package/src/legend/__tests__/size.test.ts +0 -348
- package/src/legend/__tests__/suppression.test.ts +0 -382
- package/src/legend/compute.ts +0 -748
- package/src/legend/continuous.ts +0 -327
- package/src/legend/size.ts +0 -407
- package/src/legend/suppression.ts +0 -226
- package/src/legend/wrap.ts +0 -117
- package/src/sankey/__tests__/compile-sankey.test.ts +0 -458
- package/src/sankey/__tests__/layout.test.ts +0 -165
- package/src/sankey/__tests__/node-label-wrap.test.ts +0 -114
- package/src/sankey/__tests__/node-sort.test.ts +0 -45
- package/src/sankey/compile-sankey.ts +0 -706
- package/src/sankey/layout.ts +0 -184
- package/src/sankey/types.ts +0 -41
- package/src/tables/__tests__/bar-column.test.ts +0 -147
- package/src/tables/__tests__/category-colors.test.ts +0 -230
- package/src/tables/__tests__/compile-table.test.ts +0 -236
- package/src/tables/__tests__/format-cells.test.ts +0 -143
- package/src/tables/__tests__/heatmap.test.ts +0 -148
- package/src/tables/__tests__/pagination.test.ts +0 -78
- package/src/tables/__tests__/search.test.ts +0 -94
- package/src/tables/__tests__/sort.test.ts +0 -107
- package/src/tables/__tests__/sparkline.test.ts +0 -122
- package/src/tables/bar-column.ts +0 -94
- package/src/tables/category-colors.ts +0 -78
- package/src/tables/compile-table.ts +0 -427
- package/src/tables/format-cells.ts +0 -115
- package/src/tables/heatmap.ts +0 -141
- package/src/tables/pagination.ts +0 -46
- package/src/tables/search.ts +0 -66
- package/src/tables/sort.ts +0 -69
- package/src/tables/sparkline.ts +0 -113
- package/src/tables/utils.ts +0 -16
- package/src/tilemap/__tests__/compile-tilemap.test.ts +0 -576
- package/src/tilemap/compile-tilemap.ts +0 -755
- package/src/tilemap/layout.ts +0 -172
- package/src/tilemap/types.ts +0 -33
- package/src/tooltips/__tests__/compute.test.ts +0 -582
- package/src/tooltips/compute.ts +0 -700
- package/src/transforms/__tests__/aggregate.test.ts +0 -159
- package/src/transforms/__tests__/bin.test.ts +0 -88
- package/src/transforms/__tests__/calculate.test.ts +0 -146
- package/src/transforms/__tests__/conditional.test.ts +0 -127
- package/src/transforms/__tests__/filter-relative.test.ts +0 -202
- package/src/transforms/__tests__/filter.test.ts +0 -59
- package/src/transforms/__tests__/fold.test.ts +0 -79
- package/src/transforms/__tests__/index.test.ts +0 -93
- package/src/transforms/__tests__/predicates.test.ts +0 -193
- package/src/transforms/__tests__/timeunit.test.ts +0 -129
- package/src/transforms/__tests__/window.test.ts +0 -286
- package/src/transforms/aggregate.ts +0 -131
- package/src/transforms/bin.ts +0 -87
- package/src/transforms/calculate.ts +0 -60
- package/src/transforms/conditional.ts +0 -46
- package/src/transforms/filter.ts +0 -122
- package/src/transforms/fold.ts +0 -49
- package/src/transforms/index.ts +0 -60
- package/src/transforms/predicates.ts +0 -123
- package/src/transforms/timeunit.ts +0 -88
- package/src/transforms/window.ts +0 -185
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
import type { Rect } from '@opendata-ai/openchart-core';
|
|
2
|
-
import { overlapArea } from '@opendata-ai/openchart-core';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import {
|
|
5
|
-
findBestPlacement,
|
|
6
|
-
isAutoPlacement,
|
|
7
|
-
type PlacementObstacle,
|
|
8
|
-
samplePolylineObstacles,
|
|
9
|
-
} from '../placement';
|
|
10
|
-
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// samplePolylineObstacles
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
describe('samplePolylineObstacles', () => {
|
|
16
|
-
it('returns empty array for empty input', () => {
|
|
17
|
-
expect(samplePolylineObstacles([], 'line')).toEqual([]);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('returns single padded rect for a single point', () => {
|
|
21
|
-
const pad = 3;
|
|
22
|
-
const result = samplePolylineObstacles([{ x: 50, y: 100 }], 'line', 24, pad);
|
|
23
|
-
expect(result).toHaveLength(1);
|
|
24
|
-
expect(result[0]).toEqual({
|
|
25
|
-
x: 50 - pad,
|
|
26
|
-
y: 100 - pad,
|
|
27
|
-
width: pad * 2,
|
|
28
|
-
height: pad * 2,
|
|
29
|
-
kind: 'line',
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('returns single vertical rect when all points share the same x', () => {
|
|
34
|
-
const pad = 3;
|
|
35
|
-
const points = [
|
|
36
|
-
{ x: 100, y: 10 },
|
|
37
|
-
{ x: 100, y: 50 },
|
|
38
|
-
{ x: 100, y: 30 },
|
|
39
|
-
];
|
|
40
|
-
const result = samplePolylineObstacles(points, 'line', 24, pad);
|
|
41
|
-
expect(result).toHaveLength(1);
|
|
42
|
-
expect(result[0].x).toBe(100 - pad);
|
|
43
|
-
expect(result[0].y).toBe(10 - pad);
|
|
44
|
-
expect(result[0].width).toBe(pad * 2);
|
|
45
|
-
expect(result[0].height).toBe(50 - 10 + pad * 2);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('produces gap-free coverage for a steep sine wave', () => {
|
|
49
|
-
// Generate a sine wave with enough amplitude to cross many buckets vertically
|
|
50
|
-
const bucketWidth = 20;
|
|
51
|
-
const points: Array<{ x: number; y: number }> = [];
|
|
52
|
-
for (let i = 0; i <= 200; i++) {
|
|
53
|
-
const x = i;
|
|
54
|
-
const y = Math.sin(x * 0.1) * 100;
|
|
55
|
-
points.push({ x, y });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const obstacles = samplePolylineObstacles(points, 'line', bucketWidth, 0);
|
|
59
|
-
expect(obstacles.length).toBeGreaterThan(0);
|
|
60
|
-
|
|
61
|
-
// The union of obstacle x-ranges should cover the entire polyline x-extent
|
|
62
|
-
const xMin = Math.min(...points.map((p) => p.x));
|
|
63
|
-
const xMax = Math.max(...points.map((p) => p.x));
|
|
64
|
-
|
|
65
|
-
// Sort obstacles by x and verify continuous coverage
|
|
66
|
-
const sorted = [...obstacles].sort((a, b) => a.x - b.x);
|
|
67
|
-
expect(sorted[0].x).toBeLessThanOrEqual(xMin);
|
|
68
|
-
const lastObs = sorted[sorted.length - 1];
|
|
69
|
-
expect(lastObs.x + lastObs.width).toBeGreaterThanOrEqual(xMax);
|
|
70
|
-
|
|
71
|
-
// Check no gaps between consecutive obstacles
|
|
72
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
73
|
-
const prevEnd = sorted[i - 1].x + sorted[i - 1].width;
|
|
74
|
-
expect(sorted[i].x).toBeLessThanOrEqual(prevEnd + 0.001);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('captures min/max y within each bucket for monotone increasing data', () => {
|
|
79
|
-
const bucketWidth = 50;
|
|
80
|
-
const pad = 0;
|
|
81
|
-
// Monotone increasing: y = x
|
|
82
|
-
const points: Array<{ x: number; y: number }> = [];
|
|
83
|
-
for (let i = 0; i <= 200; i += 10) {
|
|
84
|
-
points.push({ x: i, y: i });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const obstacles = samplePolylineObstacles(points, 'line', bucketWidth, pad);
|
|
88
|
-
|
|
89
|
-
for (const obs of obstacles) {
|
|
90
|
-
// Find all points within this bucket's x-range
|
|
91
|
-
const bucketXStart = obs.x;
|
|
92
|
-
const bucketXEnd = obs.x + obs.width;
|
|
93
|
-
const inBucket = points.filter((p) => p.x >= bucketXStart && p.x < bucketXEnd);
|
|
94
|
-
|
|
95
|
-
if (inBucket.length === 0) continue;
|
|
96
|
-
|
|
97
|
-
const minY = Math.min(...inBucket.map((p) => p.y));
|
|
98
|
-
const maxY = Math.max(...inBucket.map((p) => p.y));
|
|
99
|
-
|
|
100
|
-
// The obstacle's y range should contain the points' y range within this bucket
|
|
101
|
-
// (it may be larger due to interpolation at bucket edges, but should not be smaller)
|
|
102
|
-
expect(obs.y).toBeLessThanOrEqual(minY);
|
|
103
|
-
expect(obs.y + obs.height).toBeGreaterThanOrEqual(maxY);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('bucket width parameter controls granularity', () => {
|
|
108
|
-
const points = [
|
|
109
|
-
{ x: 0, y: 0 },
|
|
110
|
-
{ x: 100, y: 100 },
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
const narrow = samplePolylineObstacles(points, 'line', 10, 0);
|
|
114
|
-
const wide = samplePolylineObstacles(points, 'line', 50, 0);
|
|
115
|
-
|
|
116
|
-
// Narrower buckets should produce more obstacles
|
|
117
|
-
expect(narrow.length).toBeGreaterThan(wide.length);
|
|
118
|
-
// Each obstacle's width matches the bucket width
|
|
119
|
-
for (const obs of narrow) expect(obs.width).toBe(10);
|
|
120
|
-
for (const obs of wide) expect(obs.width).toBe(50);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// overlapArea (from @opendata-ai/openchart-core)
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
describe('overlapArea', () => {
|
|
129
|
-
it('returns 0 for disjoint rects', () => {
|
|
130
|
-
const a: Rect = { x: 0, y: 0, width: 10, height: 10 };
|
|
131
|
-
const b: Rect = { x: 20, y: 20, width: 10, height: 10 };
|
|
132
|
-
expect(overlapArea(a, b)).toBe(0);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('returns correct area for partial overlap', () => {
|
|
136
|
-
const a: Rect = { x: 0, y: 0, width: 10, height: 10 };
|
|
137
|
-
const b: Rect = { x: 5, y: 5, width: 10, height: 10 };
|
|
138
|
-
// Overlap region: x=[5,10], y=[5,10] => 5*5 = 25
|
|
139
|
-
expect(overlapArea(a, b)).toBe(25);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('returns smaller rects area when one rect is fully contained', () => {
|
|
143
|
-
const outer: Rect = { x: 0, y: 0, width: 100, height: 100 };
|
|
144
|
-
const inner: Rect = { x: 10, y: 10, width: 20, height: 30 };
|
|
145
|
-
expect(overlapArea(outer, inner)).toBe(20 * 30);
|
|
146
|
-
// Symmetric
|
|
147
|
-
expect(overlapArea(inner, outer)).toBe(20 * 30);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('returns full area for identical rects', () => {
|
|
151
|
-
const r: Rect = { x: 5, y: 5, width: 40, height: 60 };
|
|
152
|
-
expect(overlapArea(r, r)).toBe(40 * 60);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
// Scorer (via findBestPlacement)
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
|
|
160
|
-
describe('findBestPlacement scorer', () => {
|
|
161
|
-
// A large chart area and SVG rect so nothing clips
|
|
162
|
-
const chartArea: Rect = { x: 0, y: 0, width: 800, height: 600 };
|
|
163
|
-
const svgRect: Rect = { x: 0, y: 0, width: 800, height: 600 };
|
|
164
|
-
const style = { fontSize: 12, fontWeight: 400, lineHeight: 1.3 };
|
|
165
|
-
|
|
166
|
-
it('picks NE (direction index 1) first for a lower-half anchor on an empty chart', () => {
|
|
167
|
-
// Anchor in the lower half of the chart
|
|
168
|
-
const result = findBestPlacement(
|
|
169
|
-
400,
|
|
170
|
-
400,
|
|
171
|
-
'Test',
|
|
172
|
-
style,
|
|
173
|
-
undefined,
|
|
174
|
-
undefined,
|
|
175
|
-
[],
|
|
176
|
-
chartArea,
|
|
177
|
-
svgRect,
|
|
178
|
-
undefined,
|
|
179
|
-
true,
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// LOWER_HALF_ORDER = [1, 0, 7, 2, 3, 4, 5, 6] => first tried is NE (index 1)
|
|
183
|
-
// On empty chart, the first direction tried gets the lowest tiebreak and wins
|
|
184
|
-
// (all candidates score close to 0 for obstacles/clipping, tiebreak resolves it)
|
|
185
|
-
expect(result.debug).toBeDefined();
|
|
186
|
-
// The best candidate should be direction index 1 (NE)
|
|
187
|
-
const best = result.debug!.reduce((a, b) => (a.total <= b.total ? a : b));
|
|
188
|
-
expect(best.direction).toBe('NE');
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('picks SE (direction index 3) first for an upper-half anchor on an empty chart', () => {
|
|
192
|
-
// Anchor in the upper half of the chart
|
|
193
|
-
const result = findBestPlacement(
|
|
194
|
-
400,
|
|
195
|
-
100,
|
|
196
|
-
'Test',
|
|
197
|
-
style,
|
|
198
|
-
undefined,
|
|
199
|
-
undefined,
|
|
200
|
-
[],
|
|
201
|
-
chartArea,
|
|
202
|
-
svgRect,
|
|
203
|
-
undefined,
|
|
204
|
-
true,
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
// UPPER_HALF_ORDER = [3, 4, 5, 2, 1, 0, 7, 6] => first tried is SE (index 3)
|
|
208
|
-
const best = result.debug!.reduce((a, b) => (a.total <= b.total ? a : b));
|
|
209
|
-
expect(best.direction).toBe('SE');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('avoids the N direction when blocked by a hard obstacle', () => {
|
|
213
|
-
// Place a large hard obstacle directly above the anchor so that the N direction
|
|
214
|
-
// would overlap it heavily
|
|
215
|
-
const anchorX = 400;
|
|
216
|
-
const anchorY = 400;
|
|
217
|
-
const obstacle: PlacementObstacle = {
|
|
218
|
-
x: anchorX - 100,
|
|
219
|
-
y: anchorY - 80,
|
|
220
|
-
width: 200,
|
|
221
|
-
height: 60,
|
|
222
|
-
kind: 'line',
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const result = findBestPlacement(
|
|
226
|
-
anchorX,
|
|
227
|
-
anchorY,
|
|
228
|
-
'Test label',
|
|
229
|
-
style,
|
|
230
|
-
undefined,
|
|
231
|
-
undefined,
|
|
232
|
-
[obstacle],
|
|
233
|
-
chartArea,
|
|
234
|
-
svgRect,
|
|
235
|
-
undefined,
|
|
236
|
-
true,
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
// The winning direction should NOT be N (index 0), because the obstacle blocks it
|
|
240
|
-
const best = result.debug!.reduce((a, b) => (a.total <= b.total ? a : b));
|
|
241
|
-
expect(best.direction).not.toBe('N');
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('penalizes area-fill overlap less than line overlap of equal area', () => {
|
|
245
|
-
const anchorX = 400;
|
|
246
|
-
const anchorY = 300;
|
|
247
|
-
|
|
248
|
-
// Surround the anchor with obstacles on all sides so every candidate
|
|
249
|
-
// overlaps at least one. The winning candidate will necessarily overlap,
|
|
250
|
-
// making the obstacle kind weight the differentiating factor.
|
|
251
|
-
function makeWall(kind: 'line' | 'area-fill'): PlacementObstacle[] {
|
|
252
|
-
return [{ x: anchorX - 200, y: anchorY - 200, width: 400, height: 400, kind }];
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const lineResult = findBestPlacement(
|
|
256
|
-
anchorX,
|
|
257
|
-
anchorY,
|
|
258
|
-
'Test',
|
|
259
|
-
style,
|
|
260
|
-
undefined,
|
|
261
|
-
undefined,
|
|
262
|
-
makeWall('line'),
|
|
263
|
-
chartArea,
|
|
264
|
-
svgRect,
|
|
265
|
-
undefined,
|
|
266
|
-
true,
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
const areaResult = findBestPlacement(
|
|
270
|
-
anchorX,
|
|
271
|
-
anchorY,
|
|
272
|
-
'Test',
|
|
273
|
-
style,
|
|
274
|
-
undefined,
|
|
275
|
-
undefined,
|
|
276
|
-
makeWall('area-fill'),
|
|
277
|
-
chartArea,
|
|
278
|
-
svgRect,
|
|
279
|
-
undefined,
|
|
280
|
-
true,
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
// Both winners overlap their obstacle, but area-fill weight (1.5) is much
|
|
284
|
-
// lower than line weight (20), so the area-fill score should be smaller.
|
|
285
|
-
expect(areaResult.score).toBeLessThan(lineResult.score);
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
|
-
// isAutoPlacement
|
|
291
|
-
// ---------------------------------------------------------------------------
|
|
292
|
-
|
|
293
|
-
describe('isAutoPlacement', () => {
|
|
294
|
-
it('returns true for plain annotation without offset/anchor/drop-line', () => {
|
|
295
|
-
expect(isAutoPlacement({})).toBe(true);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('returns false when offset is set', () => {
|
|
299
|
-
expect(isAutoPlacement({ offset: { dx: 10 } })).toBe(false);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('returns false when anchor is not auto', () => {
|
|
303
|
-
expect(isAutoPlacement({ anchor: 'top' })).toBe(false);
|
|
304
|
-
expect(isAutoPlacement({ anchor: 'left' })).toBe(false);
|
|
305
|
-
expect(isAutoPlacement({ anchor: 'right' })).toBe(false);
|
|
306
|
-
expect(isAutoPlacement({ anchor: 'bottom' })).toBe(false);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('returns false when connector is drop-line', () => {
|
|
310
|
-
expect(isAutoPlacement({ connector: 'drop-line' })).toBe(false);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('returns true when anchor is explicitly auto', () => {
|
|
314
|
-
expect(isAutoPlacement({ anchor: 'auto' })).toBe(true);
|
|
315
|
-
});
|
|
316
|
-
});
|