@rowsncolumns/charts 28.1.17 → 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 (90) hide show
  1. package/Readme.md +18 -1
  2. package/dist/cjs/basic-chart/basic-chart.d.ts +1 -1
  3. package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
  4. package/dist/cjs/basic-chart/basic-chart.js +371 -242
  5. package/dist/cjs/basic-chart/helpers.d.ts +7 -3
  6. package/dist/cjs/basic-chart/helpers.d.ts.map +1 -1
  7. package/dist/cjs/basic-chart/helpers.js +30 -4
  8. package/dist/cjs/basic-chart/index.d.ts +7 -0
  9. package/dist/cjs/basic-chart/index.d.ts.map +1 -1
  10. package/dist/cjs/basic-chart/index.js +19 -2
  11. package/dist/cjs/components/editors/chart-editor.d.ts +7 -1
  12. package/dist/cjs/components/editors/chart-editor.d.ts.map +1 -1
  13. package/dist/cjs/components/editors/chart-editor.js +24 -14
  14. package/dist/cjs/components/editors/fields/axis-options-fields.d.ts +6 -3
  15. package/dist/cjs/components/editors/fields/axis-options-fields.d.ts.map +1 -1
  16. package/dist/cjs/components/editors/fields/axis-options-fields.js +69 -7
  17. package/dist/cjs/components/editors/fields/chart-elements-fields.d.ts +15 -0
  18. package/dist/cjs/components/editors/fields/chart-elements-fields.d.ts.map +1 -0
  19. package/dist/cjs/components/editors/fields/chart-elements-fields.js +23 -0
  20. package/dist/cjs/components/editors/fields/series-format-fields.d.ts.map +1 -1
  21. package/dist/cjs/components/editors/fields/series-format-fields.js +16 -2
  22. package/dist/cjs/config.d.ts +10 -0
  23. package/dist/cjs/config.d.ts.map +1 -1
  24. package/dist/cjs/config.js +45 -10
  25. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  26. package/dist/cjs/types.d.ts +10 -0
  27. package/dist/cjs/types.d.ts.map +1 -1
  28. package/dist/cjs/utils/chart-lines.d.ts +36 -0
  29. package/dist/cjs/utils/chart-lines.d.ts.map +1 -0
  30. package/dist/cjs/utils/chart-lines.js +111 -0
  31. package/dist/cjs/utils/data-table.d.ts +31 -0
  32. package/dist/cjs/utils/data-table.d.ts.map +1 -0
  33. package/dist/cjs/utils/data-table.js +61 -0
  34. package/dist/cjs/utils/echarts-gl-loader.d.ts +6 -0
  35. package/dist/cjs/utils/echarts-gl-loader.d.ts.map +1 -1
  36. package/dist/cjs/utils/echarts-gl-loader.js +42 -1
  37. package/dist/cjs/utils/excel-axis-scale.d.ts +35 -0
  38. package/dist/cjs/utils/excel-axis-scale.d.ts.map +1 -0
  39. package/dist/cjs/utils/excel-axis-scale.js +210 -0
  40. package/dist/cjs/utils/overrides.d.ts +24 -3
  41. package/dist/cjs/utils/overrides.d.ts.map +1 -1
  42. package/dist/cjs/utils/overrides.js +119 -14
  43. package/dist/cjs/utils/three-d.d.ts +12 -1
  44. package/dist/cjs/utils/three-d.d.ts.map +1 -1
  45. package/dist/cjs/utils/three-d.js +4 -1
  46. package/dist/esm/basic-chart/basic-chart.d.ts +1 -1
  47. package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
  48. package/dist/esm/basic-chart/basic-chart.js +237 -73
  49. package/dist/esm/basic-chart/helpers.d.ts +7 -3
  50. package/dist/esm/basic-chart/helpers.d.ts.map +1 -1
  51. package/dist/esm/basic-chart/helpers.js +26 -3
  52. package/dist/esm/basic-chart/index.d.ts +7 -0
  53. package/dist/esm/basic-chart/index.d.ts.map +1 -1
  54. package/dist/esm/basic-chart/index.js +15 -1
  55. package/dist/esm/components/editors/chart-editor.d.ts +7 -1
  56. package/dist/esm/components/editors/chart-editor.d.ts.map +1 -1
  57. package/dist/esm/components/editors/chart-editor.js +16 -4
  58. package/dist/esm/components/editors/fields/axis-options-fields.d.ts +6 -3
  59. package/dist/esm/components/editors/fields/axis-options-fields.d.ts.map +1 -1
  60. package/dist/esm/components/editors/fields/axis-options-fields.js +56 -5
  61. package/dist/esm/components/editors/fields/chart-elements-fields.d.ts +15 -0
  62. package/dist/esm/components/editors/fields/chart-elements-fields.d.ts.map +1 -0
  63. package/dist/esm/components/editors/fields/chart-elements-fields.js +17 -0
  64. package/dist/esm/components/editors/fields/series-format-fields.d.ts.map +1 -1
  65. package/dist/esm/components/editors/fields/series-format-fields.js +9 -1
  66. package/dist/esm/config.d.ts +10 -0
  67. package/dist/esm/config.d.ts.map +1 -1
  68. package/dist/esm/config.js +20 -0
  69. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  70. package/dist/esm/types.d.ts +10 -0
  71. package/dist/esm/types.d.ts.map +1 -1
  72. package/dist/esm/utils/chart-lines.d.ts +36 -0
  73. package/dist/esm/utils/chart-lines.d.ts.map +1 -0
  74. package/dist/esm/utils/chart-lines.js +121 -0
  75. package/dist/esm/utils/data-table.d.ts +31 -0
  76. package/dist/esm/utils/data-table.d.ts.map +1 -0
  77. package/dist/esm/utils/data-table.js +48 -0
  78. package/dist/esm/utils/echarts-gl-loader.d.ts +6 -0
  79. package/dist/esm/utils/echarts-gl-loader.d.ts.map +1 -1
  80. package/dist/esm/utils/echarts-gl-loader.js +40 -0
  81. package/dist/esm/utils/excel-axis-scale.d.ts +35 -0
  82. package/dist/esm/utils/excel-axis-scale.d.ts.map +1 -0
  83. package/dist/esm/utils/excel-axis-scale.js +122 -0
  84. package/dist/esm/utils/overrides.d.ts +24 -3
  85. package/dist/esm/utils/overrides.d.ts.map +1 -1
  86. package/dist/esm/utils/overrides.js +111 -10
  87. package/dist/esm/utils/three-d.d.ts +12 -1
  88. package/dist/esm/utils/three-d.d.ts.map +1 -1
  89. package/dist/esm/utils/three-d.js +4 -1
  90. 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
