@rowsncolumns/charts 28.1.18 → 28.1.19

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 (63) hide show
  1. package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
  2. package/dist/cjs/basic-chart/basic-chart.js +338 -233
  3. package/dist/cjs/basic-chart/helpers.d.ts +7 -3
  4. package/dist/cjs/basic-chart/helpers.d.ts.map +1 -1
  5. package/dist/cjs/basic-chart/helpers.js +30 -4
  6. package/dist/cjs/basic-chart/index.d.ts +7 -0
  7. package/dist/cjs/basic-chart/index.d.ts.map +1 -1
  8. package/dist/cjs/basic-chart/index.js +19 -2
  9. package/dist/cjs/components/editors/chart-editor.d.ts.map +1 -1
  10. package/dist/cjs/components/editors/chart-editor.js +21 -12
  11. package/dist/cjs/components/editors/fields/axis-options-fields.d.ts +6 -3
  12. package/dist/cjs/components/editors/fields/axis-options-fields.d.ts.map +1 -1
  13. package/dist/cjs/components/editors/fields/axis-options-fields.js +69 -7
  14. package/dist/cjs/components/editors/fields/chart-elements-fields.d.ts +15 -0
  15. package/dist/cjs/components/editors/fields/chart-elements-fields.d.ts.map +1 -0
  16. package/dist/cjs/components/editors/fields/chart-elements-fields.js +23 -0
  17. package/dist/cjs/components/editors/fields/series-format-fields.d.ts.map +1 -1
  18. package/dist/cjs/components/editors/fields/series-format-fields.js +16 -2
  19. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  20. package/dist/cjs/utils/chart-lines.d.ts +36 -0
  21. package/dist/cjs/utils/chart-lines.d.ts.map +1 -0
  22. package/dist/cjs/utils/chart-lines.js +111 -0
  23. package/dist/cjs/utils/data-table.d.ts +31 -0
  24. package/dist/cjs/utils/data-table.d.ts.map +1 -0
  25. package/dist/cjs/utils/data-table.js +61 -0
  26. package/dist/cjs/utils/excel-axis-scale.d.ts +35 -0
  27. package/dist/cjs/utils/excel-axis-scale.d.ts.map +1 -0
  28. package/dist/cjs/utils/excel-axis-scale.js +210 -0
  29. package/dist/cjs/utils/overrides.d.ts +24 -3
  30. package/dist/cjs/utils/overrides.d.ts.map +1 -1
  31. package/dist/cjs/utils/overrides.js +119 -14
  32. package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
  33. package/dist/esm/basic-chart/basic-chart.js +177 -38
  34. package/dist/esm/basic-chart/helpers.d.ts +7 -3
  35. package/dist/esm/basic-chart/helpers.d.ts.map +1 -1
  36. package/dist/esm/basic-chart/helpers.js +26 -3
  37. package/dist/esm/basic-chart/index.d.ts +7 -0
  38. package/dist/esm/basic-chart/index.d.ts.map +1 -1
  39. package/dist/esm/basic-chart/index.js +15 -1
  40. package/dist/esm/components/editors/chart-editor.d.ts.map +1 -1
  41. package/dist/esm/components/editors/chart-editor.js +12 -1
  42. package/dist/esm/components/editors/fields/axis-options-fields.d.ts +6 -3
  43. package/dist/esm/components/editors/fields/axis-options-fields.d.ts.map +1 -1
  44. package/dist/esm/components/editors/fields/axis-options-fields.js +56 -5
  45. package/dist/esm/components/editors/fields/chart-elements-fields.d.ts +15 -0
  46. package/dist/esm/components/editors/fields/chart-elements-fields.d.ts.map +1 -0
  47. package/dist/esm/components/editors/fields/chart-elements-fields.js +17 -0
  48. package/dist/esm/components/editors/fields/series-format-fields.d.ts.map +1 -1
  49. package/dist/esm/components/editors/fields/series-format-fields.js +9 -1
  50. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  51. package/dist/esm/utils/chart-lines.d.ts +36 -0
  52. package/dist/esm/utils/chart-lines.d.ts.map +1 -0
  53. package/dist/esm/utils/chart-lines.js +121 -0
  54. package/dist/esm/utils/data-table.d.ts +31 -0
  55. package/dist/esm/utils/data-table.d.ts.map +1 -0
  56. package/dist/esm/utils/data-table.js +48 -0
  57. package/dist/esm/utils/excel-axis-scale.d.ts +35 -0
  58. package/dist/esm/utils/excel-axis-scale.d.ts.map +1 -0
  59. package/dist/esm/utils/excel-axis-scale.js +122 -0
  60. package/dist/esm/utils/overrides.d.ts +24 -3
  61. package/dist/esm/utils/overrides.d.ts.map +1 -1
  62. package/dist/esm/utils/overrides.js +111 -10
  63. package/package.json +7 -7
@@ -16,20 +16,23 @@ import { computeBoxplotSummary } from "../utils/boxplot";
16
16
  import { createBubbleSizeScale, prepareBubbleSeriesData, } from "../utils/bubble";
17
17
  import { echartsGroupedCategoryAxes, resolveCategoryLevels, } from "../utils/category-levels";
18
18
  import { dataUrlToBlob } from "../utils/chart-image";
19
+ import { buildChartLineSeries, CHART_LINE_SERIES_PREFIX, } from "../utils/chart-lines";
19
20
  import { canSwitchRowColumn, cellLabelGetterFromRangeReader, switchRowColumn, } from "../utils/chart-orientation";
20
21
  import { coerceCalculatedLabel, getChartDataRanges, } from "../utils/chart-spec-utils";
21
22
  import { getChartPalette } from "../utils/chart-styles";
23
+ import { dataTableAxes, } from "../utils/data-table";
22
24
  import { clampDoughnutHole, doughnutRings } from "../utils/doughnut";
