@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
package/src/sankey/layout.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* d3-sankey layout wrapper.
|
|
3
|
-
*
|
|
4
|
-
* Extracts unique nodes from tabular data rows, configures the d3-sankey
|
|
5
|
-
* generator, and returns computed node/link positions. Clones input data
|
|
6
|
-
* before passing to d3-sankey since it mutates objects in place.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Rect, SankeyNodeAlign } from '@opendata-ai/openchart-core';
|
|
10
|
-
import type { SankeyExtraProperties, SankeyGraph, SankeyLink, SankeyNode } from 'd3-sankey';
|
|
11
|
-
import { sankey, sankeyCenter, sankeyJustify, sankeyLeft, sankeyRight } from 'd3-sankey';
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Types
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
/** Extra properties carried on our sankey nodes. */
|
|
18
|
-
interface NodeExtra extends SankeyExtraProperties {
|
|
19
|
-
id: string;
|
|
20
|
-
label: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Extra properties carried on our sankey links. */
|
|
24
|
-
interface LinkExtra extends SankeyExtraProperties {
|
|
25
|
-
/** Original data row for this link. */
|
|
26
|
-
data: Record<string, unknown>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type ComputedNode = SankeyNode<NodeExtra, LinkExtra>;
|
|
30
|
-
export type ComputedLink = SankeyLink<NodeExtra, LinkExtra>;
|
|
31
|
-
|
|
32
|
-
export interface SankeyLayoutResult {
|
|
33
|
-
nodes: ComputedNode[];
|
|
34
|
-
links: ComputedLink[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Alignment resolver
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
const ALIGN_MAP: Record<SankeyNodeAlign, typeof sankeyJustify> = {
|
|
42
|
-
justify: sankeyJustify,
|
|
43
|
-
left: sankeyLeft,
|
|
44
|
-
right: sankeyRight,
|
|
45
|
-
center: sankeyCenter,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// Public API
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Run the d3-sankey layout algorithm on tabular flow data.
|
|
54
|
-
*
|
|
55
|
-
* @param data - Array of data rows (each row is a source-target-value flow).
|
|
56
|
-
* @param sourceField - Field name for the source node.
|
|
57
|
-
* @param targetField - Field name for the target node.
|
|
58
|
-
* @param valueField - Field name for the flow value.
|
|
59
|
-
* @param area - Drawing area rect (after chrome subtracted).
|
|
60
|
-
* @param nodeWidth - Width of node rectangles in px.
|
|
61
|
-
* @param nodePadding - Vertical padding between nodes in px.
|
|
62
|
-
* @param nodeAlign - Node alignment strategy.
|
|
63
|
-
* @param iterations - Number of layout relaxation iterations.
|
|
64
|
-
* @returns Computed node and link positions.
|
|
65
|
-
*/
|
|
66
|
-
export function computeSankeyLayout(
|
|
67
|
-
data: Record<string, unknown>[],
|
|
68
|
-
sourceField: string,
|
|
69
|
-
targetField: string,
|
|
70
|
-
valueField: string,
|
|
71
|
-
area: Rect,
|
|
72
|
-
nodeWidth: number,
|
|
73
|
-
nodePadding: number,
|
|
74
|
-
nodeAlign: SankeyNodeAlign,
|
|
75
|
-
iterations: number,
|
|
76
|
-
nodeSort?: string[],
|
|
77
|
-
): SankeyLayoutResult {
|
|
78
|
-
// Extract unique node IDs from source and target columns
|
|
79
|
-
const nodeSet = new Set<string>();
|
|
80
|
-
for (const row of data) {
|
|
81
|
-
nodeSet.add(String(row[sourceField]));
|
|
82
|
-
nodeSet.add(String(row[targetField]));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Build node and link arrays (cloned so d3-sankey mutations don't affect input)
|
|
86
|
-
const nodes: Array<{ id: string; label: string }> = [...nodeSet].map((id) => ({
|
|
87
|
-
id,
|
|
88
|
-
label: id,
|
|
89
|
-
}));
|
|
90
|
-
|
|
91
|
-
const links: Array<{
|
|
92
|
-
source: string;
|
|
93
|
-
target: string;
|
|
94
|
-
value: number;
|
|
95
|
-
data: Record<string, unknown>;
|
|
96
|
-
}> = data.map((row) => ({
|
|
97
|
-
source: String(row[sourceField]),
|
|
98
|
-
target: String(row[targetField]),
|
|
99
|
-
value: Number(row[valueField]) || 0,
|
|
100
|
-
data: { ...row },
|
|
101
|
-
}));
|
|
102
|
-
|
|
103
|
-
// Configure and run d3-sankey
|
|
104
|
-
const alignFn = ALIGN_MAP[nodeAlign] ?? sankeyJustify;
|
|
105
|
-
|
|
106
|
-
const generator = sankey<SankeyGraph<NodeExtra, LinkExtra>, NodeExtra, LinkExtra>()
|
|
107
|
-
.nodeId((d) => d.id)
|
|
108
|
-
.nodeAlign(alignFn as unknown as (node: SankeyNode<NodeExtra, LinkExtra>, n: number) => number)
|
|
109
|
-
.nodeWidth(nodeWidth)
|
|
110
|
-
.nodePadding(nodePadding)
|
|
111
|
-
.extent([
|
|
112
|
-
[area.x, area.y],
|
|
113
|
-
[area.x + area.width, area.y + area.height],
|
|
114
|
-
])
|
|
115
|
-
.iterations(iterations);
|
|
116
|
-
|
|
117
|
-
// Apply explicit node ordering when provided.
|
|
118
|
-
// Builds a comparator from the ordered ID array so d3-sankey places nodes
|
|
119
|
-
// top-to-bottom within each column according to the spec's nodeSort.
|
|
120
|
-
if (nodeSort && nodeSort.length > 0) {
|
|
121
|
-
const orderMap = new Map(nodeSort.map((id, i) => [id, i]));
|
|
122
|
-
const fallback = nodeSort.length;
|
|
123
|
-
generator.nodeSort(
|
|
124
|
-
(a: SankeyNode<NodeExtra, LinkExtra>, b: SankeyNode<NodeExtra, LinkExtra>) =>
|
|
125
|
-
(orderMap.get((a as unknown as NodeExtra).id) ?? fallback) -
|
|
126
|
-
(orderMap.get((b as unknown as NodeExtra).id) ?? fallback),
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const graph = generator({
|
|
131
|
-
nodes: nodes as unknown as Array<SankeyNode<NodeExtra, LinkExtra>>,
|
|
132
|
-
links: links as unknown as Array<SankeyLink<NodeExtra, LinkExtra>>,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
nodes: graph.nodes,
|
|
137
|
-
links: graph.links,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Generate a filled ribbon SVG path for a sankey link.
|
|
143
|
-
*
|
|
144
|
-
* d3-sankey's sankeyLinkHorizontal() only produces a stroke centerline.
|
|
145
|
-
* This generates a closed area path with two cubic bezier edges (top and
|
|
146
|
-
* bottom) forming a ribbon whose width is proportional to flow value.
|
|
147
|
-
*
|
|
148
|
-
* The link object from d3-sankey provides:
|
|
149
|
-
* - y0: center y at source side
|
|
150
|
-
* - y1: center y at target side
|
|
151
|
-
* - width: thickness of the ribbon
|
|
152
|
-
* - source.x1: right edge of source node
|
|
153
|
-
* - target.x0: left edge of target node
|
|
154
|
-
*/
|
|
155
|
-
export function generateLinkPath(link: ComputedLink): string {
|
|
156
|
-
const source = link.source as ComputedNode;
|
|
157
|
-
const target = link.target as ComputedNode;
|
|
158
|
-
|
|
159
|
-
const x0 = source.x1 ?? 0;
|
|
160
|
-
const x1 = target.x0 ?? 0;
|
|
161
|
-
const y0 = link.y0 ?? 0;
|
|
162
|
-
const y1 = link.y1 ?? 0;
|
|
163
|
-
const halfWidth0 = (link.width ?? 0) / 2;
|
|
164
|
-
const halfWidth1 = halfWidth0;
|
|
165
|
-
|
|
166
|
-
// Control point x at the horizontal midpoint for smooth S-curves
|
|
167
|
-
const mx = (x0 + x1) / 2;
|
|
168
|
-
|
|
169
|
-
// Top edge: left-to-right
|
|
170
|
-
const topY0 = y0 - halfWidth0;
|
|
171
|
-
const topY1 = y1 - halfWidth1;
|
|
172
|
-
|
|
173
|
-
// Bottom edge: right-to-left
|
|
174
|
-
const botY0 = y0 + halfWidth0;
|
|
175
|
-
const botY1 = y1 + halfWidth1;
|
|
176
|
-
|
|
177
|
-
return [
|
|
178
|
-
`M${x0},${topY0}`,
|
|
179
|
-
`C${mx},${topY0} ${mx},${topY1} ${x1},${topY1}`,
|
|
180
|
-
`L${x1},${botY1}`,
|
|
181
|
-
`C${mx},${botY1} ${mx},${botY0} ${x0},${botY0}`,
|
|
182
|
-
'Z',
|
|
183
|
-
].join(' ');
|
|
184
|
-
}
|
package/src/sankey/types.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal normalized sankey spec type used by the compilation pipeline.
|
|
3
|
-
*
|
|
4
|
-
* This mirrors NormalizedChartSpec/NormalizedGraphSpec: all optional fields
|
|
5
|
-
* have been filled with sensible defaults. It's an engine implementation
|
|
6
|
-
* detail, not a public contract.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
AnimationSpec,
|
|
11
|
-
DarkMode,
|
|
12
|
-
LegendConfig,
|
|
13
|
-
SankeyEncoding,
|
|
14
|
-
SankeyLinkColor,
|
|
15
|
-
SankeyNodeAlign,
|
|
16
|
-
ThemeConfig,
|
|
17
|
-
} from '@opendata-ai/openchart-core';
|
|
18
|
-
|
|
19
|
-
import type { NormalizedChrome } from '../compiler/types';
|
|
20
|
-
|
|
21
|
-
/** A SankeySpec with all optional fields filled with defaults. */
|
|
22
|
-
export interface NormalizedSankeySpec {
|
|
23
|
-
type: 'sankey';
|
|
24
|
-
data: Record<string, unknown>[];
|
|
25
|
-
encoding: SankeyEncoding;
|
|
26
|
-
nodeWidth: number;
|
|
27
|
-
nodePadding: number;
|
|
28
|
-
nodeAlign: SankeyNodeAlign;
|
|
29
|
-
iterations: number;
|
|
30
|
-
linkStyle: SankeyLinkColor;
|
|
31
|
-
nodeLabelAlign: 'auto' | 'left' | 'right';
|
|
32
|
-
nodeSort?: string[];
|
|
33
|
-
chrome: NormalizedChrome;
|
|
34
|
-
legend?: LegendConfig;
|
|
35
|
-
theme: ThemeConfig;
|
|
36
|
-
darkMode: DarkMode;
|
|
37
|
-
watermark: boolean;
|
|
38
|
-
animation?: AnimationSpec;
|
|
39
|
-
valueFormat?: string;
|
|
40
|
-
linkOpacity?: number;
|
|
41
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import type { BarColumnConfig } from '@opendata-ai/openchart-core';
|
|
2
|
-
import { resolveTheme } from '@opendata-ai/openchart-core';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import { computeBarCell, computeColumnMax, computeColumnMin } from '../bar-column';
|
|
5
|
-
|
|
6
|
-
const theme = resolveTheme();
|
|
7
|
-
|
|
8
|
-
describe('computeBarCell', () => {
|
|
9
|
-
it('computes correct percentage for mid-range value', () => {
|
|
10
|
-
const config: BarColumnConfig = {};
|
|
11
|
-
const result = computeBarCell(50, config, 100, 0, theme, false);
|
|
12
|
-
expect(result.barPercent).toBeCloseTo(0.5);
|
|
13
|
-
expect(result.barOffset).toBe(0);
|
|
14
|
-
expect(result.isNegative).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('computes 100% for max value', () => {
|
|
18
|
-
const config: BarColumnConfig = {};
|
|
19
|
-
const result = computeBarCell(100, config, 100, 0, theme, false);
|
|
20
|
-
expect(result.barPercent).toBeCloseTo(1);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('computes 0% for zero value', () => {
|
|
24
|
-
const config: BarColumnConfig = {};
|
|
25
|
-
const result = computeBarCell(0, config, 100, 0, theme, false);
|
|
26
|
-
expect(result.barPercent).toBeCloseTo(0);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('uses config maxValue when provided', () => {
|
|
30
|
-
const config: BarColumnConfig = { maxValue: 200 };
|
|
31
|
-
const result = computeBarCell(100, config, 50, 0, theme, false);
|
|
32
|
-
// Should use config maxValue (200), not columnMax (50)
|
|
33
|
-
expect(result.barPercent).toBeCloseTo(0.5);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('clamps negative values to 0 when column has no negatives', () => {
|
|
37
|
-
const config: BarColumnConfig = {};
|
|
38
|
-
// columnMin=0 means no negative values in the column
|
|
39
|
-
const result = computeBarCell(-10, config, 100, 0, theme, false);
|
|
40
|
-
expect(result.barPercent).toBe(0);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('clamps values above max to 1', () => {
|
|
44
|
-
const config: BarColumnConfig = {};
|
|
45
|
-
const result = computeBarCell(150, config, 100, 0, theme, false);
|
|
46
|
-
expect(result.barPercent).toBe(1);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('uses custom color from config', () => {
|
|
50
|
-
const config: BarColumnConfig = { color: '#ff0000' };
|
|
51
|
-
const result = computeBarCell(50, config, 100, 0, theme, false);
|
|
52
|
-
expect(result.barColor).toBe('#ff0000');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('defaults to first categorical palette color', () => {
|
|
56
|
-
const config: BarColumnConfig = {};
|
|
57
|
-
const result = computeBarCell(50, config, 100, 0, theme, false);
|
|
58
|
-
expect(result.barColor).toBe(theme.colors.categorical[0]);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('handles NaN value', () => {
|
|
62
|
-
const config: BarColumnConfig = {};
|
|
63
|
-
const result = computeBarCell(NaN, config, 100, 0, theme, false);
|
|
64
|
-
expect(result.barPercent).toBe(0);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('handles Infinity value', () => {
|
|
68
|
-
const config: BarColumnConfig = {};
|
|
69
|
-
const result = computeBarCell(Infinity, config, 100, 0, theme, false);
|
|
70
|
-
expect(result.barPercent).toBe(0);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('handles zero columnMax', () => {
|
|
74
|
-
const config: BarColumnConfig = {};
|
|
75
|
-
const result = computeBarCell(50, config, 0, 0, theme, false);
|
|
76
|
-
expect(result.barPercent).toBe(0);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Negative value support
|
|
80
|
-
it('renders negative value as bidirectional bar', () => {
|
|
81
|
-
const config: BarColumnConfig = {};
|
|
82
|
-
// Column range: -20 to 80, totalRange = 100, zeroPos = 0.2
|
|
83
|
-
const result = computeBarCell(-10, config, 80, -20, theme, false);
|
|
84
|
-
expect(result.isNegative).toBe(true);
|
|
85
|
-
expect(result.barPercent).toBeCloseTo(0.1); // 10/100
|
|
86
|
-
expect(result.barOffset).toBeCloseTo(0.1); // zeroPos(0.2) - barPercent(0.1)
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('renders positive value with offset when column has negatives', () => {
|
|
90
|
-
const config: BarColumnConfig = {};
|
|
91
|
-
// Column range: -20 to 80, totalRange = 100, zeroPos = 0.2
|
|
92
|
-
const result = computeBarCell(40, config, 80, -20, theme, false);
|
|
93
|
-
expect(result.isNegative).toBe(false);
|
|
94
|
-
expect(result.barPercent).toBeCloseTo(0.4); // 40/100
|
|
95
|
-
expect(result.barOffset).toBeCloseTo(0.2); // zeroPos
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('uses red color for negative bars by default', () => {
|
|
99
|
-
const config: BarColumnConfig = {};
|
|
100
|
-
const result = computeBarCell(-10, config, 80, -20, theme, false);
|
|
101
|
-
expect(result.isNegative).toBe(true);
|
|
102
|
-
expect(result.barColor).toBe('#c44e52');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('uses custom color for negative bars when config.color is set', () => {
|
|
106
|
-
const config: BarColumnConfig = { color: '#ff0000' };
|
|
107
|
-
const result = computeBarCell(-10, config, 80, -20, theme, false);
|
|
108
|
-
expect(result.barColor).toBe('#ff0000');
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('computeColumnMax', () => {
|
|
113
|
-
it('finds the max numeric value in a column', () => {
|
|
114
|
-
const data = [{ value: 10 }, { value: 50 }, { value: 30 }];
|
|
115
|
-
expect(computeColumnMax(data, 'value')).toBe(50);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('ignores non-numeric values', () => {
|
|
119
|
-
const data = [{ value: 10 }, { value: 'hello' }, { value: null }, { value: 30 }];
|
|
120
|
-
expect(computeColumnMax(data, 'value')).toBe(30);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('returns 0 for empty data', () => {
|
|
124
|
-
expect(computeColumnMax([], 'value')).toBe(0);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('returns 0 when all values are non-numeric', () => {
|
|
128
|
-
const data = [{ value: 'a' }, { value: 'b' }];
|
|
129
|
-
expect(computeColumnMax(data, 'value')).toBe(0);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('computeColumnMin', () => {
|
|
134
|
-
it('finds the min numeric value in a column', () => {
|
|
135
|
-
const data = [{ value: 10 }, { value: -5 }, { value: 30 }];
|
|
136
|
-
expect(computeColumnMin(data, 'value')).toBe(-5);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('returns 0 for all-positive data', () => {
|
|
140
|
-
const data = [{ value: 10 }, { value: 20 }];
|
|
141
|
-
expect(computeColumnMin(data, 'value')).toBe(0);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('returns 0 for empty data', () => {
|
|
145
|
-
expect(computeColumnMin([], 'value')).toBe(0);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import type { ColumnConfig, ResolvedTheme } from '@opendata-ai/openchart-core';
|
|
2
|
-
import { adaptTheme, contrastRatio, resolveTheme } from '@opendata-ai/openchart-core';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import { computeCategoryColors } from '../category-colors';
|
|
5
|
-
|
|
6
|
-
function getTheme(dark = false): ResolvedTheme {
|
|
7
|
-
const theme = resolveTheme();
|
|
8
|
-
return dark ? adaptTheme(theme) : theme;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('computeCategoryColors', () => {
|
|
12
|
-
const data = [
|
|
13
|
-
{ status: 'active' },
|
|
14
|
-
{ status: 'inactive' },
|
|
15
|
-
{ status: 'active' },
|
|
16
|
-
{ status: 'pending' },
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
it('applies explicit color mapping correctly', () => {
|
|
20
|
-
const col: ColumnConfig = {
|
|
21
|
-
key: 'status',
|
|
22
|
-
categoryColors: {
|
|
23
|
-
active: '#00ff00',
|
|
24
|
-
inactive: '#ff0000',
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
const theme = getTheme();
|
|
28
|
-
const colors = computeCategoryColors(data, col, theme, false);
|
|
29
|
-
|
|
30
|
-
expect(colors.size).toBe(3);
|
|
31
|
-
// "active" rows (indices 0, 2) should have green background
|
|
32
|
-
expect(colors.get(0)!.backgroundColor).toBe('#00ff00');
|
|
33
|
-
expect(colors.get(2)!.backgroundColor).toBe('#00ff00');
|
|
34
|
-
// "inactive" row (index 1) should have red background
|
|
35
|
-
expect(colors.get(1)!.backgroundColor).toBe('#ff0000');
|
|
36
|
-
// "pending" (index 3) is not in the explicit map, should be skipped
|
|
37
|
-
expect(colors.has(3)).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('unmapped values are skipped by default', () => {
|
|
41
|
-
const col: ColumnConfig = {
|
|
42
|
-
key: 'status',
|
|
43
|
-
categoryColors: {
|
|
44
|
-
active: '#00ff00',
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
const theme = getTheme();
|
|
48
|
-
const colors = computeCategoryColors(data, col, theme, false);
|
|
49
|
-
|
|
50
|
-
// Only "active" rows (indices 0, 2) should be colored
|
|
51
|
-
expect(colors.size).toBe(2);
|
|
52
|
-
expect(colors.get(0)!.backgroundColor).toBe('#00ff00');
|
|
53
|
-
expect(colors.get(2)!.backgroundColor).toBe('#00ff00');
|
|
54
|
-
// Unmapped values should not have entries
|
|
55
|
-
expect(colors.has(1)).toBe(false); // inactive
|
|
56
|
-
expect(colors.has(3)).toBe(false); // pending
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('autoAssign: true assigns palette colors to unmapped values', () => {
|
|
60
|
-
const col: ColumnConfig = {
|
|
61
|
-
key: 'status',
|
|
62
|
-
categoryColors: {
|
|
63
|
-
active: '#00ff00',
|
|
64
|
-
},
|
|
65
|
-
autoAssign: true,
|
|
66
|
-
};
|
|
67
|
-
const theme = getTheme();
|
|
68
|
-
const colors = computeCategoryColors(data, col, theme, false);
|
|
69
|
-
|
|
70
|
-
// All 4 rows should be colored
|
|
71
|
-
expect(colors.size).toBe(4);
|
|
72
|
-
// "inactive" and "pending" should get palette colors (not the explicit green)
|
|
73
|
-
const inactiveBg = colors.get(1)!.backgroundColor!;
|
|
74
|
-
const pendingBg = colors.get(3)!.backgroundColor!;
|
|
75
|
-
expect(inactiveBg).toBeTruthy();
|
|
76
|
-
expect(pendingBg).toBeTruthy();
|
|
77
|
-
expect(inactiveBg).not.toBe('#00ff00');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('same category value gets consistent colors', () => {
|
|
81
|
-
const col: ColumnConfig = {
|
|
82
|
-
key: 'status',
|
|
83
|
-
categoryColors: { active: '#00ff00' },
|
|
84
|
-
};
|
|
85
|
-
const theme = getTheme();
|
|
86
|
-
const colors = computeCategoryColors(data, col, theme, false);
|
|
87
|
-
|
|
88
|
-
// Both "active" rows should have the same color
|
|
89
|
-
expect(colors.get(0)!.backgroundColor).toBe(colors.get(2)!.backgroundColor);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('text contrast meets AA (at least 3:1)', () => {
|
|
93
|
-
const col: ColumnConfig = {
|
|
94
|
-
key: 'status',
|
|
95
|
-
categoryColors: {
|
|
96
|
-
active: '#00ff00',
|
|
97
|
-
inactive: '#ff0000',
|
|
98
|
-
pending: '#0000ff',
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
const theme = getTheme();
|
|
102
|
-
const colors = computeCategoryColors(data, col, theme, false);
|
|
103
|
-
|
|
104
|
-
for (const [, style] of colors) {
|
|
105
|
-
const bg = style.backgroundColor!;
|
|
106
|
-
const fg = style.color!;
|
|
107
|
-
const ratio = contrastRatio(fg, bg);
|
|
108
|
-
// accessibleTextColor picks black or white; both should exceed 3:1
|
|
109
|
-
expect(ratio).toBeGreaterThanOrEqual(3);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('dark mode preserves explicit user-provided colors', () => {
|
|
114
|
-
const col: ColumnConfig = {
|
|
115
|
-
key: 'status',
|
|
116
|
-
categoryColors: {
|
|
117
|
-
active: '#00ff00',
|
|
118
|
-
inactive: '#ff0000',
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
const darkTheme = getTheme(true);
|
|
122
|
-
const colors = computeCategoryColors(data, col, darkTheme, true);
|
|
123
|
-
|
|
124
|
-
// Explicit colors should NOT be adapted for dark mode
|
|
125
|
-
expect(colors.get(0)!.backgroundColor).toBe('#00ff00');
|
|
126
|
-
expect(colors.get(1)!.backgroundColor).toBe('#ff0000');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('dark mode adapts auto-assigned palette colors but not explicit ones', () => {
|
|
130
|
-
// Use a bright yellow that will definitely get adapted in dark mode
|
|
131
|
-
const col: ColumnConfig = {
|
|
132
|
-
key: 'status',
|
|
133
|
-
categoryColors: {
|
|
134
|
-
active: '#ffff00',
|
|
135
|
-
},
|
|
136
|
-
autoAssign: true,
|
|
137
|
-
};
|
|
138
|
-
const darkTheme = getTheme(true);
|
|
139
|
-
const darkColors = computeCategoryColors(data, col, darkTheme, true);
|
|
140
|
-
|
|
141
|
-
// Explicit color should be preserved as-is (not adapted)
|
|
142
|
-
expect(darkColors.get(0)!.backgroundColor).toBe('#ffff00');
|
|
143
|
-
|
|
144
|
-
// Auto-assigned palette colors should still be present (adaptation may or
|
|
145
|
-
// may not visually change them, but the code path runs adaptColorForDarkMode)
|
|
146
|
-
expect(darkColors.has(1)).toBe(true); // inactive
|
|
147
|
-
expect(darkColors.has(3)).toBe(true); // pending
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('autoAssign: true gives same value consistent color across rows', () => {
|
|
151
|
-
const col: ColumnConfig = {
|
|
152
|
-
key: 'status',
|
|
153
|
-
categoryColors: {},
|
|
154
|
-
autoAssign: true,
|
|
155
|
-
};
|
|
156
|
-
const theme = getTheme();
|
|
157
|
-
const colors = computeCategoryColors(data, col, theme, false);
|
|
158
|
-
|
|
159
|
-
// Both "active" rows should get the same auto-assigned color
|
|
160
|
-
expect(colors.get(0)!.backgroundColor).toBe(colors.get(2)!.backgroundColor);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('dark mode text contrast still meets AA', () => {
|
|
164
|
-
const col: ColumnConfig = {
|
|
165
|
-
key: 'status',
|
|
166
|
-
categoryColors: {
|
|
167
|
-
active: '#00ff00',
|
|
168
|
-
inactive: '#ff0000',
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
const theme = getTheme(true);
|
|
172
|
-
const colors = computeCategoryColors(data, col, theme, true);
|
|
173
|
-
|
|
174
|
-
for (const [, style] of colors) {
|
|
175
|
-
const bg = style.backgroundColor!;
|
|
176
|
-
const fg = style.color!;
|
|
177
|
-
const ratio = contrastRatio(fg, bg);
|
|
178
|
-
expect(ratio).toBeGreaterThanOrEqual(3);
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('returns empty map when no categoryColors config', () => {
|
|
183
|
-
const col: ColumnConfig = { key: 'status' };
|
|
184
|
-
const theme = getTheme();
|
|
185
|
-
const colors = computeCategoryColors(data, col, theme, false);
|
|
186
|
-
expect(colors.size).toBe(0);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('skips transparent and none category colors', () => {
|
|
190
|
-
const dataWithSpecial = [
|
|
191
|
-
{ status: 'active' },
|
|
192
|
-
{ status: 'inactive' },
|
|
193
|
-
{ status: 'pending' },
|
|
194
|
-
{ status: 'disabled' },
|
|
195
|
-
];
|
|
196
|
-
const col: ColumnConfig = {
|
|
197
|
-
key: 'status',
|
|
198
|
-
categoryColors: {
|
|
199
|
-
active: '#00ff00',
|
|
200
|
-
inactive: 'transparent',
|
|
201
|
-
pending: 'none',
|
|
202
|
-
disabled: '#cccccc',
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
const theme = getTheme();
|
|
206
|
-
const colors = computeCategoryColors(dataWithSpecial, col, theme, false);
|
|
207
|
-
|
|
208
|
-
// transparent and none rows should be skipped
|
|
209
|
-
expect(colors.has(1)).toBe(false); // inactive = transparent
|
|
210
|
-
expect(colors.has(2)).toBe(false); // pending = none
|
|
211
|
-
// explicit colors should still be present
|
|
212
|
-
expect(colors.get(0)!.backgroundColor).toBe('#00ff00');
|
|
213
|
-
expect(colors.get(3)!.backgroundColor).toBe('#cccccc');
|
|
214
|
-
expect(colors.size).toBe(2);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('skips null values', () => {
|
|
218
|
-
const dataWithNull = [{ status: 'active' }, { status: null }, { status: 'inactive' }];
|
|
219
|
-
const col: ColumnConfig = {
|
|
220
|
-
key: 'status',
|
|
221
|
-
categoryColors: { active: '#00ff00', inactive: '#ff0000' },
|
|
222
|
-
};
|
|
223
|
-
const theme = getTheme();
|
|
224
|
-
const colors = computeCategoryColors(dataWithNull, col, theme, false);
|
|
225
|
-
|
|
226
|
-
// null row (index 1) should not be included
|
|
227
|
-
expect(colors.has(1)).toBe(false);
|
|
228
|
-
expect(colors.size).toBe(2);
|
|
229
|
-
});
|
|
230
|
-
});
|