- import { getEchartsGlState, loadEchartsGl, } from "../utils/echarts-gl-loader";
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();
251
- export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, locale, }) => {
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
+ ]);
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);
@@ -428,7 +455,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
428
455
  // SSR, import failure, PIE_3D) the 2-D fallback renders untouched.
429
456
  // The 3-D and region-map gates key off disjoint sourceChartType
430
457
  // tokens, so a spec can never satisfy both.
431
- const threeDRenderKind = resolve3DRenderKind(spec.sourceChartType);
458
+ const threeDRenderKind = resolve3DRenderKind(spec.sourceChartType, { enable3D });
432
459
  const canRender3D = threeDRenderKind !== null && isWebGLAvailable();
433
460
  const willRender3D = canRender3D && getEchartsGlState() === "ready";
434
461
  if (canRender3D && !willRender3D && getEchartsGlState() !== "failed") {
@@ -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,
@@ -1327,38 +1352,63 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1327
1352
  inRange: { color: [...SURFACE_COLOR_RAMP] },
1328
1353
  };
1329
1354
  }
1330
- chartRef.current?.setOption({
1331
- tooltip: {
1332
- textStyle: { fontSize: 12 },
1333
- formatter: threeDTooltipFormatter,
1334
- },
1335
- legend: threeDRenderKind === "line3D" || stackedBar3D
1336
- ? applyLegendOverrides({
1337
- ...defaultLegendConfig,
1338
- show: seriesNames.length > 1,
1339
- data: seriesNames,
1340
- }, spec.legend)
1341
- : { show: false },
1342
- xAxis3D: { ...xAxis3D, name: spec.horizontalAxisTitle ?? "" },
1343
- yAxis3D: { ...yAxis3D, name: "" },
1344
- zAxis3D: { type: "value", name: spec.verticalAxisTitle ?? "" },
1345
- grid3D: {
1346
- boxDepth: Math.max(20, Math.min(100, seriesNames.length * 20)),
1347
- viewControl: { autoRotate: false },
1348
- },
1349
- ...(visualMap ? { visualMap: [visualMap] } : {}),
1350
- series: threeDSeries,
1351
- }, {
1352
- replaceMerge: [
1353
- "series",
1354
- "xAxis3D",
1355
- "yAxis3D",
1356
- "zAxis3D",
1357
- "visualMap",
1358
- "legend",
1359
- "tooltip",
1360
- ],
1361
- });
1355
+ // echarts-gl builds its GL scene inside setOption, and that can
1356
+ // throw after the module loaded fine — claygl compiles compositor
1357
+ // sizes with `new Function`, which a CSP without 'unsafe-eval'
1358
+ // blocks (rowsncolumns/spreadsheet#1187). Latch the failure so no
1359
+ // chart retries 3-D, drop the half-built instance and draw the 2-D
1360
+ // fallback instead of leaving the chart blank.
1361
+ try {
1362
+ chartRef.current?.setOption({
1363
+ tooltip: {
1364
+ textStyle: { fontSize: 12 },
1365
+ formatter: threeDTooltipFormatter,
1366
+ },
1367
+ legend: threeDRenderKind === "line3D" || stackedBar3D
1368
+ ? applyLegendOverrides({
1369
+ ...defaultLegendConfig,
1370
+ show: seriesNames.length > 1,
1371
+ data: seriesNames,
1372
+ }, spec.legend)
1373
+ : { show: false },
1374
+ xAxis3D: { ...xAxis3D, name: spec.horizontalAxisTitle ?? "" },
1375
+ yAxis3D: { ...yAxis3D, name: "" },
1376
+ zAxis3D: { type: "value", name: spec.verticalAxisTitle ?? "" },
1377
+ grid3D: {
1378
+ boxDepth: Math.max(20, Math.min(100, seriesNames.length * 20)),
1379
+ viewControl: { autoRotate: false },
1380
+ },
1381
+ ...(visualMap ? { visualMap: [visualMap] } : {}),
1382
+ series: threeDSeries,
1383
+ }, {
1384
+ replaceMerge: [
1385
+ "series",
1386
+ "xAxis3D",
1387
+ "yAxis3D",
1388
+ "zAxis3D",
1389
+ "visualMap",
1390
+ "legend",
1391
+ "tooltip",
1392
+ ],
1393
+ });
1394
+ }
1395
+ catch (error) {
1396
+ markEchartsGlFailed(error);
1397
+ try {
1398
+ chartRef.current?.dispose();
1399
+ }
1400
+ catch {
1401
+ // A GL instance that threw mid-build may not dispose cleanly.
1402
+ }
1403
+ chartRef.current = undefined;
1404
+ renderedIn3D.current = false;
1405
+ previousChartType.current = undefined;
1406
+ previousSeriesCount.current = undefined;
1407
+ initializeChart();
1408
+ applyThemeOptions();
1409
+ await updateChart();
1410
+ return;
1411
+ }
1362
1412
  // Re-apply theme-derived title / palette / tooltip colors — lost
