@rowsncolumns/charts 28.1.1 → 28.1.4

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 (45) hide show
  1. package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
  2. package/dist/cjs/basic-chart/basic-chart.js +138 -76
  3. package/dist/cjs/basic-chart/helpers.d.ts +9 -0
  4. package/dist/cjs/basic-chart/helpers.d.ts.map +1 -1
  5. package/dist/cjs/basic-chart/helpers.js +55 -1
  6. package/dist/cjs/components/editors/chart-editor.d.ts.map +1 -1
  7. package/dist/cjs/components/editors/chart-editor.js +45 -45
  8. package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  9. package/dist/cjs/plotly/basic-chart/basic-chart.js +218 -128
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/cjs/utils/axis-number-format.d.ts +14 -0
  12. package/dist/cjs/utils/axis-number-format.d.ts.map +1 -1
  13. package/dist/cjs/utils/axis-number-format.js +41 -1
  14. package/dist/cjs/utils/chart-styles.d.ts +5 -1
  15. package/dist/cjs/utils/chart-styles.d.ts.map +1 -1
  16. package/dist/cjs/utils/chart-styles.js +1 -1
  17. package/dist/cjs/utils/empty-cells.d.ts +9 -0
  18. package/dist/cjs/utils/empty-cells.d.ts.map +1 -1
  19. package/dist/cjs/utils/empty-cells.js +26 -1
  20. package/dist/cjs/utils/legend-layout.d.ts +168 -0
  21. package/dist/cjs/utils/legend-layout.d.ts.map +1 -0
  22. package/dist/cjs/utils/legend-layout.js +338 -0
  23. package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
  24. package/dist/esm/basic-chart/basic-chart.js +96 -34
  25. package/dist/esm/basic-chart/helpers.d.ts +9 -0
  26. package/dist/esm/basic-chart/helpers.d.ts.map +1 -1
  27. package/dist/esm/basic-chart/helpers.js +30 -0
  28. package/dist/esm/components/editors/chart-editor.d.ts.map +1 -1
  29. package/dist/esm/components/editors/chart-editor.js +2 -2
  30. package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  31. package/dist/esm/plotly/basic-chart/basic-chart.js +136 -6
  32. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  33. package/dist/esm/utils/axis-number-format.d.ts +14 -0
  34. package/dist/esm/utils/axis-number-format.d.ts.map +1 -1
  35. package/dist/esm/utils/axis-number-format.js +36 -0
  36. package/dist/esm/utils/chart-styles.d.ts +5 -1
  37. package/dist/esm/utils/chart-styles.d.ts.map +1 -1
  38. package/dist/esm/utils/chart-styles.js +1 -1
  39. package/dist/esm/utils/empty-cells.d.ts +9 -0
  40. package/dist/esm/utils/empty-cells.d.ts.map +1 -1
  41. package/dist/esm/utils/empty-cells.js +21 -0
  42. package/dist/esm/utils/legend-layout.d.ts +168 -0
  43. package/dist/esm/utils/legend-layout.d.ts.map +1 -0
  44. package/dist/esm/utils/legend-layout.js +299 -0
  45. package/package.json +7 -7
@@ -482,13 +482,13 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
482
482
  }))),
483
483
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
484
484
  React.createElement("label", { className: "font-system text-sm" }, "Quick layout"),
485
- React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent w-32", value: "", "aria-label": "Quick layout", onChange: (e) => {
485
+ React.createElement("select", { className: "border border-rnc-border rounded-sm px-1 py-1 text-xs bg-transparent w-32", value: values.spec.quickLayout ?? "", "aria-label": "Quick layout", onChange: (e) => {
486
486
  const layout = CHART_QUICK_LAYOUTS.find((entry) => String(entry.id) === e.target.value);
487
487
  if (layout) {
488
488
  setFieldValue("spec", applyChartQuickLayout(values.spec, layout));
489
489
  }
490
490
  } },
491
- React.createElement("option", { value: "" }, "Choose a layout\u2026"),
491
+ React.createElement("option", { value: "", disabled: true }, "Choose a layout\u2026"),
492
492
  CHART_QUICK_LAYOUTS.map((layout) => (React.createElement("option", { key: layout.id, value: layout.id }, layout.label))))),
493
493
  React.createElement("div", null,
494
494
  React.createElement(Button, { type: "button", size: "xs", onClick: () => setFieldValue("spec", resetChartToMatchStyle(values.spec)) }, "Reset to match style")),
@@ -1 +1 @@
1
- {"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAGtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA4VxD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AA0FnD,eAAO,MAAM,UAAU,GAAI,wOAiBxB,eAAe,sBAmrEjB,CAAC"}
1
+ {"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAGtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA+WxD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AA0FnD,eAAO,MAAM,UAAU,GAAI,wOAiBxB,eAAe,sBAg0EjB,CAAC"}
@@ -6,17 +6,18 @@ import { debug, isAFormula, isNil, ssfFormat } from "@rowsncolumns/utils";
6
6
  import React, { useCallback, useMemo, useRef, useState } from "react";
7
7
  import Plot from "react-plotly.js";
8
8
  import { aggregateSeriesByDomain, getDomainValuesForAxis, sanitizeDomainValues, shouldAggregateSeries, } from "../../utils/aggregation";
