@rowsncolumns/charts 28.1.2 → 28.1.5

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 (47) hide show
  1. package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
  2. package/dist/cjs/basic-chart/basic-chart.js +152 -88
  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/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  7. package/dist/cjs/plotly/basic-chart/basic-chart.js +225 -140
  8. package/dist/cjs/plotly/plot.d.ts +12 -0
  9. package/dist/cjs/plotly/plot.d.ts.map +1 -0
  10. package/dist/cjs/plotly/plot.js +8 -0
  11. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  12. package/dist/cjs/utils/axis-number-format.d.ts +14 -0
  13. package/dist/cjs/utils/axis-number-format.d.ts.map +1 -1
  14. package/dist/cjs/utils/axis-number-format.js +41 -1
  15. package/dist/cjs/utils/empty-cells.d.ts +9 -0
  16. package/dist/cjs/utils/empty-cells.d.ts.map +1 -1
  17. package/dist/cjs/utils/empty-cells.js +26 -1
  18. package/dist/cjs/utils/legend-layout.d.ts +168 -0
  19. package/dist/cjs/utils/legend-layout.d.ts.map +1 -0
  20. package/dist/cjs/utils/legend-layout.js +338 -0
  21. package/dist/cjs/utils/trendline.d.ts +18 -5
  22. package/dist/cjs/utils/trendline.d.ts.map +1 -1
  23. package/dist/cjs/utils/trendline.js +38 -16
  24. package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
  25. package/dist/esm/basic-chart/basic-chart.js +118 -47
  26. package/dist/esm/basic-chart/helpers.d.ts +9 -0
  27. package/dist/esm/basic-chart/helpers.d.ts.map +1 -1
  28. package/dist/esm/basic-chart/helpers.js +30 -0
  29. package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
  30. package/dist/esm/plotly/basic-chart/basic-chart.js +140 -13
  31. package/dist/esm/plotly/plot.d.ts +12 -0
  32. package/dist/esm/plotly/plot.d.ts.map +1 -0
  33. package/dist/esm/plotly/plot.js +12 -0
  34. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  35. package/dist/esm/utils/axis-number-format.d.ts +14 -0
  36. package/dist/esm/utils/axis-number-format.d.ts.map +1 -1
  37. package/dist/esm/utils/axis-number-format.js +36 -0
  38. package/dist/esm/utils/empty-cells.d.ts +9 -0
  39. package/dist/esm/utils/empty-cells.d.ts.map +1 -1
  40. package/dist/esm/utils/empty-cells.js +21 -0
  41. package/dist/esm/utils/legend-layout.d.ts +168 -0
  42. package/dist/esm/utils/legend-layout.d.ts.map +1 -0
  43. package/dist/esm/utils/legend-layout.js +299 -0
  44. package/dist/esm/utils/trendline.d.ts +18 -5
  45. package/dist/esm/utils/trendline.d.ts.map +1 -1
  46. package/dist/esm/utils/trendline.js +36 -16
  47. package/package.json +9 -9
@@ -4,19 +4,19 @@ import { useSubscribeToSheet } from "@rowsncolumns/spreadsheet-state";
4
4
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuTrigger, IconButton, useIsomorphicLayoutEffect, useOnVisibilityChange, } from "@rowsncolumns/ui";
5
5
  import { debug, isAFormula, isNil, ssfFormat } from "@rowsncolumns/utils";
6
6
  import React, { useCallback, useMemo, useRef, useState } from "react";
7
- import Plot from "react-plotly.js";
8
7
  import { aggregateSeriesByDomain, getDomainValuesForAxis, sanitizeDomainValues, shouldAggregateSeries, } from "../../utils/aggregation";
9
- import { formatAxisValue, numberFormatToPlotlyTicks, resolveSeriesNumberFormats, resolveValueAxisNumberFormat, } from "../../utils/axis-number-format";
8
+ import { formatAxisValue, numberFormatToPlotlyTicks, resolveSeriesNumberFormats, resolveValueAxisNumberFormat, valueAxisNameGap, } from "../../utils/axis-number-format";
10
9
  import { computeBoxplotSummary } from "../../utils/boxplot";
11
10
  import { createBubbleSizeScale, prepareBubbleSeriesData, toLabelString, } from "../../utils/bubble";