1363
1413
  // when the instance was re-initialized on entry into 3-D.
1364
1414
  renderedIn3D.current = true;
@@ -1420,13 +1470,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1420
1470
  visible: radarLegend.show !== false,
1421
1471
  labels: radarData.map((d) => d.name),
1422
1472
  legendFontSize: radarLegend.textStyle?.fontSize ??
1423
- spec.legend?.textFormat?.fontSize ??
1424
- 12,
1473
+ fontSizePx(spec.legend?.textFormat?.fontSize, CHART_TEXT_PX),
1425
1474
  legendItemWidth: defaultLegendConfig.itemWidth,
1426
1475
  width: width ?? 0,
1427
1476
  height: height ?? 0,
1428
1477
  hasTitle: Boolean(explicitChartTitle),
1429
- titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
1478
+ titleFontSize: fontSizePx(spec.titleTextFormat?.fontSize, CHART_TITLE_PX),
1430
1479
  hasBottomAxisTitle: false,
1431
1480
  bottomAxisNameGap: 0,
1432
1481
  hasLeftAxisTitle: false,
@@ -1727,6 +1776,13 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1727
1776
  return;
1728
1777
  }
1729
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
+ };
1730
1786
  const isCircularChart = spec.chartType === "pie" || spec.chartType === "doughnut";