9
- import { formatAxisValue, numberFormatToPlotlyTicks, resolveSeriesNumberFormats, resolveValueAxisNumberFormat, } from "../../utils/axis-number-format";
9
+ import { formatAxisValue, numberFormatToPlotlyTicks, resolveSeriesNumberFormats, resolveValueAxisNumberFormat, valueAxisNameGap, } from "../../utils/axis-number-format";
10
10
  import { computeBoxplotSummary } from "../../utils/boxplot";
11
11
  import { createBubbleSizeScale, prepareBubbleSeriesData, toLabelString, } from "../../utils/bubble";
12
12
  import { canSwitchRowColumn, cellLabelGetterFromRangeReader, switchRowColumn, } from "../../utils/chart-orientation";
13
13
  import { dataUrlToBlob } from "../../utils/chart-image";
14
14
  import { getChartPalette } from "../../utils/chart-styles";
15
15
  import { coerceCalculatedLabel } from "../../utils/chart-spec-utils";
16
- import { applyEmptyCellsPolicy, connectsAcrossEmptyCells, isLineLikeChartType, } from "../../utils/empty-cells";
16
+ import { applyEmptyCellsPolicy, connectsAcrossEmptyCells, isLineLikeChartType, isolatedPointIndexes, } from "../../utils/empty-cells";
17
17
  import { computeErrorBars, resolveCustomErrorBarValues, } from "../../utils/error-bars";
18
18
  import { plotlyMulticategoryX, resolveCategoryLevels, } from "../../utils/category-levels";
19
19
  import { gradientToPlotly } from "../../utils/gradient";
20
+ import { BAND_GAP, legendLabelsFrom, measureTextWidth, reservePlotlyLegendBand, } from "../../utils/legend-layout";
20
21
  import { plotlyTitleText } from "../../utils/plotly-title";
21
22
  import { explosionToPlotlyPull, resolveExplosion, } from "../../utils/pie-explosion";
22
23
  import { resolvePlotlyClickSelection, wasDragged, } from "../../utils/plotly-click";
@@ -115,6 +116,16 @@ const applyLegendOverridesPlotly = (legend, opts) => {
115
116
  next.font = applyTextFormatToFont(next.font, opts.textFormat);
116
117
  return { legend: next, showlegend: true };
117
118
  };
