@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
package/src/legend/wrap.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Legend row-wrap geometry.
|
|
3
|
-
*
|
|
4
|
-
* Shared helper for measuring how legend entries flow across horizontal rows
|
|
5
|
-
* when wrapped at a max width. Both the main legend compute and the sankey
|
|
6
|
-
* legend compile use this to size their legends — the main legend uses
|
|
7
|
-
* `fittingCount` for truncation decisions, while sankey uses `rowCount` to
|
|
8
|
-
* reserve vertical height.
|
|
9
|
-
*
|
|
10
|
-
* The geometry matches the existing layout exactly: each entry occupies
|
|
11
|
-
* SWATCH_SIZE + SWATCH_GAP + labelWidth + ENTRY_GAP pixels, a new row is
|
|
12
|
-
* started when the accumulated row width plus the next entry would exceed
|
|
13
|
-
* maxWidth (and the current row is non-empty), and rowWidths captures the
|
|
14
|
-
* in-row accumulated width at the point of wrapping.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { LegendEntry, TextStyle } from '@opendata-ai/openchart-core';
|
|
18
|
-
import { COMPACT_WIDTH, estimateTextWidth } from '@opendata-ai/openchart-core';
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Constants
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
//
|
|
24
|
-
// Single source of truth for legend row geometry. Both compute.ts and the
|
|
25
|
-
// sankey compile site import these so the wrap math here can never drift from
|
|
26
|
-
// the layout math at the call sites.
|
|
27
|
-
|
|
28
|
-
export const SWATCH_SIZE = 12;
|
|
29
|
-
export const SWATCH_GAP = 6;
|
|
30
|
-
export const ENTRY_GAP = 16;
|
|
31
|
-
/** Tighter inter-entry gap for narrow viewports where every pixel matters. */
|
|
32
|
-
export const ENTRY_GAP_COMPACT = 10;
|
|
33
|
-
|
|
34
|
-
/** Default gap between legend bounds and chart area. Zero on narrow viewports. */
|
|
35
|
-
export const LEGEND_GAP = 8;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Minimum gap between the chrome/metrics block above and a top legend.
|
|
39
|
-
* Without it the legend sits flush against the subtitle (compact widths)
|
|
40
|
-
* or the metric bar (all widths) once the inline-tick overhang is placed
|
|
41
|
-
* below the legend where it belongs.
|
|
42
|
-
*/
|
|
43
|
-
export const TOP_LEGEND_GAP_ABOVE = 6;
|
|
44
|
-
|
|
45
|
-
/** Gap between legend and chart area, responsive to container width. */
|
|
46
|
-
export function legendGap(width: number): number {
|
|
47
|
-
return width < COMPACT_WIDTH ? 0 : LEGEND_GAP;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Public API
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
export interface LegendWrapResult {
|
|
55
|
-
/** Total number of rows the entries occupy when wrapped at maxWidth. */
|
|
56
|
-
rowCount: number;
|
|
57
|
-
/** Entries that fit within maxRows (for truncation). Equals entries.length when maxRows is not set or all entries fit. */
|
|
58
|
-
fittingCount: number;
|
|
59
|
-
/** Width (in px) of each row — callers can use for alignment. */
|
|
60
|
-
rowWidths: number[];
|
|
61
|
-
/** Per-entry placement: wrap row and x offset within that row. */
|
|
62
|
-
placements: Array<{ row: number; xOffset: number }>;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Measure how legend entries wrap across rows at a given max width.
|
|
67
|
-
*
|
|
68
|
-
* @param entries - Legend entries to measure.
|
|
69
|
-
* @param maxWidth - Maximum width (in px) available for a single row.
|
|
70
|
-
* @param labelStyle - Text style used to estimate label widths.
|
|
71
|
-
* @param maxRows - Optional cap used only for the `fittingCount` truncation decision. When provided, `fittingCount` will be the index of the first entry that would spill onto a row beyond `maxRows`. `rowCount` is always the real row count regardless of this cap.
|
|
72
|
-
*/
|
|
73
|
-
export function measureLegendWrap(
|
|
74
|
-
entries: LegendEntry[],
|
|
75
|
-
maxWidth: number,
|
|
76
|
-
labelStyle: TextStyle,
|
|
77
|
-
maxRows?: number,
|
|
78
|
-
entryGap: number = ENTRY_GAP,
|
|
79
|
-
measure?: (text: string, fontSize: number, fontWeight: number) => number,
|
|
80
|
-
): LegendWrapResult {
|
|
81
|
-
if (entries.length === 0) {
|
|
82
|
-
return { rowCount: 0, fittingCount: 0, rowWidths: [], placements: [] };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const measureWidth = measure ?? estimateTextWidth;
|
|
86
|
-
|
|
87
|
-
let rowCount = 1;
|
|
88
|
-
let rowWidth = 0;
|
|
89
|
-
const rowWidths: number[] = [];
|
|
90
|
-
const placements: Array<{ row: number; xOffset: number }> = [];
|
|
91
|
-
let fittingCount = entries.length;
|
|
92
|
-
let fittingCountLocked = false;
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < entries.length; i++) {
|
|
95
|
-
const labelWidth = measureWidth(entries[i].label, labelStyle.fontSize, labelStyle.fontWeight);
|
|
96
|
-
const entryWidth = SWATCH_SIZE + SWATCH_GAP + labelWidth + entryGap;
|
|
97
|
-
|
|
98
|
-
if (rowWidth + entryWidth > maxWidth && rowWidth > 0) {
|
|
99
|
-
rowWidths.push(rowWidth);
|
|
100
|
-
rowCount++;
|
|
101
|
-
placements.push({ row: rowCount - 1, xOffset: 0 });
|
|
102
|
-
rowWidth = entryWidth;
|
|
103
|
-
if (!fittingCountLocked && maxRows != null && rowCount > maxRows) {
|
|
104
|
-
fittingCount = i;
|
|
105
|
-
fittingCountLocked = true;
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
placements.push({ row: rowCount - 1, xOffset: rowWidth });
|
|
109
|
-
rowWidth += entryWidth;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Flush the final row width so rowWidths has one entry per row.
|
|
114
|
-
rowWidths.push(rowWidth);
|
|
115
|
-
|
|
116
|
-
return { rowCount, fittingCount, rowWidths, placements };
|
|
117
|
-
}
|
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { compileSankey } from '../../compile';
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Shared fixtures
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
const basicSpec = {
|
|
9
|
-
type: 'sankey' as const,
|
|
10
|
-
data: [
|
|
11
|
-
{ from: 'A', to: 'C', amount: 10 },
|
|
12
|
-
{ from: 'B', to: 'C', amount: 20 },
|
|
13
|
-
{ from: 'C', to: 'D', amount: 15 },
|
|
14
|
-
{ from: 'C', to: 'E', amount: 15 },
|
|
15
|
-
],
|
|
16
|
-
encoding: {
|
|
17
|
-
source: { field: 'from', type: 'nominal' as const },
|
|
18
|
-
target: { field: 'to', type: 'nominal' as const },
|
|
19
|
-
value: { field: 'amount', type: 'quantitative' as const },
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const defaultOptions = { width: 600, height: 400 };
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Tests
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
describe('compileSankey', () => {
|
|
30
|
-
it('compiles a basic sankey and returns correct node/link counts', () => {
|
|
31
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
32
|
-
|
|
33
|
-
// A, B, C, D, E = 5 nodes
|
|
34
|
-
expect(result.nodes).toHaveLength(5);
|
|
35
|
-
// A->C, B->C, C->D, C->E = 4 links
|
|
36
|
-
expect(result.links).toHaveLength(4);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('infers nodes from unique source/target values in data', () => {
|
|
40
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
41
|
-
|
|
42
|
-
const nodeIds = result.nodes.map((n) => n.nodeId).sort();
|
|
43
|
-
expect(nodeIds).toEqual(['A', 'B', 'C', 'D', 'E']);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('node positions are valid (x >= 0, y >= 0, width > 0, height > 0)', () => {
|
|
47
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
48
|
-
|
|
49
|
-
for (const node of result.nodes) {
|
|
50
|
-
expect(node.x).toBeGreaterThanOrEqual(0);
|
|
51
|
-
expect(node.y).toBeGreaterThanOrEqual(0);
|
|
52
|
-
expect(node.width).toBeGreaterThan(0);
|
|
53
|
-
expect(node.height).toBeGreaterThan(0);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('link paths are valid SVG path strings starting with M', () => {
|
|
58
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
59
|
-
|
|
60
|
-
for (const link of result.links) {
|
|
61
|
-
expect(link.path).toBeTruthy();
|
|
62
|
-
expect(link.path[0]).toBe('M');
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('nodes get default colors from theme categorical palette', () => {
|
|
67
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
68
|
-
|
|
69
|
-
for (const node of result.nodes) {
|
|
70
|
-
expect(node.fill).toBeTruthy();
|
|
71
|
-
// Should be a color string (hex, rgb, etc.)
|
|
72
|
-
expect(typeof node.fill).toBe('string');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// With 5 nodes and no color encoding, each node gets a different palette slot
|
|
76
|
-
const fills = new Set(result.nodes.map((n) => n.fill));
|
|
77
|
-
expect(fills.size).toBe(5);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('color encoding groups nodes by category value', () => {
|
|
81
|
-
const spec = {
|
|
82
|
-
...basicSpec,
|
|
83
|
-
encoding: {
|
|
84
|
-
...basicSpec.encoding,
|
|
85
|
-
color: { field: 'from', type: 'nominal' as const },
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
const result = compileSankey(spec, defaultOptions);
|
|
89
|
-
|
|
90
|
-
// Nodes that share the same color field value should share the same color.
|
|
91
|
-
// "from" field: A has "A", B has "B", C is a target (gets the color of
|
|
92
|
-
// the first row where it appears as source or target).
|
|
93
|
-
const nodeA = result.nodes.find((n) => n.nodeId === 'A')!;
|
|
94
|
-
const nodeB = result.nodes.find((n) => n.nodeId === 'B')!;
|
|
95
|
-
// A and B have different "from" categories, so different colors
|
|
96
|
-
expect(nodeA.fill).not.toBe(nodeB.fill);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('linkStyle', () => {
|
|
100
|
-
it('gradient: sourceColor and targetColor differ (match connected nodes)', () => {
|
|
101
|
-
const spec = { ...basicSpec, linkStyle: 'gradient' as const };
|
|
102
|
-
const result = compileSankey(spec, defaultOptions);
|
|
103
|
-
|
|
104
|
-
// Link from A->C: source and target have different colors
|
|
105
|
-
const acLink = result.links.find((l) => l.sourceId === 'A' && l.targetId === 'C')!;
|
|
106
|
-
const nodeA = result.nodes.find((n) => n.nodeId === 'A')!;
|
|
107
|
-
const nodeC = result.nodes.find((n) => n.nodeId === 'C')!;
|
|
108
|
-
expect(acLink.sourceColor).toBe(nodeA.fill);
|
|
109
|
-
expect(acLink.targetColor).toBe(nodeC.fill);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('source: both colors equal source node fill', () => {
|
|
113
|
-
const spec = { ...basicSpec, linkStyle: 'source' as const };
|
|
114
|
-
const result = compileSankey(spec, defaultOptions);
|
|
115
|
-
|
|
116
|
-
const acLink = result.links.find((l) => l.sourceId === 'A' && l.targetId === 'C')!;
|
|
117
|
-
const nodeA = result.nodes.find((n) => n.nodeId === 'A')!;
|
|
118
|
-
expect(acLink.sourceColor).toBe(nodeA.fill);
|
|
119
|
-
expect(acLink.targetColor).toBe(nodeA.fill);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('target: both colors equal target node fill', () => {
|
|
123
|
-
const spec = { ...basicSpec, linkStyle: 'target' as const };
|
|
124
|
-
const result = compileSankey(spec, defaultOptions);
|
|
125
|
-
|
|
126
|
-
const acLink = result.links.find((l) => l.sourceId === 'A' && l.targetId === 'C')!;
|
|
127
|
-
const nodeC = result.nodes.find((n) => n.nodeId === 'C')!;
|
|
128
|
-
expect(acLink.sourceColor).toBe(nodeC.fill);
|
|
129
|
-
expect(acLink.targetColor).toBe(nodeC.fill);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('neutral: both colors are the same muted value', () => {
|
|
133
|
-
const spec = { ...basicSpec, linkStyle: 'neutral' as const };
|
|
134
|
-
const result = compileSankey(spec, defaultOptions);
|
|
135
|
-
|
|
136
|
-
for (const link of result.links) {
|
|
137
|
-
expect(link.sourceColor).toBe(link.targetColor);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// All neutral links share the same color
|
|
141
|
-
const colors = new Set(result.links.map((l) => l.sourceColor));
|
|
142
|
-
expect(colors.size).toBe(1);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe('chrome', () => {
|
|
147
|
-
it('resolves title and subtitle in output', () => {
|
|
148
|
-
const spec = {
|
|
149
|
-
...basicSpec,
|
|
150
|
-
chrome: {
|
|
151
|
-
title: 'Energy Flow',
|
|
152
|
-
subtitle: 'US energy sources to end uses',
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
const result = compileSankey(spec, defaultOptions);
|
|
156
|
-
|
|
157
|
-
expect(result.chrome.title).toBeDefined();
|
|
158
|
-
expect(result.chrome.title!.text).toBe('Energy Flow');
|
|
159
|
-
expect(result.chrome.subtitle).toBeDefined();
|
|
160
|
-
expect(result.chrome.subtitle!.text).toBe('US energy sources to end uses');
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe('legend', () => {
|
|
165
|
-
it('entries match unique node colors when color encoding is set', () => {
|
|
166
|
-
const spec = {
|
|
167
|
-
...basicSpec,
|
|
168
|
-
encoding: {
|
|
169
|
-
...basicSpec.encoding,
|
|
170
|
-
color: { field: 'from', type: 'nominal' as const },
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
const result = compileSankey(spec, defaultOptions);
|
|
174
|
-
|
|
175
|
-
// Legend should have entries for the unique color categories
|
|
176
|
-
expect(result.legend.entries.length).toBeGreaterThan(0);
|
|
177
|
-
for (const entry of result.legend.entries) {
|
|
178
|
-
expect(entry.label).toBeTruthy();
|
|
179
|
-
expect(entry.color).toBeTruthy();
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('has no legend entries when no color encoding is set', () => {
|
|
184
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
185
|
-
|
|
186
|
-
// Without color encoding, no legend needed
|
|
187
|
-
expect(result.legend.entries).toHaveLength(0);
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
describe('tooltip descriptors', () => {
|
|
192
|
-
it('contains entries for nodes keyed as node-{id}', () => {
|
|
193
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
194
|
-
|
|
195
|
-
expect(result.tooltipDescriptors.has('node-A')).toBe(true);
|
|
196
|
-
expect(result.tooltipDescriptors.has('node-B')).toBe(true);
|
|
197
|
-
expect(result.tooltipDescriptors.has('node-C')).toBe(true);
|
|
198
|
-
expect(result.tooltipDescriptors.has('node-D')).toBe(true);
|
|
199
|
-
expect(result.tooltipDescriptors.has('node-E')).toBe(true);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('contains entries for links keyed as link-{source}-{target}-{index}', () => {
|
|
203
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
204
|
-
|
|
205
|
-
// Keys include index suffix for uniqueness with duplicate source-target pairs
|
|
206
|
-
const linkKeys = [...result.tooltipDescriptors.keys()].filter((k) => k.startsWith('link-'));
|
|
207
|
-
expect(linkKeys.length).toBe(4);
|
|
208
|
-
// Each key should have a numeric suffix
|
|
209
|
-
for (const key of linkKeys) {
|
|
210
|
-
expect(key).toMatch(/link-.+-\d+$/);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('node tooltip has title and flow field', () => {
|
|
215
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
216
|
-
|
|
217
|
-
const tooltip = result.tooltipDescriptors.get('node-A')!;
|
|
218
|
-
expect(tooltip.title).toBeTruthy();
|
|
219
|
-
expect(tooltip.fields.length).toBeGreaterThan(0);
|
|
220
|
-
expect(tooltip.fields.some((f) => f.label === 'Total flow')).toBe(true);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('link tooltip has title and flow field', () => {
|
|
224
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
225
|
-
|
|
226
|
-
// Get the first link tooltip (keyed with index suffix)
|
|
227
|
-
const linkKey = [...result.tooltipDescriptors.keys()].find((k) => k.startsWith('link-'));
|
|
228
|
-
expect(linkKey).toBeTruthy();
|
|
229
|
-
const tooltip = result.tooltipDescriptors.get(linkKey!)!;
|
|
230
|
-
expect(tooltip.title).toContain('\u2192'); // arrow character
|
|
231
|
-
expect(tooltip.fields.some((f) => f.label === 'Flow')).toBe(true);
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe('animation', () => {
|
|
236
|
-
it('node animation indices increase left-to-right by column depth', () => {
|
|
237
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
238
|
-
|
|
239
|
-
// Group nodes by depth
|
|
240
|
-
const byDepth = new Map<number, number[]>();
|
|
241
|
-
for (const node of result.nodes) {
|
|
242
|
-
const indices = byDepth.get(node.depth) ?? [];
|
|
243
|
-
indices.push(node.animationIndex);
|
|
244
|
-
byDepth.set(node.depth, indices);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// All indices in a shallower column should be less than all indices
|
|
248
|
-
// in a deeper column
|
|
249
|
-
const depths = [...byDepth.keys()].sort((a, b) => a - b);
|
|
250
|
-
for (let i = 0; i < depths.length - 1; i++) {
|
|
251
|
-
const currentMax = Math.max(...byDepth.get(depths[i])!);
|
|
252
|
-
const nextMin = Math.min(...byDepth.get(depths[i + 1])!);
|
|
253
|
-
expect(currentMax).toBeLessThan(nextMin);
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('link animation indices come after all node indices', () => {
|
|
258
|
-
const spec = { ...basicSpec, animation: true };
|
|
259
|
-
const result = compileSankey(spec, defaultOptions);
|
|
260
|
-
|
|
261
|
-
const maxNodeIndex = Math.max(...result.nodes.map((n) => n.animationIndex));
|
|
262
|
-
const minLinkIndex = Math.min(...result.links.map((l) => l.animationIndex));
|
|
263
|
-
expect(minLinkIndex).toBeGreaterThan(maxNodeIndex);
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
describe('a11y', () => {
|
|
268
|
-
it('generates descriptive alt text', () => {
|
|
269
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
270
|
-
|
|
271
|
-
expect(result.a11y.altText).toContain('5 nodes');
|
|
272
|
-
expect(result.a11y.altText).toContain('4 links');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('has a data table fallback', () => {
|
|
276
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
277
|
-
|
|
278
|
-
expect(result.a11y.dataTableFallback.length).toBeGreaterThan(0);
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
describe('dimensions', () => {
|
|
283
|
-
it('reflects the compile options', () => {
|
|
284
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
285
|
-
|
|
286
|
-
expect(result.dimensions.width).toBe(600);
|
|
287
|
-
expect(result.dimensions.height).toBe(400);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
describe('dark mode', () => {
|
|
292
|
-
it('applies dark mode theme when option is set', () => {
|
|
293
|
-
const result = compileSankey(basicSpec, { ...defaultOptions, darkMode: true });
|
|
294
|
-
|
|
295
|
-
expect(result.theme.isDark).toBe(true);
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
describe('validation', () => {
|
|
300
|
-
it('throws when data is empty', () => {
|
|
301
|
-
const spec = {
|
|
302
|
-
...basicSpec,
|
|
303
|
-
data: [],
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
expect(() => compileSankey(spec, defaultOptions)).toThrow();
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('throws when source encoding is missing', () => {
|
|
310
|
-
const spec = {
|
|
311
|
-
...basicSpec,
|
|
312
|
-
encoding: {
|
|
313
|
-
target: { field: 'to', type: 'nominal' as const },
|
|
314
|
-
value: { field: 'amount', type: 'quantitative' as const },
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
expect(() => compileSankey(spec, defaultOptions)).toThrow();
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('throws when target encoding is missing', () => {
|
|
322
|
-
const spec = {
|
|
323
|
-
...basicSpec,
|
|
324
|
-
encoding: {
|
|
325
|
-
source: { field: 'from', type: 'nominal' as const },
|
|
326
|
-
value: { field: 'amount', type: 'quantitative' as const },
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
expect(() => compileSankey(spec, defaultOptions)).toThrow();
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('throws when value encoding is missing', () => {
|
|
334
|
-
const spec = {
|
|
335
|
-
...basicSpec,
|
|
336
|
-
encoding: {
|
|
337
|
-
source: { field: 'from', type: 'nominal' as const },
|
|
338
|
-
target: { field: 'to', type: 'nominal' as const },
|
|
339
|
-
},
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
expect(() => compileSankey(spec, defaultOptions)).toThrow();
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it('throws for non-sankey specs', () => {
|
|
346
|
-
const chartSpec = {
|
|
347
|
-
mark: 'bar' as const,
|
|
348
|
-
data: [{ x: 1, y: 2 }],
|
|
349
|
-
encoding: {
|
|
350
|
-
x: { field: 'x', type: 'quantitative' as const },
|
|
351
|
-
y: { field: 'y', type: 'quantitative' as const },
|
|
352
|
-
},
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
expect(() => compileSankey(chartSpec, defaultOptions)).toThrow(/non-sankey spec/);
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
describe('special characters in node names', () => {
|
|
360
|
-
it('compiles with spaces and $ in node names', () => {
|
|
361
|
-
const spec = {
|
|
362
|
-
type: 'sankey' as const,
|
|
363
|
-
data: [
|
|
364
|
-
{ from: 'Income $104k', to: 'Essential costs', amount: 50 },
|
|
365
|
-
{ from: 'Income $104k', to: 'Taxes & fees', amount: 20 },
|
|
366
|
-
{ from: 'Essential costs', to: 'Housing #1', amount: 30 },
|
|
367
|
-
{ from: 'Essential costs', to: 'Food (groceries)', amount: 20 },
|
|
368
|
-
],
|
|
369
|
-
encoding: {
|
|
370
|
-
source: { field: 'from', type: 'nominal' as const },
|
|
371
|
-
target: { field: 'to', type: 'nominal' as const },
|
|
372
|
-
value: { field: 'amount', type: 'quantitative' as const },
|
|
373
|
-
},
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
const result = compileSankey(spec, defaultOptions);
|
|
377
|
-
expect(result.nodes.length).toBe(5);
|
|
378
|
-
expect(result.links.length).toBe(4);
|
|
379
|
-
// Node IDs should preserve the original names
|
|
380
|
-
expect(result.nodes.some((n) => n.nodeId === 'Income $104k')).toBe(true);
|
|
381
|
-
expect(result.nodes.some((n) => n.nodeId === 'Taxes & fees')).toBe(true);
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
describe('dark mode colors', () => {
|
|
386
|
-
it('preserves vivid categorical colors in dark mode', () => {
|
|
387
|
-
const spec = {
|
|
388
|
-
...basicSpec,
|
|
389
|
-
theme: { colors: ['#38bdf8', '#f87171', '#4ade80'] },
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const lightResult = compileSankey(spec, defaultOptions);
|
|
393
|
-
const darkResult = compileSankey(spec, { ...defaultOptions, darkMode: true });
|
|
394
|
-
|
|
395
|
-
// Dark mode should use the same vivid node colors, not dark-adapted ones
|
|
396
|
-
const lightColors = lightResult.nodes.map((n) => n.fill);
|
|
397
|
-
const darkColors = darkResult.nodes.map((n) => n.fill);
|
|
398
|
-
expect(darkColors).toEqual(lightColors);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it('uses higher link opacity in dark mode', () => {
|
|
402
|
-
const lightResult = compileSankey(basicSpec, defaultOptions);
|
|
403
|
-
const darkResult = compileSankey(basicSpec, { ...defaultOptions, darkMode: true });
|
|
404
|
-
|
|
405
|
-
const lightOpacity = lightResult.links[0].fillOpacity;
|
|
406
|
-
const darkOpacity = darkResult.links[0].fillOpacity;
|
|
407
|
-
expect(darkOpacity).toBeGreaterThan(lightOpacity);
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
describe('valueFormat', () => {
|
|
412
|
-
it('formats tooltip values when valueFormat is set', () => {
|
|
413
|
-
const spec = { ...basicSpec, valueFormat: '.0f%' };
|
|
414
|
-
const result = compileSankey(spec, defaultOptions);
|
|
415
|
-
|
|
416
|
-
// Check that node tooltips use the format
|
|
417
|
-
const nodeTooltip = result.tooltipDescriptors.get('node-C');
|
|
418
|
-
expect(nodeTooltip?.fields[0].value).toContain('%');
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
it('uses default formatting when valueFormat is undefined', () => {
|
|
422
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
423
|
-
|
|
424
|
-
const nodeTooltip = result.tooltipDescriptors.get('node-C');
|
|
425
|
-
expect(nodeTooltip?.fields[0].value).not.toContain('%');
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it('falls back to default on invalid format string', () => {
|
|
429
|
-
const spec = { ...basicSpec, valueFormat: 'not-a-format' };
|
|
430
|
-
// Should not throw
|
|
431
|
-
const result = compileSankey(spec, defaultOptions);
|
|
432
|
-
expect(result.nodes.length).toBeGreaterThan(0);
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
describe('linkOpacity', () => {
|
|
437
|
-
it('uses custom linkOpacity when specified', () => {
|
|
438
|
-
const spec = { ...basicSpec, linkOpacity: 0.9 };
|
|
439
|
-
const result = compileSankey(spec, defaultOptions);
|
|
440
|
-
|
|
441
|
-
expect(result.links[0].fillOpacity).toBe(0.9);
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
it('uses default opacity when linkOpacity is not set', () => {
|
|
445
|
-
const result = compileSankey(basicSpec, defaultOptions);
|
|
446
|
-
// Light mode default
|
|
447
|
-
expect(result.links[0].fillOpacity).toBe(0.5);
|
|
448
|
-
});
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
describe('dimensions', () => {
|
|
452
|
-
it('dimensions match the provided container size', () => {
|
|
453
|
-
const result = compileSankey(basicSpec, { width: 600, height: 800 });
|
|
454
|
-
expect(result.dimensions.width).toBe(600);
|
|
455
|
-
expect(result.dimensions.height).toBe(800);
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
});
|