1731
1787
  // Pie-of-pie / bar-of-pie composite. The of-pie branch below fills
1732
1788
  // `ofPieLayout` so the grid/axis assembly at the bottom can add the
@@ -1753,7 +1809,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1753
1809
  show: showAxis,
1754
1810
  axisLine: { show: true },
1755
1811
  axisTick: { show: true },
1756
- splitLine: { show: true },
1812
+ splitLine: { show: false },
1757
1813
  name: isHorizontalBarChart
1758
1814
  ? spec.verticalAxisTitle ?? ""
1759
1815
  : spec.horizontalAxisTitle ?? "",
@@ -1777,12 +1833,15 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1777
1833
  : spec.horizontalAxisTitle ?? ""
1778
1834
  : undefined;
1779
1835
  chartDomains.push({
1836
+ ...axisTextStyle,
1780
1837
  type: "category",
1781
1838
  data: axisData,
1782
1839
  show: showAxis && !isCircularChart,
1783
1840
  axisLine: { show: !isCircularChart },
1784
1841
  axisTick: { show: !isCircularChart },
1785
- 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 },
1786
1845
  name: axisTitle,
1787
1846
  nameLocation: "middle",
1788
1847
  nameGap: CATEGORY_AXIS_NAME_GAP,
@@ -1790,11 +1849,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1790
1849
  });