12
11
  import { canSwitchRowColumn, cellLabelGetterFromRangeReader, switchRowColumn, } from "../../utils/chart-orientation";
13
12
  import { dataUrlToBlob } from "../../utils/chart-image";
14
13
  import { getChartPalette } from "../../utils/chart-styles";
15
14
  import { coerceCalculatedLabel } from "../../utils/chart-spec-utils";
16
- import { applyEmptyCellsPolicy, connectsAcrossEmptyCells, isLineLikeChartType, } from "../../utils/empty-cells";
15
+ import { applyEmptyCellsPolicy, connectsAcrossEmptyCells, isLineLikeChartType, isolatedPointIndexes, } from "../../utils/empty-cells";
17
16
  import { computeErrorBars, resolveCustomErrorBarValues, } from "../../utils/error-bars";
18
17
  import { plotlyMulticategoryX, resolveCategoryLevels, } from "../../utils/category-levels";
19
18
  import { gradientToPlotly } from "../../utils/gradient";
19
+ import { BAND_GAP, legendLabelsFrom, measureTextWidth, reservePlotlyLegendBand, } from "../../utils/legend-layout";
20
20
  import { plotlyTitleText } from "../../utils/plotly-title";
21
21
  import { explosionToPlotlyPull, resolveExplosion, } from "../../utils/pie-explosion";
22
22
  import { resolvePlotlyClickSelection, wasDragged, } from "../../utils/plotly-click";
@@ -29,6 +29,7 @@ import { buildRadarIndicators } from "../../utils/radar";
29
29
  import { buildSequentialColorRamp, isRegionMapSpec, resolveGeoScope, WORLD_GEO_SCOPE, } from "../../utils/region-map";
30
30
  import { assignStockRoles, toLineSeries, toVolumeSeries, } from "../../utils/stock";
31
31
  import { computeTrendline } from "../../utils/trendline";
32
+ import { Plot, Plotly } from "../plot";
32
33
  import { chartTypeToPlotlyType, getPlotlyTraceStyle, mapMarkerSymbolToPlotly, showXAxis, } from "./helpers";
33
34
  const BASIC_CHART_TYPES = new Set([
34
35
  "bar",
@@ -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
@@ -339,7 +350,6 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
339
350
  const hierarchyTitleRef = useRef(undefined);
340
351
  const explicitChartTitle = chart.spec.title ?? undefined;
341
352
  const title = explicitChartTitle ?? hierarchyTitleRef.current;
342
- const plotRef = useRef(null);
343
353
  // The plotted `<div>` plotly hands back on init — what `Plotly.toImage`
344
354
  // rasterises for Copy as Picture and the print preview.
345
355
  const graphDivRef = useRef(null);
@@ -1006,7 +1016,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1006
1016
  // to 100 (plotly `groupnorm`); Excel's "Line with Markers"
1007
1017
  // (spec.showMarkers) or a series' own marker switches the line trace
1008
1018
  // to lines+markers, and a series marker of "none" hides them.
1009
- const lineTraceVariant = (series, effectiveChartType) => {
1019
+ const lineTraceVariant = (series, effectiveChartType, values) => {
1010
1020
  if (effectiveChartType !== "line" && effectiveChartType !== "area") {
1011
1021
  return {};
1012
1022
  }
@@ -1027,6 +1037,22 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1027
1037
  size: marker?.size ?? 6,
1028
1038
  };
1029
1039
  }
1040
+ else {
1041
+ // A marker-less line draws nothing for a point with a gap on both sides (a data
1042
+ // row followed by a blank row — the first row included), so that row vanishes from
1043
+ // the chart. Excel plots an isolated marker there: keep the line and give only those
1044
+ // points a marker (plotly's `marker.size` / `marker.opacity` are per-point arrays;
1045
+ // a 0-size, 0-opacity marker paints nothing on the connected points).
1046
+ const isolated = new Set(isolatedPointIndexes(values, connectsAcrossEmptyCells(spec, true)));
1047
+ if (isolated.size) {
1048
+ extra.mode = "lines+markers";
1049
+ extra.marker = {
1050
+ symbol: "circle",
1051
+ size: values.map((_, i) => (isolated.has(i) ? 6 : 0)),
1052
+ opacity: values.map((_, i) => (isolated.has(i) ? 1 : 0)),
1053
+ };
1054
+ }
1055
+ }
1030
1056
  }
1031
1057
  return extra;
1032
1058
  };