23
25
  import { getEchartsGlState, loadEchartsGl, markEchartsGlFailed, } from "../utils/echarts-gl-loader";
24
26
  import { buildEchartsOverlaySeries } from "../utils/echarts-overlays";
25
27
  import { applyEmptyCellsPolicy, connectsAcrossEmptyCells, isLineLikeChartType, isolatedPointIndexes, } from "../utils/empty-cells";
28
+ import { excelAutoAxisScale, valueAxisExtentValues } from "../utils/excel-axis-scale";
26
29
  import { gradientToEcharts } from "../utils/gradient";
27
30
  import { buildHierarchyTree, contrastTextColor, extractHierarchyRows, getHierarchyHeaderTitle, } from "../utils/hierarchy";
28
31
  import { aggregateByCategory, binHistogram, computePareto, isParetoSpec, } from "../utils/histogram";
29
32
  import { categoryAxisNameGap, legendLabelsFrom, reserveEchartsLegendBand, } from "../utils/legend-layout";
30
33
  import { clampSecondPieSize, isOfPieChartSpec, otherSliceHalfAngle, resolveOfPieType, splitOfPie, } from "../utils/of-pie";
31
34
  import { applyAxisOverrides, applyLegendOverrides, buildSeriesLabelOption, dataLabelFormatter, resolveSeriesDataLabels, } from "../utils/overrides";
32
- import { themedTextStyle } from "../utils/overrides";
35
+ import { CHART_TEXT_PX, CHART_TITLE_PX, fontSizePx, pointsToPixels, themedTextStyle, } from "../utils/overrides";
33
36
  import { explosionOffsetPx, pieRadiusPx, resolveExplosion, } from "../utils/pie-explosion";
34
37
  import { buildRadarIndicators, buildRadarSeriesData } from "../utils/radar";
35
38
  import { buildRegionMapData, buildSequentialColorRamp, isRegionMapSpec, resolveGeoScope, warnOnceForUnmatchedRegions, } from "../utils/region-map";
@@ -40,7 +43,7 @@ import { STOCK_ROLES, toCandlestick, toLineSeries, toVolumeSeries, } from "../ut
40
43
  import { buildBar3DData, buildContourData, buildLine3DSeries, buildSurfaceData, getCategoryCount, getValueExtent, isContourSpec, resolve3DRenderKind, SURFACE_COLOR_RAMP, } from "../utils/three-d";
41
44
  import { computeWaterfall } from "../utils/waterfall";
42
45
  import { isWebGLAvailable } from "../utils/webgl";
43
- import { chartTypeToEchartsType, circularLegendEntries, getEchartsSeriesStyle, isCircularChartType, mapMarkerSymbolToEcharts, showXAxis, } from "./helpers";
46
+ import { automaticMarkerSymbol, chartTypeToEchartsType, circularLegendEntries, getEchartsSeriesStyle, isCircularChartType, mapMarkerSymbolToEcharts, showXAxis, } from "./helpers";
44
47
  // Excel error values that should be treated as null in charts
45
48
  const EXCEL_ERROR_VALUES = new Set([
46
49
  "#N/A",
@@ -248,9 +251,33 @@ const defaultLegendConfig = {
248
251
  };
249
252
  /** Category-axis title gap: clears one row of tick labels (#986). */
250
253
  const CATEGORY_AXIS_NAME_GAP = categoryAxisNameGap();
254
+ /**
255
+ * Excel's chart text colour: Text 1 at 65% (`<a:schemeClr val="tx1"><a:lumMod
256
+ * val="65000"/><a:lumOff val="35000"/>`, #595959 on the light theme) — RD-24.
257
+ */
258
+ const chartTextColor = (theme, isDarkMode) => getStringifiedColor({ theme: 1, tint: isDarkMode ? 1 : 0.35 }, theme);
259
+ /** The theme's body (minor) font, which Excel's chart text uses. */
260
+ const chartFontFamily = (theme) => theme?.primaryFontFamily ? `${theme.primaryFontFamily}, sans-serif` : "sans-serif";
261
+ /** Chart types whose value axes take Excel's automatic bounds and major unit. */
262
+ const AUTO_SCALE_CHART_TYPES = new Set([
263
+ "column",
264
+ "bar",
265
+ "line",
266
+ "area",
267
+ "combo",
268
+ ]);
251
269
  export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, locale, enable3D, }) => {
252
270
  const spec = chart.spec;
253
- const explicitChartTitle = chart.spec.title ?? undefined;
271
+ // A title linked to a cell shows the cell's text (FT-08); `title` is its last copy.
272
+ const titleSource = chart.spec.titleSource;
273
+ const linkedTitleValue = titleSource
274
+ ? getDomainValuesFromRange(titleSource)?.[0]
275
+ : undefined;
276
+ const explicitChartTitle = (linkedTitleValue !== undefined && linkedTitleValue !== null && linkedTitleValue !== ""
277
+ ? String(linkedTitleValue)
278
+ : undefined) ??
279
+ chart.spec.title ??
280
+ undefined;
254
281
  const hierarchyTitleRef = useRef(explicitChartTitle);
255
282
  const title = explicitChartTitle ?? hierarchyTitleRef.current;
256
283
  const el = useRef(null);
@@ -790,20 +817,19 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
790
817
  visible: bubbleLegend.show !== false,
791
818
  labels: preparedSeries.map((entry) => entry.name),
792
819
  legendFontSize: bubbleLegend.textStyle?.fontSize ??
793
- spec.legend?.textFormat?.fontSize ??
794
- 12,
820
+ fontSizePx(spec.legend?.textFormat?.fontSize, CHART_TEXT_PX),
795
821
  legendItemWidth: defaultLegendConfig.itemWidth,
796
822
  width: width ?? 0,
797
823
  height: height ?? 0,
798
824
  hasTitle: Boolean(explicitChartTitle),
799
- titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
825
+ titleFontSize: fontSizePx(spec.titleTextFormat?.fontSize, CHART_TITLE_PX),
800
826
  hasBottomAxisTitle: Boolean(spec.horizontalAxisTitle),
801
827
  bottomAxisNameGap: 30,
802
828
  hasLeftAxisTitle: Boolean(spec.verticalAxisTitle),
803
829
  });
