@rowsncolumns/charts 28.1.7 → 28.1.10

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.
Files changed (61) hide show
  1. package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
  2. package/dist/cjs/basic-chart/basic-chart.js +525 -419
  3. package/dist/cjs/basic-chart/helpers.d.ts.map +1 -1
  4. package/dist/cjs/basic-chart/helpers.js +2 -0
  5. package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  6. package/dist/cjs/plotly/basic-chart/basic-chart.js +351 -192
  7. package/dist/cjs/plotly/basic-chart/helpers.d.ts.map +1 -1
  8. package/dist/cjs/plotly/basic-chart/helpers.js +2 -0
  9. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  10. package/dist/cjs/utils/bubble.d.ts +34 -1
  11. package/dist/cjs/utils/bubble.d.ts.map +1 -1
  12. package/dist/cjs/utils/bubble.js +39 -13
  13. package/dist/cjs/utils/doughnut.d.ts +19 -0
  14. package/dist/cjs/utils/doughnut.d.ts.map +1 -0
  15. package/dist/cjs/utils/doughnut.js +28 -0
  16. package/dist/cjs/utils/echarts-overlays.d.ts +46 -0
  17. package/dist/cjs/utils/echarts-overlays.d.ts.map +1 -0
  18. package/dist/cjs/utils/echarts-overlays.js +134 -0
  19. package/dist/cjs/utils/hierarchy.d.ts +23 -1
  20. package/dist/cjs/utils/hierarchy.d.ts.map +1 -1
  21. package/dist/cjs/utils/hierarchy.js +179 -55
  22. package/dist/cjs/utils/overrides.d.ts +9 -0
  23. package/dist/cjs/utils/overrides.d.ts.map +1 -1
  24. package/dist/cjs/utils/overrides.js +18 -3
  25. package/dist/cjs/utils/radar.d.ts +31 -5
  26. package/dist/cjs/utils/radar.d.ts.map +1 -1
  27. package/dist/cjs/utils/radar.js +67 -27
  28. package/dist/cjs/utils/trendline.d.ts +7 -1
  29. package/dist/cjs/utils/trendline.d.ts.map +1 -1
  30. package/dist/cjs/utils/trendline.js +82 -18
  31. package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
  32. package/dist/esm/basic-chart/basic-chart.js +376 -248
  33. package/dist/esm/basic-chart/helpers.d.ts.map +1 -1
  34. package/dist/esm/basic-chart/helpers.js +2 -0
  35. package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  36. package/dist/esm/plotly/basic-chart/basic-chart.js +279 -69
  37. package/dist/esm/plotly/basic-chart/helpers.d.ts.map +1 -1
  38. package/dist/esm/plotly/basic-chart/helpers.js +2 -0
  39. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  40. package/dist/esm/utils/bubble.d.ts +34 -1
  41. package/dist/esm/utils/bubble.d.ts.map +1 -1
  42. package/dist/esm/utils/bubble.js +35 -11
  43. package/dist/esm/utils/doughnut.d.ts +19 -0
  44. package/dist/esm/utils/doughnut.d.ts.map +1 -0
  45. package/dist/esm/utils/doughnut.js +21 -0
  46. package/dist/esm/utils/echarts-overlays.d.ts +46 -0
  47. package/dist/esm/utils/echarts-overlays.d.ts.map +1 -0
  48. package/dist/esm/utils/echarts-overlays.js +135 -0
  49. package/dist/esm/utils/hierarchy.d.ts +23 -1
  50. package/dist/esm/utils/hierarchy.d.ts.map +1 -1
  51. package/dist/esm/utils/hierarchy.js +168 -50
  52. package/dist/esm/utils/overrides.d.ts +9 -0
  53. package/dist/esm/utils/overrides.d.ts.map +1 -1
  54. package/dist/esm/utils/overrides.js +16 -2
  55. package/dist/esm/utils/radar.d.ts +31 -5
  56. package/dist/esm/utils/radar.d.ts.map +1 -1
  57. package/dist/esm/utils/radar.js +44 -16
  58. package/dist/esm/utils/trendline.d.ts +7 -1
  59. package/dist/esm/utils/trendline.d.ts.map +1 -1
  60. package/dist/esm/utils/trendline.js +67 -14
  61. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,sBAAsB,GACjC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,yGA6DzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SAuBX,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,SAAS,KACnD,OAAgD,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,aAAa,CAAC,OAAO,CAAC,KAC7B,MAAM,EAmBR,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;aASzC,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,YAejE,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,sBAAsB,GACjC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,yGA6DzC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SAuBX,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,SAAS,KACnD,OAAgD,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,aAAa,CAAC,OAAO,CAAC,KAC7B,MAAM,EAmBR,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;aASzC,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,YAiBjE,CAAC"}