@@ -1714,7 +1740,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1714
1740
  name: dataLabel,
1715
1741
  stackgroup: isStacked ? "one" : undefined,
1716
1742
  ...seriesTraceStyle,
1717
- ...lineTraceVariant(series, effectiveChartType),
1743
+ ...lineTraceVariant(series, effectiveChartType, seriesData.values),
1718
1744
  };
1719
1745
  // Ties the trace back to spec.series for click selection
1720
1746
  trace.meta = {
@@ -1744,7 +1770,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1744
1770
  name: dataLabel,
1745
1771
  stackgroup: isStacked ? "one" : undefined,
1746
1772
  ...seriesTraceStyle,
1747
- ...lineTraceVariant(series, effectiveChartType),
1773
+ ...lineTraceVariant(series, effectiveChartType, numericValues),
1748
1774
  };
1749
1775
  // Ties the trace back to spec.series for click selection
1750
1776
  trace.meta = {
@@ -1786,6 +1812,65 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1786
1812
  getFormattingFromRange,
1787
1813
  hiddenDimensionStrategy: spec.hiddenDimensionStrategy,
1788
1814
  });
1815
+ // Legend position / visibility from the spec, then a band reserved
1816
+ // for it outside the axis titles and tick labels so it never paints
1817
+ // over them — Excel shrinks the plot area instead (#986).
1818
+ const { legend: legendOverrides, showlegend } = applyLegendOverridesPlotly({ font: { color: textColor } }, spec.legend);
1819
+ const axisFontSize = 12;
1820
+ const primaryValues = traces.flatMap((trace) => {
1821
+ const values = isHorizontalBarChart ? trace?.x : trace?.y;
1822
+ return trace?.[valueAxisKey] === secondaryValueAxisRef || !Array.isArray(values)
1823
+ ? []
1824
+ : values;
1825
+ });
1826
+ // Tick-label depth of the left axis: value labels formatted from the
1827
+ // plotted extremes on a vertical chart, the widest category on a
1828
+ // horizontal bar; plus the rotated axis title band when present.
1829
+ const leftAxisLabelDepth = isHorizontalBarChart
1830
+ ? Math.ceil(Math.max(0, ...(domainData ?? []).map((label) => measureTextWidth(String(label ?? ""), axisFontSize)))) + 8
1831
+ : valueAxisNameGap({
1832
+ values: primaryValues,
1833
+ pattern: primaryAxisNumberFormat,
1834
+ fontSize: axisFontSize,
1835
+ minimum: 0,
1836
+ }) -
1837
+ axisFontSize -
1838
+ 6;
1839
+ const leftAxisDepth = leftAxisLabelDepth +
1840
+ (spec.verticalAxisTitle
1841
+ ? VALUE_AXIS_TITLE_STANDOFF + Math.ceil(axisFontSize * 1.3)
1842
+ : 0);
1843
+ const rightAxisDepth = hasSecondaryAxis && !isHorizontalBarChart
1844
+ ? valueAxisNameGap({
1845
+ values: traces.flatMap((trace) => trace?.[valueAxisKey] === secondaryValueAxisRef && Array.isArray(trace?.y)
1846
+ ? trace.y
1847
+ : []),
1848
+ pattern: secondaryAxisNumberFormat,
1849
+ fontSize: axisFontSize,
1850
+ minimum: 0,
1851
+ }) -
1852
+ axisFontSize -
1853
+ 6
1854
+ : 0;
1855
+ const bottomAxisTitleStandoff = isHorizontalBarChart
1856
+ ? VALUE_AXIS_TITLE_STANDOFF
1857
+ : BAND_GAP;
1858
+ const legendBand = reservePlotlyLegendBand({
1859
+ position: spec.legend?.position,
1860
+ visible: showlegend !== false,
1861
+ labels: legendLabelsFrom(undefined, traces.map((trace) => trace?.name)),
1862
+ legendFontSize: spec.legend?.textFormat?.fontSize ?? axisFontSize,
1863
+ width: Math.max(0, (width ?? 0) - 2),
1864
+ height: Math.max(0, (height ?? 0) - 2),
1865
+ hasTitle: Boolean(title),
1866
+ titleFontSize: authoredTitleFmt?.fontSize ?? 16,
1867
+ hasBottomAxisTitle: Boolean(spec.horizontalAxisTitle),
1868
+ hasLeftAxisTitle: Boolean(spec.verticalAxisTitle),
1869
+ leftAxisDepth,
1870
+ rightAxisDepth,
1871
+ bottomAxisTitleStandoff,
1872
+ axisFontSize,
1873
+ });
1789
1874
  const newLayout = {
1790
1875
  title: {
1791
1876
  text: plotlyTitleText(title, spec.titleTextFormat),
@@ -1796,7 +1881,32 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1796
1881
  }, authoredTitleFmt),