119
+ /**
120
+ * `title.standoff` for a VALUE axis. Without it plotly places the axis title
121
+ * a fixed ~34px from the axis line and only nudges it off the tick labels
122
+ * after measuring them — a currency axis ("$2,000,000.00") is wider than
123
+ * that, and on the first paint (before `automargin` has grown the margin)
124
+ * the title lands on top of its own tick labels. With a standoff plotly
125
+ * positions the title at `tick-label depth + standoff`, so it always clears
126
+ * the labels, and `automargin` grows the margin to fit (#37265 A8).
127
+ */
128
+ const VALUE_AXIS_TITLE_STANDOFF = 12;
118
129
  /**
119
130
  * `numberFormatPattern` is the axis' RESOLVED format (explicit pattern or
120
131
  * the linked source-cell format). Plotly formats ticks AND hover values
@@ -1006,7 +1017,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1006
1017
  // to 100 (plotly `groupnorm`); Excel's "Line with Markers"
1007
1018
  // (spec.showMarkers) or a series' own marker switches the line trace
1008
1019
  // to lines+markers, and a series marker of "none" hides them.
1009
- const lineTraceVariant = (series, effectiveChartType) => {
1020
+ const lineTraceVariant = (series, effectiveChartType, values) => {
1010
1021
  if (effectiveChartType !== "line" && effectiveChartType !== "area") {
1011
1022
  return {};
1012
1023
  }
@@ -1027,6 +1038,22 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1027
1038
  size: marker?.size ?? 6,
1028
1039
  };
1029
1040
  }
1041
+ else {
1042
+ // A marker-less line draws nothing for a point with a gap on both sides (a data
1043
+ // row followed by a blank row — the first row included), so that row vanishes from
1044
+ // the chart. Excel plots an isolated marker there: keep the line and give only those
1045
+ // points a marker (plotly's `marker.size` / `marker.opacity` are per-point arrays;
1046
+ // a 0-size, 0-opacity marker paints nothing on the connected points).
1047
+ const isolated = new Set(isolatedPointIndexes(values, connectsAcrossEmptyCells(spec, true)));
1048
+ if (isolated.size) {
1049
+ extra.mode = "lines+markers";
1050
+ extra.marker = {
1051
+ symbol: "circle",
1052
+ size: values.map((_, i) => (isolated.has(i) ? 6 : 0)),
1053
+ opacity: values.map((_, i) => (isolated.has(i) ? 1 : 0)),
1054
+ };
1055
+ }
1056
+ }
1030
1057
  }
1031
1058
  return extra;
1032
1059
  };
@@ -1714,7 +1741,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1714
1741
  name: dataLabel,
1715
1742
  stackgroup: isStacked ? "one" : undefined,
1716
1743
  ...seriesTraceStyle,
1717
- ...lineTraceVariant(series, effectiveChartType),
1744
+ ...lineTraceVariant(series, effectiveChartType, seriesData.values),
1718
1745
  };
1719
1746
  // Ties the trace back to spec.series for click selection
1720
1747
  trace.meta = {
@@ -1744,7 +1771,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1744
1771
  name: dataLabel,
1745
1772
  stackgroup: isStacked ? "one" : undefined,
1746
1773
  ...seriesTraceStyle,
1747
- ...lineTraceVariant(series, effectiveChartType),
1774
+ ...lineTraceVariant(series, effectiveChartType, numericValues),
1748
1775
  };
1749
1776
  // Ties the trace back to spec.series for click selection
1750
1777
  trace.meta = {
@@ -1786,6 +1813,65 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1786
1813
  getFormattingFromRange,
1787
1814
  hiddenDimensionStrategy: spec.hiddenDimensionStrategy,
1788
1815
  });
1816
+ // Legend position / visibility from the spec, then a band reserved
1817
+ // for it outside the axis titles and tick labels so it never paints
1818
+ // over them — Excel shrinks the plot area instead (#986).
1819
+ const { legend: legendOverrides, showlegend } = applyLegendOverridesPlotly({ font: { color: textColor } }, spec.legend);
1820
+ const axisFontSize = 12;
1821
+ const primaryValues = traces.flatMap((trace) => {
1822
+ const values = isHorizontalBarChart ? trace?.x : trace?.y;
1823
+ return trace?.[valueAxisKey] === secondaryValueAxisRef || !Array.isArray(values)
1824
+ ? []
1825
+ : values;
1826
+ });
1827
+ // Tick-label depth of the left axis: value labels formatted from the
1828
+ // plotted extremes on a vertical chart, the widest category on a
1829
+ // horizontal bar; plus the rotated axis title band when present.
1830
+ const leftAxisLabelDepth = isHorizontalBarChart
1831
+ ? Math.ceil(Math.max(0, ...(domainData ?? []).map((label) => measureTextWidth(String(label ?? ""), axisFontSize)))) + 8
1832
+ : valueAxisNameGap({
1833
+ values: primaryValues,
1834
+ pattern: primaryAxisNumberFormat,
1835
+ fontSize: axisFontSize,
1836
+ minimum: 0,
1837
+ }) -
1838
+ axisFontSize -
1839
+ 6;
1840
+ const leftAxisDepth = leftAxisLabelDepth +
1841
+ (spec.verticalAxisTitle
1842
+ ? VALUE_AXIS_TITLE_STANDOFF + Math.ceil(axisFontSize * 1.3)
1843
+ : 0);
1844
+ const rightAxisDepth = hasSecondaryAxis && !isHorizontalBarChart
1845
+ ? valueAxisNameGap({
1846
+ values: traces.flatMap((trace) => trace?.[valueAxisKey] === secondaryValueAxisRef && Array.isArray(trace?.y)
1847
+ ? trace.y
1848
+ : []),
1849
+ pattern: secondaryAxisNumberFormat,
1850
+ fontSize: axisFontSize,
1851
+ minimum: 0,
1852
+ }) -
1853
+ axisFontSize -
1854
+ 6
1855
+ : 0;
1856
+ const bottomAxisTitleStandoff = isHorizontalBarChart
1857
+ ? VALUE_AXIS_TITLE_STANDOFF
1858
+ : BAND_GAP;
1859
+ const legendBand = reservePlotlyLegendBand({
1860
+ position: spec.legend?.position,
1861
+ visible: showlegend !== false,
1862
+ labels: legendLabelsFrom(undefined, traces.map((trace) => trace?.name)),
1863
+ legendFontSize: spec.legend?.textFormat?.fontSize ?? axisFontSize,
1864
+ width: Math.max(0, (width ?? 0) - 2),
1865
+ height: Math.max(0, (height ?? 0) - 2),
1866
+ hasTitle: Boolean(title),
1867
+ titleFontSize: authoredTitleFmt?.fontSize ?? 16,
1868
+ hasBottomAxisTitle: Boolean(spec.horizontalAxisTitle),
1869
+ hasLeftAxisTitle: Boolean(spec.verticalAxisTitle),
1870
+ leftAxisDepth,
1871
+ rightAxisDepth,
1872
+ bottomAxisTitleStandoff,
1873
+ axisFontSize,
1874
+ });
1789
1875
  const newLayout = {
1790
1876
  title: {
1791
1877
  text: plotlyTitleText(title, spec.titleTextFormat),
@@ -1796,7 +1882,32 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1796
1882
  }, authoredTitleFmt),
1797
1883
  x: 0.02, // Small left margin (0.02 = 2% from left edge)
1798
1884
  xanchor: "left",
1885
+ ...legendBand.title,
1799
1886
  },
1887
+ showlegend: showlegend === undefined ? true : showlegend,
1888
+ legend: { ...legendOverrides, ...(legendBand.legend ?? {}) },
1889
+ margin: legendBand.margin,
1890
+ autosize: true,
1891
+ // Bar/column stacking. Plotly's `barmode: "stack"` stacks bars
1892
+ // at the same X (or Y for horizontal). `barnorm: "percent"`
1893
+ // normalises each category's stack to 100% — Excel's
1894
+ // `percentStacked` grouping. Default is grouped (clustered).
1895
+ ...((spec.chartType === "bar" || spec.chartType === "column") &&
1896
+ spec.stackedType
1897
+ ? {
1898
+ barmode: spec.stackedType === "STACKED" ||
1899
+ spec.stackedType === "PERCENT_STACKED"
1900
+ ? "stack"
1901
+ : "group",
1902
+ ...(isPercentStackedBars ? { barnorm: "percent" } : {}),
1903
+ }
1904
+ : {}),
1905
+ // gapWidth — Excel stores 0..500 as a percentage of bar width.
1906
+ // Plotly's `bargap` is a fraction of the slot (0..1). Approx:
1907
+ // gapWidth=150 (default) → ~0.4 bargap.
1908
+ ...(typeof spec.gapWidth === "number"
1909
+ ? { bargap: Math.max(0, Math.min(0.95, spec.gapWidth / 400)) }
1910
+ : {}),
1800
1911
  font: {
1801
1912
  family: "Untitled Sans, apple-system, sans-serif",
1802
1913
  size: 12,
@@ -1808,6 +1919,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1808
1919
  title: {
1809
1920
  text: spec.horizontalAxisTitle ?? "",
1810
1921
  font: { color: textColor },
1922
+ standoff: bottomAxisTitleStandoff,
1811
1923
  },
1812
1924
  automargin: true,
1813
1925
  showgrid: true,
@@ -1827,6 +1939,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1827
1939
  title: {
1828
1940
  text: spec.verticalAxisTitle ?? "",
1829
1941
  font: { color: textColor },
1942
+ ...(isHorizontalBarChart
1943
+ ? {}
1944
+ : { standoff: VALUE_AXIS_TITLE_STANDOFF }),
1830
1945
  },
1831
1946
  showgrid: !isHorizontalBarChart,
1832
1947
  gridcolor: gridColor,
@@ -1845,7 +1960,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1845
1960
  ...(hasSecondaryAxis
1846
1961
  ? {
1847
1962
  [isHorizontalBarChart ? "xaxis2" : "yaxis2"]: applyAxisOverridesPlotly({
1848
- title: { font: { color: textColor } },
1963
+ title: {
1964
+ font: { color: textColor },
1965
+ standoff: VALUE_AXIS_TITLE_STANDOFF,
1966
+ },
1849
1967
  overlaying: isHorizontalBarChart ? "x" : "y",
1850
1968
  side: isHorizontalBarChart ? "top" : "right",
1851
1969
  showgrid: false,
@@ -1875,6 +1993,18 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1875
1993
  },
1876
1994
  }
1877
1995
  : {}),
1996
+ // Of-pie composite — connector-line shapes plus (bar-of-pie) the
1997
+ // hidden secondary axes and stacked bar mode. Spread last: an
1998
+ // of-pie is a single pie series, so its `xaxis2`/`yaxis2` own the
1999
+ // secondary-axis slots.
2000
+ ...(ofPieExtras
2001
+ ? {
2002
+ shapes: ofPieExtras.shapes,
2003
+ ...(ofPieExtras.barmode ? { barmode: ofPieExtras.barmode } : {}),
2004
+ ...(ofPieExtras.xaxis2 ? { xaxis2: ofPieExtras.xaxis2 } : {}),
2005
+ ...(ofPieExtras.yaxis2 ? { yaxis2: ofPieExtras.yaxis2 } : {}),
2006
+ }
2007
+ : {}),
1878
2008
  };
1879
2009
  // Phase 5.1: apply data labels per trace, and set the doughnut hole
1880
2010
  // when chartType === "doughnut".
@@ -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/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/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/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"}
@@ -65,5 +65,19 @@ export type PlotlyTickFormat = {
65
65
  * section is used — plotly applies one format to every tick.
66
66
  */