@@ -119,6 +119,8 @@ export const showXAxis = (type) => {
119
119
  type === "column" ||
120
120
  type === "bubble" ||
121
121
  type === "histogram" ||
122
+ // A waterfall's steps are categories (Opening, Sales, …) (#1070).
123
+ type === "waterfall" ||
122
124
  // Stock is line-derived and lives on a category x-axis. Radar is
123
125
  // axis-less (it uses a polar `radar` indicator instead).
124
126
  type === "stock") {
@@ -1 +1 @@
1
- {"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAuZxD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AA0FnD,eAAO,MAAM,UAAU,GAAI,gPAkBxB,eAAe,sBA00EjB,CAAC"}
1
+ {"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA6ZxD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAmGnD,eAAO,MAAM,UAAU,GAAI,gPAkBxB,eAAe,sBAgkFjB,CAAC"}
@@ -17,12 +17,14 @@ import { computeErrorBars, resolveCustomErrorBarValues, } from "../../utils/erro
17
17
  import { plotlyMulticategoryX, resolveCategoryLevels, } from "../../utils/category-levels";
18
18
  import { gradientToPlotly } from "../../utils/gradient";
19
19
  import { BAND_GAP, legendLabelsFrom, measureTextWidth, reservePlotlyLegendBand, } from "../../utils/legend-layout";
20
+ import { clampDoughnutHole, doughnutRings } from "../../utils/doughnut";
21
+ import { dataLabelFormatter, resolveSeriesDataLabels, } from "../../utils/overrides";
20
22
  import { plotlyTitleText } from "../../utils/plotly-title";
21
23
  import { explosionToPlotlyPull, resolveExplosion, } from "../../utils/pie-explosion";
22
24
  import { resolvePlotlyClickSelection, wasDragged, } from "../../utils/plotly-click";
23
25
  import { buildPlotlySurfaceTrace } from "../../utils/plotly-surface";
24
26
  import { normalize3DSourceType, } from "../../utils/three-d";
25
- import { extractHierarchyRows, getHierarchyHeaderTitle, toPlotlyHierarchy, } from "../../utils/hierarchy";
27
+ import { extractHierarchyRows, getHierarchyHeaderTitle, hierarchyNodeColors, toPlotlyHierarchy, } from "../../utils/hierarchy";
26
28
  import { binHistogram, computePareto, isParetoSpec, } from "../../utils/histogram";
27
29
  import { clampSecondPieSize, isOfPieChartSpec, otherSliceHalfAngle, resolveOfPieType, splitOfPie, } from "../../utils/of-pie";
28
30
  import { buildRadarIndicators } from "../../utils/radar";
@@ -337,6 +339,13 @@ const sanitizeChartValue = (value) => {
337
339
  * (Excel): every range's points in sequence. A single range passes through
338
340
  * untouched so the common path renders exactly as before.
339
341
  */
342
+ /**
343
+ * The plain value array of a series' data. (`"values" in seriesData` is
344
+ * true for an ARRAY too — `Array.prototype.values` — which handed the
345
+ * waterfall / funnel / histogram / boxplot branches that method instead
346
+ * of the numbers.)
347
+ */
348
+ const seriesValuesOf = (seriesData) => Array.isArray(seriesData) ? seriesData : seriesData.values;
340
349
  const mergeSeriesData = (parts) => {
341
350
  if (parts.length === 1)
342
351
  return parts[0];
@@ -387,6 +396,11 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
387
396
  // The plotted `<div>` plotly hands back on init — what `Plotly.toImage`
388
397
  // rasterises for Copy as Picture and the print preview.
389
398
  const graphDivRef = useRef(null);
399
+ // Plotly zooms a treemap / sunburst into the clicked branch by default;
400
+ // Excel has no drill-down, so the click handler cancels it unless the
401
+ // spec opts in (`drillDown`).
402
+ const drillDownRef = useRef(false);
403
+ drillDownRef.current = Boolean(spec.drillDown);
390
404
  const focusSheet = useFocusSheet();
391
405
  // Excel selects a data series (or one data point) when it is clicked
392
406
  // inside the chart and the grid highlights its source cells. The plot
@@ -514,8 +528,16 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
514
528
  sizeValues: sharedSizeValues,
515
529
  series: seriesInputs,
516
530
  });
517
- const sizeScale = createBubbleSizeScale(sizeValues);
531
+ // Bubble diameters scale from zero (area by default, width when
532
+ // `sizeRepresents` says so) and the largest bubble is a fraction of
533
+ // the plot area, which the margins below leave of the chart (#1068).
534
+ const sizeScale = createBubbleSizeScale(sizeValues, {
535
+ sizeRepresents: spec.sizeRepresents,
536
+ bubbleScale: spec.bubbleScale,
537
+ plotMinSide: Math.min((width ?? 0) - 80, (height ?? 0) - 100),
538
+ });
518
539
  const hasStringX = categories.length > 0;
540
+ const hasBubbleSecondaryAxis = (spec.series ?? []).some((seriesEntry) => seriesEntry?.useSecondaryAxis);
519
541
  const palette = getChartPalette(spec, [
520
542
  theme.themeColors.accent1,
521
543
  theme.themeColors.accent2,
@@ -551,16 +573,56 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
551
573
  const lineShapeFor = (style) => style === "smooth" || style === "smoothMarker"
552
574
  ? "spline"
553
575
  : "linear";
554
- const traces = preparedSeries.map((entry, index) => {
576
+ const traces = [];
577
+ preparedSeries.forEach((entry, index) => {
555
578
  const xValues = entry.points.map((point) => hasStringX
556
579
  ? point.xLabel ??
557
580
  categories[Math.round(point.value[0])] ??
558
581
  point.value[0]
559
582
  : point.value[0]);
560
583
  const yValues = entry.points.map((point) => point.value[1]);
561
- const labelValuesForSeries = entry.points.map((point) => point.name);
562
- const seriesColor = spec.series[index]?.color ?? chartPaletteColorAt(palette, index);
563
- const perSeriesMarker = spec.series[index]?.marker ?? undefined;
584
+ const rawSizes = entry.points.map((point) => point.value[2]);
585
+ const seriesSpec = spec.series[index];
586
+ const seriesColor = seriesSpec?.color ?? chartPaletteColorAt(palette, index);
587
+ const perSeriesMarker = seriesSpec?.marker ?? undefined;
588
+ // Per-point colours (`<c:dPt>`) key on the point's source row
589
+ // (`BubbleSeriesPoint.index` — points without a Y are skipped).
590
+ const pointColorOverrides = new Map();
591
+ for (const dp of seriesSpec?.dataPointColors ?? []) {
592
+ if (typeof dp?.idx === "number" && typeof dp?.color === "string") {
593
+ pointColorOverrides.set(dp.idx, dp.color);
594
+ }
595
+ }
596
+ const pointColors = (base) => pointColorOverrides.size
597
+ ? entry.points.map((point) => pointColorOverrides.get(point.index) ?? base)
598
+ : base;
599
+ // Hover reads the bubble label and size from `customdata`, leaving
600
+ // `text` to the data labels.
601
+ const customdata = entry.points.map((point) => [point.name, point.value[2]]);
602
+ const onSecondary = seriesSpec?.useSecondaryAxis ? { yaxis: "y2" } : {};
603
+ // This series' data labels: Y value by default, the bubble's size
604
+ // with `showBubbleSize`, the bubble label with `showCategoryName`.
605
+ const labelOpts = resolveSeriesDataLabels(spec.dataLabels, seriesSpec);
606
+ const labelText = labelOpts?.show
607
+ ? entry.points.map((point) => dataLabelFormatter(labelOpts, locale)({
608
+ value: point.value,
609
+ name: point.name,
610
+ seriesName: entry.name,
611
+ data: point,
612
+ }))
613
+ : undefined;
614
+ const labelPosition = labelOpts?.show
615
+ ? (labelOpts.position && PLOTLY_DLBL_POS_POINT[labelOpts.position]) ??
616
+ "top center"
617
+ : undefined;
618
+ const withLabels = (trace) => labelText
619
+ ? {
620
+ ...trace,
621
+ mode: withTextMode(trace.mode),
622
+ text: labelText,
623
+ textposition: labelPosition,
624
+ }
625
+ : trace;
564
626
  if (isScatter) {
565
627
  // Per-series marker.symbol = "none" → pure line (no markers).
566
628
  // Per-series marker.size overrides the default. Falls back to
@@ -572,14 +634,14 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
572
634
  : plotlyModeForScatterStyle(scatterStyle);
573
635
  const markerSize = perSeriesMarker?.size ?? 6;
574
636
  const markerColor = perSeriesMarker?.color ?? seriesColor;
575
- return {
637
+ traces.push(withLabels({
576
638
  type: "scatter",
577
639
  mode,
578
640
  x: xValues,
579
641
  y: yValues,
580
- text: labelValuesForSeries,
642
+ customdata,
581
643
  name: entry.name,
582
- hovertemplate: "%{text}<br>X: %{x}<br>Y: %{y}<extra></extra>",
644
+ hovertemplate: "%{customdata[0]}<br>X: %{x}<br>Y: %{y}<extra></extra>",
583
645
  line: {
584
646
  shape: lineShapeFor(scatterStyle),
585
647
  color: seriesColor,
@@ -590,31 +652,82 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
590
652
  : {
591
653
  marker: {
592
654
  size: markerSize,
593
- color: markerColor,
655
+ color: pointColors(markerColor),
594
656
  symbol: mapMarkerSymbolToPlotly(symbol),
595
657
  },
596
658
  }),
597
- };
659
+ ...onSecondary,
660
+ }));
661
+ }
662
+ else {
663
+ // Bubble — third dimension scaled into marker diameter.
664
+ const scaledSizes = rawSizes.map((value) => sizeScale(value));
665
+ traces.push(withLabels({
666
+ type: "scatter",
667
+ mode: "markers",
668
+ x: xValues,
669
+ y: yValues,
670
+ customdata,
671
+ name: entry.name,
672
+ hovertemplate: "%{customdata[0]}<br>X: %{x}<br>Y: %{y}<br>Size: %{customdata[1]}<extra></extra>",
673
+ marker: {
674
+ size: scaledSizes,
675
+ sizemode: "diameter",
676
+ color: pointColors(seriesColor),
677
+ opacity: 0.7,
678
+ },
679
+ ...onSecondary,
680
+ }));
681
+ }
682
+ // Trendlines on the real (x, y) points — a regression of Y on X,
683
+ // not on the point index — and native error bars (#1067).
684
+ const numericX = entry.points.map((point) => point.value[0]);
685
+ for (const tl of seriesSpec?.trendlines ?? []) {
686
+ const result = computeTrendline(yValues, {
687
+ type: tl.type,
688
+ order: tl.order,
689
+ period: tl.period,
690
+ forecastForward: tl.forecastForward,
691
+ forecastBackward: tl.forecastBackward,
692
+ }, numericX);
693
+ if (!result || result.points.length < 2)
694
+ continue;
695
+ const tlName = tl.name ??
696
+ `${entry.name ? entry.name + " " : ""}${tl.type ?? "linear"} trend`;
697
+ traces.push({
698
+ type: "scatter",
699
+ mode: "lines",
700
+ name: tlName,
701
+ x: result.points.map((p) => hasStringX
702
+ ? categories[Math.round(p.x)] ?? p.x
703
+ : p.x),
704
+ y: result.points.map((p) => p.y),
705
+ line: { color: tl.color ?? seriesColor, dash: "dash", width: 2 },
706
+ hoverinfo: "skip",
707
+ ...onSecondary,
708
+ });
709
+ }
710
+ if (seriesSpec?.errorBars) {
711
+ const direction = seriesSpec.errorBars.direction ?? "y";
712
+ // An X error bar sizes from the X values (a percentage or
713
+ // deviation of X).
714
+ const magnitudes = computeErrorBars(direction === "x" ? numericX : yValues, resolveCustomErrorBarValues(seriesSpec.errorBars, (range) => getSeriesValuesFromRange(range, spec.hiddenDimensionStrategy)));
715
+ if (magnitudes) {
716
+ const symmetric = magnitudes.plus.every((plus, i) => plus === magnitudes.minus[i]);
717
+ const errorConfig = {
718
+ type: "data",
719
+ symmetric,
720
+ array: magnitudes.plus,
721
+ ...(symmetric ? {} : { arrayminus: magnitudes.minus }),
722
+ color: seriesColor,
723
+ ...(seriesSpec.errorBars.noEndCap ? { width: 0 } : {}),
724
+ };
725
+ const target = traces[traces.length - 1 - (seriesSpec.trendlines?.length ?? 0)];
726
+ if (target) {
727
+ target[direction === "x" ? "error_x" : "error_y"] = errorConfig;
728
+ }
729
+ }
598
730
  }
599
- // Bubble — third dimension scaled into marker diameter.
600
- const rawSizes = entry.points.map((point) => point.value[2]);
601
- const scaledSizes = rawSizes.map((value) => sizeScale(value));
602
- return {
603
- type: "scatter",
604
- mode: "markers",
605
- x: xValues,
606
- y: yValues,
607
- text: labelValuesForSeries,
608
- customdata: rawSizes,
609
- name: entry.name,
610
- hovertemplate: "%{text}<br>X: %{x}<br>Y: %{y}<br>Size: %{customdata}<extra></extra>",
611
- marker: {
612
- size: scaledSizes,
613
- sizemode: "diameter",
614
- color: seriesColor,
615
- opacity: 0.7,
616
- },
617
- };
618
731
  });
619
732
  // Resolve text formats: chart-authored values (via OOXML <c:txPr>)
620
733
  // override the theme-derived defaults. `spec.titleTextFormat`,
@@ -634,7 +747,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
634
747
  x: 0.02,
635
748
  xanchor: "left",
636
749
  },
637
- xaxis: {
750
+ // Axis min / max / reverse / log / visibility from the spec
751
+ // (`categoryAxisOptions` is the X axis, as in the echarts
752
+ // renderer).
753
+ xaxis: applyAxisOverridesPlotly({
638
754
  title: {
639
755
  text: spec.horizontalAxisTitle ?? "",
640
756
  font: { color: xAxisFmt?.color ?? textColor },
@@ -646,8 +762,8 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
646
762
  ...(xAxisFmt?.fontSize ? { size: xAxisFmt.fontSize } : {}),
647
763
  },
648
764
  type: hasStringX ? "category" : "linear",
649
- },
650
- yaxis: {
765
+ }, spec.categoryAxisOptions),
766
+ yaxis: applyAxisOverridesPlotly({
651
767
  title: {
652
768
  text: spec.verticalAxisTitle ?? "",
653
769
  font: { color: yAxisFmt?.color ?? textColor },
@@ -658,7 +774,20 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
658
774
  ...(yAxisFmt?.fontFamily ? { family: yAxisFmt.fontFamily } : {}),
659
775
  ...(yAxisFmt?.fontSize ? { size: yAxisFmt.fontSize } : {}),
660
776
  },
661
- },
777
+ }, spec.valueAxisOptions),
778
+ // Secondary value axis on the right for the series that opt in
779
+ // (`useSecondaryAxis`), as the category layout has (#1067).
780
+ ...(hasBubbleSecondaryAxis
781
+ ? {
782
+ yaxis2: applyAxisOverridesPlotly({
783
+ overlaying: "y",
784
+ side: "right",
785
+ showgrid: false,
786
+ automargin: true,
787
+ tickfont: { color: yAxisFmt?.color ?? textColor },
788
+ }, spec.secondaryValueAxisOptions),
789
+ }
790
+ : {}),
662
791
  legend: {
663
792
  font: {
664
793
  color: legendFmt?.color ?? textColor,
@@ -669,7 +798,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
669
798
  paper_bgcolor: bgColor,
670
799
  plot_bgcolor: bgColor,
671
800
  showlegend: preparedSeries.length > 1,
672
- margin: { l: 60, r: 20, t: 50, b: 50 },
801
+ margin: { l: 60, r: hasBubbleSecondaryAxis ? 60 : 20, t: 50, b: 50 },
673
802
  };
674
803
  setPlotData(traces);
675
804
  setLayout(bubbleLayout);
@@ -691,14 +820,40 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
691
820
  const seriesName = hierarchyData.labels.length === 1
692
821
  ? hierarchyData.labels[0] ?? ""
693
822
  : explicitChartTitle ?? headerTitle ?? "";
823
+ // One solid colour per top-level category, inherited by its branch
824
+ // (#1072); nodes reference each other by id so a leaf name may
825
+ // repeat under different parents.
826
+ const hierarchyPalette = getChartPalette(spec, [
827
+ theme.themeColors.accent1,
828
+ theme.themeColors.accent2,
829
+ theme.themeColors.accent3,
830
+ theme.themeColors.accent4,
831
+ theme.themeColors.accent5,
832
+ theme.themeColors.accent6,
833
+ ]);
834
+ const nodeColors = hierarchyNodeColors(hierarchyData, hierarchyPalette);
835
+ const isSunburst = spec.chartType === "sunburst";
836
+ // Excel's sunburst leaves the centre empty — every level, the first
837
+ // included, is a ring (#1073). Plotly draws the top level as a ring
838
+ // only around a root node, so an invisible one holds the centre.
839
+ const SUNBURST_ROOT_ID = "__rnc_sunburst_root__";
840
+ const rootTotal = hierarchyData.values.reduce((sum, value, i) => (hierarchyData.parents[i] === "" ? sum + value : sum), 0);
694
841
  const hierarchyTrace = {
695
842
  type: chartType,
696
- labels: hierarchyData.labels,
697
- parents: hierarchyData.parents,
698
- values: hierarchyData.values,
843
+ labels: isSunburst ? ["", ...hierarchyData.labels] : hierarchyData.labels,
844
+ ids: isSunburst ? [SUNBURST_ROOT_ID, ...hierarchyData.ids] : hierarchyData.ids,
845
+ parents: isSunburst
846
+ ? ["", ...hierarchyData.parents.map((parent) => parent || SUNBURST_ROOT_ID)]
847
+ : hierarchyData.parents,
848
+ values: isSunburst ? [rootTotal, ...hierarchyData.values] : hierarchyData.values,
849
+ marker: {
850
+ colors: isSunburst ? ["rgba(0,0,0,0)", ...nodeColors] : nodeColors,
851
+ ...(isSunburst ? { line: { width: 1 } } : {}),
852
+ },
699
853
  name: seriesName,
700
854
  textinfo: "label+value",
701
855
  branchvalues: "total",
856
+ ...(isSunburst ? {} : { textposition: "bottom left" }),
702
857
  };
703
858
  const layoutForHierarchy = {
704
859
  title: {
@@ -882,7 +1037,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
882
1037
  }
883
1038
  // buildRadarIndicators keeps category/value alignment consistent
884
1039
  // with the echarts renderer (and validates we have axes).
885
- const indicators = buildRadarIndicators(categories, radarInputs);
1040
+ // One value scale for every axis (Excel), from the spec's value-axis
1041
+ // min / max when set (#1066).
1042
+ const radarAxisOptions = spec.valueAxisOptions;
1043
+ const indicators = buildRadarIndicators(categories, radarInputs, radarAxisOptions);
886
1044
  for (let i = 0; i < radarInputs.length; i++) {
887
1045
  const input = radarInputs[i];
888
1046
  const seriesColor = spec.series?.[i]?.color ?? undefined;
@@ -898,7 +1056,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
898
1056
  ...(seriesColor ? { line: { color: seriesColor } } : {}),
899
1057
  });
900
1058
  }
901
- const maxR = indicators.reduce((m, ind) => Math.max(m, ind.max), 0);
1059
+ const radarScale = indicators[0] ?? { min: 0, max: 1 };
902
1060
  setPlotData(radarTraces);
903
1061
  setLayout({
904
1062
  title: { text: plotlyTitleText(title, spec.titleTextFormat) },
@@ -908,8 +1066,8 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
908
1066
  showlegend: radarTraces.length > 1,
909
1067
  polar: {
910
1068
  radialaxis: {
911
- visible: true,
912
- range: [0, maxR > 0 ? maxR : 1],
1069
+ visible: radarAxisOptions?.visible !== false,
1070
+ range: [radarScale.min, radarScale.max],
913
1071
  },
914
1072
  },
915
1073
  margin: { l: 50, r: 50, t: 50, b: 50 },
@@ -1185,7 +1343,8 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1185
1343
  // by category index, falling back to the series color for points
1186
1344
  // without an override.
1187
1345
  const isPie = chartTypeForStyling === "pie";
1188
- if ((isBarOrColumn || isPie) &&
1346
+ const isFunnel = chartTypeForStyling === "funnel";
1347
+ if ((isBarOrColumn || isPie || isFunnel) &&
1189
1348
  Array.isArray(options?.dataPointColors) &&
1190
1349
  options?.dataPointColors.length) {
1191
1350
  const overrides = new Map();
@@ -1440,9 +1599,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1440
1599
  : {}),
1441
1600
  };
1442
1601
  if (effectiveChartType === "histogram") {
1443
- const rawValues = "values" in seriesData
1444
- ? seriesData.values
1445
- : seriesData;
1602
+ const rawValues = seriesValuesOf(seriesData);
1446
1603
  const binCount = spec.binCount;
1447
1604
  if (isPareto) {
1448
1605
  // Plotly's native histogram trace bins in axis order and
@@ -1492,9 +1649,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1492
1649
  }
1493
1650
  }
1494
1651
  else if (effectiveChartType === "boxplot") {
1495
- const rawValues = "values" in seriesData
1496
- ? seriesData.values
1497
- : seriesData;
1652
+ const rawValues = seriesValuesOf(seriesData);
1498
1653
  const quartileMethod = spec.quartileMethod;
1499
1654
  const showOutliers = spec.showOutliers;
1500
1655
  const showMeanMarker = spec.showMeanMarker === true;
@@ -1561,37 +1716,51 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1561
1716
  traces.push(trace);
1562
1717
  }
1563
1718
  else if (effectiveChartType === "waterfall") {
1564
- const rawValues = "values" in seriesData
1565
- ? seriesData.values
1566
- : seriesData;
1719
+ const rawValues = seriesValuesOf(seriesData);
1567
1720
  // Plotly's native waterfall draws connector lines itself and
1568
1721
  // renders "total" measures from the zero baseline with its
1569
1722
  // own distinct fill.
1570
1723
  const totalIndices = new Set(spec.totalIndices ?? []);
1724
+ // Increases take the series colour, decreases its negative colour
1725
+ // and totals the theme's third accent, as the echarts renderer and
1726
+ // Excel's default waterfall do (plotly's own defaults are green /
1727
+ // red / blue). Plotly paints a waterfall by measure, not per bar, so
1728
+ // `dataPointColors` cannot recolour one step here.
1729
+ const increaseColor = series.color ?? chartPaletteColorAt(themeColorPalette, currentSeriesIndex);
1571
1730
  const trace = {
1572
1731
  type: "waterfall",
1573
1732
  x: categoriesFor(series),
1574
1733
  y: rawValues.map((v) => (v ?? null)),
1575
1734
  measure: rawValues.map((_, i) => totalIndices.has(i) ? "total" : "relative"),
1576
1735
  name: dataLabel,
1736
+ increasing: { marker: { color: increaseColor } },
1737
+ decreasing: {
1738
+ marker: {
1739
+ color: series.negativeColor ?? theme.themeColors.accent2 ?? increaseColor,
1740
+ },
1741
+ },
1742
+ totals: { marker: { color: theme.themeColors.accent3 ?? increaseColor } },
1743
+ connector: {
1744
+ line: {
1745
+ color: isDarkMode ? "rgba(255,255,255,0.45)" : "#999999",
1746
+ width: 1,
1747
+ },
1748
+ },
1577
1749
  };
1578
- applyTraceColor(trace, series.color ?? undefined, {
1579
- noFill: series.noFill,
1580
- dataPointColors: series.dataPointColors,
1581
- seriesIndex: currentSeriesIndex,
1582
- effectiveChartType,
1583
- });
1584
1750
  traces.push(trace);
1585
1751
  }
1586
1752
  else if (effectiveChartType === "funnel") {
1587
- const rawValues = "values" in seriesData
1588
- ? seriesData.values
1589
- : seriesData;
1753
+ const rawValues = seriesValuesOf(seriesData);
1754
+ // Horizontal bars with the stage names on the category axis to
1755
+ // the left; the series colour (per-stage `<c:dPt>` overrides) and
1756
+ // the values inside the bars follow below (#1069).
1590
1757
  const trace = {
1591
1758
  type: "funnel",
1759
+ orientation: "h",
1592
1760
  y: categoriesFor(series),
1593
1761
  x: rawValues.map((v) => (v ?? null)),
1594
1762
  name: dataLabel,
1763
+ textinfo: "none",
1595
1764
  };
1596
1765
  applyTraceColor(trace, series.color ?? undefined, {
1597
1766
  noFill: series.noFill,
@@ -2044,8 +2213,26 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2044
2213
  : {}),
2045
2214
  };
2046
2215
  // Phase 5.1: apply data labels per trace, and set the doughnut hole
2047
- // when chartType === "doughnut".
2216
+ // when chartType === "doughnut". A series' own `dataLabels` replace the
2217
+ // chart-level ones for its trace (#1059).
2048
2218
  const dlOpts = spec.dataLabels;
2219
+ // Excel nests a doughnut's series as concentric rings, series 0
2220
+ // innermost (#1065): each pie trace is scaled into a centred sub-domain
2221
+ // whose radius is its ring's outer edge, with the hole at the inner one.
2222
+ const doughnutSeriesOrder = spec.chartType === "doughnut"
2223
+ ? traces
2224
+ .filter((t) => t?.type === "pie")
2225
+ .map((t) => t?.meta?.rncSeriesIndex)
2226
+ .filter((i) => typeof i === "number")
2227
+ .sort((a, b) => a - b)
2228
+ : [];
2229
+ const doughnutRingSet = spec.chartType === "doughnut"
2230
+ ? doughnutRings(clampDoughnutHole(spec.doughnutHoleSize), doughnutSeriesOrder.length)
2231
+ : [];
2232
+ const traceDataLabels = (trace) => {
2233
+ const specIndex = trace?.meta?.rncSeriesIndex;
2234
+ return resolveSeriesDataLabels(dlOpts, typeof specIndex === "number" ? spec.series?.[specIndex] : undefined);
2235
+ };
2049
2236
  const doughnutHole = spec.doughnutHoleSize;
2050
2237
  // Data labels are linked to each series' OWN source cells (Excel's
2051
2238
  // `<c:dLbls><c:numFmt sourceLinked="1">`); the axis format only steps
@@ -2064,8 +2251,21 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2064
2251
  const piePull = isOfPie ? undefined : explosionToPlotlyPull(resolveExplosion(spec));
2065
2252
  const formattedTraces = traces.map((trace) => {
2066
2253
  const pattern = traceNumberFormat(trace);
2067
- let next = applyDataLabelsToTracePlotly(trace, dlOpts, spec.chartType, pattern, locale);
2254
+ let next = applyDataLabelsToTracePlotly(trace, traceDataLabels(trace), spec.chartType, pattern, locale);
2255
+ if (next.type === "funnel") {
2256
+ // The formatted values ride on `text`; a funnel prints its own
2257
+ // `value` unless told to draw the text (or nothing).
2258
+ next = {
2259
+ ...next,
2260
+ textinfo: traceDataLabels(trace)?.show ? "text" : "none",
2261
+ };
2262
+ }
2068
2263
  if (next.type === "pie") {
2264
+ // Plotly's pie prints every slice's percentage by default; Excel
2265
+ // draws nothing until data labels are on (#1062).
2266
+ if (!traceDataLabels(trace)?.show) {
2267
+ next = { ...next, textinfo: "none" };
2268
+ }
2069
2269
  // Hover values format like the slice's source cell.
2070
2270
  if (pattern && !next.text) {
2071
2271
  const values = Array.isArray(next.values) ? next.values : [];
@@ -2108,12 +2308,17 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2108
2308
  // (doughnutHoleSize); clamp like the echarts renderer and default
2109
2309
  // to 0.5 when unset.
2110
2310
  if (spec.chartType === "doughnut" && next.type === "pie") {
2111
- next = {
2112
- ...next,
2113
- hole: typeof doughnutHole === "number"
2114
- ? Math.max(0, Math.min(0.9, doughnutHole))
2115
- : 0.5,
2116
- };
2311
+ const rank = doughnutSeriesOrder.indexOf(next.meta?.rncSeriesIndex);
2312
+ const ring = doughnutRingSet[rank] ?? doughnutRings(clampDoughnutHole(doughnutHole), 1)[0];
2313
+ // Plotly's `hole` is a fraction of the trace's own radius.
2314
+ next = { ...next, hole: ring.inner / ring.outer };
2315
+ if (doughnutRingSet.length > 1) {
2316
+ const half = ring.outer / 2;
2317
+ next = {
2318
+ ...next,
2319
+ domain: { x: [0.5 - half, 0.5 + half], y: [0.5 - half, 0.5 + half] },
2320
+ };
2321
+ }
2117
2322
  }
2118
2323
  return next;
2119
2324
  });
@@ -2245,5 +2450,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2245
2450
  React.createElement("div", { className: "flex-1 rnc-plotly-chart-container", onPointerDownCapture: handlePointerDownCapture },
2246
2451
  React.createElement(Plot, { data: plotData, layout: plotLayout, config: config, onClick: handlePlotClick, onInitialized: (_figure, graphDiv) => {
2247
2452
  graphDivRef.current = graphDiv;
2453
+ const plotDiv = graphDiv;
2454
+ // Returning false from these handlers keeps the clicked branch
2455
+ // from becoming the new root.
2456
+ plotDiv.on?.("plotly_treemapclick", () => drillDownRef.current);
2457
+ plotDiv.on?.("plotly_sunburstclick", () => drillDownRef.current);
2248
2458
  }, style: { width: "100%", height: "100%" } }))));
2249
2459
  };
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,KACvC,MAmEF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;;;;;;;;CAgBzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SA0BX,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,YAejE,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,qBAAqB,GAChC,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,KACvC,MAmEF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC;;;;;;;;;CAgBzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SA0BX,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,YAiBjE,CAAC"}
@@ -101,6 +101,8 @@ export const showXAxis = (type) => {
101
101
  type === "column" ||
102
102
  type === "bubble" ||
103
103
  type === "histogram" ||
104
+ // A waterfall's steps are categories (Opening, Sales, …) (#1070).
105
+ type === "waterfall" ||
104
106
  // Stock is line-derived on a category x-axis. Radar is polar and
105
107
  // does not use the cartesian x-axis.
106
108
  type === "stock") {
@@ -1 +1 @@
1
- {"root":["../../config.ts","../../echarts-gl.d.ts","../../geo-modules.d.ts","../../index.ts","../../plotly-dist.d.ts","../../types.ts","../../vitest.config.ts","../../atoms/editing-chart.atom.ts","../../atoms/pivot-chart.atom.ts","../../basic-chart/basic-chart.tsx","../../basic-chart/helpers.ts","../../basic-chart/index.tsx","../../components/dialogs/chart-editor-dialog.tsx","../../components/dialogs/pivot-chart-dialog.tsx","../../components/editors/chart-editor.tsx","../../components/editors/chart-range-input.tsx","../../hooks/use-chart-editor.ts","../../hooks/use-charts.ts","../../hooks/use-on-create-chart.ts","../../hooks/use-on-delete-chart.ts","../../hooks/use-on-move-chart-to-sheet.ts","../../hooks/use-on-move-chart.ts","../../hooks/use-on-resize-chart.ts","../../hooks/use-on-update-chart.ts","../../interface/create-chart.ts","../../interface/delete-chart.ts","../../interface/move-chart-to-sheet.ts","../../interface/move-chart.ts","../../interface/resize-chart.ts","../../interface/shift-chart-ranges.ts","../../interface/update-chart.ts","../../plotly/plot.ts","../../plotly/basic-chart/basic-chart.tsx","../../plotly/basic-chart/helpers.ts","../../plotly/basic-chart/index.tsx","../../utils/aggregation.ts","../../utils/axis-number-format.ts","../../utils/bar-layout.ts","../../utils/boxplot.ts","../../utils/bubble.ts","../../utils/category-levels.ts","../../utils/chart-image.ts","../../utils/chart-orientation.ts","../../utils/chart-spec-utils.ts","../../utils/chart-styles.ts","../../utils/echarts-gl-loader.ts","../../utils/empty-cells.ts","../../utils/error-bars.ts","../../utils/gradient.ts","../../utils/hierarchy.ts","../../utils/histogram.ts","../../utils/legend-layout.ts","../../utils/of-pie.ts","../../utils/overrides.ts","../../utils/pie-explosion.ts","../../utils/plotly-click.ts","../../utils/plotly-surface.ts","../../utils/plotly-title.ts","../../utils/radar.ts","../../utils/region-map-loader.ts","../../utils/region-map.ts","../../utils/series-click.ts","../../utils/stock.ts","../../utils/three-d.ts","../../utils/trendline.ts","../../utils/waterfall.ts","../../utils/webgl.ts"],"version":"5.8.3"}
1
+ {"root":["../../config.ts","../../echarts-gl.d.ts","../../geo-modules.d.ts","../../index.ts","../../plotly-dist.d.ts","../../types.ts","../../vitest.config.ts","../../atoms/editing-chart.atom.ts","../../atoms/pivot-chart.atom.ts","../../basic-chart/basic-chart.tsx","../../basic-chart/helpers.ts","../../basic-chart/index.tsx","../../components/dialogs/chart-editor-dialog.tsx","../../components/dialogs/pivot-chart-dialog.tsx","../../components/editors/chart-editor.tsx","../../components/editors/chart-range-input.tsx","../../hooks/use-chart-editor.ts","../../hooks/use-charts.ts","../../hooks/use-on-create-chart.ts","../../hooks/use-on-delete-chart.ts","../../hooks/use-on-move-chart-to-sheet.ts","../../hooks/use-on-move-chart.ts","../../hooks/use-on-resize-chart.ts","../../hooks/use-on-update-chart.ts","../../interface/create-chart.ts","../../interface/delete-chart.ts","../../interface/move-chart-to-sheet.ts","../../interface/move-chart.ts","../../interface/resize-chart.ts","../../interface/shift-chart-ranges.ts","../../interface/update-chart.ts","../../plotly/plot.ts","../../plotly/basic-chart/basic-chart.tsx","../../plotly/basic-chart/helpers.ts","../../plotly/basic-chart/index.tsx","../../utils/aggregation.ts","../../utils/axis-number-format.ts","../../utils/bar-layout.ts","../../utils/boxplot.ts","../../utils/bubble.ts","../../utils/category-levels.ts","../../utils/chart-image.ts","../../utils/chart-orientation.ts","../../utils/chart-spec-utils.ts","../../utils/chart-styles.ts","../../utils/doughnut.ts","../../utils/echarts-gl-loader.ts","../../utils/echarts-overlays.ts","../../utils/empty-cells.ts","../../utils/error-bars.ts","../../utils/gradient.ts","../../utils/hierarchy.ts","../../utils/histogram.ts","../../utils/legend-layout.ts","../../utils/of-pie.ts","../../utils/overrides.ts","../../utils/pie-explosion.ts","../../utils/plotly-click.ts","../../utils/plotly-surface.ts","../../utils/plotly-title.ts","../../utils/radar.ts","../../utils/region-map-loader.ts","../../utils/region-map.ts","../../utils/series-click.ts","../../utils/stock.ts","../../utils/three-d.ts","../../utils/trendline.ts","../../utils/waterfall.ts","../../utils/webgl.ts"],"version":"5.8.3"}