1797
1882
  x: 0.02, // Small left margin (0.02 = 2% from left edge)
1798
1883
  xanchor: "left",
1884
+ ...legendBand.title,
1799
1885
  },
1886
+ showlegend: showlegend === undefined ? true : showlegend,
1887
+ legend: { ...legendOverrides, ...(legendBand.legend ?? {}) },
1888
+ margin: legendBand.margin,
1889
+ autosize: true,
1890
+ // Bar/column stacking. Plotly's `barmode: "stack"` stacks bars
1891
+ // at the same X (or Y for horizontal). `barnorm: "percent"`
1892
+ // normalises each category's stack to 100% — Excel's
1893
+ // `percentStacked` grouping. Default is grouped (clustered).
1894
+ ...((spec.chartType === "bar" || spec.chartType === "column") &&
1895
+ spec.stackedType
1896
+ ? {
1897
+ barmode: spec.stackedType === "STACKED" ||
1898
+ spec.stackedType === "PERCENT_STACKED"
1899
+ ? "stack"
1900
+ : "group",
1901
+ ...(isPercentStackedBars ? { barnorm: "percent" } : {}),
1902
+ }
1903
+ : {}),
1904
+ // gapWidth — Excel stores 0..500 as a percentage of bar width.
1905
+ // Plotly's `bargap` is a fraction of the slot (0..1). Approx:
1906
+ // gapWidth=150 (default) → ~0.4 bargap.
1907
+ ...(typeof spec.gapWidth === "number"
1908
+ ? { bargap: Math.max(0, Math.min(0.95, spec.gapWidth / 400)) }
1909
+ : {}),
1800
1910
  font: {
1801
1911
  family: "Untitled Sans, apple-system, sans-serif",
1802
1912
  size: 12,
@@ -1808,6 +1918,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1808
1918
  title: {
1809
1919
  text: spec.horizontalAxisTitle ?? "",
1810
1920
  font: { color: textColor },
1921
+ standoff: bottomAxisTitleStandoff,
1811
1922
  },
1812
1923
  automargin: true,
1813
1924
  showgrid: true,
@@ -1827,6 +1938,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1827
1938
  title: {
1828
1939
  text: spec.verticalAxisTitle ?? "",
1829
1940
  font: { color: textColor },
1941
+ ...(isHorizontalBarChart
1942
+ ? {}
1943
+ : { standoff: VALUE_AXIS_TITLE_STANDOFF }),
1830
1944
  },
1831
1945
  showgrid: !isHorizontalBarChart,
1832
1946
  gridcolor: gridColor,
@@ -1845,7 +1959,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1845
1959
  ...(hasSecondaryAxis
1846
1960
  ? {
1847
1961
  [isHorizontalBarChart ? "xaxis2" : "yaxis2"]: applyAxisOverridesPlotly({
1848
- title: { font: { color: textColor } },
1962
+ title: {
1963
+ font: { color: textColor },
1964
+ standoff: VALUE_AXIS_TITLE_STANDOFF,
1965
+ },
1849
1966
  overlaying: isHorizontalBarChart ? "x" : "y",
1850
1967
  side: isHorizontalBarChart ? "top" : "right",
1851
1968
  showgrid: false,
@@ -1875,6 +1992,18 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1875
1992
  },
1876
1993
  }
1877
1994
  : {}),
1995
+ // Of-pie composite — connector-line shapes plus (bar-of-pie) the
1996
+ // hidden secondary axes and stacked bar mode. Spread last: an
1997
+ // of-pie is a single pie series, so its `xaxis2`/`yaxis2` own the
1998
+ // secondary-axis slots.
1999
+ ...(ofPieExtras
2000
+ ? {
2001
+ shapes: ofPieExtras.shapes,
2002
+ ...(ofPieExtras.barmode ? { barmode: ofPieExtras.barmode } : {}),
2003
+ ...(ofPieExtras.xaxis2 ? { xaxis2: ofPieExtras.xaxis2 } : {}),
2004
+ ...(ofPieExtras.yaxis2 ? { yaxis2: ofPieExtras.yaxis2 } : {}),
2005
+ }
2006
+ : {}),
1878
2007
  };
1879
2008
  // Phase 5.1: apply data labels per trace, and set the doughnut hole
1880
2009
  // when chartType === "doughnut".
@@ -2028,14 +2157,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2028
2157
  onUpdateChart({ ...chart, spec: next });
2029
2158
  }