67
67
  export declare const numberFormatToPlotlyTicks: (pattern: string | undefined) => PlotlyTickFormat | undefined;
68
+ /**
69
+ * echarts `nameGap` for a value axis whose title sits in the middle, rotated
70
+ * beside the tick labels. The gap is the distance from the axis line to the
71
+ * title's centre, so it must clear the tick labels' full width: the labels
72
+ * ("$2,000.00", "1,500,000") are formatted from the plotted extremes and
73
+ * measured; the fixed default of 50px only fits labels up to ~4 digits and
74
+ * lets a currency axis title paint over its own tick labels.
75
+ */
76
+ export declare const valueAxisNameGap: ({ values, pattern, fontSize, minimum, }: {
77
+ values: readonly unknown[];
78
+ pattern: string | undefined;
79
+ fontSize?: number;
80
+ minimum?: number;
81
+ }) => number;
68
82
  export {};
69
83
  //# sourceMappingURL=axis-number-format.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"axis-number-format.d.ts","sourceRoot":"","sources":["../../../utils/axis-number-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,yBAAyB,EACzB,KAAK,EACL,UAAU,EACX,MAAM,2BAA2B,CAAC;AAWnC,KAAK,iBAAiB,GAAG,CACvB,UAAU,EAAE,UAAU,EACtB,uBAAuB,CAAC,EAAE,yBAAyB,KAChD,OAAO,EAAE,CAAC;AAMf;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,YAGjC,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAS,UAAU,EAAE,GAAG,SAAS,EACjC,wBAAwB,iBAAiB,GAAG,SAAS,EACrD,0BAA0B,yBAAyB,GAAG,IAAI,KACzD,MAAM,GAAG,SAWX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,GAAI,2EAK1C;IACD,WAAW,EAAE,KAAK,CAAC;QAAE,mBAAmB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC;IACxE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;IACrC,sBAAsB,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtD,uBAAuB,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAC;CAC5D,KAAG,MAAM,GAAG,SAQZ,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,GAAG,SAAS,EAC3B,OAAO,OAAO,KACb,MAIF,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,GAAI,SAAS,MAAM,GAAG,SAAS,cAC/C,OAAO,wBAAgD,CAAC;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,MAAM,GAAG,SAAS,cAEnD,OAAO,mCAIN,CAAC;AAEhB;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GACrC,QAAQ,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,EAChD,wBAAwB,iBAAiB,GAAG,SAAS,EACrD,0BAA0B,yBAAyB,GAAG,IAAI,KACzD,CAAC,MAAM,GAAG,SAAS,CAAC,EAOpB,CAAC;AAEJ,yFAAyF;AACzF,eAAO,MAAM,wBAAwB,GAClC,SAAS,MAAM,GAAG,SAAS,MAC3B,QAAQ;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,WAGvD,CAAC;AAET,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAS,MAAM,GAAG,SAAS,KAC1B,gBAAgB,GAAG,SA4DrB,CAAC"}
