@opendata-ai/openchart-engine 8.1.1 → 8.2.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _opendata_ai_openchart_core from '@opendata-ai/openchart-core';
2
- import { AnimationEase, LegendLayout, ResolvedChrome, TooltipContent, A11yMetadata, ResolvedTheme, LegendEntry, CompileOptions, ChartLayout, LayerSpec, CompileTableOptions, TableLayout, ResolvedAnimationPhase, AnimationSpec, ResolvedAnimation, Point, DataRow, BarListEncoding, ThemeConfig, DarkMode, GeoMapGeo, GeoMapEncoding, LegendConfig, GeoMapPointsLayer, TileMapEncoding, TileMapPalette, MarkType, MarkDef, Encoding, ChromeText, Annotation, LabelConfig, Display, ColumnConfig, GraphSpec, GraphEncoding, GraphLayoutConfig, NodeOverride, GraphAnimationSpec, GraphInteractionConfig, SankeyEncoding, SankeyNodeAlign, SankeyLinkColor, ScaleType, EncodingChannel, FieldFormatContext, Rect, LayoutStrategy, VizSpec, Mark, BinTransform, CalculateTransform, ConditionalValueDef, FilterPredicate, TimeUnitTransform, Transform } from '@opendata-ai/openchart-core';
2
+ import { AnimationEase, LegendLayout, ResolvedChrome, TooltipContent, A11yMetadata, ResolvedTheme, LegendEntry, CompileOptions, ChartLayout, LayerSpec, CompileTableOptions, TableLayout, ResolvedAnimationPhase, AnimationSpec, ResolvedAnimation, Point, DataRow, BarListEncoding, ThemeConfig, DarkMode, GeoMapGeo, GeoMapEncoding, LegendConfig, GeoMapPointsLayer, SankeyEncoding, SankeyNodeAlign, SankeyLinkColor, TileMapEncoding, TileMapPalette, MarkType, MarkDef, Encoding, ChromeText, Annotation, LabelConfig, Display, ColumnConfig, GraphSpec, GraphEncoding, GraphLayoutConfig, NodeOverride, GraphAnimationSpec, GraphInteractionConfig, ScaleType, EncodingChannel, FieldFormatContext, Rect, LayoutStrategy, VizSpec, Mark, BinTransform, CalculateTransform, ConditionalValueDef, FilterPredicate, TimeUnitTransform, Transform } from '@opendata-ai/openchart-core';
3
3
  export { BarListLayout, BarListSpec, ChartLayout, ChartSpec, CompileOptions, CompileTableOptions, GeoMapLayout, GeoMapSpec, GraphLayout, GraphSpec, LayerSpec, SankeyLayout, SankeySpec, TableLayout, TableSpec, TileMapLayout, TileMapSpec, VizSpec } from '@opendata-ai/openchart-core';
4
4
  import { ScaleLinear, ScaleTime, ScaleLogarithmic, ScalePower, ScaleSymLog, ScaleBand, ScalePoint, ScaleOrdinal, ScaleQuantile, ScaleQuantize, ScaleThreshold } from 'd3-scale';
5
5
 
@@ -434,6 +434,36 @@ interface NormalizedGeoMapSpec {
434
434
  points?: GeoMapPointsLayer;
435
435
  }
436
436
 
437
+ /**
438
+ * Internal normalized sankey spec type used by the compilation pipeline.
439
+ *
440
+ * This mirrors NormalizedChartSpec/NormalizedGraphSpec: all optional fields
441
+ * have been filled with sensible defaults. It's an engine implementation
442
+ * detail, not a public contract.
443
+ */
444
+
445
+ /** A SankeySpec with all optional fields filled with defaults. */
446
+ interface NormalizedSankeySpec {
447
+ type: 'sankey';
448
+ data: Record<string, unknown>[];
449
+ encoding: SankeyEncoding;
450
+ nodeWidth: number;
451
+ nodePadding: number;
452
+ nodeAlign: SankeyNodeAlign;
453
+ iterations: number;
454
+ linkStyle: SankeyLinkColor;
455
+ nodeLabelAlign: 'auto' | 'left' | 'right';
456
+ nodeSort?: string[];
457
+ chrome: NormalizedChrome;
458
+ legend?: LegendConfig;
459
+ theme: ThemeConfig;
460
+ darkMode: DarkMode;
461
+ watermark: boolean;
462
+ animation?: AnimationSpec;
463
+ valueFormat?: string;
464
+ linkOpacity?: number;
465
+ }
466
+
437
467
  /**
438
468
  * Internal normalized tilemap spec type used by the compilation pipeline.
439
469
  *
@@ -635,36 +665,6 @@ interface CompileResult {
635
665
  warnings: string[];
636
666
  }
637
667
 
638
- /**
639
- * Internal normalized sankey spec type used by the compilation pipeline.
640
- *
641
- * This mirrors NormalizedChartSpec/NormalizedGraphSpec: all optional fields
642
- * have been filled with sensible defaults. It's an engine implementation
643
- * detail, not a public contract.
644
- */
645
-
646
- /** A SankeySpec with all optional fields filled with defaults. */
647
- interface NormalizedSankeySpec {
648
- type: 'sankey';
649
- data: Record<string, unknown>[];
650
- encoding: SankeyEncoding;
651
- nodeWidth: number;
652
- nodePadding: number;
653
- nodeAlign: SankeyNodeAlign;
654
- iterations: number;
655
- linkStyle: SankeyLinkColor;
656
- nodeLabelAlign: 'auto' | 'left' | 'right';
657
- nodeSort?: string[];
658
- chrome: NormalizedChrome;
659
- legend?: LegendConfig;
660
- theme: ThemeConfig;
661
- darkMode: DarkMode;
662
- watermark: boolean;
663
- animation?: AnimationSpec;
664
- valueFormat?: string;
665
- linkOpacity?: number;
666
- }
667
-
668
668
  /** Continuous D3 scales (linear, time, log, pow, sqrt, symlog) that support .ticks() and .nice(). */