1791
1850
  if (chartDomains.length === 0 && !isCircularChart) {
1792
1851
  chartDomains.push({
1852
+ ...axisTextStyle,
1793
1853
  type: "category",
1794
1854
  show: showAxis,
1795
1855
  axisLine: { show: true },
1796
1856
  axisTick: { show: true },
1797
- splitLine: { show: true },
1857
+ splitLine: { show: false },
1798
1858
  gridIndex: 0,
1799
1859
  name: isHorizontalBarChart
1800
1860
  ? spec.verticalAxisTitle ?? ""
@@ -1964,8 +2024,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
1964
2024
  seriesEntry.showSymbol = wantsMarkers || labelsWithoutMarkers;
1965
2025
  if (wantsMarkers) {
1966
2026
  seriesEntry.symbol =
1967
- mapMarkerSymbolToEcharts(markerSymbol) ?? "circle";
1968
- 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);
1969
2030
  }
1970
2031
  else {
1971
2032
  if (labelsWithoutMarkers) {
@@ -2694,18 +2755,41 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2694
2755
  }
2695
2756
  legend.data = [];
2696
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
+ }
2697
2778
  // Only redraw chart
2698
2779
  // if chart type has changed
2699
2780
  // if series count has changed. Use the rendered series count
2700
2781
  // (`chartSeries.length`) rather than `spec.series.length` because
2701
2782
  // trendline / error-bar overlays add extra series; comparing the
2702
2783
  // raw spec count would miss those and skip the structural redraw.
2703
- 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);
2704
2787
  const replaceMerge = previousChartType.current !== spec.chartType ||
2705
2788
  previousSeriesCount.current !== renderedSeriesCount
2706
2789
  ? ["series", "xAxis", "yAxis", "legend", "grid", "tooltip"]
2707
2790
  : [];
2708
2791
  const valueAxisBase = {
2792
+ ...axisTextStyle,
2709
2793
  type: "value",
2710
2794
  show: !isCircularChart,
2711
2795
  // echarts draws the value axis line at the zero of a value-type x
@@ -2785,13 +2869,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2785
2869
  visible: finalLegend.show !== false,
2786
2870
  labels: legendLabelsFrom(finalLegend.data, chartSeries.map((s) => s?.name)),
2787
2871
  legendFontSize: finalLegend.textStyle?.fontSize ??
2788
- spec.legend?.textFormat?.fontSize ??
2789
- 12,
2872
+ fontSizePx(spec.legend?.textFormat?.fontSize, CHART_TEXT_PX),
2790
2873
  legendItemWidth: defaultLegendConfig.itemWidth,
2791
2874
  width: width ?? 0,
2792
2875
  height: height ?? 0,
2793
2876
  hasTitle: Boolean(title),
2794
- titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
2877
+ titleFontSize: fontSizePx(spec.titleTextFormat?.fontSize, CHART_TITLE_PX),
2795
2878
  hasBottomAxisTitle: !isCircularChart && Boolean(spec.horizontalAxisTitle),
2796
2879
  bottomAxisNameGap,
2797
2880
  hasLeftAxisTitle: !isCircularChart && Boolean(spec.verticalAxisTitle),
@@ -2822,12 +2905,20 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2822
2905
  const seriesLabelOptionFor = (s) => {
2823
2906
  const specIndex = chartSeriesSpecIndex(s?.id);
2824
2907
  const dataLabelOptions = resolveSeriesDataLabels(chartDataLabels, specIndex === undefined ? undefined : spec.series?.[specIndex]);
2825
- return buildSeriesLabelOption(dataLabelOptions && !dataLabelOptions.numberFormatPattern
2908
+ const withFormat = dataLabelOptions && !dataLabelOptions.numberFormatPattern
2826
2909
  ? {
2827
2910
  ...dataLabelOptions,
2828
2911
  numberFormatPattern: seriesNumberFormatFor(s),
2829
2912
  }
2830
- : 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);
2831
2922
  };
2832
2923
  // For doughnut, surface the inner-radius hole on each pie series.
2833
2924
  // Excel persists `holeSize` as a 0-90 percentage; we normalize to a
@@ -2893,7 +2984,8 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2893
2984
  }
2894
2985
  const seriesWithLabels = chartSeries.map((s) => {
2895
2986
  const next = { ...s };
2896
- if (DECORATION_SERIES_IDS.has(s?.id)) {
2987
+ if (DECORATION_SERIES_IDS.has(s?.id) ||
2988
+ String(s?.id ?? "").startsWith(CHART_LINE_SERIES_PREFIX)) {
2897
2989
  return next;
2898
2990
  }
2899
2991
  const seriesLabelOption = seriesLabelOptionFor(s);
@@ -2961,6 +3053,66 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
2961
3053
  }
2962
3054
  return next;
2963
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
+ };
2964
3116
  // Update charts
2965
3117
  chartRef.current?.setOption({
2966
3118
  xAxis: (() => {
@@ -3005,10 +3157,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3005
3157
  }
3006
3158
  if (isHorizontalBarChart) {
3007
3159
  return [
3008
- applyAxisOverrides({
3160
+ withExcelAutoScale(applyAxisOverrides({
3009
3161
  ...valueAxisBase,
3010
3162
  name: spec.horizontalAxisTitle ?? "",
3011
- }, spec.valueAxisOptions, primaryAxisNumberFormat, locale),
3163
+ }, spec.valueAxisOptions, primaryAxisNumberFormat, locale, "x"), spec.valueAxisOptions, 0),
3012
3164
  ];
3013
3165
  }
3014
3166
  const base = chartDomains.length > 0
@@ -3019,14 +3171,18 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3019
3171
  show: showAxis,
3020
3172
  axisLine: { show: true },
3021
3173
  axisTick: { show: true },
3022
- splitLine: { show: true },
3174
+ splitLine: { show: false },
3023
3175
  name: spec.horizontalAxisTitle ?? "",
3024
3176
  nameLocation: "middle",
3025
3177
  nameGap: CATEGORY_AXIS_NAME_GAP,
3026
3178
  },
3027
3179
  ];
3028
3180
  const axes = base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions));
3029
- return boxplotOverlayAxis ? [...axes, boxplotOverlayAxis] : axes;
3181
+ return [
3182
+ ...axes,
3183
+ ...(boxplotOverlayAxis ? [boxplotOverlayAxis] : []),
3184
+ ...dataTableAxisList,
3185
+ ];
3030
3186
  })(),