804
830
  chartRef.current?.setOption({
805
831
  xAxis: [
806
- applyAxisOverrides({ ...xAxisConfig }, spec.categoryAxisOptions),
832
+ applyAxisOverrides({ ...xAxisConfig }, spec.categoryAxisOptions, undefined, undefined, "x"),
807
833
  ],
808
834
  yAxis: [
809
835
  applyAxisOverrides({
@@ -936,13 +962,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
936
962
  visible: legendShown && hierarchyData.length > 0,
937
963
  labels: hierarchyData.map((node) => node.name),
938
964
  legendFontSize: hierarchyLegend.textStyle?.fontSize ??
939
- spec.legend?.textFormat?.fontSize ??
940
- 12,
965
+ fontSizePx(spec.legend?.textFormat?.fontSize, CHART_TEXT_PX),
941
966
  legendItemWidth: defaultLegendConfig.itemWidth,
942
967
  width: width ?? 0,
943
968
  height: height ?? 0,
944
969
  hasTitle: Boolean(explicitChartTitle ?? headerTitle),
945
- titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
970
+ titleFontSize: fontSizePx(spec.titleTextFormat?.fontSize, CHART_TITLE_PX),
946
971
  hasBottomAxisTitle: false,
947
972
  bottomAxisNameGap: 0,
948
973
  hasLeftAxisTitle: false,
@@ -1445,13 +1470,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1445
1470
  visible: radarLegend.show !== false,
1446
1471
  labels: radarData.map((d) => d.name),
1447
1472
  legendFontSize: radarLegend.textStyle?.fontSize ??
1448
- spec.legend?.textFormat?.fontSize ??
1449
- 12,
1473
+ fontSizePx(spec.legend?.textFormat?.fontSize, CHART_TEXT_PX),
1450
1474
  legendItemWidth: defaultLegendConfig.itemWidth,
1451
1475
  width: width ?? 0,
1452
1476
  height: height ?? 0,
1453
1477
  hasTitle: Boolean(explicitChartTitle),
1454
- titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
1478
+ titleFontSize: fontSizePx(spec.titleTextFormat?.fontSize, CHART_TITLE_PX),
1455
1479
  hasBottomAxisTitle: false,
1456
1480
  bottomAxisNameGap: 0,
1457
1481
  hasLeftAxisTitle: false,
@@ -1752,6 +1776,13 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1752
1776
  return;
1753
1777
  }
1754
1778
  const isStacked = spec.stackedType && spec.stackedType !== "UNSTACKED";
1779
+ // Tick labels and axis titles in Excel's chart text style (RD-24): 9 pt
1780
+ // labels, 10 pt titles, Text 1 at 65%. An axis' `<c:txPr>` overrides it.
1781
+ const axisTextColor = chartTextColor(theme, isDarkMode);
1782
+ const axisTextStyle = {
1783
+ axisLabel: { color: axisTextColor, fontSize: CHART_TEXT_PX },
1784
+ nameTextStyle: { color: axisTextColor, fontSize: pointsToPixels(10) },
1785
+ };
1755
1786
  const isCircularChart = spec.chartType === "pie" || spec.chartType === "doughnut";
1756
1787
  // Pie-of-pie / bar-of-pie composite. The of-pie branch below fills
1757
1788
  // `ofPieLayout` so the grid/axis assembly at the bottom can add the
@@ -1778,7 +1809,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1778
1809
  show: showAxis,
1779
1810
  axisLine: { show: true },
1780
1811
  axisTick: { show: true },
1781
- splitLine: { show: true },
1812
+ splitLine: { show: false },
1782
1813
  name: isHorizontalBarChart
1783
1814
  ? spec.verticalAxisTitle ?? ""
1784
1815
  : spec.horizontalAxisTitle ?? "",
@@ -1802,12 +1833,15 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1802
1833
  : spec.horizontalAxisTitle ?? ""
1803
1834
  : undefined;
1804
1835
  chartDomains.push({
1836
+ ...axisTextStyle,
1805
1837
  type: "category",
1806
1838
  data: axisData,
1807
1839
  show: showAxis && !isCircularChart,
1808
1840
  axisLine: { show: !isCircularChart },
1809
1841
  axisTick: { show: !isCircularChart },
1810
- splitLine: { show: !isCircularChart },
1842
+ // Excel draws vertical (category) gridlines only when the axis asks
1843
+ // for them (`<c:catAx><c:majorGridlines/>`) — LV-05.
1844
+ splitLine: { show: false },
1811
1845
  name: axisTitle,
1812
1846
  nameLocation: "middle",
1813
1847
  nameGap: CATEGORY_AXIS_NAME_GAP,
@@ -1815,11 +1849,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1815
1849
  });
1816
1850
  if (chartDomains.length === 0 && !isCircularChart) {
1817
1851
  chartDomains.push({
1852
+ ...axisTextStyle,
1818
1853
  type: "category",
1819
1854
  show: showAxis,
1820
1855
  axisLine: { show: true },
1821
1856
  axisTick: { show: true },
1822
- splitLine: { show: true },
1857
+ splitLine: { show: false },
1823
1858
  gridIndex: 0,
1824
1859
  name: isHorizontalBarChart
1825
1860
  ? spec.verticalAxisTitle ?? ""
@@ -1989,8 +2024,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1989
2024
  seriesEntry.showSymbol = wantsMarkers || labelsWithoutMarkers;
1990
2025
  if (wantsMarkers) {
1991
2026
  seriesEntry.symbol =
1992
- mapMarkerSymbolToEcharts(markerSymbol) ?? "circle";
1993
- seriesEntry.symbolSize = seriesAny.marker?.size ?? 6;
2027
+ mapMarkerSymbolToEcharts(markerSymbol ?? automaticMarkerSymbol(specSeriesIndex)) ?? "circle";
2028
+ // Excel's automatic marker is 5 pt (7 px); an authored size is points too.
2029
+ seriesEntry.symbolSize = Math.round(((seriesAny.marker?.size ?? 5) * 4) / 3);
1994
2030
  }
1995
2031
  else {
1996
2032
  if (labelsWithoutMarkers) {
@@ -2719,18 +2755,41 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2719
2755
  }
2720
2756
  legend.data = [];
2721
2757
  }
2758
+ // Add Chart Element → Lines / Up/Down Bars (FT-08): over the primary
2759
+ // axis' line series, in spec order.
2760
+ const lineElementOptions = spec;
2761
+ if (!isCircularChart &&
2762
+ (lineElementOptions.dropLines ||
2763
+ (spec.chartType === "line" &&
2764
+ (lineElementOptions.hiLowLines || lineElementOptions.upDownBars)))) {
2765
+ const lineData = chartSeries
2766
+ .filter((s) => s?.type === "line" &&
2767
+ s.yAxisIndex !== 1 &&
2768
+ chartSeriesSpecIndex(s.id) !== undefined &&
2769
+ Array.isArray(s.data))
2770
+ .map((s) => s.data);
2771
+ chartSeries.push(...buildChartLineSeries({
2772
+ dropLines: lineElementOptions.dropLines &&
2773
+ (spec.chartType === "line" || spec.chartType === "area"),
2774
+ hiLowLines: lineElementOptions.hiLowLines,
2775
+ upDownBars: lineElementOptions.upDownBars,
2776
+ }, lineData, { isDarkMode }));
2777
+ }
2722
2778
  // Only redraw chart
2723
2779
  // if chart type has changed
2724
2780
  // if series count has changed. Use the rendered series count
2725
2781
  // (`chartSeries.length`) rather than `spec.series.length` because
2726
2782
  // trendline / error-bar overlays add extra series; comparing the
2727
2783
  // raw spec count would miss those and skip the structural redraw.
2728
- const renderedSeriesCount = chartSeries.length;
2784
+ // A data table's rows are extra x axes: turning it on or off is a
2785
+ // structural change too, so it shifts the count the redraw compares.
2786
+ const renderedSeriesCount = chartSeries.length + (spec.dataTable ? 10_000 : 0);
2729
2787
  const replaceMerge = previousChartType.current !== spec.chartType ||
2730
2788
  previousSeriesCount.current !== renderedSeriesCount
2731
2789
  ? ["series", "xAxis", "yAxis", "legend", "grid", "tooltip"]
2732
2790
  : [];
2733
2791
  const valueAxisBase = {
2792
+ ...axisTextStyle,
2734
2793
  type: "value",
2735
2794
  show: !isCircularChart,
2736
2795
  // echarts draws the value axis line at the zero of a value-type x
@@ -2810,13 +2869,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2810
2869
  visible: finalLegend.show !== false,
2811
2870
  labels: legendLabelsFrom(finalLegend.data, chartSeries.map((s) => s?.name)),
2812
2871
  legendFontSize: finalLegend.textStyle?.fontSize ??
2813
- spec.legend?.textFormat?.fontSize ??
2814
- 12,
2872
+ fontSizePx(spec.legend?.textFormat?.fontSize, CHART_TEXT_PX),
2815
2873
  legendItemWidth: defaultLegendConfig.itemWidth,
2816
2874
  width: width ?? 0,
2817
2875
  height: height ?? 0,
2818
2876
  hasTitle: Boolean(title),
2819
- titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
2877
+ titleFontSize: fontSizePx(spec.titleTextFormat?.fontSize, CHART_TITLE_PX),
2820
2878
  hasBottomAxisTitle: !isCircularChart && Boolean(spec.horizontalAxisTitle),
2821
2879
  bottomAxisNameGap,
2822
2880
  hasLeftAxisTitle: !isCircularChart && Boolean(spec.verticalAxisTitle),
@@ -2847,12 +2905,20 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2847
2905
  const seriesLabelOptionFor = (s) => {
2848
2906
  const specIndex = chartSeriesSpecIndex(s?.id);
2849
2907
  const dataLabelOptions = resolveSeriesDataLabels(chartDataLabels, specIndex === undefined ? undefined : spec.series?.[specIndex]);
2850
- return buildSeriesLabelOption(dataLabelOptions && !dataLabelOptions.numberFormatPattern
2908
+ const withFormat = dataLabelOptions && !dataLabelOptions.numberFormatPattern
2851
2909
  ? {
2852
2910
  ...dataLabelOptions,
2853
2911
  numberFormatPattern: seriesNumberFormatFor(s),
2854
2912
  }
2855
- : dataLabelOptions, s?.type, locale);
2913
+ : dataLabelOptions;
2914
+ // Value From Cells: the label range's cells, point by point.
2915
+ const cellRange = withFormat?.valueFromCells;
2916
+ return buildSeriesLabelOption(cellRange
2917
+ ? {
2918
+ ...withFormat,
2919
+ cellLabels: getDomainValuesFromRange(cellRange, spec.hiddenDimensionStrategy) ?? [],
2920
+ }
2921
+ : withFormat, s?.type, locale);
2856
2922
  };
2857
2923
  // For doughnut, surface the inner-radius hole on each pie series.
2858
2924
  // Excel persists `holeSize` as a 0-90 percentage; we normalize to a
@@ -2918,7 +2984,8 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2918
2984
  }
2919
2985
  const seriesWithLabels = chartSeries.map((s) => {
2920
2986
  const next = { ...s };
2921
- if (DECORATION_SERIES_IDS.has(s?.id)) {
2987
+ if (DECORATION_SERIES_IDS.has(s?.id) ||
2988
+ String(s?.id ?? "").startsWith(CHART_LINE_SERIES_PREFIX)) {
2922
2989
  return next;
2923
2990
  }
2924
2991
  const seriesLabelOption = seriesLabelOptionFor(s);
@@ -2986,6 +3053,66 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2986
3053
  }
2987
3054
  return next;
2988
3055
  });
3056
+ // A chart with no category range (`<c:ser>` without `<c:cat>`) labels its
3057
+ // points 1, 2, 3… like Excel — echarts' bare category axis would number
3058
+ // them from 0 (LV-05).
3059
+ const bareCategoryAxis = chartDomains[0];
3060
+ if (!isCircularChart &&
3061
+ !categoryLevels &&
3062
+ !spec.domains?.length &&
3063
+ bareCategoryAxis &&
3064
+ bareCategoryAxis.data === undefined) {
3065
+ const pointCount = seriesWithLabels.reduce((count, s) => (s?.type === "bar" || s?.type === "line") && Array.isArray(s.data)
3066
+ ? Math.max(count, s.data.length)
3067
+ : count, 0);
3068
+ bareCategoryAxis.data = Array.from({ length: pointCount }, (_, i) => String(i + 1));
3069
+ }
3070
+ // Add Chart Element → Data Table (FT-08): a row per plotted series under
3071
+ // the category axis, values in the series' own number format.
3072
+ const dataTableOptions = spec.dataTable;
3073
+ const dataTableRows = dataTableOptions && !isCircularChart && !isHorizontalBarChart && !isOfPie
3074
+ ? seriesWithLabels
3075
+ .filter((s) => (s?.type === "line" || s?.type === "bar") &&
3076
+ chartSeriesSpecIndex(s?.id) !== undefined &&
3077
+ Array.isArray(s.data))
3078
+ .map((s) => {
3079
+ const specIndex = chartSeriesSpecIndex(s.id);
3080
+ const pattern = seriesNumberFormatFor(s);
3081
+ return {
3082
+ name: String(s.name ?? ""),
3083
+ color: typeof s.itemStyle?.color === "string"
3084
+ ? s.itemStyle.color
3085
+ : spec.series?.[specIndex]?.color ?? paletteColorAt(specIndex),
3086
+ values: s.data.map((d) => {
3087
+ const v = d !== null && typeof d === "object"
3088
+ ? d.originalValue ?? d.value
3089
+ : d;
3090
+ return v === null || v === undefined ? "" : formatAxisValue(pattern, v, locale);
3091
+ }),
3092
+ };
3093
+ })
3094
+ : [];
3095
+ const dataTableAxisList = dataTableRows.length
3096
+ ? dataTableAxes(dataTableRows, dataTableOptions ?? {}, {
3097
+ textColor: axisTextColor,
3098
+ fontSize: CHART_TEXT_PX,
3099
+ borderColor: isDarkMode ? "rgba(255,255,255,0.3)" : "#D9D9D9",
3100
+ })
3101
+ : [];
3102
+ // Excel's automatic bounds and major unit on the cartesian value axes
3103
+ // (LV-05: echarts' own nicing drew steps of 3 where Excel draws 2).
3104
+ const autoScaleEligible = !isCircularChart &&
3105
+ !isOfPie &&
3106
+ AUTO_SCALE_CHART_TYPES.has(spec.chartType) &&
3107
+ !spec.sourceChartType &&
3108
+ spec.stackedType !== "PERCENT_STACKED";
3109
+ const withExcelAutoScale = (axis, opts, axisIndex) => {
3110
+ if (!autoScaleEligible || axis?.type === "log" || opts?.logarithmic) {
3111
+ return axis;
3112
+ }
3113
+ const scale = excelAutoAxisScale(valueAxisExtentValues(seriesWithLabels, axisIndex), { min: opts?.min, max: opts?.max, majorUnit: opts?.majorUnit });
3114
+ return scale ? { ...axis, ...scale } : axis;
3115
+ };
2989
3116
  // Update charts
2990
3117
  chartRef.current?.setOption({
2991
3118
  xAxis: (() => {
@@ -3030,10 +3157,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3030
3157
  }
3031
3158
  if (isHorizontalBarChart) {
3032
3159
  return [
3033
- applyAxisOverrides({
3160
+ withExcelAutoScale(applyAxisOverrides({
3034
3161
  ...valueAxisBase,
3035
3162
  name: spec.horizontalAxisTitle ?? "",
3036
- }, spec.valueAxisOptions, primaryAxisNumberFormat, locale),
3163
+ }, spec.valueAxisOptions, primaryAxisNumberFormat, locale, "x"), spec.valueAxisOptions, 0),
3037
3164
  ];
3038
3165
  }
3039
3166
  const base = chartDomains.length > 0
@@ -3044,14 +3171,18 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3044
3171
  show: showAxis,
3045
3172
  axisLine: { show: true },
3046
3173
  axisTick: { show: true },
3047
- splitLine: { show: true },
3174
+ splitLine: { show: false },
3048
3175
  name: spec.horizontalAxisTitle ?? "",
3049
3176
  nameLocation: "middle",
3050
3177
  nameGap: CATEGORY_AXIS_NAME_GAP,
3051
3178
  },
3052
3179
  ];
3053
3180
  const axes = base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions));
3054
- return boxplotOverlayAxis ? [...axes, boxplotOverlayAxis] : axes;
3181
+ return [
3182
+ ...axes,
3183
+ ...(boxplotOverlayAxis ? [boxplotOverlayAxis] : []),
3184
+ ...dataTableAxisList,
3185
+ ];
3055
3186
  })(),
3056
3187
  yAxis: (() => {
3057
3188
  if (isOfPie) {
@@ -3100,13 +3231,13 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3100
3231
  show: showAxis,
3101
3232
  axisLine: { show: true },
3102
3233
  axisTick: { show: true },
3103
- splitLine: { show: true },
3234
+ splitLine: { show: false },
3104
3235
  name: spec.verticalAxisTitle ?? "",
3105
3236
  nameLocation: "middle",
3106
3237
  nameGap: CATEGORY_AXIS_NAME_GAP,
3107
3238
  },
3108
3239
  ];
3109
- return base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions));
3240
+ return base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions, undefined, undefined, "y"));
3110
3241
  }