669
669
  type D3ContinuousScale = ScaleLinear<number, number> | ScaleTime<number, number> | ScaleLogarithmic<number, number> | ScalePower<number, number> | ScaleSymLog<number, number>;
670
670
  /** Discretizing D3 scales (quantile, quantize, threshold). */
@@ -701,6 +701,27 @@ interface ResolvedScales {
701
701
  defaultColor?: string | _opendata_ai_openchart_core.GradientDef;
702
702
  }
703
703
 
704
+ /**
705
+ * Pie / donut chart mark computation.
706
+ *
707
+ * Uses d3.pie() for angle calculation and d3.arc() for SVG path
708
+ * generation. Supports sorting by value (largest first), small-slice
709
+ * grouping into "Other", and donut variant with inner radius.
710
+ */
711
+
712
+ /**
713
+ * Rebuild the SVG path for an arc from its resolved geometry. Used by the
714
+ * vanilla transition driver to interpolate arc marks per frame; must stay in
715
+ * lockstep with the `arcGenerator` below or the tween's final frame won't
716
+ * match the rendered mark.
717
+ */
718
+ declare function buildArcPath(geom: {
719
+ innerRadius: number;
720
+ outerRadius: number;
721
+ startAngle: number;
722
+ endAngle: number;
723
+ }): string;
724
+
704
725
  /** Default font size for annotation labels. */
705
726
  declare const DEFAULT_ANNOTATION_FONT_SIZE = 13;
706
727
  /** Default line height multiplier for annotation text. */
@@ -1084,4 +1105,4 @@ declare function runTimeUnit(data: DataRow[], transform: TimeUnitTransform): Dat
1084
1105
  */
1085
1106
  declare function runTransforms(data: DataRow[], transforms: Transform[]): DataRow[];
