@opendata-ai/openchart-engine 8.0.0 → 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 +27 -1
- package/dist/index.js +113 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -200,6 +200,12 @@ interface GraphCompilation {
|
|
|
200
200
|
field: string;
|
|
201
201
|
values: string[];
|
|
202
202
|
};
|
|
203
|
+
/**
|
|
204
|
+
* Ids exempt from highlight/filter dimming (currently just `spec.seedNode`).
|
|
205
|
+
* Empty when no seed is set. Array-shaped so a future `pinnedNodes` needs no
|
|
206
|
+
* type change.
|
|
207
|
+
*/
|
|
208
|
+
seedNodeIds: string[];
|
|
203
209
|
/** Edge legend entries (nominal edgeColor with >1 category), or undefined. */
|
|
204
210
|
edgeLegend?: LegendEntry[];
|
|
205
211
|
}
|
|
@@ -580,6 +586,11 @@ interface NormalizedGraphSpec {
|
|
|
580
586
|
encoding: GraphEncoding;
|
|
581
587
|
layout: GraphLayoutConfig;
|
|
582
588
|
nodeOverrides?: Record<string, NodeOverride>;
|
|
589
|
+
/** Seed node, always in object form (the string shorthand is expanded). */
|
|
590
|
+
seedNode?: {
|
|
591
|
+
id: string;
|
|
592
|
+
style?: NodeOverride;
|
|
593
|
+
};
|
|
583
594
|
chrome: NormalizedChrome;
|
|
584
595
|
annotations: Annotation[];
|
|
585
596
|
theme: ThemeConfig;
|
|
@@ -861,6 +872,21 @@ declare function validateSpec(spec: unknown): ValidationResult;
|
|
|
861
872
|
*/
|
|
862
873
|
declare function compile(spec: unknown): CompileResult;
|
|
863
874
|
|
|
875
|
+
/**
|
|
876
|
+
* Resolve which backend paints a chart's point marks: SVG (one `<circle>` per
|
|
877
|
+
* datum) or a canvas mark layer (batched paint calls).
|
|
878
|
+
*
|
|
879
|
+
* High-cardinality scatter plots are the only case where the trade is worth
|
|
880
|
+
* it, so canvas is refused for every other mark type and for the layout shapes
|
|
881
|
+
* the canvas layer cannot express (facets, layers, sparklines). Refusals are
|
|
882
|
+
* advisory: an explicit `'canvas'` request always warns, and an `'auto'`
|
|
883
|
+
* refusal warns only once the chart is dense enough for the SVG fallback to
|
|
884
|
+
* actually hurt (AUTO_CANVAS_REFUSAL_WARN_THRESHOLD).
|
|
885
|
+
*/
|
|
886
|
+
|
|
887
|
+
/** Point count above which `'auto'` prefers canvas over SVG. */
|
|
888
|
+
declare const AUTO_CANVAS_THRESHOLD = 1000;
|
|
889
|
+
|
|
864
890
|
/**
|
|
865
891
|
* Chart renderer registry.
|
|
866
892
|
*
|
|
@@ -1058,4 +1084,4 @@ declare function runTimeUnit(data: DataRow[], transform: TimeUnitTransform): Dat
|
|
|
1058
1084
|
*/
|
|
1059
1085
|
declare function runTransforms(data: DataRow[], transforms: Transform[]): DataRow[];
|
|
1060
1086
|
|
|
1061
|
-
export { ARROWHEAD_LENGTH, type AnnotationContext, type AnnotationMeasureTextFn, type ArrowheadPoints, BOLD_SPAN_FONT_WEIGHT, type ChartRenderer, type CompileResult, type CompiledGraphEdge, type CompiledGraphNode, DEFAULT_ANNOTATION_FONT_SIZE, DEFAULT_LINE_HEIGHT, ENTER_DEFAULTS, EXIT_DEFAULTS, type GraphCompilation, type NormalizedBarListSpec, type NormalizedChartSpec, type NormalizedChrome, type NormalizedGeoMapSpec, type NormalizedGraphSpec, type NormalizedSankeySpec, type NormalizedSpec, type NormalizedTableSpec, type NormalizedTileMapSpec, type ObstacleKind, type PlacementObstacle, type RichSpan, type SimulationConfig, UPDATE_DEFAULTS, type ValidationError, type ValidationErrorCode, type ValidationResult, buildAreaPath, buildEdgeTooltip, buildLinePath, clampStaggerDelay, clearRenderers, compile, compileBarList, compileChart, compileGeoMap, compileGraph, compileLayer, compileSankey, compileTable, compileTileMap, computeArrowheadPoints, dedupeKeys, evaluatePredicate, facetMinHeight, getChartRenderer, heuristicMeasure, isConditionalValueDef, legendGap, normalizeSpec, parseAnnotationSpans, registerChartRenderer, resolveAnimation, resolveConditionalValue, runBin, runCalculate, runFilter, runTimeUnit, runTransforms, serializeKeyValue, validateSpec };
|
|
1087
|
+
export { ARROWHEAD_LENGTH, AUTO_CANVAS_THRESHOLD, type AnnotationContext, type AnnotationMeasureTextFn, type ArrowheadPoints, BOLD_SPAN_FONT_WEIGHT, type ChartRenderer, type CompileResult, type CompiledGraphEdge, type CompiledGraphNode, DEFAULT_ANNOTATION_FONT_SIZE, DEFAULT_LINE_HEIGHT, ENTER_DEFAULTS, EXIT_DEFAULTS, type GraphCompilation, type NormalizedBarListSpec, type NormalizedChartSpec, type NormalizedChrome, type NormalizedGeoMapSpec, type NormalizedGraphSpec, type NormalizedSankeySpec, type NormalizedSpec, type NormalizedTableSpec, type NormalizedTileMapSpec, type ObstacleKind, type PlacementObstacle, type RichSpan, type SimulationConfig, UPDATE_DEFAULTS, type ValidationError, type ValidationErrorCode, type ValidationResult, buildAreaPath, buildEdgeTooltip, buildLinePath, clampStaggerDelay, clearRenderers, compile, compileBarList, compileChart, compileGeoMap, compileGraph, compileLayer, compileSankey, compileTable, compileTileMap, computeArrowheadPoints, dedupeKeys, evaluatePredicate, facetMinHeight, getChartRenderer, heuristicMeasure, isConditionalValueDef, legendGap, normalizeSpec, parseAnnotationSpans, registerChartRenderer, resolveAnimation, resolveConditionalValue, runBin, runCalculate, runFilter, runTimeUnit, runTransforms, serializeKeyValue, validateSpec };
|
package/dist/index.js
CHANGED
|
@@ -10595,11 +10595,16 @@ function normalizeSankeySpec(spec, _warnings) {
|
|
|
10595
10595
|
watermark: spec.watermark ?? true
|
|
10596
10596
|
};
|
|
10597
10597
|
}
|
|
10598
|
-
function normalizeGraphSpec(spec) {
|
|
10598
|
+
function normalizeGraphSpec(spec, warnings) {
|
|
10599
10599
|
const layout = {
|
|
10600
10600
|
type: "force",
|
|
10601
10601
|
...spec.layout
|
|
10602
10602
|
};
|
|
10603
|
+
let seedNode = typeof spec.seedNode === "string" ? { id: spec.seedNode } : spec.seedNode;
|
|
10604
|
+
if (seedNode && !spec.nodes.some((n) => n.id === seedNode?.id)) {
|
|
10605
|
+
warnings.push(`seedNode "${seedNode.id}" does not match any node id; it will be ignored.`);
|
|
10606
|
+
seedNode = void 0;
|
|
10607
|
+
}
|
|
10603
10608
|
return {
|
|
10604
10609
|
type: "graph",
|
|
10605
10610
|
nodes: spec.nodes,
|
|
@@ -10607,6 +10612,7 @@ function normalizeGraphSpec(spec) {
|
|
|
10607
10612
|
encoding: spec.encoding ?? {},
|
|
10608
10613
|
layout,
|
|
10609
10614
|
nodeOverrides: spec.nodeOverrides,
|
|
10615
|
+
seedNode,
|
|
10610
10616
|
chrome: normalizeChrome(spec.chrome),
|
|
10611
10617
|
annotations: normalizeAnnotations(spec.annotations),
|
|
10612
10618
|
theme: spec.theme ?? {},
|
|
@@ -10722,7 +10728,7 @@ function normalizeSpec(spec, warnings = []) {
|
|
|
10722
10728
|
return normalizeTableSpec(spec, warnings);
|
|
10723
10729
|
}
|
|
10724
10730
|
if (isGraphSpec(spec)) {
|
|
10725
|
-
return normalizeGraphSpec(spec);
|
|
10731
|
+
return normalizeGraphSpec(spec, warnings);
|
|
10726
10732
|
}
|
|
10727
10733
|
if (isSankeySpec(spec)) {
|
|
10728
10734
|
return normalizeSankeySpec(spec, warnings);
|
|
@@ -11624,6 +11630,35 @@ function validateGraphSpec(spec, errors) {
|
|
|
11624
11630
|
nodeIds.add(node.id);
|
|
11625
11631
|
}
|
|
11626
11632
|
}
|
|
11633
|
+
if (spec.seedNode !== void 0) {
|
|
11634
|
+
if (typeof spec.seedNode === "string") {
|
|
11635
|
+
if (spec.seedNode === "") {
|
|
11636
|
+
errors.push({
|
|
11637
|
+
message: 'Spec error: "seedNode" must be a non-empty node id',
|
|
11638
|
+
path: "seedNode",
|
|
11639
|
+
code: "MISSING_FIELD",
|
|
11640
|
+
suggestion: `Use one of the existing node ids, e.g. seedNode: "${[...nodeIds][0] ?? "a"}"`
|
|
11641
|
+
});
|
|
11642
|
+
}
|
|
11643
|
+
} else if (typeof spec.seedNode !== "object" || spec.seedNode === null || Array.isArray(spec.seedNode)) {
|
|
11644
|
+
errors.push({
|
|
11645
|
+
message: `Spec error: "seedNode" must be a node id string or an object like { id, style }, got ${Array.isArray(spec.seedNode) ? "array" : typeof spec.seedNode}`,
|
|
11646
|
+
path: "seedNode",
|
|
11647
|
+
code: "INVALID_TYPE",
|
|
11648
|
+
suggestion: `Use the id shorthand (seedNode: "${[...nodeIds][0] ?? "a"}") or the object form (seedNode: { id: "${[...nodeIds][0] ?? "a"}", style: { radius: 14 } })`
|
|
11649
|
+
});
|
|
11650
|
+
} else {
|
|
11651
|
+
const seedId = spec.seedNode.id;
|
|
11652
|
+
if (typeof seedId !== "string" || seedId === "") {
|
|
11653
|
+
errors.push({
|
|
11654
|
+
message: 'Spec error: "seedNode" must have a non-empty string "id" field',
|
|
11655
|
+
path: "seedNode.id",
|
|
11656
|
+
code: "MISSING_FIELD",
|
|
11657
|
+
suggestion: `Add the seed node id, e.g. seedNode: { id: "${[...nodeIds][0] ?? "a"}" }`
|
|
11658
|
+
});
|
|
11659
|
+
}
|
|
11660
|
+
}
|
|
11661
|
+
}
|
|
11627
11662
|
if (!Array.isArray(spec.edges)) {
|
|
11628
11663
|
errors.push({
|
|
11629
11664
|
message: 'Spec error: graph spec requires an "edges" array',
|
|
@@ -13513,9 +13548,9 @@ function buildBandScale(channel, data, rangeStart, rangeEnd, defaultPadding = DE
|
|
|
13513
13548
|
}
|
|
13514
13549
|
return { scale, type: "band", channel };
|
|
13515
13550
|
}
|
|
13516
|
-
function buildPointScale(channel, data, rangeStart, rangeEnd) {
|
|
13551
|
+
function buildPointScale(channel, data, rangeStart, rangeEnd, defaultPadding = 0.5) {
|
|
13517
13552
|
const values = channel.scale?.domain ? channel.scale.domain : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
13518
|
-
const padding = channel.scale?.padding ?? channel.scale?.paddingOuter ??
|
|
13553
|
+
const padding = channel.scale?.padding ?? channel.scale?.paddingOuter ?? defaultPadding;
|
|
13519
13554
|
const scale = point().domain(values).range([rangeStart, rangeEnd]).padding(padding);
|
|
13520
13555
|
if (channel.scale?.reverse) {
|
|
13521
13556
|
const [r0, r1] = scale.range();
|
|
@@ -13592,7 +13627,7 @@ function buildPositionalScale(channel, data, rangeStart, rangeEnd, chartType, ax
|
|
|
13592
13627
|
case "point":
|
|
13593
13628
|
return buildPointScale(channel, data, rangeStart, rangeEnd);
|
|
13594
13629
|
case "ordinal":
|
|
13595
|
-
return
|
|
13630
|
+
return buildCategoricalScale(channel, data, rangeStart, rangeEnd, chartType, axis);
|
|
13596
13631
|
}
|
|
13597
13632
|
}
|
|
13598
13633
|
switch (channel.type) {
|
|
@@ -13602,17 +13637,21 @@ function buildPositionalScale(channel, data, rangeStart, rangeEnd, chartType, ax
|
|
|
13602
13637
|
return buildLinearScale(channel, data, rangeStart, rangeEnd);
|
|
13603
13638
|
case "nominal":
|
|
13604
13639
|
case "ordinal":
|
|
13605
|
-
|
|
13606
|
-
return buildBandScale(channel, data, rangeStart, rangeEnd, HEATMAP_BAND_PADDING);
|
|
13607
|
-
}
|
|
13608
|
-
if (chartType === "bar" || chartType === "beeswarm" || chartType === "range" || (chartType === "circle" || chartType === "lollipop") && axis === "y") {
|
|
13609
|
-
return buildBandScale(channel, data, rangeStart, rangeEnd);
|
|
13610
|
-
}
|
|
13611
|
-
return buildPointScale(channel, data, rangeStart, rangeEnd);
|
|
13640
|
+
return buildCategoricalScale(channel, data, rangeStart, rangeEnd, chartType, axis);
|
|
13612
13641
|
default:
|
|
13613
13642
|
return buildLinearScale(channel, data, rangeStart, rangeEnd);
|
|
13614
13643
|
}
|
|
13615
13644
|
}
|
|
13645
|
+
function buildCategoricalScale(channel, data, rangeStart, rangeEnd, chartType, axis) {
|
|
13646
|
+
if (chartType === "rect") {
|
|
13647
|
+
return buildBandScale(channel, data, rangeStart, rangeEnd, HEATMAP_BAND_PADDING);
|
|
13648
|
+
}
|
|
13649
|
+
if (chartType === "bar" || chartType === "beeswarm" || chartType === "range" || (chartType === "circle" || chartType === "lollipop") && axis === "y") {
|
|
13650
|
+
return buildBandScale(channel, data, rangeStart, rangeEnd);
|
|
13651
|
+
}
|
|
13652
|
+
const isTrendMark = chartType === "line" || chartType === "area";
|
|
13653
|
+
return buildPointScale(channel, data, rangeStart, rangeEnd, isTrendMark ? 0 : 0.5);
|
|
13654
|
+
}
|
|
13616
13655
|
function computeScales(spec, chartArea, data) {
|
|
13617
13656
|
const result = {};
|
|
13618
13657
|
const encoding = spec.encoding;
|
|
@@ -14764,6 +14803,32 @@ function applyBandLabelStride(ticks2, stride) {
|
|
|
14764
14803
|
const lastIdx = ticks2.length - 1;
|
|
14765
14804
|
return ticks2.filter((_, i) => (lastIdx - i) % stride === 0);
|
|
14766
14805
|
}
|
|
14806
|
+
var LABEL_EDGE_GUARD = 2;
|
|
14807
|
+
function clampXTickLabels(ticks2, totalWidth, tickAngle, fontSize, fontWeight, measureText) {
|
|
14808
|
+
const rotated = tickAngle !== void 0 && Math.abs(tickAngle) > 10;
|
|
14809
|
+
const rad = rotated ? Math.abs(tickAngle) * Math.PI / 180 : 0;
|
|
14810
|
+
const across = rotated ? fontSize * 1.2 * Math.sin(rad) / 2 : 0;
|
|
14811
|
+
return ticks2.map((tick) => {
|
|
14812
|
+
const width = measureLabel(tick.label, fontSize, fontWeight, measureText);
|
|
14813
|
+
let left2;
|
|
14814
|
+
let right2;
|
|
14815
|
+
if (rotated) {
|
|
14816
|
+
const along = width * Math.cos(rad);
|
|
14817
|
+
const trailsLeft = tickAngle < 0;
|
|
14818
|
+
left2 = trailsLeft ? -(along + across) : -across;
|
|
14819
|
+
right2 = trailsLeft ? across : along + across;
|
|
14820
|
+
} else {
|
|
14821
|
+
left2 = -width / 2;
|
|
14822
|
+
right2 = width / 2;
|
|
14823
|
+
}
|
|
14824
|
+
let x2 = tick.position;
|
|
14825
|
+
const overRight = x2 + right2 - (totalWidth - LABEL_EDGE_GUARD);
|
|
14826
|
+
if (overRight > 0) x2 -= overRight;
|
|
14827
|
+
const overLeft = LABEL_EDGE_GUARD - (x2 + left2);
|
|
14828
|
+
if (overLeft > 0) x2 += overLeft;
|
|
14829
|
+
return x2 === tick.position ? tick : { ...tick, labelPosition: x2 };
|
|
14830
|
+
});
|
|
14831
|
+
}
|
|
14767
14832
|
function computeAxes(scales, chartArea, strategy, theme, measureText, dataContext) {
|
|
14768
14833
|
const result = {};
|
|
14769
14834
|
const baseDensity = strategy.axisLabelDensity;
|
|
@@ -14976,6 +15041,14 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
14976
15041
|
const totalWidth = dataContext?.totalWidth ?? chartArea.x + chartArea.width;
|
|
14977
15042
|
if (result.x) {
|
|
14978
15043
|
const isRotated = !!result.x.tickAngle && Math.abs(result.x.tickAngle) > 10;
|
|
15044
|
+
result.x.ticks = clampXTickLabels(
|
|
15045
|
+
result.x.ticks,
|
|
15046
|
+
totalWidth,
|
|
15047
|
+
result.x.tickAngle,
|
|
15048
|
+
result.x.tickLabelStyle.fontSize,
|
|
15049
|
+
result.x.tickLabelStyle.fontWeight,
|
|
15050
|
+
measureText
|
|
15051
|
+
);
|
|
14979
15052
|
result.x.extent = computeXAxisExtentFromLabels({
|
|
14980
15053
|
labels: result.x.ticks.map((t) => t.label),
|
|
14981
15054
|
tickAngle: result.x.tickAngle,
|
|
@@ -19498,6 +19571,16 @@ function resolveGraphInteraction(cfg) {
|
|
|
19498
19571
|
|
|
19499
19572
|
// src/graphs/compile-graph.ts
|
|
19500
19573
|
var graphNumberFormatter = defaultNumberFormatter8({ allIntegers: false, surface: "chart" });
|
|
19574
|
+
function mergeOverrides(...layers) {
|
|
19575
|
+
const merged = {};
|
|
19576
|
+
for (const layer of layers) {
|
|
19577
|
+
if (!layer) continue;
|
|
19578
|
+
for (const [key, value2] of Object.entries(layer)) {
|
|
19579
|
+
if (value2 !== void 0) merged[key] = value2;
|
|
19580
|
+
}
|
|
19581
|
+
}
|
|
19582
|
+
return merged;
|
|
19583
|
+
}
|
|
19501
19584
|
var SWATCH_SIZE2 = 12;
|
|
19502
19585
|
var SWATCH_GAP2 = 6;
|
|
19503
19586
|
var ENTRY_GAP2 = 16;
|
|
@@ -19658,12 +19741,23 @@ function compileGraph(spec, options) {
|
|
|
19658
19741
|
if (options.darkMode) {
|
|
19659
19742
|
theme = adaptTheme3(theme);
|
|
19660
19743
|
}
|
|
19744
|
+
const seedNodeIds = graphSpec.seedNode ? [graphSpec.seedNode.id] : [];
|
|
19745
|
+
const nodeOverrides = graphSpec.seedNode ? {
|
|
19746
|
+
...graphSpec.nodeOverrides,
|
|
19747
|
+
[graphSpec.seedNode.id]: mergeOverrides(
|
|
19748
|
+
// Radius is deliberately absent so the seed doesn't fight a nodeSize
|
|
19749
|
+
// encoding; a host wanting a bigger seed sets seedNode.style.radius.
|
|
19750
|
+
{ stroke: theme.colors.text, strokeWidth: 2, alwaysShowLabel: true },
|
|
19751
|
+
graphSpec.seedNode.style,
|
|
19752
|
+
graphSpec.nodeOverrides?.[graphSpec.seedNode.id]
|
|
19753
|
+
)
|
|
19754
|
+
} : graphSpec.nodeOverrides;
|
|
19661
19755
|
const compiledNodes = resolveNodeVisuals(
|
|
19662
19756
|
graphSpec.nodes,
|
|
19663
19757
|
graphSpec.encoding,
|
|
19664
19758
|
graphSpec.edges,
|
|
19665
19759
|
theme,
|
|
19666
|
-
|
|
19760
|
+
nodeOverrides
|
|
19667
19761
|
);
|
|
19668
19762
|
const clusteringField = graphSpec.layout.clustering?.field;
|
|
19669
19763
|
const hasCommunities = !!clusteringField;
|
|
@@ -19769,6 +19863,7 @@ function compileGraph(spec, options) {
|
|
|
19769
19863
|
interaction,
|
|
19770
19864
|
legendField,
|
|
19771
19865
|
initialHighlight,
|
|
19866
|
+
seedNodeIds,
|
|
19772
19867
|
edgeLegend
|
|
19773
19868
|
};
|
|
19774
19869
|
}
|
|
@@ -23610,6 +23705,10 @@ function compileChart(spec, optionsInput) {
|
|
|
23610
23705
|
watermark = false;
|
|
23611
23706
|
chartSpec = { ...chartSpec, watermark: false };
|
|
23612
23707
|
}
|
|
23708
|
+
if (heightClass === "cramped" && !chartSpec.userExplicit.watermark) {
|
|
23709
|
+
watermark = false;
|
|
23710
|
+
chartSpec = { ...chartSpec, watermark: false };
|
|
23711
|
+
}
|
|
23613
23712
|
const mergedThemeConfig = options.theme ? { ...chartSpec.theme, ...options.theme } : chartSpec.theme;
|
|
23614
23713
|
let theme = resolveTheme7(mergedThemeConfig);
|
|
23615
23714
|
if (options.darkMode) {
|
|
@@ -24218,6 +24317,7 @@ function compileBarList2(spec, options) {
|
|
|
24218
24317
|
}
|
|
24219
24318
|
export {
|
|
24220
24319
|
ARROWHEAD_LENGTH,
|
|
24320
|
+
AUTO_CANVAS_THRESHOLD,
|
|
24221
24321
|
BOLD_SPAN_FONT_WEIGHT,
|
|
24222
24322
|
DEFAULT_ANNOTATION_FONT_SIZE,
|
|
24223
24323
|
DEFAULT_LINE_HEIGHT,
|