@opendata-ai/openchart-engine 8.0.0-rc.2 → 8.0.0-rc.20
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 +140 -2
- package/dist/index.js +1403 -210
- 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 -2339
- package/src/__tests__/annotation-behavior.test.ts +0 -721
- 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 -370
- 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 -539
- 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 -626
- package/src/compile/size-scale.ts +0 -139
- 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 -1450
- 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 -86
- package/src/compiler/__tests__/validate-highlight.test.ts +0 -112
- package/src/compiler/__tests__/validate-near-miss.test.ts +0 -89
- 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 -733
- package/src/compiler/sparkline-defaults.ts +0 -138
- package/src/compiler/types.ts +0 -241
- package/src/compiler/validate.ts +0 -1810
- 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 -621
- package/src/geo/compile-map.ts +0 -856
- package/src/geo/join.ts +0 -115
- package/src/geo/projections.ts +0 -45
- package/src/geo/types.ts +0 -24
- 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 -747
- 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,566 +0,0 @@
|
|
|
1
|
-
import type { ChartLayout, LineMark } from '@opendata-ai/openchart-core';
|
|
2
|
-
import { estimateTextWidth } from '@opendata-ai/openchart-core';
|
|
3
|
-
import { describe, expect, test } from 'vitest';
|
|
4
|
-
import { ANCHOR_OFFSET, ARROWHEAD_LENGTH, MIN_CONNECTOR_LENGTH } from '../annotations/constants';
|
|
5
|
-
import { compileChart } from '../compile';
|
|
6
|
-
|
|
7
|
-
describe('known layout bugs', () => {
|
|
8
|
-
test('annotation label does not overlap the line it annotates', () => {
|
|
9
|
-
// Generate ~25 smooth sine-wave data points over two years
|
|
10
|
-
const data: { date: string; value: number }[] = [];
|
|
11
|
-
const startDate = new Date('2022-01-01');
|
|
12
|
-
for (let i = 0; i < 25; i++) {
|
|
13
|
-
const d = new Date(startDate);
|
|
14
|
-
d.setDate(d.getDate() + i * 30); // ~monthly
|
|
15
|
-
// Sine wave: value ranges ~20-80 with a peak near i=6 (mid-2022)
|
|
16
|
-
const value = 50 + 30 * Math.sin((i / 25) * 2 * Math.PI);
|
|
17
|
-
data.push({ date: d.toISOString().slice(0, 10), value });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// The peak is near i=6 (~July 2022), where sin peaks
|
|
21
|
-
const peakIndex = 6;
|
|
22
|
-
const peakDate = data[peakIndex].date;
|
|
23
|
-
const peakValue = data[peakIndex].value;
|
|
24
|
-
|
|
25
|
-
const spec = {
|
|
26
|
-
mark: 'line' as const,
|
|
27
|
-
data,
|
|
28
|
-
encoding: {
|
|
29
|
-
x: { field: 'date' as const, type: 'temporal' as const },
|
|
30
|
-
y: { field: 'value' as const, type: 'quantitative' as const },
|
|
31
|
-
},
|
|
32
|
-
annotations: [
|
|
33
|
-
{
|
|
34
|
-
type: 'text' as const,
|
|
35
|
-
x: peakDate,
|
|
36
|
-
y: peakValue,
|
|
37
|
-
text: 'Peak season',
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const layout: ChartLayout = compileChart(spec, { width: 600, height: 400 });
|
|
43
|
-
|
|
44
|
-
// Find the annotation
|
|
45
|
-
const annotation = layout.annotations[0];
|
|
46
|
-
expect(annotation).toBeDefined();
|
|
47
|
-
expect(annotation.label).toBeDefined();
|
|
48
|
-
|
|
49
|
-
const label = annotation.label!;
|
|
50
|
-
const fontSize = label.style.fontSize;
|
|
51
|
-
const fontWeight = label.style.fontWeight;
|
|
52
|
-
|
|
53
|
-
// Estimate the label bounding box
|
|
54
|
-
const labelWidth = estimateTextWidth(label.text, fontSize, fontWeight);
|
|
55
|
-
const labelHeight = fontSize * 1.3;
|
|
56
|
-
|
|
57
|
-
// Label bounding box (y is the baseline, so top of text is y - fontSize)
|
|
58
|
-
const labelLeft =
|
|
59
|
-
label.style.textAnchor === 'middle'
|
|
60
|
-
? label.x - labelWidth / 2
|
|
61
|
-
: label.style.textAnchor === 'end'
|
|
62
|
-
? label.x - labelWidth
|
|
63
|
-
: label.x;
|
|
64
|
-
const labelRight = labelLeft + labelWidth;
|
|
65
|
-
const labelTop = label.y - fontSize;
|
|
66
|
-
const labelBottom = labelTop + labelHeight;
|
|
67
|
-
|
|
68
|
-
// Find the LineMark
|
|
69
|
-
const lineMark = layout.marks.find((m): m is LineMark => m.type === 'line');
|
|
70
|
-
expect(lineMark).toBeDefined();
|
|
71
|
-
|
|
72
|
-
const points = lineMark!.points;
|
|
73
|
-
|
|
74
|
-
// Find line segments that fall within the label's x-range
|
|
75
|
-
let lineMinY = Infinity;
|
|
76
|
-
let lineMaxY = -Infinity;
|
|
77
|
-
|
|
78
|
-
for (let i = 0; i < points.length - 1; i++) {
|
|
79
|
-
const p1 = points[i];
|
|
80
|
-
const p2 = points[i + 1];
|
|
81
|
-
|
|
82
|
-
// Check if this segment overlaps the label's x-range
|
|
83
|
-
const segLeft = Math.min(p1.x, p2.x);
|
|
84
|
-
const segRight = Math.max(p1.x, p2.x);
|
|
85
|
-
|
|
86
|
-
if (segRight >= labelLeft && segLeft <= labelRight) {
|
|
87
|
-
// This segment is within the label's x-range
|
|
88
|
-
// Find the y-range of the line within the label's x-span
|
|
89
|
-
const overlapLeft = Math.max(segLeft, labelLeft);
|
|
90
|
-
const overlapRight = Math.min(segRight, labelRight);
|
|
91
|
-
|
|
92
|
-
// Interpolate y values at the overlap boundaries
|
|
93
|
-
const segWidth = p2.x - p1.x;
|
|
94
|
-
if (Math.abs(segWidth) < 0.001) {
|
|
95
|
-
lineMinY = Math.min(lineMinY, p1.y, p2.y);
|
|
96
|
-
lineMaxY = Math.max(lineMaxY, p1.y, p2.y);
|
|
97
|
-
} else {
|
|
98
|
-
const t1 = (overlapLeft - p1.x) / segWidth;
|
|
99
|
-
const t2 = (overlapRight - p1.x) / segWidth;
|
|
100
|
-
const y1 = p1.y + t1 * (p2.y - p1.y);
|
|
101
|
-
const y2 = p1.y + t2 * (p2.y - p1.y);
|
|
102
|
-
lineMinY = Math.min(lineMinY, y1, y2);
|
|
103
|
-
lineMaxY = Math.max(lineMaxY, y1, y2);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// The label bounding box should NOT vertically overlap with the line's y-range.
|
|
109
|
-
// The label sits ANCHOR_OFFSET (28px) off its anchor point on the line, which
|
|
110
|
-
// is sized to clear both the marker and the line path.
|
|
111
|
-
const verticallyOverlaps = labelTop < lineMaxY && labelBottom > lineMinY;
|
|
112
|
-
expect(verticallyOverlaps).toBe(false);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Regression: the collision pass calls `refreshConnector` on every annotation it
|
|
116
|
-
// moves, and that rebuilt `from`/`to` from a ray-box exit toward the target. A
|
|
117
|
-
// drop-line is not a leader — it's a vertical rule pinned to the data point's x
|
|
118
|
-
// — so the rebuild tilted it into a diagonal (which the renderer then draws with
|
|
119
|
-
// shape-rendering: crispEdges, assuming axis alignment), and the
|
|
120
|
-
// MIN_CONNECTOR_LENGTH rule could delete it outright.
|
|
121
|
-
//
|
|
122
|
-
// Two drop-lines placed close enough to collide is what makes this observable:
|
|
123
|
-
// a lone annotation is never shifted, so `refreshConnector` never runs and the
|
|
124
|
-
// bug hides. Against the unfixed code this pair yields a 12.9px-diagonal first
|
|
125
|
-
// connector and a *deleted* second one.
|
|
126
|
-
describe('drop-line connectors survive the collision pass', () => {
|
|
127
|
-
const collidingDropLines = () =>
|
|
128
|
-
compileChart(
|
|
129
|
-
{
|
|
130
|
-
mark: 'line' as const,
|
|
131
|
-
data: [
|
|
132
|
-
{ m: 'Jan', v: 10 },
|
|
133
|
-
{ m: 'Feb', v: 50 },
|
|
134
|
-
{ m: 'Mar', v: 48 },
|
|
135
|
-
{ m: 'Apr', v: 30 },
|
|
136
|
-
],
|
|
137
|
-
encoding: {
|
|
138
|
-
x: { field: 'm' as const, type: 'ordinal' as const },
|
|
139
|
-
y: { field: 'v' as const, type: 'quantitative' as const },
|
|
140
|
-
},
|
|
141
|
-
annotations: [
|
|
142
|
-
{
|
|
143
|
-
type: 'text' as const,
|
|
144
|
-
x: 'Feb',
|
|
145
|
-
y: 50,
|
|
146
|
-
text: 'First drop-line here',
|
|
147
|
-
connector: 'drop-line' as const,
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
type: 'text' as const,
|
|
151
|
-
x: 'Mar',
|
|
152
|
-
y: 48,
|
|
153
|
-
text: 'Second drop-line here',
|
|
154
|
-
connector: 'drop-line' as const,
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
},
|
|
158
|
-
{ width: 800, height: 460 },
|
|
159
|
-
).annotations ?? [];
|
|
160
|
-
|
|
161
|
-
// One test, not three. The `x` pinning is the only assertion the generic
|
|
162
|
-
// ray-box rebuild actually breaks -- it preserves `style` through its
|
|
163
|
-
// `{ ...connector }` spread, and this geometry happens to land on
|
|
164
|
-
// `exit: 'vertical'` either way, so splitting those into their own tests just
|
|
165
|
-
// manufactured two that pass with the fix reverted.
|
|
166
|
-
test('stay vertical, with x pinned to the data point', () => {
|
|
167
|
-
const annotations = collidingDropLines();
|
|
168
|
-
expect(annotations).toHaveLength(2);
|
|
169
|
-
for (const annotation of annotations) {
|
|
170
|
-
const connector = annotation.label?.connector;
|
|
171
|
-
// Survived the min-length rule (the generic rebuild can shorten a
|
|
172
|
-
// drop-line enough to delete it outright).
|
|
173
|
-
expect(connector).toBeDefined();
|
|
174
|
-
// The whole contract of a drop-line: from.x === to.x === the point's x.
|
|
175
|
-
expect(connector?.from.x).toBeCloseTo(connector?.to.x ?? Number.NaN, 5);
|
|
176
|
-
expect(connector?.from.x).toBeCloseTo(annotation.dot?.x ?? Number.NaN, 5);
|
|
177
|
-
expect(connector?.style).toBe('drop-line');
|
|
178
|
-
expect(connector?.exit).toBe('vertical');
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// Regression: the engine gated connectors on `length >= MIN_CONNECTOR_LENGTH`,
|
|
184
|
-
// but the renderer stops an ARROWED stroke `ARROWHEAD_LENGTH` (7px) short and
|
|
185
|
-
// spends that budget on the head. So an 8.1px arrowed connector cleared the 8px
|
|
186
|
-
// gate and then shipped a 1.1px stub with an arrowhead stuck on the end. Sharing
|
|
187
|
-
// the constant didn't fix it: both sides had the number, only one reasoned about
|
|
188
|
-
// it. The gate now measures what actually gets stroked.
|
|
189
|
-
describe('the connector min-length gate accounts for the arrowhead', () => {
|
|
190
|
-
const strokeFor = (arrow: boolean, dy: number) => {
|
|
191
|
-
const annotation = compileChart(
|
|
192
|
-
{
|
|
193
|
-
mark: 'line' as const,
|
|
194
|
-
data: [
|
|
195
|
-
{ m: 'Jan', v: 10 },
|
|
196
|
-
{ m: 'Feb', v: 50 },
|
|
197
|
-
{ m: 'Mar', v: 30 },
|
|
198
|
-
],
|
|
199
|
-
encoding: {
|
|
200
|
-
x: { field: 'm' as const, type: 'ordinal' as const },
|
|
201
|
-
y: { field: 'v' as const, type: 'quantitative' as const },
|
|
202
|
-
},
|
|
203
|
-
annotations: [
|
|
204
|
-
{
|
|
205
|
-
type: 'text' as const,
|
|
206
|
-
x: 'Mar',
|
|
207
|
-
y: 30,
|
|
208
|
-
text: 'Note',
|
|
209
|
-
anchor: 'top' as const,
|
|
210
|
-
offset: { dx: 0, dy },
|
|
211
|
-
connector: arrow ? { type: 'straight' as const, arrow: true } : 'straight',
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
},
|
|
215
|
-
{ width: 800, height: 460 },
|
|
216
|
-
).annotations?.[0];
|
|
217
|
-
|
|
218
|
-
const connector = annotation?.label?.connector;
|
|
219
|
-
if (!connector) return null;
|
|
220
|
-
const length = Math.hypot(
|
|
221
|
-
connector.to.x - connector.from.x,
|
|
222
|
-
connector.to.y - connector.from.y,
|
|
223
|
-
);
|
|
224
|
-
// What the renderer actually strokes.
|
|
225
|
-
return arrow ? length - ARROWHEAD_LENGTH : length;
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
test('an arrowed connector never ships a stroke shorter than the minimum', () => {
|
|
229
|
-
// dy: 6 is the case that used to slip through -- an 8.10px connector that
|
|
230
|
-
// stroked 1.10px once the head took its 7px.
|
|
231
|
-
for (let dy = 0; dy <= 8; dy++) {
|
|
232
|
-
const stroke = strokeFor(true, dy);
|
|
233
|
-
if (stroke === null) continue; // suppressed outright, which is correct
|
|
234
|
-
expect(stroke).toBeGreaterThanOrEqual(MIN_CONNECTOR_LENGTH);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test('the plain (non-arrowed) gate is unchanged', () => {
|
|
239
|
-
// The head costs nothing here, so the full length is the stroke and the
|
|
240
|
-
// original 8px rule still holds. Guards against "fixing" the arrow case by
|
|
241
|
-
// tightening the gate for everyone.
|
|
242
|
-
for (let dy = 0; dy <= 8; dy++) {
|
|
243
|
-
const stroke = strokeFor(false, dy);
|
|
244
|
-
if (stroke === null) continue;
|
|
245
|
-
expect(stroke).toBeGreaterThanOrEqual(MIN_CONNECTOR_LENGTH);
|
|
246
|
-
}
|
|
247
|
-
// And a short-but-legal plain connector still draws.
|
|
248
|
-
expect(strokeFor(false, 0)).not.toBeNull();
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Regression: `resolveTextAnnotation` bolts the lede rule on (a `subtitle`
|
|
253
|
-
// promotes the primary line to 700) BEFORE branching to the drop-line resolver,
|
|
254
|
-
// which used to resolve no subtitle at all. Net effect: authoring a subtitle on
|
|
255
|
-
// a drop-line silently threw the subtitle away but still bolded the primary --
|
|
256
|
-
// a field whose only visible effect was a mystery weight change.
|
|
257
|
-
describe('a drop-line annotation honors its subtitle', () => {
|
|
258
|
-
// Annotate Mar, not the Feb maximum: a callout on the chart's top value clamps
|
|
259
|
-
// against the chart edge and has nowhere to go, which is a separate (and
|
|
260
|
-
// legitimate) degradation. This is the ordinary case.
|
|
261
|
-
const dropLineWithSubtitle = () =>
|
|
262
|
-
compileChart(
|
|
263
|
-
{
|
|
264
|
-
mark: 'line' as const,
|
|
265
|
-
data: [
|
|
266
|
-
{ m: 'Jan', v: 10 },
|
|
267
|
-
{ m: 'Feb', v: 50 },
|
|
268
|
-
{ m: 'Mar', v: 30 },
|
|
269
|
-
],
|
|
270
|
-
encoding: {
|
|
271
|
-
x: { field: 'm' as const, type: 'ordinal' as const },
|
|
272
|
-
y: { field: 'v' as const, type: 'quantitative' as const },
|
|
273
|
-
},
|
|
274
|
-
annotations: [
|
|
275
|
-
{
|
|
276
|
-
type: 'text' as const,
|
|
277
|
-
x: 'Mar',
|
|
278
|
-
y: 30,
|
|
279
|
-
text: 'Peak',
|
|
280
|
-
subtitle: 'the highest month',
|
|
281
|
-
connector: 'drop-line' as const,
|
|
282
|
-
},
|
|
283
|
-
],
|
|
284
|
-
},
|
|
285
|
-
{ width: 800, height: 460 },
|
|
286
|
-
).annotations?.[0];
|
|
287
|
-
|
|
288
|
-
test('resolves the subtitle instead of dropping it', () => {
|
|
289
|
-
const annotation = dropLineWithSubtitle();
|
|
290
|
-
expect(annotation?.subtitle?.text).toBe('the highest month');
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
test('still promotes the primary line to a bold lede', () => {
|
|
294
|
-
expect(dropLineWithSubtitle()?.label?.style.fontWeight).toBe(700);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test('renders the subtitle below the primary line, left edges aligned', () => {
|
|
298
|
-
const annotation = dropLineWithSubtitle();
|
|
299
|
-
expect(annotation?.subtitle?.y).toBeGreaterThan(annotation?.label?.y ?? Number.NaN);
|
|
300
|
-
expect(annotation?.subtitle?.x).toBeCloseTo(annotation?.label?.x ?? Number.NaN, 5);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
test('the whole block stays above the data point, so the line still drops', () => {
|
|
304
|
-
// A drop-line runs DOWN from the block to the point. The subtitle makes the
|
|
305
|
-
// block taller and wider, which made it collide with the line mark -- and the
|
|
306
|
-
// obstacle nudge then shoved it *below* the point, inverting the connector so
|
|
307
|
-
// it pointed up, away from the data, with the block sitting on the mark.
|
|
308
|
-
const annotation = dropLineWithSubtitle();
|
|
309
|
-
const bounds = annotation?.bounds;
|
|
310
|
-
const dotY = annotation?.dot?.y ?? Number.NaN;
|
|
311
|
-
expect((bounds?.y ?? Number.NaN) + (bounds?.height ?? Number.NaN)).toBeLessThanOrEqual(dotY);
|
|
312
|
-
|
|
313
|
-
const connector = annotation?.label?.connector;
|
|
314
|
-
expect(connector?.from.y).toBeLessThan(connector?.to.y ?? Number.NaN);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test('the block bounds cover the subtitle, so collisions see it', () => {
|
|
318
|
-
const annotation = dropLineWithSubtitle();
|
|
319
|
-
const bounds = annotation?.bounds;
|
|
320
|
-
const labelBounds = annotation?.label?.bounds;
|
|
321
|
-
// `bounds` is the union; `label.bounds` stays label-only (the renderer sizes
|
|
322
|
-
// the background plate from it).
|
|
323
|
-
expect(bounds?.height).toBeGreaterThan(labelBounds?.height ?? Number.NaN);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// Regression: `compute.ts` stamped `result.bounds` (the UNION of label and
|
|
328
|
-
// subtitle, which is what placement scores) into `label.bounds`, whose contract
|
|
329
|
-
// is the label-only text box. The renderer sizes the `background` plate from
|
|
330
|
-
// `label.bounds`, so an auto-placed annotation with a subtitle got a plate sized
|
|
331
|
-
// to both lines while the explicit path got one sized to the label.
|
|
332
|
-
test('auto-placed label bounds are the label box, not the label+subtitle union', () => {
|
|
333
|
-
const layout = compileChart(
|
|
334
|
-
{
|
|
335
|
-
mark: 'line' as const,
|
|
336
|
-
data: [
|
|
337
|
-
{ m: 'Jan', v: 10 },
|
|
338
|
-
{ m: 'Feb', v: 50 },
|
|
339
|
-
{ m: 'Mar', v: 30 },
|
|
340
|
-
],
|
|
341
|
-
encoding: {
|
|
342
|
-
x: { field: 'm' as const, type: 'ordinal' as const },
|
|
343
|
-
y: { field: 'v' as const, type: 'quantitative' as const },
|
|
344
|
-
},
|
|
345
|
-
annotations: [
|
|
346
|
-
// No anchor/offset => the auto-placement path.
|
|
347
|
-
{
|
|
348
|
-
type: 'text' as const,
|
|
349
|
-
x: 'Feb',
|
|
350
|
-
y: 50,
|
|
351
|
-
text: 'Peak',
|
|
352
|
-
subtitle: 'a considerably wider subtitle line',
|
|
353
|
-
},
|
|
354
|
-
],
|
|
355
|
-
},
|
|
356
|
-
{ width: 800, height: 460 },
|
|
357
|
-
);
|
|
358
|
-
const annotation = layout.annotations?.[0];
|
|
359
|
-
const labelBounds = annotation?.label?.bounds;
|
|
360
|
-
const unionBounds = annotation?.bounds;
|
|
361
|
-
expect(labelBounds).toBeDefined();
|
|
362
|
-
expect(unionBounds).toBeDefined();
|
|
363
|
-
|
|
364
|
-
// The subtitle is wider and sits below, so the union must be strictly bigger.
|
|
365
|
-
// If label.bounds were the union, these would be identical.
|
|
366
|
-
expect(labelBounds?.width).toBeLessThan(unionBounds?.width ?? 0);
|
|
367
|
-
expect(labelBounds?.height).toBeLessThan(unionBounds?.height ?? 0);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// Regression: `anchor: 'top'`/`'bottom'` render with textAnchor 'start', so the
|
|
371
|
-
// block used to hang entirely to the RIGHT of its data point — "above" read as
|
|
372
|
-
// "up and to the right", and authors had to hand-compute a negative dx of about
|
|
373
|
-
// half the block width to re-center it. The block now straddles the point
|
|
374
|
-
// horizontally; the TEXT inside stays left-aligned (ragged right).
|
|
375
|
-
describe('top/bottom anchored blocks straddle their data point', () => {
|
|
376
|
-
const centerOffsetFor = (annotation: Record<string, unknown>) => {
|
|
377
|
-
const layout = compileChart(
|
|
378
|
-
{
|
|
379
|
-
mark: 'line' as const,
|
|
380
|
-
data: [
|
|
381
|
-
{ m: 'Jan', v: 10 },
|
|
382
|
-
{ m: 'Feb', v: 50 },
|
|
383
|
-
{ m: 'Mar', v: 30 },
|
|
384
|
-
],
|
|
385
|
-
encoding: {
|
|
386
|
-
x: { field: 'm' as const, type: 'ordinal' as const },
|
|
387
|
-
y: { field: 'v' as const, type: 'quantitative' as const },
|
|
388
|
-
},
|
|
389
|
-
annotations: [annotation],
|
|
390
|
-
},
|
|
391
|
-
{ width: 800, height: 460 },
|
|
392
|
-
);
|
|
393
|
-
const resolved = layout.annotations?.[0];
|
|
394
|
-
const bounds = resolved?.label?.bounds;
|
|
395
|
-
const pointX = resolved?.dot?.x;
|
|
396
|
-
if (!bounds || pointX === undefined) throw new Error('annotation did not resolve');
|
|
397
|
-
return bounds.x + bounds.width / 2 - pointX;
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
/** The block's horizontal span expressed relative to the data point. */
|
|
401
|
-
const spanAround = (annotation: Record<string, unknown>) => {
|
|
402
|
-
const layout = compileChart(
|
|
403
|
-
{
|
|
404
|
-
mark: 'line' as const,
|
|
405
|
-
data: [
|
|
406
|
-
{ m: 'Jan', v: 10 },
|
|
407
|
-
{ m: 'Feb', v: 50 },
|
|
408
|
-
{ m: 'Mar', v: 30 },
|
|
409
|
-
],
|
|
410
|
-
encoding: {
|
|
411
|
-
x: { field: 'm' as const, type: 'ordinal' as const },
|
|
412
|
-
y: { field: 'v' as const, type: 'quantitative' as const },
|
|
413
|
-
},
|
|
414
|
-
annotations: [annotation],
|
|
415
|
-
},
|
|
416
|
-
{ width: 800, height: 460 },
|
|
417
|
-
);
|
|
418
|
-
const resolved = layout.annotations?.[0];
|
|
419
|
-
const bounds = resolved?.label?.bounds;
|
|
420
|
-
const pointX = resolved?.dot?.x;
|
|
421
|
-
if (!bounds || pointX === undefined) throw new Error('annotation did not resolve');
|
|
422
|
-
return { left: bounds.x - pointX, right: bounds.x + bounds.width - pointX };
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
test('a single-line top-anchored block centers on its point', () => {
|
|
426
|
-
expect(
|
|
427
|
-
centerOffsetFor({ type: 'text', x: 'Feb', y: 50, text: 'Peak here', anchor: 'top' }),
|
|
428
|
-
).toBeCloseTo(0, 1);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test('a multi-line top-anchored block centers on its point', () => {
|
|
432
|
-
expect(
|
|
433
|
-
centerOffsetFor({
|
|
434
|
-
type: 'text',
|
|
435
|
-
x: 'Feb',
|
|
436
|
-
y: 50,
|
|
437
|
-
text: 'Hurricanes and\na strike cut',
|
|
438
|
-
anchor: 'top',
|
|
439
|
-
}),
|
|
440
|
-
).toBeCloseTo(0, 1);
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
test('a multi-line bottom-anchored block centers on its point', () => {
|
|
444
|
-
expect(
|
|
445
|
-
centerOffsetFor({
|
|
446
|
-
type: 'text',
|
|
447
|
-
x: 'Feb',
|
|
448
|
-
y: 50,
|
|
449
|
-
text: 'Two lines\nhere now',
|
|
450
|
-
anchor: 'bottom',
|
|
451
|
-
}),
|
|
452
|
-
).toBeCloseTo(0, 1);
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
test('the text inside stays left-aligned, not center-aligned', () => {
|
|
456
|
-
const layout = compileChart(
|
|
457
|
-
{
|
|
458
|
-
mark: 'line' as const,
|
|
459
|
-
data: [
|
|
460
|
-
{ m: 'Jan', v: 10 },
|
|
461
|
-
{ m: 'Feb', v: 50 },
|
|
462
|
-
],
|
|
463
|
-
encoding: {
|
|
464
|
-
x: { field: 'm' as const, type: 'ordinal' as const },
|
|
465
|
-
y: { field: 'v' as const, type: 'quantitative' as const },
|
|
466
|
-
},
|
|
467
|
-
annotations: [
|
|
468
|
-
{ type: 'text' as const, x: 'Feb', y: 50, text: 'Two\nlines', anchor: 'top' as const },
|
|
469
|
-
],
|
|
470
|
-
},
|
|
471
|
-
{ width: 800, height: 460 },
|
|
472
|
-
);
|
|
473
|
-
expect(layout.annotations?.[0]?.label?.style?.textAnchor).toBe('start');
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
test('left/right anchors are unaffected (block sits beside the point)', () => {
|
|
477
|
-
// "Beside", not "straddling": the whole block clears the point by exactly the
|
|
478
|
-
// ANCHOR_OFFSET setback (28px), so the point lies OUTSIDE the block's x-span.
|
|
479
|
-
// A sign check alone (`centerOffset < 0`) still passes for a centered block
|
|
480
|
-
// that has merely drifted — which is the regression this guards.
|
|
481
|
-
|
|
482
|
-
const left = spanAround({ type: 'text', x: 'Feb', y: 50, text: 'Left side', anchor: 'left' });
|
|
483
|
-
// Whole block to the LEFT: its near (right) edge stops one setback short of
|
|
484
|
-
// the point, so the point never falls inside the span.
|
|
485
|
-
expect(left.right).toBeCloseTo(-ANCHOR_OFFSET, 1);
|
|
486
|
-
expect(left.left).toBeLessThan(left.right);
|
|
487
|
-
|
|
488
|
-
const right = spanAround({
|
|
489
|
-
type: 'text',
|
|
490
|
-
x: 'Feb',
|
|
491
|
-
y: 50,
|
|
492
|
-
text: 'Right side',
|
|
493
|
-
anchor: 'right',
|
|
494
|
-
});
|
|
495
|
-
// Mirror image: near (left) edge starts one setback past the point.
|
|
496
|
-
expect(right.left).toBeCloseTo(ANCHOR_OFFSET, 1);
|
|
497
|
-
expect(right.right).toBeGreaterThan(right.left);
|
|
498
|
-
|
|
499
|
-
// And the contrast that gives "beside" its meaning: a top anchor STRADDLES,
|
|
500
|
-
// i.e. the point lies strictly inside the block's x-span.
|
|
501
|
-
const top = spanAround({ type: 'text', x: 'Feb', y: 50, text: 'Left side', anchor: 'top' });
|
|
502
|
-
expect(top.left).toBeLessThan(0);
|
|
503
|
-
expect(top.right).toBeGreaterThan(0);
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
describe('annotation typography survives normalization', () => {
|
|
508
|
-
const baseSpec = (annotation: Record<string, unknown>) => ({
|
|
509
|
-
mark: 'line' as const,
|
|
510
|
-
data: [
|
|
511
|
-
{ year: '1980', value: 0.26 },
|
|
512
|
-
{ year: '2025', value: 1.17 },
|
|
513
|
-
],
|
|
514
|
-
encoding: {
|
|
515
|
-
x: { field: 'year' as const, type: 'ordinal' as const },
|
|
516
|
-
y: { field: 'value' as const, type: 'quantitative' as const },
|
|
517
|
-
},
|
|
518
|
-
annotations: [annotation],
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
const styleOf = (annotation: Record<string, unknown>) => {
|
|
522
|
-
const layout = compileChart(baseSpec(annotation), { width: 800, height: 460 });
|
|
523
|
-
const style = layout.annotations?.[0]?.label?.style;
|
|
524
|
-
if (!style) throw new Error('annotation did not resolve a label style');
|
|
525
|
-
return style;
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
test('a subtitle promotes the primary line to a bold lede', () => {
|
|
529
|
-
const style = styleOf({
|
|
530
|
-
type: 'text',
|
|
531
|
-
x: '2025',
|
|
532
|
-
y: 1.17,
|
|
533
|
-
text: 'Peak',
|
|
534
|
-
subtitle: 'the warmest year on record',
|
|
535
|
-
});
|
|
536
|
-
expect(style.fontWeight).toBe(700);
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
test('a plain annotation stays regular weight', () => {
|
|
540
|
-
const style = styleOf({ type: 'text', x: '2025', y: 1.17, text: 'Peak' });
|
|
541
|
-
expect(style.fontWeight).toBe(400);
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
test('the default font size reaches the compiled output', () => {
|
|
545
|
-
const style = styleOf({ type: 'text', x: '2025', y: 1.17, text: 'Peak' });
|
|
546
|
-
expect(style.fontSize).toBe(13);
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
test('an explicit fontWeight beats the lede rule', () => {
|
|
550
|
-
const style = styleOf({
|
|
551
|
-
type: 'text',
|
|
552
|
-
x: '2025',
|
|
553
|
-
y: 1.17,
|
|
554
|
-
text: 'Peak',
|
|
555
|
-
subtitle: 'context',
|
|
556
|
-
fontWeight: 300,
|
|
557
|
-
});
|
|
558
|
-
expect(style.fontWeight).toBe(300);
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
test('an explicit fontSize beats the default', () => {
|
|
562
|
-
const style = styleOf({ type: 'text', x: '2025', y: 1.17, text: 'Peak', fontSize: 18 });
|
|
563
|
-
expect(style.fontSize).toBe(18);
|
|
564
|
-
});
|
|
565
|
-
});
|
|
566
|
-
});
|