1086
1107
 
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 };
1108
+ 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, buildArcPath, 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
@@ -8356,6 +8356,16 @@ var parliamentRenderer = (spec, scales, chartArea, strategy, theme) => {
8356
8356
  // src/charts/pie/compute.ts
8357
8357
  import { formatPercent as formatPercent2, isConditionalDef, isGradientDef as isGradientDef4 } from "@opendata-ai/openchart-core";
8358
8358
  var SMALL_SLICE_THRESHOLD = 0.03;
8359
+ var PIE_PAD_ANGLE = 0.01;
8360
+ function buildArcPath(geom) {
8361
+ return arc_default()({
8362
+ innerRadius: geom.innerRadius,
8363
+ outerRadius: geom.outerRadius,
8364
+ startAngle: geom.startAngle,
8365
+ endAngle: geom.endAngle,
8366
+ padAngle: PIE_PAD_ANGLE
8367
+ }) ?? "";
8368
+ }
8359
8369
  var DEFAULT_PALETTE = [
8360
8370
  "#1b7fa3",
8361
8371
  "#c44e52",
@@ -8454,7 +8464,7 @@ function computePieMarks(spec, scales, chartArea, _strategy, isDonut = false) {
8454
8464
  if (slices.length === 0) return [];
8455
8465
  slices.sort((a, b) => b.value - a.value);
8456
8466
  slices = groupSmallSlices(slices, SMALL_SLICE_THRESHOLD);
8457
- const pieGenerator = pie_default().value((d) => d.value).sort(null).padAngle(0.01).startAngle(startAngle).endAngle(endAngle);
8467
+ const pieGenerator = pie_default().value((d) => d.value).sort(null).padAngle(PIE_PAD_ANGLE).startAngle(startAngle).endAngle(endAngle);
8458
8468
  const arcs = pieGenerator(slices);
8459
8469
  const sweepBounds = computeSweepBounds(startAngle, endAngle);
8460
8470
  const sweepWidth = sweepBounds.maxX - sweepBounds.minX;
@@ -12640,6 +12650,15 @@ function compileBarList(spec, options) {
12640
12650
  const maxLabelPct = isNarrow ? 0.35 : 0.4;
12641
12651
  const labelWidth = Math.max(50, Math.min(maxCombinedWidth, fullArea.width * maxLabelPct));
12642
12652
  const barAreaWidth = fullArea.width - labelWidth - labelBarGap - barValueGap - valueWidth;
12653
+ const ellipsize = (text, fullWidth, budget, fontSize, fontWeight) => {
12654
+ const seedLen = Math.min(text.length, Math.ceil(text.length * budget / fullWidth) + 2);
12655
+ let t = text.slice(0, seedLen);
12656
+ while (t.length > 1 && measureText(`${t.trimEnd()}\u2026`, fontSize, fontWeight).width > budget) {
12657
+ t = t.slice(0, -1);
12658
+ }
12659
+ const out = `${t.trimEnd()}\u2026`;
12660
+ return { text: out, width: measureText(out, fontSize, fontWeight).width };
12661
+ };
12643
12662
  const labelColor = theme.colors.text;
12644
12663
  const subtitleColor = options.darkMode ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.45)";
12645
12664
  const valueColor = options.darkMode ? "rgba(255,255,255,0.6)" : "rgba(0,0,0,0.55)";
@@ -12654,6 +12673,17 @@ function compileBarList(spec, options) {
12654
12673
  const rowY = fullArea.y + i * rowHeight;
12655
12674
  const centerY = rowY + rowContentHeight / 2;
12656
12675
  const labelX = fullArea.x;
12676
+ let displayLabel = labelText;
12677
+ let displayLabelW = perRowLabelWidths.get(i) ?? 0;
12678
+ if (displayLabelW > labelWidth) {
12679
+ ({ text: displayLabel, width: displayLabelW } = ellipsize(
12680
+ labelText,
12681
+ displayLabelW,
12682
+ labelWidth,
12683
+ LABEL_FONT_SIZE7,
12684
+ LABEL_FONT_WEIGHT7
12685
+ ));
12686
+ }
12657
12687
  const labelStyle = {
12658
12688
  fontFamily: theme.fonts.family,
12659
12689
  fontSize: LABEL_FONT_SIZE7,
@@ -12663,21 +12693,39 @@ function compileBarList(spec, options) {
12663
12693
  };
12664
12694
  let subtitle;
12665
12695
  if (subtitleField && row[subtitleField] != null) {
12666
- const subtitleText = String(row[subtitleField]);
12667
- const subtitleX = labelX + (perRowLabelWidths.get(i) ?? 0) + 6;
12668
- subtitle = {
12669
- text: subtitleText,
12670
- x: subtitleX,
12671
- y: centerY,
12672
- style: {
12673
- fontFamily: theme.fonts.family,
12674
- fontSize: SUBTITLE_FONT_SIZE,
12675
- fontWeight: SUBTITLE_FONT_WEIGHT2,
12676
- fill: subtitleColor,
12677
- lineHeight: 1.4
12678
- },
12679
- visible: true
12680
- };
12696
+ let subtitleText = String(row[subtitleField]);
12697
+ const subtitleBudget = labelWidth - displayLabelW - 6;
12698
+ const subtitleW = measureText(subtitleText, SUBTITLE_FONT_SIZE, SUBTITLE_FONT_WEIGHT2).width;
12699
+ if (subtitleW > subtitleBudget) {
12700
+ const minW = measureText("W\u2026", SUBTITLE_FONT_SIZE, SUBTITLE_FONT_WEIGHT2).width;
12701
+ if (subtitleBudget < minW) {
12702
+ subtitleText = "";
12703
+ } else {
12704
+ ({ text: subtitleText } = ellipsize(
12705
+ subtitleText,
12706
+ subtitleW,
12707
+ subtitleBudget,
12708
+ SUBTITLE_FONT_SIZE,
12709
+ SUBTITLE_FONT_WEIGHT2
12710
+ ));
12711
+ }
12712
+ }
12713
+ if (subtitleText) {
12714
+ const subtitleX = labelX + displayLabelW + 6;
12715
+ subtitle = {
12716
+ text: subtitleText,
12717
+ x: subtitleX,
12718
+ y: centerY,
12719
+ style: {
12720
+ fontFamily: theme.fonts.family,
12721
+ fontSize: SUBTITLE_FONT_SIZE,
12722
+ fontWeight: SUBTITLE_FONT_WEIGHT2,
12723
+ fill: subtitleColor,
12724
+ lineHeight: 1.4
12725
+ },
12726
+ visible: true
12727
+ };
12728
+ }
12681
12729
  }
12682
12730
  const trackX = fullArea.x + labelWidth + labelBarGap;
12683
12731
  const trackY = centerY - barHeight / 2;
@@ -12698,7 +12746,7 @@ function compileBarList(spec, options) {
12698
12746
  y: rowY,
12699
12747
  height: rowHeight,
12700
12748
  label: {
12701
- text: labelText,
12749
+ text: displayLabel,
12702
12750
  x: labelX,
12703
12751
  y: centerY,
12704
12752
  style: labelStyle,
@@ -24324,6 +24372,7 @@ export {
24324
24372
  ENTER_DEFAULTS,
24325
24373
  EXIT_DEFAULTS,
24326
24374
  UPDATE_DEFAULTS,
24375
+ buildArcPath,
24327
24376
  buildAreaPath,
24328
24377
  buildEdgeTooltip,
24329
24378
  buildLinePath,