3031
3187
  yAxis: (() => {
3032
3188
  if (isOfPie) {
@@ -3075,13 +3231,13 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3075
3231
  show: showAxis,
3076
3232
  axisLine: { show: true },
3077
3233
  axisTick: { show: true },
3078
- splitLine: { show: true },
3234
+ splitLine: { show: false },
3079
3235
  name: spec.verticalAxisTitle ?? "",
3080
3236
  nameLocation: "middle",
3081
3237
  nameGap: CATEGORY_AXIS_NAME_GAP,
3082
3238
  },
3083
3239
  ];
3084
- return base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions));
3240
+ return base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions, undefined, undefined, "y"));
3085
3241
  }
3086
3242
  const axes = [
3087
3243
  applyAxisOverrides({
@@ -3100,6 +3256,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3100
3256
  }),
3101
3257
  }, spec.valueAxisOptions, primaryAxisNumberFormat, locale),
3102
3258
  ];
3259
+ axes[0] = withExcelAutoScale(axes[0], spec.valueAxisOptions, 0);
3103
3260
  if (hasSecondaryAxis) {
3104
3261
  axes.push(applyAxisOverrides({
3105
3262
  ...valueAxisBase,
@@ -3114,6 +3271,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3114
3271
  }
3115
3272
  : {}),
3116
3273
  }, spec.secondaryValueAxisOptions, secondaryAxisNumberFormat, locale));
3274
+ if (!isPareto) {
3275
+ axes[1] = withExcelAutoScale(axes[1], spec.secondaryValueAxisOptions, 1);
3276
+ }
3117
3277
  }
3118
3278
  return axes;
3119
3279
  })(),
@@ -3160,10 +3320,8 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3160
3320
  // re-apply these.
3161
3321
  const applyThemeOptions = () => {
3162
3322
  if (chartRef.current) {
3163
- const textColor = getStringifiedColor({
3164
- theme: 1,
3165
- tint: isDarkMode ? 1 : 0,
3166
- }, theme);
3323
+ const textColor = chartTextColor(theme, isDarkMode);
3324
+ const fontFamily = chartFontFamily(theme);
3167
3325
  const bgColor = getStringifiedColor({ theme: 0, tint: isDarkMode ? -1 : 1 }, theme);
3168
3326
  const showAxis = showXAxis(chart.spec.chartType);
3169
3327
  // Authored chart-level text formats override the theme-derived
@@ -3179,18 +3337,24 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
3179
3337
  // Every key is written (echarts merges setOption calls, so a key
3180
3338
  // left out keeps its previous value — turning bold off used to
3181
3339
  // leave the title bold, rowsncolumns/spreadsheet#1105).
3340
+ left: "center",
3182
3341
  textStyle: themedTextStyle(authoredTitleFmt, {
3183
3342
  color: textColor,
3184
- fontSize: 18,
3185
- fontWeight: "bold",
3343
+ fontSize: CHART_TITLE_PX,
3344
+ fontFamily,
3345
+ fontWeight: "normal",
3186
3346
  }),
3187
3347
  },
3188
3348
  legend: {
3189
3349
  textStyle: themedTextStyle(authoredLegendFmt, {
3190
3350
  color: textColor,
3191
- fontSize: 12,
3351
+ fontSize: CHART_TEXT_PX,
3352
+ fontFamily,
3192
3353
  }),
3193
3354
  },
3355
+ // Every other chart text (tick labels, axis titles, data labels)
3356
+ // inherits the theme body font.
3357
+ textStyle: { fontFamily },
3194
3358
  axisLabel: {
3195
3359
  color: authoredDataLabelFmt?.color ?? textColor,
3196
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
  /**