2030
2159
  : undefined;
2031
- // Copy as Picture / print preview. `plotly.js/dist/plotly` is the module
2032
- // `react-plotly.js` itself is built on, loaded on demand so the render
2033
- // path stays as it was.
2160
+ // Copy as Picture / print preview, through the plotly module the chart
2161
+ // renders with.
2034
2162
  const exportChartPng = async () => {
2035
2163
  const graphDiv = graphDivRef.current;
2036
2164
  if (!graphDiv)
2037
2165
  return undefined;
2038
- const { default: Plotly } = await import("plotly.js/dist/plotly");
2039
2166
  const url = await Plotly.toImage(graphDiv, {
2040
2167
  format: "png",
2041
2168
  width: graphDiv.clientWidth || width || 600,
@@ -2071,7 +2198,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2071
2198
  onDeleteChart?.(chart.chartId);
2072
2199
  } }, "Delete chart")))))) : null,
2073
2200
  React.createElement("div", { className: "flex-1 rnc-plotly-chart-container", onPointerDownCapture: handlePointerDownCapture },
2074
- React.createElement(Plot, { ref: plotRef, data: plotData, layout: plotLayout, config: config, onClick: handlePlotClick, onInitialized: (_figure, graphDiv) => {
2201
+ React.createElement(Plot, { data: plotData, layout: plotLayout, config: config, onClick: handlePlotClick, onInitialized: (_figure, graphDiv) => {
2075
2202
  graphDivRef.current = graphDiv;
2076
2203
  }, style: { width: "100%", height: "100%" } }))));
2077
2204
  };
@@ -0,0 +1,12 @@
1
+ import Plotly from "plotly.js-dist-min";
2
+ /**
3
+ * The one plotly module the plotly charts render and export with:
4
+ * `plotly.js-dist-min`, the full bundle (every trace type) already minified.
5
+ * react-plotly.js's default entry would load the separate
6
+ * `plotly.js/dist/plotly` build instead, so a host that ships
7
+ * `plotly.js-dist-min` for its own charts would carry two copies of the same
8
+ * plotly release.
9
+ */
10
+ export { Plotly };
11
+ export declare const Plot: import("react").ComponentType<import("react-plotly.js").PlotParams>;
12
+ //# sourceMappingURL=plot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plot.d.ts","sourceRoot":"","sources":["../../../plotly/plot.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAGxC;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,eAAO,MAAM,IAAI,qEAAgC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import Plotly from "plotly.js-dist-min";
2
+ import createPlotlyComponent from "react-plotly.js/factory";
3
+ /**
4
+ * The one plotly module the plotly charts render and export with:
5
+ * `plotly.js-dist-min`, the full bundle (every trace type) already minified.
6
+ * react-plotly.js's default entry would load the separate
7
+ * `plotly.js/dist/plotly` build instead, so a host that ships
8
+ * `plotly.js-dist-min` for its own charts would carry two copies of the same
9
+ * plotly release.
10
+ */
11
+ export { Plotly };
12
+ export const Plot = createPlotlyComponent(Plotly);
@@ -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/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"}
@@ -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
+ };
@@ -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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"}