1
+ {"version":3,"file":"axis-number-format.d.ts","sourceRoot":"","sources":["../../../utils/axis-number-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,yBAAyB,EACzB,KAAK,EACL,UAAU,EACX,MAAM,2BAA2B,CAAC;AAWnC,KAAK,iBAAiB,GAAG,CACvB,UAAU,EAAE,UAAU,EACtB,uBAAuB,CAAC,EAAE,yBAAyB,KAChD,OAAO,EAAE,CAAC;AAMf;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,YAGjC,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAS,UAAU,EAAE,GAAG,SAAS,EACjC,wBAAwB,iBAAiB,GAAG,SAAS,EACrD,0BAA0B,yBAAyB,GAAG,IAAI,KACzD,MAAM,GAAG,SAWX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,GAAI,2EAK1C;IACD,WAAW,EAAE,KAAK,CAAC;QAAE,mBAAmB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC;IACxE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;IACrC,sBAAsB,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtD,uBAAuB,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAC;CAC5D,KAAG,MAAM,GAAG,SAQZ,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,GAAG,SAAS,EAC3B,OAAO,OAAO,KACb,MAIF,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,GAAI,SAAS,MAAM,GAAG,SAAS,cAC/C,OAAO,wBAAgD,CAAC;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,MAAM,GAAG,SAAS,cAEnD,OAAO,mCAIN,CAAC;AAEhB;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GACrC,QAAQ,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,EAChD,wBAAwB,iBAAiB,GAAG,SAAS,EACrD,0BAA0B,yBAAyB,GAAG,IAAI,KACzD,CAAC,MAAM,GAAG,SAAS,CAAC,EAOpB,CAAC;AAEJ,yFAAyF;AACzF,eAAO,MAAM,wBAAwB,GAClC,SAAS,MAAM,GAAG,SAAS,MAC3B,QAAQ;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,WAGvD,CAAC;AAET,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAS,MAAM,GAAG,SAAS,KAC1B,gBAAgB,GAAG,SA4DrB,CAAC;AA2BF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,yCAK9B;IACD,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,KAAG,MAgBH,CAAC"}
@@ -168,3 +168,39 @@ const firstSection = (pattern) => {
168
168
  }
169
169
  return pattern;
170
170
  };