3111
3242
  const axes = [
3112
3243
  applyAxisOverrides({
@@ -3125,6 +3256,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3125
3256
  }),
3126
3257
  }, spec.valueAxisOptions, primaryAxisNumberFormat, locale),
3127
3258
  ];
3259
+ axes[0] = withExcelAutoScale(axes[0], spec.valueAxisOptions, 0);
3128
3260
  if (hasSecondaryAxis) {
3129
3261
  axes.push(applyAxisOverrides({
3130
3262
  ...valueAxisBase,
@@ -3139,6 +3271,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3139
3271
  }
3140
3272
  : {}),
3141
3273
  }, spec.secondaryValueAxisOptions, secondaryAxisNumberFormat, locale));
3274
+ if (!isPareto) {
3275
+ axes[1] = withExcelAutoScale(axes[1], spec.secondaryValueAxisOptions, 1);
3276
+ }
3142
3277
  }
3143
3278
  return axes;
3144
3279
  })(),
@@ -3185,10 +3320,8 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3185
3320
  // re-apply these.
3186
3321
  const applyThemeOptions = () => {
3187
3322
  if (chartRef.current) {
3188
- const textColor = getStringifiedColor({
3189
- theme: 1,
3190
- tint: isDarkMode ? 1 : 0,
3191
- }, theme);
3323
+ const textColor = chartTextColor(theme, isDarkMode);
3324
+ const fontFamily = chartFontFamily(theme);
3192
3325
  const bgColor = getStringifiedColor({ theme: 0, tint: isDarkMode ? -1 : 1 }, theme);
3193
3326
  const showAxis = showXAxis(chart.spec.chartType);
3194
3327
  // Authored chart-level text formats override the theme-derived
@@ -3204,18 +3337,24 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3204
3337
  // Every key is written (echarts merges setOption calls, so a key
3205
3338
  // left out keeps its previous value — turning bold off used to
3206
3339
  // leave the title bold, rowsncolumns/spreadsheet#1105).
3340
+ left: "center",
3207
3341
  textStyle: themedTextStyle(authoredTitleFmt, {
3208
3342
  color: textColor,
3209
- fontSize: 18,
3210
- fontWeight: "bold",
3343
+ fontSize: CHART_TITLE_PX,
3344
+ fontFamily,
3345
+ fontWeight: "normal",
3211
3346
  }),
3212
3347
  },
3213
3348
  legend: {
3214
3349
  textStyle: themedTextStyle(authoredLegendFmt, {
3215
3350
  color: textColor,
3216
- fontSize: 12,
3351
+ fontSize: CHART_TEXT_PX,
3352
+ fontFamily,
3217
3353
  }),
3218
3354
  },
3355
+ // Every other chart text (tick labels, axis titles, data labels)
3356
+ // inherits the theme body font.
3357
+ textStyle: { fontFamily },
3219
3358
  axisLabel: {
3220
3359
  color: authoredDataLabelFmt?.color ?? textColor,
3221
3360
  },
@@ -1,10 +1,14 @@
1
1
  import type { EmbeddedChart } from "@rowsncolumns/spreadsheet";
2
2
  export declare const chartTypeToEchartsType: (type: EmbeddedChart["spec"]["chartType"]) => "bar" | "line" | "pie" | "radar" | "boxplot" | "funnel" | "treemap" | "sunburst" | "scatter" | "map";
3
+ export declare const mapMarkerSymbolToEcharts: (symbol: string | undefined | null) => string | undefined;
3
4
  /**
4
- * Map OOXML `<c:marker><c:symbol>` values to echarts symbol names.
5
- * Returns undefined for "auto" / unknown — caller defaults to "circle".
5
+ * The marker Excel draws for a "Line with Markers" series that names no
6
+ * symbol of its own (`<c:lineChart><c:marker val="1"/>` with no
7
+ * `<c:ser><c:marker>`): the automatic cycle by series index — diamond,
8
+ * square, triangle, x, star, circle, plus (LV-05).
6
9
  */
7
- export declare const mapMarkerSymbolToEcharts: (symbol: string | undefined | null) => string | undefined;
10
+ export declare const EXCEL_AUTOMATIC_MARKERS: readonly ["diamond", "square", "triangle", "x", "star", "circle", "plus"];
11
+ export declare const automaticMarkerSymbol: (seriesIndex: number) => "x" | "plus" | "circle" | "square" | "diamond" | "triangle" | "star";
8
12
  /** Pie and doughnut — the pie-family types whose points are slices. */
9
13
  export declare const isCircularChartType: (type: EmbeddedChart["spec"]["chartType"] | undefined) => boolean;
10
14
  /**
@@ -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,YAmBjE,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;AAcF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,KAChC,MAAM,GAAG,SAqBX,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,2EAQ1B,CAAC;AAEX,eAAO,MAAM,qBAAqB,GAAI,aAAa,MAAM,yEAKtD,CAAC;AAEJ,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,YAmBjE,CAAC"}
@@ -50,6 +50,13 @@ export const chartTypeToEchartsType = (type) => {
50
50
  * Map OOXML `<c:marker><c:symbol>` values to echarts symbol names.
51
51
  * Returns undefined for "auto" / unknown — caller defaults to "circle".
52
52
  */
53
+ /** Excel's x / plus / asterisk / dash markers as echarts path symbols (filled outlines). */
54
+ const MARKER_PATHS = {
55
+ x: "path://M2,0L5,3L8,0L10,2L7,5L10,8L8,10L5,7L2,10L0,8L3,5L0,2Z",
56
+ plus: "path://M4,0H6V4H10V6H6V10H4V6H0V4H4Z",
57
+ star: "path://M4.3,0H5.7V3.8L9,1.9L9.7,3.1L6.4,5L9.7,6.9L9,8.1L5.7,6.2V10H4.3V6.2L1,8.1L0.3,6.9L3.6,5L0.3,3.1L1,1.9L4.3,3.8Z",
58
+ dash: "path://M0,4H10V6H0Z",
59
+ };
53
60
  export const mapMarkerSymbolToEcharts = (symbol) => {
54
61
  switch (symbol) {
55
62
  case "circle":
@@ -63,17 +70,33 @@ export const mapMarkerSymbolToEcharts = (symbol) => {
63
70
  return "triangle";
64
71
  case "x":
65
72
  case "plus":
66
- return "pin";
67
73
  case "star":
68
- return "diamond";
69
74
  case "dash":
70
- return "rect";
75
+ return MARKER_PATHS[symbol];
71
76
  case "none":
72
77
  return "none";
73
78
  default:
74
79
  return undefined;
75
80
  }
76
81
  };
82
+ /**
83
+ * The marker Excel draws for a "Line with Markers" series that names no
84
+ * symbol of its own (`<c:lineChart><c:marker val="1"/>` with no
85
+ * `<c:ser><c:marker>`): the automatic cycle by series index — diamond,
86
+ * square, triangle, x, star, circle, plus (LV-05).
87
+ */
88
+ export const EXCEL_AUTOMATIC_MARKERS = [
89
+ "diamond",
90
+ "square",
91
+ "triangle",
92
+ "x",
93
+ "star",
94
+ "circle",
95
+ "plus",
96
+ ];
97
+ export const automaticMarkerSymbol = (seriesIndex) => EXCEL_AUTOMATIC_MARKERS[((seriesIndex % EXCEL_AUTOMATIC_MARKERS.length) +
98
+ EXCEL_AUTOMATIC_MARKERS.length) %
99
+ EXCEL_AUTOMATIC_MARKERS.length];
77
100
  /** Pie and doughnut — the pie-family types whose points are slices. */
78
101
  export const isCircularChartType = (type) => type === "pie" || type === "doughnut";
79
102
  /**
@@ -1,5 +1,12 @@
1
1
  import React from "react";
2
2
  import type { EChartComponentProps } from "../types";
3
+ /**
4
+ * Excel's chart area outline: a thin Text 1 at 15% line (#D9D9D9), no drop
5
+ * shadow (LV-05). A chart with its own Format Chart Area border
6
+ * (`EmbeddedChart.borderColor`) is outlined by the grid's embed frame
7
+ * instead, so it gets none here.
8
+ */
9
+ export declare const chartFrameBorder: (chart: EChartComponentProps["chart"], isDarkMode?: boolean) => string | undefined;
3
10
  export declare const ChartComponent: (props: EChartComponentProps) => React.JSX.Element;
4
11
  export default ChartComponent;
5
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../basic-chart/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAGrD,eAAO,MAAM,cAAc,GAAI,OAAO,oBAAoB,sBAMzD,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../basic-chart/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAGrD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,OAAO,oBAAoB,CAAC,OAAO,CAAC,EACpC,aAAa,OAAO,KACnB,MAAM,GAAG,SAGyD,CAAC;AAEtE,eAAO,MAAM,cAAc,GAAI,OAAO,oBAAoB,sBAazD,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -1,7 +1,21 @@
1
1
  import React from "react";
2
2
  import { BasicChart } from "./basic-chart";
3
+ /**
4
+ * Excel's chart area outline: a thin Text 1 at 15% line (#D9D9D9), no drop
5
+ * shadow (LV-05). A chart with its own Format Chart Area border
6
+ * (`EmbeddedChart.borderColor`) is outlined by the grid's embed frame
7
+ * instead, so it gets none here.
8
+ */
9
+ export const chartFrameBorder = (chart, isDarkMode) => chart?.borderColor
10
+ ? undefined
11
+ : `1px solid ${isDarkMode ? "rgba(255,255,255,0.2)" : "#D9D9D9"}`;
3
12
  export const ChartComponent = (props) => {
4
- return (React.createElement("div", { className: "shadow-lg bg-rnc-background flex-1 flex break-words flex-col" },
13
+ return (React.createElement("div", { className: "bg-rnc-background flex-1 flex break-words flex-col",
14
+ // An inset outline: drawn over the plot's edge without shrinking it.
15
+ style: {
16
+ outline: chartFrameBorder(props.chart, props.isDarkMode),
17
+ outlineOffset: -1,
18
+ } },
5
19
  React.createElement(BasicChart, { ...props })));
6
20
  };
7
21
  export default ChartComponent;
@@ -1 +1 @@
1
- {"version":3,"file":"chart-editor.d.ts","sourceRoot":"","sources":["../../../../components/editors/chart-editor.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,aAAa,EAGd,MAAM,2BAA2B,CAAC;AAwBnC,OAAO,KAAkB,MAAM,OAAO,CAAC;AAIvC,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,+BAA+B,CAAC;AAuDvC,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC;;;;OAIG;IACH,kBAAkB,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAChD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,oBAAoB,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAuBF,eAAO,MAAM,WAAW,GAAI,gFAOzB,gBAAgB,sBAmhClB,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"chart-editor.d.ts","sourceRoot":"","sources":["../../../../components/editors/chart-editor.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,aAAa,EAGd,MAAM,2BAA2B,CAAC;AAwBnC,OAAO,KAAkB,MAAM,OAAO,CAAC;AAIvC,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,+BAA+B,CAAC;AAwDvC,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC;;;;OAIG;IACH,kBAAkB,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAChD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,oBAAoB,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAuBF,eAAO,MAAM,WAAW,GAAI,gFAOzB,gBAAgB,sBAujClB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -14,6 +14,7 @@ import { applyChartColorPalette, applyChartQuickLayout, applyChartStyle, CHART_C
14
14
  import { getChartEditorCapabilities } from "./chart-editor-capabilities";
15
15
  import { ChartRangeInput } from "./chart-range-input";
16
16
  import { AxisOptionsFields } from "./fields/axis-options-fields";
17
+ import { ChartElementsFields } from "./fields/chart-elements-fields";
17
18
  import { chartTypeHasOptions, ChartTypeOptionsFields, } from "./fields/chart-type-options";
18
19
  import { DataLabelsFields } from "./fields/data-labels-fields";
19
20
  import { SeriesFormatFields } from "./fields/series-format-fields";
@@ -322,7 +323,14 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
322
323
  React.createElement(Section, { title: "Chart style", defaultOpen: true },
323
324
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
324
325
  React.createElement("label", { className: "font-system text-sm" }, "Chart title"),
325
- React.createElement(Input, { value: values?.spec.title ?? "", type: "text", onChange: (e) => setFieldValue(`spec.title`, e.target.value), className: "text-xs", placeholder: "Enter chart title" })),
326
+ React.createElement(Input, { value: values?.spec.title ?? "", type: "text", onChange: (e) => setFieldValue(`spec.title`, e.target.value), className: "text-xs", placeholder: "Enter chart title" }),
327
+ React.createElement("label", { className: "font-system text-xs" }, "Link title to cell"),
328
+ React.createElement(ChartRangeInput, { sheetId: sheetId, range: values.spec.titleSource ?? undefined, onChangeRange: (titleSource) => setFieldValue("spec.titleSource", {
329
+ ...titleSource,
330
+ endRowIndex: titleSource.startRowIndex,
331
+ endColumnIndex: titleSource.startColumnIndex,
332
+ }), placeholder: "Select a cell", className: "mb-0" }),
333
+ values.spec.titleSource ? (React.createElement("button", { type: "button", className: "font-system text-xs text-rnc-muted-foreground underline self-start", onClick: () => setFieldValue("spec.titleSource", undefined) }, "Unlink title")) : null),
326
334
  React.createElement(Stack, { direction: "vertical", className: "gap-1 pb-2" },
327
335
  React.createElement("label", { className: "font-system text-sm" }, "Subtitle"),
328
336
  React.createElement(Input, { value: values.spec.subtitle ?? "", type: "text", onChange: (e) => setFieldValue("spec.subtitle", e.target.value || undefined), className: "text-xs", placeholder: "Enter chart subtitle", "aria-label": "Chart subtitle" })),
@@ -400,6 +408,9 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
400
408
  React.createElement("label", { className: "font-system text-sm" }, "Horizontal axis title"),
401
409
  React.createElement(Input, { value: values?.spec.horizontalAxisTitle ?? "", type: "text", onChange: (e) => setFieldValue(`spec.horizontalAxisTitle`, e.target.value), className: "text-xs", placeholder: "Horizontal axis title" }))) : null,
402
410
  caps.hasCategoryAxis ? (React.createElement(AxisOptionsFields, { label: "Category axis", kind: caps.category === "bubble" ? "value" : "category", value: values.spec.categoryAxisOptions, gridlinesDefault: false, onChange: (next) => setFieldValue("spec.categoryAxisOptions", next) })) : null)) : null,
411
+ caps.hasValueAxis &&
412
+ ["line", "area", "column", "combo"].includes(values.spec.chartType) ? (React.createElement(Section, { title: "Chart elements" },
413
+ React.createElement(ChartElementsFields, { chartType: values.spec.chartType, spec: values.spec, onChange: (key, value) => setFieldValue(`spec.${key}`, value) }))) : null,
403
414
  caps.hasDataLabels ? (React.createElement(Section, { title: "Data labels" },
404
415
  React.createElement(DataLabelsFields, { value: values.spec.dataLabels, capabilities: caps, onChange: (dataLabels) => setFieldValue("spec.dataLabels", dataLabels) }))) : null,
405
416
  hasTypeOptions ? (React.createElement(Section, { title: "Chart options" },
@@ -1,9 +1,12 @@
1
1
  import type { ChartAxisOptions } from "@rowsncolumns/spreadsheet";
2
2
  import React from "react";
3
3
  /**
4
- * One axis' options (`ChartAxisOptions`): a value axis has a scale
5
- * (min / max / logarithmic), both have direction, gridlines, visibility
6
- * and tick-label font; a value axis also has its number format.
4
+ * One axis' options (`ChartAxisOptions`) — Excel's Format Axis pane
5
+ * (demo-app-monorepo#49969 FT-08): a value axis has its bounds, log scale
6
+ * and base, major / minor units, display units and number format; a
7
+ * category axis its label / tick-mark intervals; both have where the other
8
+ * axis crosses, direction, major / minor gridlines, tick marks, visibility
9
+ * and tick-label font.
7
10
  */
8
11
  export declare const AxisOptionsFields: ({ label, kind, value, onChange, gridlinesDefault, }: {
9
12
  label: string;