@opendata-ai/openchart-engine 8.0.0-rc.12 → 8.0.0-rc.3
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 +0 -7
- package/dist/index.js +161 -579
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/__test-fixtures__/specs.ts +182 -0
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +2339 -0
- package/src/__tests__/annotation-behavior.test.ts +721 -0
- package/src/__tests__/axes.test.ts +1579 -0
- package/src/__tests__/compile-animation.test.ts +161 -0
- package/src/__tests__/compile-chart.test.ts +1303 -0
- package/src/__tests__/compile-footnotes.test.ts +277 -0
- package/src/__tests__/compile-layer.test.ts +969 -0
- package/src/__tests__/compile-snapshot.test.ts +156 -0
- package/src/__tests__/compound-labels.test.ts +147 -0
- package/src/__tests__/dimensions.test.ts +918 -0
- package/src/__tests__/encoding-sugar.test.ts +390 -0
- package/src/__tests__/facet.test.ts +215 -0
- package/src/__tests__/helpers/invariants.ts +299 -0
- package/src/__tests__/known-bugs.test.ts +566 -0
- package/src/__tests__/layout-fuzz.test.ts +358 -0
- package/src/__tests__/layout-invariants.test.ts +535 -0
- package/src/__tests__/legend.test.ts +721 -0
- package/src/__tests__/mark-keys.test.ts +294 -0
- package/src/__tests__/ranking-recipes.test.ts +260 -0
- package/src/__tests__/rotation.test.ts +83 -0
- package/src/__tests__/scales.test.ts +323 -0
- package/src/__tests__/series-search.test.ts +92 -0
- package/src/__tests__/spec-sugar.test.ts +801 -0
- package/src/__tests__/you-draw-it.test.ts +160 -0
- package/src/annotations/__tests__/compute.test.ts +1672 -0
- package/src/annotations/__tests__/geometry.test.ts +66 -0
- package/src/annotations/__tests__/placement-fuzz.test.ts +256 -0
- package/src/annotations/__tests__/placement-invariants.test.ts +345 -0
- package/src/annotations/__tests__/placement.test.ts +316 -0
- package/src/annotations/__tests__/resolve-text.test.ts +788 -0
- package/src/annotations/__tests__/rich-text.test.ts +91 -0
- package/src/annotations/__tests__/thinning.test.ts +285 -0
- package/src/annotations/collisions.ts +326 -0
- package/src/annotations/compute.ts +333 -0
- package/src/annotations/constants.ts +162 -0
- package/src/annotations/geometry.ts +370 -0
- package/src/annotations/move.ts +135 -0
- package/src/annotations/placement.ts +636 -0
- package/src/annotations/position.ts +131 -0
- package/src/annotations/resolve-range.ts +117 -0
- package/src/annotations/resolve-refline.ts +157 -0
- package/src/annotations/resolve-text.ts +539 -0
- package/src/annotations/rich-text.ts +65 -0
- package/src/annotations/thinning.ts +190 -0
- package/src/barlist/__tests__/compile-barlist.test.ts +200 -0
- package/src/barlist/compile-barlist.ts +391 -0
- package/src/barlist/types.ts +28 -0
- package/src/charts/__tests__/post-process.test.ts +237 -0
- package/src/charts/__tests__/registry.test.ts +116 -0
- package/src/charts/__tests__/utils.test.ts +195 -0
- package/src/charts/_shared/__tests__/density-filter.test.ts +32 -0
- package/src/charts/_shared/density-filter.ts +26 -0
- package/src/charts/_shared/format-label-value.ts +16 -0
- package/src/charts/bar/__tests__/compute.test.ts +934 -0
- package/src/charts/bar/__tests__/gradient-orient.test.ts +127 -0
- package/src/charts/bar/__tests__/labels.test.ts +292 -0
- package/src/charts/bar/compute.ts +539 -0
- package/src/charts/bar/index.ts +60 -0
- package/src/charts/bar/labels.ts +311 -0
- package/src/charts/beeswarm/__tests__/compute.test.ts +280 -0
- package/src/charts/beeswarm/__tests__/dodge.test.ts +148 -0
- package/src/charts/beeswarm/compute.ts +203 -0
- package/src/charts/beeswarm/dodge.ts +155 -0
- package/src/charts/beeswarm/index.ts +32 -0
- package/src/charts/builtin.ts +75 -0
- package/src/charts/calendar/__tests__/compute.test.ts +417 -0
- package/src/charts/calendar/compute.ts +319 -0
- package/src/charts/calendar/index.ts +32 -0
- package/src/charts/column/__tests__/compute.test.ts +540 -0
- package/src/charts/column/__tests__/labels.test.ts +179 -0
- package/src/charts/column/compute.ts +493 -0
- package/src/charts/column/index.ts +53 -0
- package/src/charts/column/labels.ts +229 -0
- package/src/charts/dot/__tests__/compute.test.ts +380 -0
- package/src/charts/dot/__tests__/labels.test.ts +98 -0
- package/src/charts/dot/compute.ts +285 -0
- package/src/charts/dot/index.ts +62 -0
- package/src/charts/dot/labels.ts +121 -0
- package/src/charts/line/__tests__/compute.test.ts +1194 -0
- package/src/charts/line/__tests__/curves.test.ts +51 -0
- package/src/charts/line/__tests__/labels.test.ts +94 -0
- package/src/charts/line/__tests__/mark-options.test.ts +175 -0
- package/src/charts/line/area.ts +484 -0
- package/src/charts/line/compute.ts +356 -0
- package/src/charts/line/curves.ts +85 -0
- package/src/charts/line/index.ts +176 -0
- package/src/charts/line/labels.ts +177 -0
- package/src/charts/parliament/__tests__/compute.test.ts +411 -0
- package/src/charts/parliament/compute.ts +313 -0
- package/src/charts/parliament/index.ts +32 -0
- package/src/charts/pie/__tests__/compute.test.ts +384 -0
- package/src/charts/pie/__tests__/labels.test.ts +132 -0
- package/src/charts/pie/compute.ts +324 -0
- package/src/charts/pie/index.ts +49 -0
- package/src/charts/pie/labels.ts +143 -0
- package/src/charts/post-process.ts +229 -0
- package/src/charts/range/__tests__/compute.test.ts +486 -0
- package/src/charts/range/compute.ts +273 -0
- package/src/charts/range/index.ts +82 -0
- package/src/charts/range/labels.ts +169 -0
- package/src/charts/rect/__tests__/compute.test.ts +120 -0
- package/src/charts/rect/index.ts +127 -0
- package/src/charts/registry.ts +72 -0
- package/src/charts/rule/index.ts +139 -0
- package/src/charts/scatter/__tests__/compute.test.ts +488 -0
- package/src/charts/scatter/__tests__/trendline.test.ts +191 -0
- package/src/charts/scatter/compute.ts +192 -0
- package/src/charts/scatter/index.ts +46 -0
- package/src/charts/scatter/trendline.ts +100 -0
- package/src/charts/text/__tests__/compute.test.ts +307 -0
- package/src/charts/text/index.ts +141 -0
- package/src/charts/tick/__tests__/compute.test.ts +75 -0
- package/src/charts/tick/index.ts +99 -0
- package/src/charts/utils.ts +170 -0
- package/src/charts/waffle/__tests__/compute.test.ts +305 -0
- package/src/charts/waffle/compute.ts +197 -0
- package/src/charts/waffle/index.ts +31 -0
- package/src/compile/__tests__/color-scale-range.test.ts +158 -0
- package/src/compile/__tests__/color-scale-reverse.test.ts +89 -0
- package/src/compile/__tests__/data-clip.test.ts +127 -0
- package/src/compile/__tests__/fill-patterns.test.ts +154 -0
- package/src/compile/__tests__/size-scale.test.ts +108 -0
- package/src/compile/__tests__/watermark-obstacle.test.ts +93 -0
- package/src/compile/color-scale-range.ts +101 -0
- package/src/compile/data-clip.ts +48 -0
- package/src/compile/fill-patterns.ts +74 -0
- package/src/compile/layer.ts +626 -0
- package/src/compile/size-scale.ts +141 -0
- package/src/compile/spec-sugar.ts +758 -0
- package/src/compile/watermark-obstacle.ts +54 -0
- package/src/compile/you-draw-it.ts +123 -0
- package/src/compile.ts +1450 -0
- package/src/compiler/__tests__/a11y-warnings.test.ts +148 -0
- package/src/compiler/__tests__/animation.test.ts +177 -0
- package/src/compiler/__tests__/compile.test.ts +87 -0
- package/src/compiler/__tests__/fixtures/vl-parity/axis-null.json +19 -0
- package/src/compiler/__tests__/fixtures/vl-parity/color-value.json +20 -0
- package/src/compiler/__tests__/fixtures/vl-parity/count-aggregate.json +22 -0
- package/src/compiler/__tests__/fixtures/vl-parity/data-values.json +19 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-axis-null.json +14 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-count.json +34 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-data-values.json +22 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-legend-null.json +41 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-scheme.json +38 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-sort.json +28 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-theta.json +28 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-title.json +30 -0
- package/src/compiler/__tests__/fixtures/vl-parity/layer-value-def.json +29 -0
- package/src/compiler/__tests__/fixtures/vl-parity/legend-null.json +32 -0
- package/src/compiler/__tests__/fixtures/vl-parity/scheme.json +25 -0
- package/src/compiler/__tests__/fixtures/vl-parity/sort-by-value.json +19 -0
- package/src/compiler/__tests__/fixtures/vl-parity/theta-arc.json +19 -0
- package/src/compiler/__tests__/fixtures/vl-parity/title-subtitle.json +21 -0
- package/src/compiler/__tests__/fixtures/vl-parity/width-height.json +23 -0
- package/src/compiler/__tests__/keys.test.ts +83 -0
- package/src/compiler/__tests__/normalize-highlight.test.ts +103 -0
- package/src/compiler/__tests__/normalize.test.ts +399 -0
- package/src/compiler/__tests__/sparkline-defaults.test.ts +153 -0
- package/src/compiler/__tests__/validate-did-you-mean.test.ts +86 -0
- package/src/compiler/__tests__/validate-highlight.test.ts +112 -0
- package/src/compiler/__tests__/validate-near-miss.test.ts +89 -0
- package/src/compiler/__tests__/validate.test.ts +629 -0
- package/src/compiler/__tests__/vl-parity.test.ts +68 -0
- package/src/compiler/a11y-warnings.ts +110 -0
- package/src/compiler/animation.ts +142 -0
- package/src/compiler/index.ts +47 -0
- package/src/compiler/keys.ts +55 -0
- package/src/compiler/normalize.ts +734 -0
- package/src/compiler/sparkline-defaults.ts +138 -0
- package/src/compiler/types.ts +241 -0
- package/src/compiler/validate.ts +1871 -0
- package/src/endpoint-labels/__tests__/compute.test.ts +587 -0
- package/src/endpoint-labels/compute.ts +621 -0
- package/src/endpoint-labels/constants.ts +54 -0
- package/src/endpoint-labels/format.ts +60 -0
- package/src/endpoint-labels/predict.ts +139 -0
- package/src/format/__tests__/field-format.test.ts +164 -0
- package/src/format/field-format.ts +27 -0
- package/src/geo/__tests__/compile-map.test.ts +832 -0
- package/src/geo/compile-map.ts +1243 -0
- package/src/geo/join.ts +115 -0
- package/src/geo/projections.ts +45 -0
- package/src/geo/types.ts +26 -0
- package/src/graphs/__tests__/community.test.ts +228 -0
- package/src/graphs/__tests__/compile-graph.test.ts +396 -0
- package/src/graphs/__tests__/encoding.test.ts +415 -0
- package/src/graphs/community.ts +92 -0
- package/src/graphs/compile-graph.ts +329 -0
- package/src/graphs/encoding.ts +356 -0
- package/src/graphs/types.ts +106 -0
- package/src/index.ts +177 -0
- package/src/layout/__tests__/metrics.test.ts +97 -0
- package/src/layout/__tests__/scales-highlight.test.ts +192 -0
- package/src/layout/axes/rotation.ts +152 -0
- package/src/layout/axes/thinning.ts +98 -0
- package/src/layout/axes/ticks.ts +409 -0
- package/src/layout/axes.ts +757 -0
- package/src/layout/dimensions.ts +912 -0
- package/src/layout/facet.ts +91 -0
- package/src/layout/gridlines.ts +84 -0
- package/src/layout/metrics.ts +132 -0
- package/src/layout/plan.ts +631 -0
- package/src/layout/scales.ts +1053 -0
- package/src/layout/shared.ts +44 -0
- package/src/legend/__tests__/continuous.test.ts +318 -0
- package/src/legend/__tests__/redundancy.test.ts +108 -0
- package/src/legend/__tests__/size.test.ts +348 -0
- package/src/legend/__tests__/suppression.test.ts +382 -0
- package/src/legend/compute.ts +747 -0
- package/src/legend/continuous.ts +327 -0
- package/src/legend/size.ts +407 -0
- package/src/legend/suppression.ts +226 -0
- package/src/legend/wrap.ts +117 -0
- package/src/sankey/__tests__/compile-sankey.test.ts +458 -0
- package/src/sankey/__tests__/layout.test.ts +165 -0
- package/src/sankey/__tests__/node-label-wrap.test.ts +114 -0
- package/src/sankey/__tests__/node-sort.test.ts +45 -0
- package/src/sankey/compile-sankey.ts +706 -0
- package/src/sankey/layout.ts +184 -0
- package/src/sankey/types.ts +41 -0
- package/src/tables/__tests__/bar-column.test.ts +147 -0
- package/src/tables/__tests__/category-colors.test.ts +230 -0
- package/src/tables/__tests__/compile-table.test.ts +236 -0
- package/src/tables/__tests__/format-cells.test.ts +143 -0
- package/src/tables/__tests__/heatmap.test.ts +148 -0
- package/src/tables/__tests__/pagination.test.ts +78 -0
- package/src/tables/__tests__/search.test.ts +94 -0
- package/src/tables/__tests__/sort.test.ts +107 -0
- package/src/tables/__tests__/sparkline.test.ts +122 -0
- package/src/tables/bar-column.ts +94 -0
- package/src/tables/category-colors.ts +78 -0
- package/src/tables/compile-table.ts +427 -0
- package/src/tables/format-cells.ts +115 -0
- package/src/tables/heatmap.ts +141 -0
- package/src/tables/pagination.ts +46 -0
- package/src/tables/search.ts +66 -0
- package/src/tables/sort.ts +69 -0
- package/src/tables/sparkline.ts +113 -0
- package/src/tables/utils.ts +16 -0
- package/src/tilemap/__tests__/compile-tilemap.test.ts +576 -0
- package/src/tilemap/compile-tilemap.ts +755 -0
- package/src/tilemap/layout.ts +172 -0
- package/src/tilemap/types.ts +33 -0
- package/src/tooltips/__tests__/compute.test.ts +582 -0
- package/src/tooltips/compute.ts +700 -0
- package/src/transforms/__tests__/aggregate.test.ts +159 -0
- package/src/transforms/__tests__/bin.test.ts +88 -0
- package/src/transforms/__tests__/calculate.test.ts +146 -0
- package/src/transforms/__tests__/conditional.test.ts +127 -0
- package/src/transforms/__tests__/filter-relative.test.ts +202 -0
- package/src/transforms/__tests__/filter.test.ts +59 -0
- package/src/transforms/__tests__/fold.test.ts +79 -0
- package/src/transforms/__tests__/index.test.ts +93 -0
- package/src/transforms/__tests__/predicates.test.ts +193 -0
- package/src/transforms/__tests__/timeunit.test.ts +129 -0
- package/src/transforms/__tests__/window.test.ts +286 -0
- package/src/transforms/aggregate.ts +131 -0
- package/src/transforms/bin.ts +87 -0
- package/src/transforms/calculate.ts +60 -0
- package/src/transforms/conditional.ts +46 -0
- package/src/transforms/filter.ts +122 -0
- package/src/transforms/fold.ts +49 -0
- package/src/transforms/index.ts +60 -0
- package/src/transforms/predicates.ts +123 -0
- package/src/transforms/timeunit.ts +88 -0
- package/src/transforms/window.ts +185 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph compilation types.
|
|
3
|
+
*
|
|
4
|
+
* These types represent the engine output for graph specs. Unlike GraphLayout
|
|
5
|
+
* (which includes x/y positions for adapter rendering), GraphCompilation
|
|
6
|
+
* contains resolved visual properties WITHOUT positional layout. The force
|
|
7
|
+
* simulation in the adapter sets node positions at runtime.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
A11yMetadata,
|
|
12
|
+
LegendLayout,
|
|
13
|
+
ResolvedChrome,
|
|
14
|
+
ResolvedTheme,
|
|
15
|
+
TooltipContent,
|
|
16
|
+
} from '@opendata-ai/openchart-core';
|
|
17
|
+
|
|
18
|
+
/** A compiled graph node with resolved visual properties (no x/y position). */
|
|
19
|
+
export interface CompiledGraphNode {
|
|
20
|
+
/** Node identifier from the spec. */
|
|
21
|
+
id: string;
|
|
22
|
+
/** Computed radius from nodeSize encoding (3-20px range, default 5px). */
|
|
23
|
+
radius: number;
|
|
24
|
+
/** Computed fill color from nodeColor encoding or community assignment. */
|
|
25
|
+
fill: string;
|
|
26
|
+
/** Stroke color, slightly darker than fill. */
|
|
27
|
+
stroke: string;
|
|
28
|
+
/** Stroke width in pixels. Default 1. */
|
|
29
|
+
strokeWidth: number;
|
|
30
|
+
/** Label text from nodeLabel encoding or node id. */
|
|
31
|
+
label: string | undefined;
|
|
32
|
+
/** Label priority for level-of-detail rendering (0-1, degree/maxDegree). */
|
|
33
|
+
labelPriority: number;
|
|
34
|
+
/** Community/cluster assignment from the clustering field. */
|
|
35
|
+
community: string | undefined;
|
|
36
|
+
/** Original node data (all fields from the spec node). */
|
|
37
|
+
data: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A compiled graph edge with resolved visual properties (no positional endpoints). */
|
|
41
|
+
export interface CompiledGraphEdge {
|
|
42
|
+
/** Source node id. */
|
|
43
|
+
source: string;
|
|
44
|
+
/** Target node id. */
|
|
45
|
+
target: string;
|
|
46
|
+
/** Stroke color from edgeColor encoding or theme default. */
|
|
47
|
+
stroke: string;
|
|
48
|
+
/** Stroke width from edgeWidth encoding (0.5-4px range, default 1px). */
|
|
49
|
+
strokeWidth: number;
|
|
50
|
+
/** Line style. */
|
|
51
|
+
style: 'solid' | 'dashed' | 'dotted';
|
|
52
|
+
/** Original edge data (all fields from the spec edge). */
|
|
53
|
+
data: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Configuration for the force simulation, derived from the spec layout. */
|
|
57
|
+
export interface SimulationConfig {
|
|
58
|
+
/** Repulsion strength between nodes. Negative = repulsion. */
|
|
59
|
+
chargeStrength: number;
|
|
60
|
+
/** Target distance between linked nodes. */
|
|
61
|
+
linkDistance: number;
|
|
62
|
+
/** Clustering configuration, or null if no clustering. */
|
|
63
|
+
clustering: { field: string; strength: number } | null;
|
|
64
|
+
/** How quickly the simulation cools. Default 0.0228. */
|
|
65
|
+
alphaDecay: number;
|
|
66
|
+
/** Velocity damping. Default 0.4. */
|
|
67
|
+
velocityDecay: number;
|
|
68
|
+
/** Collision radius: max node radius + padding. */
|
|
69
|
+
collisionRadius: number;
|
|
70
|
+
/** Extra px added to node radius for collision (default 2). */
|
|
71
|
+
collisionPadding?: number;
|
|
72
|
+
/** Link force strength override. */
|
|
73
|
+
linkStrength?: number;
|
|
74
|
+
/** Whether to apply center force (default true). */
|
|
75
|
+
centerForce?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The complete engine output for graph specs.
|
|
80
|
+
*
|
|
81
|
+
* Contains resolved visual properties for nodes and edges, but does NOT
|
|
82
|
+
* include x/y positions. The adapter's force simulation assigns positions
|
|
83
|
+
* at runtime using the simulationConfig.
|
|
84
|
+
*/
|
|
85
|
+
export interface GraphCompilation {
|
|
86
|
+
/** Compiled nodes with visual properties. */
|
|
87
|
+
nodes: CompiledGraphNode[];
|
|
88
|
+
/** Compiled edges with visual properties. */
|
|
89
|
+
edges: CompiledGraphEdge[];
|
|
90
|
+
/** Legend layout (community colors or nodeColor categories). */
|
|
91
|
+
legend: LegendLayout;
|
|
92
|
+
/** Resolved chrome text elements. */
|
|
93
|
+
chrome: ResolvedChrome;
|
|
94
|
+
/** Tooltip descriptors keyed by node id. */
|
|
95
|
+
tooltipDescriptors: Map<string, TooltipContent>;
|
|
96
|
+
/** Accessibility metadata. */
|
|
97
|
+
a11y: A11yMetadata;
|
|
98
|
+
/** Resolved theme used for rendering. */
|
|
99
|
+
theme: ResolvedTheme;
|
|
100
|
+
/** Total available dimensions. */
|
|
101
|
+
dimensions: { width: number; height: number };
|
|
102
|
+
/** Force simulation configuration. */
|
|
103
|
+
simulationConfig: SimulationConfig;
|
|
104
|
+
/** Whether to show the brand watermark. */
|
|
105
|
+
watermark: boolean;
|
|
106
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @opendata-ai/openchart-engine
|
|
3
|
+
*
|
|
4
|
+
* Headless computation engine that takes a declarative spec and produces
|
|
5
|
+
* structured layout objects (ChartLayout, TableLayout, GraphCompilation).
|
|
6
|
+
*
|
|
7
|
+
* The engine does pure math and data transformation. It knows nothing about
|
|
8
|
+
* the DOM, React, or any rendering target.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Main compile API
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
compileBarList,
|
|
17
|
+
compileChart,
|
|
18
|
+
compileGraph,
|
|
19
|
+
compileLayer,
|
|
20
|
+
compileMap,
|
|
21
|
+
compileSankey,
|
|
22
|
+
compileTable,
|
|
23
|
+
compileTileMap,
|
|
24
|
+
} from './compile';
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Animation resolution
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
clampStaggerDelay,
|
|
32
|
+
ENTER_DEFAULTS,
|
|
33
|
+
EXIT_DEFAULTS,
|
|
34
|
+
resolveAnimation,
|
|
35
|
+
UPDATE_DEFAULTS,
|
|
36
|
+
} from './compiler/animation';
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Mark keying for data-update transitions
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export { dedupeKeys, serializeKeyValue } from './compiler/keys';
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Geometry helpers for path reconstruction
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export { buildAreaPath, buildLinePath } from './charts/line/curves';
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Graph compilation types
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
export type {
|
|
55
|
+
CompiledGraphEdge,
|
|
56
|
+
CompiledGraphNode,
|
|
57
|
+
GraphCompilation,
|
|
58
|
+
SimulationConfig,
|
|
59
|
+
} from './graphs/types';
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Sankey compilation types
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
export type { NormalizedSankeySpec } from './sankey/types';
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// TileMap compilation types
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
export type { NormalizedBarListSpec } from './barlist/types';
|
|
72
|
+
|
|
73
|
+
export type { NormalizedMapSpec } from './geo/types';
|
|
74
|
+
|
|
75
|
+
export type { NormalizedTileMapSpec } from './tilemap/types';
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Annotations
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
export type { AnnotationContext } from './annotations/compute';
|
|
82
|
+
export {
|
|
83
|
+
ARROWHEAD_LENGTH,
|
|
84
|
+
DEFAULT_ANNOTATION_FONT_SIZE,
|
|
85
|
+
DEFAULT_LINE_HEIGHT,
|
|
86
|
+
} from './annotations/constants';
|
|
87
|
+
export type { AnnotationMeasureTextFn, ArrowheadPoints } from './annotations/geometry';
|
|
88
|
+
export {
|
|
89
|
+
computeArrowheadPoints,
|
|
90
|
+
heuristicMeasure,
|
|
91
|
+
} from './annotations/geometry';
|
|
92
|
+
export type { ObstacleKind, PlacementObstacle } from './annotations/placement';
|
|
93
|
+
export type { RichSpan } from './annotations/rich-text';
|
|
94
|
+
export { BOLD_SPAN_FONT_WEIGHT, parseAnnotationSpans } from './annotations/rich-text';
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Compiler pipeline (spec validation, normalization, generic compile)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
export type {
|
|
101
|
+
CompileResult,
|
|
102
|
+
NormalizedChartSpec,
|
|
103
|
+
NormalizedChrome,
|
|
104
|
+
NormalizedGraphSpec,
|
|
105
|
+
NormalizedSpec,
|
|
106
|
+
NormalizedTableSpec,
|
|
107
|
+
ValidationError,
|
|
108
|
+
ValidationErrorCode,
|
|
109
|
+
ValidationResult,
|
|
110
|
+
} from './compiler/index';
|
|
111
|
+
export {
|
|
112
|
+
compile,
|
|
113
|
+
normalizeSpec,
|
|
114
|
+
validateSpec,
|
|
115
|
+
} from './compiler/index';
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Chart renderer plugin API
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
export type { ChartRenderer } from './charts/registry';
|
|
122
|
+
export {
|
|
123
|
+
clearRenderers,
|
|
124
|
+
getChartRenderer,
|
|
125
|
+
registerChartRenderer,
|
|
126
|
+
} from './charts/registry';
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Legend geometry
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
// Used by the vanilla mount to compute facet height growth for auto-height
|
|
133
|
+
// figures; single-sourcing avoids constant divergence with facet.ts.
|
|
134
|
+
export { facetMinHeight } from './layout/facet';
|
|
135
|
+
// Used by the vanilla mount to compute the top-legend block when growing
|
|
136
|
+
// auto-height figures; exporting it keeps the gap math single-sourced.
|
|
137
|
+
export { legendGap } from './legend/wrap';
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Data transforms
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
evaluatePredicate,
|
|
145
|
+
isConditionalValueDef,
|
|
146
|
+
resolveConditionalValue,
|
|
147
|
+
runBin,
|
|
148
|
+
runCalculate,
|
|
149
|
+
runFilter,
|
|
150
|
+
runTimeUnit,
|
|
151
|
+
runTransforms,
|
|
152
|
+
} from './transforms';
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Re-export core types for convenience
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
export type {
|
|
159
|
+
BarListLayout,
|
|
160
|
+
BarListSpec,
|
|
161
|
+
ChartLayout,
|
|
162
|
+
ChartSpec,
|
|
163
|
+
CompileOptions,
|
|
164
|
+
CompileTableOptions,
|
|
165
|
+
GraphLayout,
|
|
166
|
+
GraphSpec,
|
|
167
|
+
LayerSpec,
|
|
168
|
+
MapLayout,
|
|
169
|
+
MapSpec,
|
|
170
|
+
SankeyLayout,
|
|
171
|
+
SankeySpec,
|
|
172
|
+
TableLayout,
|
|
173
|
+
TableSpec,
|
|
174
|
+
TileMapLayout,
|
|
175
|
+
TileMapSpec,
|
|
176
|
+
VizSpec,
|
|
177
|
+
} from '@opendata-ai/openchart-core';
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Metric } from '@opendata-ai/openchart-core';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
computeMetricBar,
|
|
5
|
+
METRIC_BAR_INTERNALS,
|
|
6
|
+
type MetricFontSizes,
|
|
7
|
+
metricBarHeight,
|
|
8
|
+
} from '../metrics';
|
|
9
|
+
|
|
10
|
+
const { TOP_GAP, BOTTOM_GAP, INTER_ROW_GAP, LABEL_LINE_HEIGHT_RATIO, VALUE_LINE_HEIGHT_RATIO } =
|
|
11
|
+
METRIC_BAR_INTERNALS;
|
|
12
|
+
|
|
13
|
+
const defaultFonts: MetricFontSizes = { label: 10, value: 22 };
|
|
14
|
+
const largeFonts: MetricFontSizes = { label: 16, value: 32 };
|
|
15
|
+
|
|
16
|
+
const metrics: Metric[] = [
|
|
17
|
+
{ label: 'Revenue', value: '$1.2B', delta: '+12%', deltaTone: 'up' },
|
|
18
|
+
{ label: 'Users', value: '4.5M' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const area = { x: 20, width: 600 };
|
|
22
|
+
|
|
23
|
+
describe('metricBarHeight', () => {
|
|
24
|
+
it('returns the expected height at default font sizes', () => {
|
|
25
|
+
const expected =
|
|
26
|
+
TOP_GAP +
|
|
27
|
+
defaultFonts.label * LABEL_LINE_HEIGHT_RATIO +
|
|
28
|
+
INTER_ROW_GAP +
|
|
29
|
+
defaultFonts.value * VALUE_LINE_HEIGHT_RATIO +
|
|
30
|
+
BOTTOM_GAP;
|
|
31
|
+
expect(metricBarHeight()).toBe(expected);
|
|
32
|
+
expect(metricBarHeight(defaultFonts)).toBe(expected);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('grows when font sizes increase', () => {
|
|
36
|
+
const large = metricBarHeight(largeFonts);
|
|
37
|
+
expect(large).toBeGreaterThan(metricBarHeight(defaultFonts));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('matches the formula for custom sizes', () => {
|
|
41
|
+
const expected =
|
|
42
|
+
TOP_GAP +
|
|
43
|
+
largeFonts.label * LABEL_LINE_HEIGHT_RATIO +
|
|
44
|
+
INTER_ROW_GAP +
|
|
45
|
+
largeFonts.value * VALUE_LINE_HEIGHT_RATIO +
|
|
46
|
+
BOTTOM_GAP;
|
|
47
|
+
expect(metricBarHeight(largeFonts)).toBe(expected);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('computeMetricBar', () => {
|
|
52
|
+
it('uses custom font sizes for layout positions', () => {
|
|
53
|
+
const topY = 50;
|
|
54
|
+
const defaultBar = computeMetricBar(metrics, topY, area, 400, undefined, defaultFonts)!;
|
|
55
|
+
const largeBar = computeMetricBar(metrics, topY, area, 400, undefined, largeFonts)!;
|
|
56
|
+
|
|
57
|
+
expect(defaultBar).toBeDefined();
|
|
58
|
+
expect(largeBar).toBeDefined();
|
|
59
|
+
|
|
60
|
+
expect(largeBar.height).toBeGreaterThan(defaultBar.height);
|
|
61
|
+
expect(largeBar.height).toBe(metricBarHeight(largeFonts));
|
|
62
|
+
|
|
63
|
+
expect(largeBar.cells[0].labelY).toBeGreaterThan(defaultBar.cells[0].labelY);
|
|
64
|
+
expect(largeBar.cells[0].valueY).toBeGreaterThan(defaultBar.cells[0].valueY);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('computes labelY and valueY from font sizes', () => {
|
|
68
|
+
const topY = 50;
|
|
69
|
+
const bar = computeMetricBar(metrics, topY, area, 400, undefined, largeFonts)!;
|
|
70
|
+
|
|
71
|
+
expect(bar.cells[0].labelY).toBe(topY + TOP_GAP + largeFonts.label);
|
|
72
|
+
expect(bar.cells[0].valueY).toBe(
|
|
73
|
+
topY +
|
|
74
|
+
TOP_GAP +
|
|
75
|
+
largeFonts.label * LABEL_LINE_HEIGHT_RATIO +
|
|
76
|
+
INTER_ROW_GAP +
|
|
77
|
+
largeFonts.value,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('uses custom font size for overflow detection', () => {
|
|
82
|
+
const tinyCell: Metric[] = [{ label: 'A', value: 'WWWWWWWWWWWWWWWWWWWWWWWWWW' }];
|
|
83
|
+
const narrowArea = { x: 0, width: 500 };
|
|
84
|
+
|
|
85
|
+
const withSmall = computeMetricBar(tinyCell, 0, narrowArea, 400, undefined, {
|
|
86
|
+
label: 10,
|
|
87
|
+
value: 12,
|
|
88
|
+
});
|
|
89
|
+
const withLarge = computeMetricBar(tinyCell, 0, narrowArea, 400, undefined, {
|
|
90
|
+
label: 10,
|
|
91
|
+
value: 40,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(withSmall).toBeDefined();
|
|
95
|
+
expect(withLarge).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { ChartSpec, LineMark } from '@opendata-ai/openchart-core';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { compileChart } from '../../compile';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Test data
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const MUTED_COLOR = '#bfc3c8';
|
|
10
|
+
|
|
11
|
+
const data = [
|
|
12
|
+
{ date: '2020', country: 'Germany', value: 100 },
|
|
13
|
+
{ date: '2020', country: 'France', value: 90 },
|
|
14
|
+
{ date: '2020', country: 'Italy', value: 80 },
|
|
15
|
+
{ date: '2020', country: 'Spain', value: 70 },
|
|
16
|
+
{ date: '2020', country: 'Poland', value: 60 },
|
|
17
|
+
{ date: '2021', country: 'Germany', value: 110 },
|
|
18
|
+
{ date: '2021', country: 'France', value: 95 },
|
|
19
|
+
{ date: '2021', country: 'Italy', value: 85 },
|
|
20
|
+
{ date: '2021', country: 'Spain', value: 75 },
|
|
21
|
+
{ date: '2021', country: 'Poland', value: 65 },
|
|
22
|
+
{ date: '2022', country: 'Germany', value: 120 },
|
|
23
|
+
{ date: '2022', country: 'France', value: 100 },
|
|
24
|
+
{ date: '2022', country: 'Italy', value: 90 },
|
|
25
|
+
{ date: '2022', country: 'Spain', value: 80 },
|
|
26
|
+
{ date: '2022', country: 'Poland', value: 70 },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const compileOptions = { width: 600, height: 400 };
|
|
30
|
+
|
|
31
|
+
function makeSpec(highlight?: string | string[]): ChartSpec {
|
|
32
|
+
const colorChannel: Record<string, unknown> = {
|
|
33
|
+
field: 'country',
|
|
34
|
+
type: 'nominal',
|
|
35
|
+
};
|
|
36
|
+
if (highlight !== undefined) {
|
|
37
|
+
colorChannel.highlight = highlight;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
mark: 'line',
|
|
41
|
+
data,
|
|
42
|
+
encoding: {
|
|
43
|
+
x: { field: 'date', type: 'temporal' },
|
|
44
|
+
y: { field: 'value', type: 'quantitative' },
|
|
45
|
+
color: colorChannel,
|
|
46
|
+
},
|
|
47
|
+
} as ChartSpec;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Color partitioning
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
describe('highlight — color partitioning', () => {
|
|
55
|
+
it('assigns palette color to highlighted series and muted color to others', () => {
|
|
56
|
+
const layout = compileChart(makeSpec(['Germany']), compileOptions);
|
|
57
|
+
const marks = layout.marks as LineMark[];
|
|
58
|
+
|
|
59
|
+
const germany = marks.find((m) => m.seriesKey === 'Germany');
|
|
60
|
+
const france = marks.find((m) => m.seriesKey === 'France');
|
|
61
|
+
const italy = marks.find((m) => m.seriesKey === 'Italy');
|
|
62
|
+
|
|
63
|
+
expect(germany).toBeDefined();
|
|
64
|
+
expect(france).toBeDefined();
|
|
65
|
+
expect(italy).toBeDefined();
|
|
66
|
+
|
|
67
|
+
// Germany should have a real palette color (not the muted gray)
|
|
68
|
+
expect(germany!.stroke).not.toBe(MUTED_COLOR);
|
|
69
|
+
|
|
70
|
+
// Non-highlighted series should get the muted color
|
|
71
|
+
expect(france!.stroke).toBe(MUTED_COLOR);
|
|
72
|
+
expect(italy!.stroke).toBe(MUTED_COLOR);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('assigns palette colors to multiple highlighted series', () => {
|
|
76
|
+
const layout = compileChart(makeSpec(['Germany', 'France']), compileOptions);
|
|
77
|
+
const marks = layout.marks as LineMark[];
|
|
78
|
+
|
|
79
|
+
const germany = marks.find((m) => m.seriesKey === 'Germany');
|
|
80
|
+
const france = marks.find((m) => m.seriesKey === 'France');
|
|
81
|
+
const italy = marks.find((m) => m.seriesKey === 'Italy');
|
|
82
|
+
|
|
83
|
+
// Both highlighted series should have distinct palette colors
|
|
84
|
+
expect(germany!.stroke).not.toBe(MUTED_COLOR);
|
|
85
|
+
expect(france!.stroke).not.toBe(MUTED_COLOR);
|
|
86
|
+
expect(germany!.stroke).not.toBe(france!.stroke);
|
|
87
|
+
|
|
88
|
+
// Non-highlighted series should be muted
|
|
89
|
+
expect(italy!.stroke).toBe(MUTED_COLOR);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('assigns all series palette colors when no highlight is active', () => {
|
|
93
|
+
const layout = compileChart(makeSpec(), compileOptions);
|
|
94
|
+
const marks = layout.marks as LineMark[];
|
|
95
|
+
|
|
96
|
+
// Every series should get a real palette color
|
|
97
|
+
for (const mark of marks) {
|
|
98
|
+
expect(mark.stroke).not.toBe(MUTED_COLOR);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Z-order: muted marks sort before highlighted marks
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
describe('highlight — z-order', () => {
|
|
108
|
+
it('places muted series marks before highlighted series marks', () => {
|
|
109
|
+
const layout = compileChart(makeSpec(['Germany']), compileOptions);
|
|
110
|
+
const marks = layout.marks as LineMark[];
|
|
111
|
+
|
|
112
|
+
// Find the index of the first highlighted mark and the last muted mark
|
|
113
|
+
const germanyIndex = marks.findIndex((m) => m.seriesKey === 'Germany');
|
|
114
|
+
const lastMutedIndex = marks.reduce(
|
|
115
|
+
(maxIdx, m, i) => (m.seriesKey !== 'Germany' ? Math.max(maxIdx, i) : maxIdx),
|
|
116
|
+
-1,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// All muted marks should come before the highlighted mark in the array
|
|
120
|
+
expect(germanyIndex).toBeGreaterThan(lastMutedIndex);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('places all muted marks before all highlighted marks with multiple highlights', () => {
|
|
124
|
+
const layout = compileChart(makeSpec(['Germany', 'France']), compileOptions);
|
|
125
|
+
const marks = layout.marks as LineMark[];
|
|
126
|
+
const highlightSet = new Set(['Germany', 'France']);
|
|
127
|
+
|
|
128
|
+
const firstHighlightIndex = marks.findIndex((m) => highlightSet.has(m.seriesKey ?? ''));
|
|
129
|
+
const lastMutedIndex = marks.reduce(
|
|
130
|
+
(maxIdx, m, i) => (!highlightSet.has(m.seriesKey ?? '') ? Math.max(maxIdx, i) : maxIdx),
|
|
131
|
+
-1,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(firstHighlightIndex).toBeGreaterThan(lastMutedIndex);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Muted line stroke width
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
describe('highlight — muted line stroke width', () => {
|
|
143
|
+
it('reduces stroke width for muted series', () => {
|
|
144
|
+
const layout = compileChart(makeSpec(['Germany']), compileOptions);
|
|
145
|
+
const marks = layout.marks as LineMark[];
|
|
146
|
+
|
|
147
|
+
const germany = marks.find((m) => m.seriesKey === 'Germany');
|
|
148
|
+
const france = marks.find((m) => m.seriesKey === 'France');
|
|
149
|
+
|
|
150
|
+
expect(germany).toBeDefined();
|
|
151
|
+
expect(france).toBeDefined();
|
|
152
|
+
|
|
153
|
+
expect(germany!.strokeWidth).toBeDefined();
|
|
154
|
+
expect(france!.strokeWidth).toBeDefined();
|
|
155
|
+
expect(france!.strokeWidth).toBeLessThan(germany!.strokeWidth);
|
|
156
|
+
expect(france!.strokeWidth).toBeGreaterThanOrEqual(1);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Endpoint label suppression
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
describe('highlight — endpoint labels', () => {
|
|
165
|
+
it('only includes highlighted series in endpoint labels', () => {
|
|
166
|
+
const spec = { ...makeSpec(['Germany']), endpointLabels: { show: true } } as ChartSpec;
|
|
167
|
+
const layout = compileChart(spec, compileOptions);
|
|
168
|
+
|
|
169
|
+
expect(layout.endpointLabels).toBeDefined();
|
|
170
|
+
expect(layout.endpointLabels!.entries.length).toBeGreaterThan(0);
|
|
171
|
+
const labelSeriesKeys = layout.endpointLabels!.entries.map((e) => e.seriesKey);
|
|
172
|
+
|
|
173
|
+
expect(labelSeriesKeys).toContain('Germany');
|
|
174
|
+
expect(labelSeriesKeys).not.toContain('France');
|
|
175
|
+
expect(labelSeriesKeys).not.toContain('Italy');
|
|
176
|
+
expect(labelSeriesKeys).not.toContain('Spain');
|
|
177
|
+
expect(labelSeriesKeys).not.toContain('Poland');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('includes all series in endpoint labels when no highlight is active', () => {
|
|
181
|
+
const spec = makeSpec();
|
|
182
|
+
const layout = compileChart(spec, compileOptions);
|
|
183
|
+
|
|
184
|
+
expect(layout.endpointLabels).toBeDefined();
|
|
185
|
+
expect(layout.endpointLabels!.entries.length).toBeGreaterThan(0);
|
|
186
|
+
const labelSeriesKeys = layout.endpointLabels!.entries.map((e) => e.seriesKey);
|
|
187
|
+
|
|
188
|
+
expect(labelSeriesKeys).toContain('Germany');
|
|
189
|
+
expect(labelSeriesKeys).toContain('France');
|
|
190
|
+
expect(labelSeriesKeys).toContain('Italy');
|
|
191
|
+
});
|
|
192
|
+
});
|