171
+ /** Approximate rendered width of an axis tick label in px at `fontSize` (canvas when available). */
172
+ const measureLabelWidth = (text, fontSize) => {
173
+ if (typeof document !== "undefined") {
174
+ const ctx = document.createElement("canvas").getContext("2d");
175
+ if (ctx) {
176
+ ctx.font = `${fontSize}px sans-serif`;
177
+ const width = ctx.measureText(text).width;
178
+ if (Number.isFinite(width) && width > 0)
179
+ return width;
180
+ }
181
+ }
182
+ // Headless fallback: ~0.6em per character (digits / punctuation heavy).
183
+ return text.length * fontSize * 0.6;
184
+ };
185
+ /**
186
+ * echarts `nameGap` for a value axis whose title sits in the middle, rotated
187
+ * beside the tick labels. The gap is the distance from the axis line to the
188
+ * title's centre, so it must clear the tick labels' full width: the labels
189
+ * ("$2,000.00", "1,500,000") are formatted from the plotted extremes and
190
+ * measured; the fixed default of 50px only fits labels up to ~4 digits and
191
+ * lets a currency axis title paint over its own tick labels.
192
+ */
193
+ export const valueAxisNameGap = ({ values, pattern, fontSize = 12, minimum = 50, }) => {
194
+ const numbers = values.filter((v) => typeof v === "number" && Number.isFinite(v));
195
+ if (!numbers.length)
196
+ return minimum;
197
+ const min = Math.min(...numbers, 0);
198
+ const max = Math.max(...numbers, 0);
199
+ // echarts' default axis label puts thousands separators on plain numbers.
200
+ const defaultLabel = (v) => v.toLocaleString("en-US");
201
+ const labels = [min, max].map((v) => pattern ? formatAxisValue(pattern, v) : defaultLabel(v));
202
+ const widest = Math.max(...labels.map((l) => measureLabelWidth(l, fontSize)));
203
+ // 8px tick-label margin + the rotated title's own line box + breathing room.
204
+ const AXIS_LABEL_MARGIN = 8;
205
+ return Math.max(minimum, Math.ceil(widest + AXIS_LABEL_MARGIN + fontSize + 6));
206
+ };
@@ -60,7 +60,11 @@ export type ChartStylePreset = {
60
60
  */
61
61
  export declare const CHART_STYLES: readonly ChartStylePreset[];
62
62
  export declare const applyChartStyle: (spec: ChartSpec, style: ChartStylePreset) => ChartSpec;
63
- /** A Quick Layouts gallery entry — where the title, legend, labels and axis titles go. */
63
+ /**
64
+ * A Quick Layouts gallery entry — where the title, legend, labels and axis
65
+ * titles go. `applyChartQuickLayout` stamps the entry's `id` on
66
+ * `spec.quickLayout` so the editor's control shows the applied layout.
67
+ */
64
68
  export type ChartQuickLayout = {
65
69
  id: number;
66
70
  label: string;
@@ -1 +1 @@
1
- {"version":3,"file":"chart-styles.d.ts","sourceRoot":"","sources":["../../../utils/chart-styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EAEV,MAAM,2BAA2B,CAAC;AAEnC;;;;;;GAMG;AAEH,wFAAwF;AACxF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,GAAG,eAAe,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,iBAAiB,EA4C5D,CAAC;AAsBF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,SAAS,EACf,SAAS,iBAAiB,KACzB,SAGD,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM,SAAS,KAAG,SAC9B,CAAC;AAE5B;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,MAAM,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,SAAS,EAC5C,UAAU,SAAS,MAAM,EAAE,KAC1B,MAAM,EAGU,CAAC;AAEpB;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChD,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;CACnC,CAAC;AAmDF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,gBAAgB,EAwInD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,MAAM,SAAS,EACf,OAAO,gBAAgB,KACtB,SAA8B,CAAC;AAElC,0FAA0F;AAC1F,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;CACnC,CAAC;AA0BF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,gBAAgB,EAqE1D,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,MAAM,SAAS,EACf,QAAQ,gBAAgB,KACvB,SAA+B,CAAC"}
1
+ {"version":3,"file":"chart-styles.d.ts","sourceRoot":"","sources":["../../../utils/chart-styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EAEV,MAAM,2BAA2B,CAAC;AAEnC;;;;;;GAMG;AAEH,wFAAwF;AACxF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,GAAG,eAAe,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,iBAAiB,EA4C5D,CAAC;AAsBF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,SAAS,EACf,SAAS,iBAAiB,KACzB,SAGD,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM,SAAS,KAAG,SAC9B,CAAC;AAE5B;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,MAAM,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,SAAS,EAC5C,UAAU,SAAS,MAAM,EAAE,KAC1B,MAAM,EAGU,CAAC;AAEpB;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChD,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;CACnC,CAAC;AAmDF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,gBAAgB,EAwInD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,MAAM,SAAS,EACf,OAAO,gBAAgB,KACtB,SAA8B,CAAC;AAElC;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;CACnC,CAAC;AA0BF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,gBAAgB,EAqE1D,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,MAAM,SAAS,EACf,QAAQ,gBAAgB,KACvB,SAAgE,CAAC"}
@@ -354,4 +354,4 @@ export const CHART_QUICK_LAYOUTS = [
354
354
  }),
355
355
  },
356
356
  ];
357
- export const applyChartQuickLayout = (spec, layout) => layout.apply(spec);
357
+ export const applyChartQuickLayout = (spec, layout) => ({ ...layout.apply(spec), quickLayout: layout.id });
@@ -26,5 +26,14 @@ export declare const connectsAcrossEmptyCells: (spec: EmptyCellsSpec, lineLike:
26
26
  * pre-2016 #N/A behaviour), and any other series leaves a gap.
27
27
  */
28
28
  export declare const applyEmptyCellsPolicy: (values: readonly (number | null | undefined)[], rawValues: readonly unknown[], spec: EmptyCellsSpec, lineLike: boolean) => (number | null)[];
29
+ /**
30
+ * Indexes of the plotted points that have no plottable neighbour to draw a
31
+ * line segment to — a value between two gaps (or at an edge next to a gap).
32
+ * A line-only series (no markers) draws nothing for them, so a row of data
33
+ * followed by a blank row vanishes from the chart; Excel still plots an
34
+ * isolated marker there. When the series connects across gaps every point
35
+ * links to the next plotted one, so only a lone single point is isolated.
36
+ */
37
+ export declare const isolatedPointIndexes: (values: readonly (number | null | undefined)[], connectAcrossGaps?: boolean) => number[];
29
38
  export {};
30
39
  //# sourceMappingURL=empty-cells.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"empty-cells.d.ts","sourceRoot":"","sources":["../../../utils/empty-cells.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,KAAK,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;AAEvC,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;AAEpE,KAAK,cAAc,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,GAAG,eAAe,CAAC,CAAC;AAetE,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,OACuB,CAAC;AAE3E,gCAAgC;AAChC,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,gBAChC,CAAC;AAE5B,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,GAC9B,WAAW,SAAS,CAAC,WAAW,CAAC,GAAG,IAAI,GAAG,SAAS,KACnD,OAAuD,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,MAAM,cAAc,EACpB,UAAU,OAAO,KAChB,OAA8D,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EAC9C,WAAW,SAAS,OAAO,EAAE,EAC7B,MAAM,cAAc,EACpB,UAAU,OAAO,KAChB,CAAC,MAAM,GAAG,IAAI,CAAC,EA4BjB,CAAC"}
1
+ {"version":3,"file":"empty-cells.d.ts","sourceRoot":"","sources":["../../../utils/empty-cells.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,KAAK,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;AAEvC,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;AAEpE,KAAK,cAAc,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,GAAG,eAAe,CAAC,CAAC;AAetE,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,OACuB,CAAC;AAE3E,gCAAgC;AAChC,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,gBAChC,CAAC;AAE5B,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,GAC9B,WAAW,SAAS,CAAC,WAAW,CAAC,GAAG,IAAI,GAAG,SAAS,KACnD,OAAuD,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,MAAM,cAAc,EACpB,UAAU,OAAO,KAChB,OAA8D,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EAC9C,WAAW,SAAS,OAAO,EAAE,EAC7B,MAAM,cAAc,EACpB,UAAU,OAAO,KAChB,CAAC,MAAM,GAAG,IAAI,CAAC,EA4BjB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EAC9C,2BAAyB,KACxB,MAAM,EAYR,CAAC"}
@@ -65,3 +65,24 @@ export const applyEmptyCellsPolicy = (values, rawValues, spec, lineLike) => {
65
65
  }
66
66
  return plotted;
67
67
  };
68
+ /**
69
+ * Indexes of the plotted points that have no plottable neighbour to draw a
70
+ * line segment to — a value between two gaps (or at an edge next to a gap).
71
+ * A line-only series (no markers) draws nothing for them, so a row of data
72
+ * followed by a blank row vanishes from the chart; Excel still plots an
73
+ * isolated marker there. When the series connects across gaps every point
74
+ * links to the next plotted one, so only a lone single point is isolated.
75
+ */
76
+ export const isolatedPointIndexes = (values, connectAcrossGaps = false) => {
77
+ const plotted = (i) => i >= 0 && i < values.length && typeof values[i] === "number";
78
+ const indexes = [];
79
+ if (connectAcrossGaps) {
80
+ const all = values.flatMap((_, i) => (plotted(i) ? [i] : []));
81
+ return all.length === 1 ? all : [];
82
+ }
83
+ for (let i = 0; i < values.length; i++) {
84
+ if (plotted(i) && !plotted(i - 1) && !plotted(i + 1))
85
+ indexes.push(i);
86
+ }
87
+ return indexes;
88
+ };
@@ -0,0 +1,168 @@
1
+ export type LegendBandPosition = "top" | "bottom" | "left" | "right" | "topRight" | "none";
2
+ /** Space kept between neighbouring bands (legend ↔ axis title ↔ labels). */
3
+ export declare const BAND_GAP = 8;
4
+ /** Inset of the outermost band from the chart frame. */
5
+ export declare const EDGE_PAD = 10;
6
+ /** echarts `legend.padding` default used by both adapters' legend boxes. */
7
+ export declare const LEGEND_PADDING = 10;
8
+ /** echarts `legend.itemGap` default. */
9
+ export declare const LEGEND_ITEM_GAP = 10;
10
+ /** Approximate rendered width of `text` at `fontSize` (canvas when available). */
11
+ export declare const measureTextWidth: (text: string, fontSize: number) => number;
12
+ export type LegendBandInput = {
13
+ /** Legend entry labels, one per item. */
14
+ labels: readonly string[];
15
+ /** Legend text size in px. */
16
+ fontSize?: number;
17
+ /** Legend icon width in px. */
18
+ itemWidth?: number;
19
+ /** Gap between items (and between wrapped rows / columns) in px. */
20
+ itemGap?: number;
21
+ /** Legend box padding in px (each side). */
22
+ padding?: number;
23
+ /** Horizontal (top / bottom) or vertical (left / right) item flow. */
24
+ orientation: "horizontal" | "vertical";
25
+ /** Frame width available to a horizontal legend before it wraps. */
26
+ availableWidth: number;
27
+ /** Frame height available to a vertical legend before it wraps. */
28
+ availableHeight: number;
29
+ };
30
+ export type LegendBand = {
31
+ /** Rows of items (horizontal legends wrap long series lists). */
32
+ rows: number;
33
+ /** Columns of items (vertical legends wrap when taller than the frame). */
34
+ columns: number;
35
+ /** Rendered width in px including padding. */
36
+ width: number;
37
+ /** Rendered height in px including padding. */
38
+ height: number;
39
+ };
40
+ /**
41
+ * Estimate the legend's rendered box from its items. Horizontal legends
42
+ * pack items greedily into rows no wider than the frame; vertical legends
43
+ * stack items and spill into a new column when taller than the frame.
44
+ */
45
+ export declare const estimateLegendBand: ({ labels, fontSize, itemWidth, itemGap, padding, orientation, availableWidth, availableHeight, }: LegendBandInput) => LegendBand;
46
+ /**
47
+ * Pull the legend entry labels out of an echarts `legend.data` (strings or
48
+ * `{ name }` items); falls back to the series names, which is what echarts
49
+ * itself lists when `data` is empty.
50
+ */
51
+ export declare const legendLabelsFrom: (legendData: readonly unknown[] | undefined, seriesNames: readonly unknown[]) => string[];
52
+ /**
53
+ * Depth (px) of one row of echarts axis tick labels measured from the axis
54
+ * line: 5px tick + 8px label margin + one 12px line, rounded up. With
55
+ * `grid.containLabel` this is the part the grid already accounts for; the
56
+ * axis title sits beyond it.
57
+ */
58
+ export declare const echartsAxisLabelDepth: (fontSize?: number) => number;
59
+ /**
60
+ * echarts `nameGap` for a category axis whose title sits in the middle
61
+ * below the tick labels: clear one row of labels plus a small gap, measured
62
+ * to the title's centre line.
63
+ */
64
+ export declare const categoryAxisNameGap: (fontSize?: number) => number;
65
+ /** Height of the echarts chart-title band (padding 10 each side + one line). */
66
+ export declare const echartsTitleBand: (titleFontSize?: number) => number;
67
+ export type EchartsLegendBandInput = {
68
+ /** Resolved legend position (`undefined` = echarts default: top). */
69
+ position: LegendBandPosition | null | undefined;
70
+ /** Whether the legend renders at all (`show !== false` and has items). */
71
+ visible: boolean;
72
+ labels: readonly string[];
73
+ legendFontSize?: number;
74
+ legendItemWidth?: number;
75
+ width: number;
76
+ height: number;
77
+ /** A chart title is drawn across the top. */
78
+ hasTitle: boolean;
79
+ titleFontSize?: number;
80
+ /** The bottom axis carries a title (`name`). */
81
+ hasBottomAxisTitle: boolean;
82
+ /** `nameGap` used for the bottom axis title (px from axis line to title centre). */
83
+ bottomAxisNameGap: number;
84
+ /** The left axis carries a title (`name`). */
85
+ hasLeftAxisTitle: boolean;
86
+ axisFontSize?: number;
87
+ };
88
+ export type EchartsLegendBandLayout = {
89
+ band: LegendBand;
90
+ grid: {
91
+ left: number;
92
+ right: number;
93
+ top: number;
94
+ bottom: number;
95
+ containLabel: true;
96
+ };
97
+ /** Legend placement to spread over the positioned legend option. */
98
+ legend: {
99
+ top?: number | string;
100
+ bottom?: number | string;
101
+ left?: number | string;
102
+ right?: number | string;
103
+ };
104
+ };
105
+ /**
106
+ * Echarts cartesian geometry with a band reserved for the legend in its
107
+ * position, outside the axis-title band, outside the tick labels.
108
+ */
109
+ export declare const reserveEchartsLegendBand: ({ position, visible, labels, legendFontSize, legendItemWidth, width, height, hasTitle, titleFontSize, hasBottomAxisTitle, bottomAxisNameGap, hasLeftAxisTitle, axisFontSize, }: EchartsLegendBandInput) => EchartsLegendBandLayout;
110
+ export type PlotlyLegendBandInput = {
111
+ /** Resolved legend position (`undefined` = plotly default: right). */
112
+ position: LegendBandPosition | null | undefined;
113
+ visible: boolean;
114
+ labels: readonly string[];
115
+ legendFontSize?: number;
116
+ width: number;
117
+ height: number;
118
+ hasTitle: boolean;
119
+ titleFontSize?: number;
120
+ hasBottomAxisTitle: boolean;
121
+ hasLeftAxisTitle: boolean;
122
+ /**
123
+ * Px from the left axis line to the outer edge of its tick labels (and
124
+ * title when present) — what the legend must clear on the left.
125
+ */
126
+ leftAxisDepth: number;
127
+ /** Same for a secondary value axis drawn on the right (0 when absent). */
128
+ rightAxisDepth?: number;
129
+ /** `title.standoff` the caller sets on the bottom axis (px labels → title). */
130
+ bottomAxisTitleStandoff?: number;
131
+ axisFontSize?: number;
132
+ };
133
+ export type PlotlyLegendBandLayout = {
134
+ band: LegendBand;
135
+ margin: {
136
+ l: number;
137
+ r: number;
138
+ t: number;
139
+ b: number;
140
+ };
141
+ /** Legend anchor + orientation to spread over the legend option. */
142
+ legend: {
143
+ orientation: "h" | "v";
144
+ x: number;
145
+ y: number;
146
+ xanchor: "left" | "center" | "right";
147
+ yanchor: "top" | "middle" | "bottom";
148
+ } | null;
149
+ /** Bottom axis title standoff (px between tick labels and title). */
150
+ bottomAxisTitleStandoff: number;
151
+ /** Title pinned to the top of the container so it never drifts into the legend band. */
152
+ title: {
153
+ yref: "container";
154
+ y: number;
155
+ yanchor: "top";
156
+ pad: {
157
+ t: number;
158
+ };
159
+ };
160
+ };
161
+ /**
162
+ * Plotly geometry with a band reserved for the legend in its position.
163
+ * Legend coordinates are paper-relative (fractions of the plot area), so
164
+ * they are derived from the margins this returns; `automargin` on the
165
+ * axes may still grow a margin further for unusually deep tick labels.
166
+ */
167
+ export declare const reservePlotlyLegendBand: ({ position, visible, labels, legendFontSize, width, height, hasTitle, titleFontSize, hasBottomAxisTitle, hasLeftAxisTitle, leftAxisDepth, rightAxisDepth, bottomAxisTitleStandoff, axisFontSize, }: PlotlyLegendBandInput) => PlotlyLegendBandLayout;
168
+ //# sourceMappingURL=legend-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legend-layout.d.ts","sourceRoot":"","sources":["../../../utils/legend-layout.ts"],"names":[],"mappings":"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"}