@rowsncolumns/charts 28.1.5 → 28.1.7
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.
- package/dist/cjs/basic-chart/basic-chart.d.ts +1 -1
- package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/cjs/basic-chart/basic-chart.js +161 -143
- package/dist/cjs/components/editors/chart-editor.d.ts.map +1 -1
- package/dist/cjs/components/editors/chart-editor.js +5 -1
- package/dist/cjs/plotly/basic-chart/basic-chart.d.ts +1 -1
- package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/cjs/plotly/basic-chart/basic-chart.js +63 -16
- package/dist/cjs/utils/axis-number-format.d.ts +26 -7
- package/dist/cjs/utils/axis-number-format.d.ts.map +1 -1
- package/dist/cjs/utils/axis-number-format.js +94 -16
- package/dist/cjs/utils/overrides.d.ts +3 -3
- package/dist/cjs/utils/overrides.d.ts.map +1 -1
- package/dist/cjs/utils/overrides.js +32 -15
- package/dist/esm/basic-chart/basic-chart.d.ts +1 -1
- package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/basic-chart/basic-chart.js +75 -53
- package/dist/esm/components/editors/chart-editor.d.ts.map +1 -1
- package/dist/esm/components/editors/chart-editor.js +5 -1
- package/dist/esm/plotly/basic-chart/basic-chart.d.ts +1 -1
- package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/plotly/basic-chart/basic-chart.js +63 -18
- package/dist/esm/utils/axis-number-format.d.ts +26 -7
- package/dist/esm/utils/axis-number-format.d.ts.map +1 -1
- package/dist/esm/utils/axis-number-format.js +83 -13
- package/dist/esm/utils/overrides.d.ts +3 -3
- package/dist/esm/utils/overrides.d.ts.map +1 -1
- package/dist/esm/utils/overrides.js +33 -16
- package/package.json +4 -4
|
@@ -3,7 +3,7 @@ import { getStringifiedColor, useFocusSheet, useRegisterChartActions, useSetSele
|
|
|
3
3
|
import { useSubscribeToMultipleRanges } from "@rowsncolumns/spreadsheet-state";
|
|
4
4
|
import { createDownloadableFile } from "@rowsncolumns/toolkit";
|
|
5
5
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, DropdownRightSlot, IconButton, useIsomorphicLayoutEffect, useOnVisibilityChange, } from "@rowsncolumns/ui";
|
|
6
|
-
import { debug, isAFormula, isNil } from "@rowsncolumns/utils";
|
|
6
|
+
import { chartPaletteColorAt, debug, expandChartPalette, isAFormula, isNil, } from "@rowsncolumns/utils";
|
|
7
7
|
import { BarChart, BoxplotChart, CandlestickChart, CustomChart, FunnelChart, HeatmapChart, LineChart, PieChart, RadarChart as EchartsRadarChart, ScatterChart, SunburstChart, TreemapChart as EchartsTreemapChart, } from "echarts/charts";
|
|
8
8
|
import { DatasetComponent, GridComponent, LegendComponent, RadarComponent, TitleComponent, TooltipComponent, VisualMapComponent, } from "echarts/components";
|
|
9
9
|
import * as echarts from "echarts/core";
|
|
@@ -183,7 +183,7 @@ const defaultLegendConfig = {
|
|
|
183
183
|
};
|
|
184
184
|
/** Category-axis title gap: clears one row of tick labels (#986). */
|
|
185
185
|
const CATEGORY_AXIS_NAME_GAP = categoryAxisNameGap();
|
|
186
|
-
export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, }) => {
|
|
186
|
+
export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, locale, }) => {
|
|
187
187
|
const spec = chart.spec;
|
|
188
188
|
const explicitChartTitle = chart.spec.title ?? undefined;
|
|
189
189
|
const hierarchyTitleRef = useRef(explicitChartTitle);
|
|
@@ -454,7 +454,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
454
454
|
? "rgba(255,255,255,0.08)"
|
|
455
455
|
: "rgba(0,0,0,0.06)";
|
|
456
456
|
const borderColor = isDarkMode ? "rgba(255,255,255,0.3)" : "#FFFFFF";
|
|
457
|
-
const regionSeriesLabel = buildSeriesLabelOption(spec.dataLabels);
|
|
457
|
+
const regionSeriesLabel = buildSeriesLabelOption(spec.dataLabels, undefined, locale);
|
|
458
458
|
const seriesName = seriesEntry?.dataLabel && !isAFormula(seriesEntry.dataLabel)
|
|
459
459
|
? seriesEntry.dataLabel
|
|
460
460
|
: "";
|
|
@@ -582,7 +582,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
582
582
|
// Spec-level overrides — axis min/max/visibility/textFormat, legend
|
|
583
583
|
// position/visibility, dataLabel toggles. Same helpers as the
|
|
584
584
|
// non-bubble layout below.
|
|
585
|
-
const bubbleSeriesLabel = buildSeriesLabelOption(spec.dataLabels);
|
|
585
|
+
const bubbleSeriesLabel = buildSeriesLabelOption(spec.dataLabels, undefined, locale);
|
|
586
586
|
chartRef.current?.setOption({
|
|
587
587
|
xAxis: [
|
|
588
588
|
applyAxisOverrides({ ...xAxisConfig }, spec.categoryAxisOptions),
|
|
@@ -593,7 +593,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
593
593
|
name: spec.verticalAxisTitle ?? "",
|
|
594
594
|
nameLocation: "middle",
|
|
595
595
|
nameGap: 45,
|
|
596
|
-
}, spec.valueAxisOptions),
|
|
596
|
+
}, spec.valueAxisOptions, undefined, locale),
|
|
597
597
|
],
|
|
598
598
|
legend: applyLegendOverrides({
|
|
599
599
|
...legend,
|
|
@@ -635,15 +635,20 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
635
635
|
scatterStyle === "smoothMarker" ||
|
|
636
636
|
!scatterStyle);
|
|
637
637
|
const smooth = scatterStyle === "smooth" || scatterStyle === "smoothMarker";
|
|
638
|
+
// Labels hang off the point symbols: a marker-less line keeps
|
|
639
|
+
// an invisible 0-size symbol per point when they show (#1049).
|
|
640
|
+
const labelsWithoutMarkers = !wantsMarkers && Boolean(spec.dataLabels?.show);
|
|
638
641
|
return wantsLine
|
|
639
642
|
? {
|
|
640
643
|
type: "line",
|
|
641
644
|
name: entry.name,
|
|
642
645
|
data: entry.points,
|
|
643
|
-
showSymbol: wantsMarkers,
|
|
646
|
+
showSymbol: wantsMarkers || labelsWithoutMarkers,
|
|
644
647
|
smooth,
|
|
645
648
|
symbol: mapMarkerSymbolToEcharts(symbol) ?? "circle",
|
|
646
|
-
symbolSize:
|
|
649
|
+
symbolSize: labelsWithoutMarkers
|
|
650
|
+
? 0
|
|
651
|
+
: perMarker?.size ?? 6,
|
|
647
652
|
...(seriesColor
|
|
648
653
|
? {
|
|
649
654
|
itemStyle: { color: seriesColor },
|
|
@@ -715,14 +720,14 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
715
720
|
const value = Array.isArray(params.value)
|
|
716
721
|
? params.value[params.value.length - 1]
|
|
717
722
|
: params.value ?? "";
|
|
718
|
-
return `${params.name}: ${formatAxisValue(sizeNumberFormat, value)}`;
|
|
723
|
+
return `${params.name}: ${formatAxisValue(sizeNumberFormat, value, locale)}`;
|
|
719
724
|
};
|
|
720
725
|
// Spec-level overrides — treemap/sunburst default to no legend,
|
|
721
726
|
// but an authored legend (visibility/position) still wins.
|
|
722
727
|
const hierarchyDataLabels = spec.dataLabels;
|
|
723
728
|
const hierarchySeriesLabel = buildSeriesLabelOption(hierarchyDataLabels && !hierarchyDataLabels.numberFormatPattern
|
|
724
729
|
? { ...hierarchyDataLabels, numberFormatPattern: sizeNumberFormat }
|
|
725
|
-
: hierarchyDataLabels);
|
|
730
|
+
: hierarchyDataLabels, undefined, locale);
|
|
726
731
|
chartRef.current?.setOption({
|
|
727
732
|
xAxis: [
|
|
728
733
|
{
|
|
@@ -818,7 +823,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
818
823
|
const [zMin, zMax] = getValueExtent(contourPoints.filter((point) => typeof point[2] === "number"));
|
|
819
824
|
// Values format like the cells of the series (row) they came from.
|
|
820
825
|
const contourFormats = resolveSeriesNumberFormats(spec.series, getFormattingFromRange, spec.hiddenDimensionStrategy);
|
|
821
|
-
const contourLabel = buildSeriesLabelOption(spec.dataLabels);
|
|
826
|
+
const contourLabel = buildSeriesLabelOption(spec.dataLabels, undefined, locale);
|
|
822
827
|
if (!renderedAsContour.current && chartRef.current) {
|
|
823
828
|
chartRef.current.dispose();
|
|
824
829
|
chartRef.current = undefined;
|
|
@@ -835,7 +840,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
835
840
|
const [x, y, z] = (params?.value ?? []);
|
|
836
841
|
const value = z === "-" || z === undefined
|
|
837
842
|
? ""
|
|
838
|
-
: formatAxisValue(contourFormats[y], z);
|
|
843
|
+
: formatAxisValue(contourFormats[y], z, locale);
|
|
839
844
|
return `${seriesNames[y] ?? ""}<br/>${categories[x] ?? ""}: ${value}`;
|
|
840
845
|
},
|
|
841
846
|
},
|
|
@@ -871,7 +876,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
871
876
|
right: 0,
|
|
872
877
|
top: "center",
|
|
873
878
|
inRange: { color: [...SURFACE_COLOR_RAMP] },
|
|
874
|
-
formatter: (value) => formatAxisValue(contourFormats[0], value),
|
|
879
|
+
formatter: (value) => formatAxisValue(contourFormats[0], value, locale),
|
|
875
880
|
},
|
|
876
881
|
],
|
|
877
882
|
series: [
|
|
@@ -888,7 +893,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
888
893
|
const [, y, z] = (params?.value ?? []);
|
|
889
894
|
return z === "-" || z === undefined
|
|
890
895
|
? ""
|
|
891
|
-
: formatAxisValue(contourFormats[y], z);
|
|
896
|
+
: formatAxisValue(contourFormats[y], z, locale);
|
|
892
897
|
},
|
|
893
898
|
},
|
|
894
899
|
}
|
|
@@ -942,7 +947,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
942
947
|
theme.themeColors.accent6,
|
|
943
948
|
]);
|
|
944
949
|
const seriesColorAt = (index) => spec.series?.[index]?.color ??
|
|
945
|
-
threeDPalette
|
|
950
|
+
chartPaletteColorAt(threeDPalette, index);
|
|
946
951
|
// Entering 3-D from a 2-D render: the cartesian option doesn't
|
|
947
952
|
// merge into a grid3D one, so re-initialize the echarts instance
|
|
948
953
|
// from scratch (theme options are re-applied after setOption).
|
|
@@ -980,7 +985,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
980
985
|
: seriesNames[Math.round(value[1] ?? 0)] ?? params?.seriesName ?? "";
|
|
981
986
|
const valueLabel = spec.stackedType === "PERCENT_STACKED" && stackedBar3D
|
|
982
987
|
? `${Math.round((value[2] ?? 0) * 10) / 10}%`
|
|
983
|
-
: formatAxisValue(threeDFormats[seriesIndex], value[2]);
|
|
988
|
+
: formatAxisValue(threeDFormats[seriesIndex], value[2], locale);
|
|
984
989
|
return `${seriesLabel ? `${seriesLabel}<br/>` : ""}${categoryLabel}: ${valueLabel}`;
|
|
985
990
|
};
|
|
986
991
|
let threeDSeries;
|
|
@@ -1152,11 +1157,11 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1152
1157
|
...radarDataLabels,
|
|
1153
1158
|
numberFormatPattern: radarFormats[seriesIndex],
|
|
1154
1159
|
}
|
|
1155
|
-
: radarDataLabels);
|
|
1160
|
+
: radarDataLabels, undefined, locale);
|
|
1156
1161
|
const radarTooltipFormatter = (params) => {
|
|
1157
1162
|
const values = Array.isArray(params?.value) ? params.value : [];
|
|
1158
1163
|
const pattern = radarFormats[params?.dataIndex ?? 0];
|
|
1159
|
-
const lines = indicators.map((indicator, i) => `${indicator.name}: ${formatAxisValue(pattern, values[i])}`);
|
|
1164
|
+
const lines = indicators.map((indicator, i) => `${indicator.name}: ${formatAxisValue(pattern, values[i], locale)}`);
|
|
1160
1165
|
return [params?.name ?? "", ...lines].filter(Boolean).join("<br/>");
|
|
1161
1166
|
};
|
|
1162
1167
|
chartRef.current?.setOption({
|
|
@@ -1230,7 +1235,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1230
1235
|
const stockDataLabels = spec.dataLabels;
|
|
1231
1236
|
const stockSeriesLabelFor = (role) => buildSeriesLabelOption(stockDataLabels && !stockDataLabels.numberFormatPattern
|
|
1232
1237
|
? { ...stockDataLabels, numberFormatPattern: stockFormatFor(role) }
|
|
1233
|
-
: stockDataLabels);
|
|
1238
|
+
: stockDataLabels, undefined, locale);
|
|
1234
1239
|
// V-variants (VHLC / VOHLC) carry a volume series that renders as
|
|
1235
1240
|
// a column sub-pane under the price plot: two grids stacked
|
|
1236
1241
|
// vertically (price ~70% of the plot, volume below on its own
|
|
@@ -1295,10 +1300,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1295
1300
|
value: p.value,
|
|
1296
1301
|
itemStyle: { color: p.up ? upColor : downColor },
|
|
1297
1302
|
})),
|
|
1298
|
-
...(tooltipValueFormatter(stockFormatFor("volume"))
|
|
1303
|
+
...(tooltipValueFormatter(stockFormatFor("volume"), locale)
|
|
1299
1304
|
? {
|
|
1300
1305
|
tooltip: {
|
|
1301
|
-
valueFormatter: tooltipValueFormatter(stockFormatFor("volume")),
|
|
1306
|
+
valueFormatter: tooltipValueFormatter(stockFormatFor("volume"), locale),
|
|
1302
1307
|
},
|
|
1303
1308
|
}
|
|
1304
1309
|
: {}),
|
|
@@ -1326,7 +1331,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1326
1331
|
nameLocation: "middle",
|
|
1327
1332
|
nameGap: 50,
|
|
1328
1333
|
gridIndex: 0,
|
|
1329
|
-
}, spec.valueAxisOptions),
|
|
1334
|
+
}, spec.valueAxisOptions, undefined, locale),
|
|
1330
1335
|
...(hasVolume ? [volumeYAxis] : []),
|
|
1331
1336
|
],
|
|
1332
1337
|
legend: applyLegendOverrides({ ...legend, show: false }, spec.legend),
|
|
@@ -1344,10 +1349,10 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1344
1349
|
borderColor: upColor,
|
|
1345
1350
|
borderColor0: downColor,
|
|
1346
1351
|
},
|
|
1347
|
-
...(tooltipValueFormatter(stockFormatFor("close"))
|
|
1352
|
+
...(tooltipValueFormatter(stockFormatFor("close"), locale)
|
|
1348
1353
|
? {
|
|
1349
1354
|
tooltip: {
|
|
1350
|
-
valueFormatter: tooltipValueFormatter(stockFormatFor("close")),
|
|
1355
|
+
valueFormatter: tooltipValueFormatter(stockFormatFor("close"), locale),
|
|
1351
1356
|
},
|
|
1352
1357
|
}
|
|
1353
1358
|
: {}),
|
|
@@ -1385,7 +1390,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1385
1390
|
nameLocation: "middle",
|
|
1386
1391
|
nameGap: 50,
|
|
1387
1392
|
gridIndex: 0,
|
|
1388
|
-
}, spec.valueAxisOptions),
|
|
1393
|
+
}, spec.valueAxisOptions, undefined, locale),
|
|
1389
1394
|
...(hasVolume ? [volumeYAxis] : []),
|
|
1390
1395
|
],
|
|
1391
1396
|
legend: applyLegendOverrides({
|
|
@@ -1402,15 +1407,15 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1402
1407
|
name: s.name,
|
|
1403
1408
|
data: s.values,
|
|
1404
1409
|
showSymbol: false,
|
|
1405
|
-
itemStyle: { color: stockColors
|
|
1406
|
-
lineStyle: { color: stockColors
|
|
1410
|
+
itemStyle: { color: chartPaletteColorAt(stockColors, i) },
|
|
1411
|
+
lineStyle: { color: chartPaletteColorAt(stockColors, i) },
|
|
1407
1412
|
...(stockSeriesLabelFor(s.role)
|
|
1408
1413
|
? { label: stockSeriesLabelFor(s.role) }
|
|
1409
1414
|
: {}),
|
|
1410
|
-
...(tooltipValueFormatter(stockFormatFor(s.role))
|
|
1415
|
+
...(tooltipValueFormatter(stockFormatFor(s.role), locale)
|
|
1411
1416
|
? {
|
|
1412
1417
|
tooltip: {
|
|
1413
|
-
valueFormatter: tooltipValueFormatter(stockFormatFor(s.role)),
|
|
1418
|
+
valueFormatter: tooltipValueFormatter(stockFormatFor(s.role), locale),
|
|
1414
1419
|
},
|
|
1415
1420
|
}
|
|
1416
1421
|
: {}),
|
|
@@ -1688,7 +1693,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1688
1693
|
theme.themeColors.accent5,
|
|
1689
1694
|
theme.themeColors.accent6,
|
|
1690
1695
|
]);
|
|
1691
|
-
const paletteColorAt = (index) => seriesPalette
|
|
1696
|
+
const paletteColorAt = (index) => chartPaletteColorAt(seriesPalette, index);
|
|
1692
1697
|
for (const [specSeriesIndex, series] of (spec.series ?? []).entries()) {
|
|
1693
1698
|
const dataLabel = !isNil(series.dataLabel) && isAFormula(series.dataLabel)
|
|
1694
1699
|
? coerceCalculatedLabel(await onRequestCalculate(series.dataLabel, position.sheetId ?? sheetId, position.overlayPosition.anchorCell.rowIndex, position.overlayPosition.anchorCell.columnIndex))
|
|
@@ -1779,26 +1784,38 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1779
1784
|
const wantsMarkers = markerSymbol === "none"
|
|
1780
1785
|
? false
|
|
1781
1786
|
: Boolean(spec.showMarkers) || !isNil(markerSymbol);
|
|
1782
|
-
|
|
1787
|
+
// echarts draws a line's data labels on its point symbols, so a
|
|
1788
|
+
// marker-less line with labels (Excel's default "Line" with values
|
|
1789
|
+
// shown) keeps an invisible 0-size symbol per point for the label
|
|
1790
|
+
// to hang off (#1049).
|
|
1791
|
+
const labelsWithoutMarkers = !wantsMarkers && Boolean(spec.dataLabels?.show);
|
|
1792
|
+
seriesEntry.showSymbol = wantsMarkers || labelsWithoutMarkers;
|
|
1783
1793
|
if (wantsMarkers) {
|
|
1784
1794
|
seriesEntry.symbol =
|
|
1785
1795
|
mapMarkerSymbolToEcharts(markerSymbol) ?? "circle";
|
|
1786
1796
|
seriesEntry.symbolSize = seriesAny.marker?.size ?? 6;
|
|
1787
1797
|
}
|
|
1788
|
-
else
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1798
|
+
else {
|
|
1799
|
+
if (labelsWithoutMarkers) {
|
|
1800
|
+
seriesEntry.symbol = "circle";
|
|
1801
|
+
seriesEntry.symbolSize = 0;
|
|
1802
|
+
}
|
|
1803
|
+
if (Array.isArray(data)) {
|
|
1804
|
+
// A marker-less line draws nothing for a point with a gap on both
|
|
1805
|
+
// sides (a data row followed by a blank row), so that row vanishes
|
|
1806
|
+
// from the chart. Excel plots an isolated marker there: keep the
|
|
1807
|
+
// series symbol-less and mark only those points.
|
|
1808
|
+
const isolated = isolatedPointIndexes(data, connectsAcrossEmptyCells(spec, lineLike));
|
|
1809
|
+
if (isolated.length) {
|
|
1810
|
+
seriesEntry.showSymbol = true;
|
|
1811
|
+
if (!labelsWithoutMarkers)
|
|
1812
|
+
seriesEntry.symbol = "none";
|
|
1813
|
+
const marked = [...data];
|
|
1814
|
+
for (const i of isolated) {
|
|
1815
|
+
marked[i] = { value: marked[i], symbol: "circle", symbolSize: 6 };
|
|
1816
|
+
}
|
|
1817
|
+
seriesEntry.data = marked;
|
|
1800
1818
|
}
|
|
1801
|
-
seriesEntry.data = marked;
|
|
1802
1819
|
}
|
|
1803
1820
|
}
|
|
1804
1821
|
}
|
|
@@ -1969,7 +1986,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1969
1986
|
theme.themeColors.accent5,
|
|
1970
1987
|
theme.themeColors.accent6,
|
|
1971
1988
|
]);
|
|
1972
|
-
const paletteAt = (index) => ofPiePalette
|
|
1989
|
+
const paletteAt = (index) => chartPaletteColorAt(ofPiePalette, index);
|
|
1973
1990
|
chartSeries.push({
|
|
1974
1991
|
type: "pie",
|
|
1975
1992
|
name: seriesEntry?.dataLabel ?? "",
|
|
@@ -2389,7 +2406,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2389
2406
|
trigger: isCircularChart ? "item" : "axis",
|
|
2390
2407
|
formatter: isCircularChart
|
|
2391
2408
|
? primaryAxisNumberFormat
|
|
2392
|
-
? circularTooltipFormatter(primaryAxisNumberFormat)
|
|
2409
|
+
? circularTooltipFormatter(primaryAxisNumberFormat, locale)
|
|
2393
2410
|
: "{b}: {c} ({d}%)"
|
|
2394
2411
|
: undefined,
|
|
2395
2412
|
};
|
|
@@ -2446,7 +2463,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2446
2463
|
...dataLabelOptions,
|
|
2447
2464
|
numberFormatPattern: seriesNumberFormatFor(s),
|
|
2448
2465
|
}
|
|
2449
|
-
: dataLabelOptions);
|
|
2466
|
+
: dataLabelOptions, s?.type, locale);
|
|
2450
2467
|
// For doughnut, surface the inner-radius hole on each pie series.
|
|
2451
2468
|
// Excel persists `holeSize` as a 0-90 percentage; we normalize to a
|
|
2452
2469
|
// 0-1 fraction (doughnutHoleSize). Default to 0.5 when unset.
|
|
@@ -2505,7 +2522,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2505
2522
|
// Tooltip values of the value-axis series (a boxplot / candlestick
|
|
2506
2523
|
// tooltip lists its own statistics and keeps echarts' default).
|
|
2507
2524
|
const seriesTooltipValueFormatter = VALUE_TOOLTIP_SERIES_TYPES.has(s?.type)
|
|
2508
|
-
? valueAxisLabelFormatter(seriesNumberFormatFor(s))
|
|
2525
|
+
? valueAxisLabelFormatter(seriesNumberFormatFor(s), locale)
|
|
2509
2526
|
: undefined;
|
|
2510
2527
|
if (seriesTooltipValueFormatter && s?.tooltip?.show !== false) {
|
|
2511
2528
|
next.tooltip = {
|
|
@@ -2518,7 +2535,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2518
2535
|
if (pieNumberFormat && s?.tooltip?.show !== false) {
|
|
2519
2536
|
next.tooltip = {
|
|
2520
2537
|
...(s?.tooltip ?? {}),
|
|
2521
|
-
formatter: circularTooltipFormatter(pieNumberFormat),
|
|
2538
|
+
formatter: circularTooltipFormatter(pieNumberFormat, locale),
|
|
2522
2539
|
};
|
|
2523
2540
|
}
|
|
2524
2541
|
if (explosionOffset > 0 && Array.isArray(next.data)) {
|
|
@@ -2584,7 +2601,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2584
2601
|
applyAxisOverrides({
|
|
2585
2602
|
...valueAxisBase,
|
|
2586
2603
|
name: spec.horizontalAxisTitle ?? "",
|
|
2587
|
-
}, spec.valueAxisOptions, primaryAxisNumberFormat),
|
|
2604
|
+
}, spec.valueAxisOptions, primaryAxisNumberFormat, locale),
|
|
2588
2605
|
];
|
|
2589
2606
|
}
|
|
2590
2607
|
const base = chartDomains.length > 0
|
|
@@ -2671,8 +2688,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2671
2688
|
? s.data.map((d) => d !== null && typeof d === "object" ? d.value : d)
|
|
2672
2689
|
: []),
|
|
2673
2690
|
pattern: primaryAxisNumberFormat,
|
|
2691
|
+
locale,
|
|
2674
2692
|
}),
|
|
2675
|
-
}, spec.valueAxisOptions, primaryAxisNumberFormat),
|
|
2693
|
+
}, spec.valueAxisOptions, primaryAxisNumberFormat, locale),
|
|
2676
2694
|
];
|
|
2677
2695
|
if (hasSecondaryAxis) {
|
|
2678
2696
|
axes.push(applyAxisOverrides({
|
|
@@ -2687,7 +2705,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2687
2705
|
axisLabel: { formatter: "{value}%" },
|
|
2688
2706
|
}
|
|
2689
2707
|
: {}),
|
|
2690
|
-
}, spec.secondaryValueAxisOptions, secondaryAxisNumberFormat));
|
|
2708
|
+
}, spec.secondaryValueAxisOptions, secondaryAxisNumberFormat, locale));
|
|
2691
2709
|
}
|
|
2692
2710
|
return axes;
|
|
2693
2711
|
})(),
|
|
@@ -2782,14 +2800,17 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2782
2800
|
color: textColor,
|
|
2783
2801
|
},
|
|
2784
2802
|
},
|
|
2785
|
-
|
|
2803
|
+
// Unrolled through Excel's luminance variations: echarts cycles this
|
|
2804
|
+
// list over pie slices and over series, and the seventh must not
|
|
2805
|
+
// repeat the first (rowsncolumns/spreadsheet#1045).
|
|
2806
|
+
color: expandChartPalette(getChartPalette(chart.spec, [
|
|
2786
2807
|
theme.themeColors.accent1,
|
|
2787
2808
|
theme.themeColors.accent2,
|
|
2788
2809
|
theme.themeColors.accent3,
|
|
2789
2810
|
theme.themeColors.accent4,
|
|
2790
2811
|
theme.themeColors.accent5,
|
|
2791
2812
|
theme.themeColors.accent6,
|
|
2792
|
-
]),
|
|
2813
|
+
])),
|
|
2793
2814
|
// Excel's Format Chart Area fill; `transparent` (echarts' own
|
|
2794
2815
|
// default) so clearing the fill undoes an earlier setOption.
|
|
2795
2816
|
backgroundColor: chart.spec.backgroundColor ?? "transparent",
|
|
@@ -2832,6 +2853,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2832
2853
|
getDomainValuesFromRange,
|
|
2833
2854
|
getSeriesValuesFromRange,
|
|
2834
2855
|
getFormattingFromRange,
|
|
2856
|
+
locale,
|
|
2835
2857
|
glUpgradeTick,
|
|
2836
2858
|
]);
|
|
2837
2859
|
// Resize chart
|
|
@@ -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;AAuBnC,OAAO,KAAkB,MAAM,OAAO,CAAC;AAMvC,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,+BAA+B,CAAC;AA4CvC,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;CAC1C,CAAC;AAuBF,eAAO,MAAM,WAAW,GAAI,sEAMzB,gBAAgB,
|
|
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;AAuBnC,OAAO,KAAkB,MAAM,OAAO,CAAC;AAMvC,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,+BAA+B,CAAC;AA4CvC,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;CAC1C,CAAC;AAuBF,eAAO,MAAM,WAAW,GAAI,sEAMzB,gBAAgB,sBAw6DlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -622,7 +622,11 @@ export const ChartEditor = ({ chart, sheetId, onSubmit, onRequestMoveChart, getF
|
|
|
622
622
|
React.createElement("option", { value: "outside" }, "Outside"),
|
|
623
623
|
React.createElement("option", { value: "insideEnd" }, "Inside end"),
|
|
624
624
|
React.createElement("option", { value: "outsideEnd" }, "Outside end"),
|
|
625
|
-
React.createElement("option", { value: "bestFit" }, "Best fit")
|
|
625
|
+
React.createElement("option", { value: "bestFit" }, "Best fit"),
|
|
626
|
+
React.createElement("option", { value: "above" }, "Above"),
|
|
627
|
+
React.createElement("option", { value: "below" }, "Below"),
|
|
628
|
+
React.createElement("option", { value: "left" }, "Left"),
|
|
629
|
+
React.createElement("option", { value: "right" }, "Right")),
|
|
626
630
|
React.createElement("label", { className: "font-system text-xs" }, "Number format pattern"),
|
|
627
631
|
React.createElement(Input, { value: values?.spec.dataLabels?.numberFormatPattern ?? "", type: "text", onChange: (e) => setFieldValue("spec.dataLabels.numberFormatPattern", e.target.value || undefined), className: "text-xs w-32", placeholder: "#,##0.00" }))) : null)),
|
|
628
632
|
basicSpec && showChartOptions ? (React.createElement(Section, { title: "Chart options" },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { EChartComponentProps } from "../../types";
|
|
3
3
|
export type BasicChartProps = EChartComponentProps;
|
|
4
|
-
export declare const BasicChart: ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, }: BasicChartProps) => React.JSX.Element;
|
|
4
|
+
export declare const BasicChart: ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, locale, }: BasicChartProps) => React.JSX.Element;
|
|
5
5
|
//# sourceMappingURL=basic-chart.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAuZxD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AA0FnD,eAAO,MAAM,UAAU,GAAI,gPAkBxB,eAAe,sBA00EjB,CAAC"}
|
|
@@ -2,10 +2,10 @@ import { DotsVerticalIcon } from "@rowsncolumns/icons";
|
|
|
2
2
|
import { getStringifiedColor, useFocusSheet, useRegisterChartActions, useSetSelectedChartSeries, } from "@rowsncolumns/spreadsheet";
|
|
3
3
|
import { useSubscribeToSheet } from "@rowsncolumns/spreadsheet-state";
|
|
4
4
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuTrigger, IconButton, useIsomorphicLayoutEffect, useOnVisibilityChange, } from "@rowsncolumns/ui";
|
|
5
|
-
import { debug, isAFormula, isNil, ssfFormat } from "@rowsncolumns/utils";
|
|
5
|
+
import { chartPaletteColorAt, debug, expandChartPalette, isAFormula, isNil, ssfFormat, } from "@rowsncolumns/utils";
|
|
6
6
|
import React, { useCallback, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { aggregateSeriesByDomain, getDomainValuesForAxis, sanitizeDomainValues, shouldAggregateSeries, } from "../../utils/aggregation";
|
|
8
|
-
import { formatAxisValue, numberFormatToPlotlyTicks, resolveSeriesNumberFormats, resolveValueAxisNumberFormat, valueAxisNameGap, } from "../../utils/axis-number-format";
|
|
8
|
+
import { formatAxisValue, numberFormatToPlotlyTicks, plotlySeparators, resolveSeriesNumberFormats, resolveValueAxisNumberFormat, valueAxisNameGap, } from "../../utils/axis-number-format";
|
|
9
9
|
import { computeBoxplotSummary } from "../../utils/boxplot";
|
|
10
10
|
import { createBubbleSizeScale, prepareBubbleSeriesData, toLabelString, } from "../../utils/bubble";
|
|
11
11
|
import { canSwitchRowColumn, cellLabelGetterFromRangeReader, switchRowColumn, } from "../../utils/chart-orientation";
|
|
@@ -179,14 +179,44 @@ const PLOTLY_DLBL_POS_BAR = {
|
|
|
179
179
|
insideEnd: "inside",
|
|
180
180
|
outsideEnd: "outside",
|
|
181
181
|
bestFit: "outside",
|
|
182
|
+
above: "outside",
|
|
183
|
+
below: "outside",
|
|
184
|
+
left: "outside",
|
|
185
|
+
right: "outside",
|
|
182
186
|
};
|
|
183
|
-
|
|
187
|
+
// A scatter trace (line / area / scatter) places its text with the compass
|
|
188
|
+
// positions, and only draws it when `mode` carries `text`. Excel's line
|
|
189
|
+
// placements are centre / left / right / above / below; the bar-only ones
|
|
190
|
+
// land on Excel's default for a line, above the point (#1049).
|
|
191
|
+
const PLOTLY_DLBL_POS_POINT = {
|
|
192
|
+
center: "middle center",
|
|
193
|
+
inside: "middle center",
|
|
194
|
+
outside: "top center",
|
|
195
|
+
insideEnd: "top center",
|
|
196
|
+
outsideEnd: "top center",
|
|
197
|
+
bestFit: "top center",
|
|
198
|
+
above: "top center",
|
|
199
|
+
below: "bottom center",
|
|
200
|
+
left: "middle left",
|
|
201
|
+
right: "middle right",
|
|
202
|
+
};
|
|
203
|
+
const withTextMode = (mode) => {
|
|
204
|
+
const base = typeof mode === "string" && mode ? mode : "lines+markers";
|
|
205
|
+
return base.split("+").includes("text") ? base : `${base}+text`;
|
|
206
|
+
};
|
|
207
|
+
const applyDataLabelsToTracePlotly = (trace, opts, chartType, sourceNumberFormat, locale) => {
|
|
184
208
|
if (!opts?.show)
|
|
185
209
|
return trace;
|
|
186
210
|
const next = { ...trace };
|
|
187
211
|
const isCircular = chartType === "pie" || chartType === "doughnut";
|
|
188
|
-
const
|
|
189
|
-
const
|
|
212
|
+
const isPoint = !isCircular && next.type === "scatter";
|
|
213
|
+
const posMap = isCircular
|
|
214
|
+
? PLOTLY_DLBL_POS_PIE
|
|
215
|
+
: isPoint
|
|
216
|
+
? PLOTLY_DLBL_POS_POINT
|
|
217
|
+
: PLOTLY_DLBL_POS_BAR;
|
|
218
|
+
const fallback = isPoint ? "top center" : "inside";
|
|
219
|
+
const position = opts.position ? posMap[opts.position] ?? fallback : fallback;
|
|
190
220
|
if (isCircular) {
|
|
191
221
|
// Plotly's pie trace uses `textinfo` to control which fields render.
|
|
192
222
|
const parts = [];
|
|
@@ -204,7 +234,7 @@ const applyDataLabelsToTracePlotly = (trace, opts, chartType, sourceNumberFormat
|
|
|
204
234
|
const pattern = opts.numberFormatPattern ?? sourceNumberFormat;
|
|
205
235
|
if (pattern) {
|
|
206
236
|
const values = Array.isArray(next.values) ? next.values : [];
|
|
207
|
-
next.text = values.map((v) => formatAxisValue(pattern, v));
|
|
237
|
+
next.text = values.map((v) => formatAxisValue(pattern, v, locale));
|
|
208
238
|
next.texttemplate = next.textinfo
|
|
209
239
|
.split("+")
|
|
210
240
|
.map((part) => (part === "value" ? "%{text}" : `%{${part}}`))
|
|
@@ -223,9 +253,13 @@ const applyDataLabelsToTracePlotly = (trace, opts, chartType, sourceNumberFormat
|
|
|
223
253
|
const values = next.orientation === "h" ? next.x : next.y;
|
|
224
254
|
const vals = Array.isArray(values) ? values : [];
|
|
225
255
|
next.text = pattern
|
|
226
|
-
? vals.map((v) =>
|
|
227
|
-
:
|
|
256
|
+
? vals.map((v) => v === null || v === undefined ? "" : ssfFormat(pattern, v, locale))
|
|
257
|
+
: locale
|
|
258
|
+
? vals.map((v) => formatAxisValue(undefined, v, locale))
|
|
259
|
+
: values;
|
|
228
260
|
next.textposition = position;
|
|
261
|
+
if (isPoint)
|
|
262
|
+
next.mode = withTextMode(next.mode);
|
|
229
263
|
}
|
|
230
264
|
return next;
|
|
231
265
|
};
|
|
@@ -345,7 +379,7 @@ const addAlphaToColor = (color, alpha) => {
|
|
|
345
379
|
}
|
|
346
380
|
return color;
|
|
347
381
|
};
|
|
348
|
-
export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, }) => {
|
|
382
|
+
export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, locale, }) => {
|
|
349
383
|
const spec = chart.spec;
|
|
350
384
|
const hierarchyTitleRef = useRef(undefined);
|
|
351
385
|
const explicitChartTitle = chart.spec.title ?? undefined;
|
|
@@ -525,7 +559,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
525
559
|
: point.value[0]);
|
|
526
560
|
const yValues = entry.points.map((point) => point.value[1]);
|
|
527
561
|
const labelValuesForSeries = entry.points.map((point) => point.name);
|
|
528
|
-
const seriesColor = spec.series[index]?.color ?? palette
|
|
562
|
+
const seriesColor = spec.series[index]?.color ?? chartPaletteColorAt(palette, index);
|
|
529
563
|
const perSeriesMarker = spec.series[index]?.marker ?? undefined;
|
|
530
564
|
if (isScatter) {
|
|
531
565
|
// Per-series marker.symbol = "none" → pure line (no markers).
|
|
@@ -1144,7 +1178,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1144
1178
|
const effectiveColor = options?.gradientColor ??
|
|
1145
1179
|
seriesColor ??
|
|
1146
1180
|
(options?.seriesIndex !== undefined
|
|
1147
|
-
? themeColorPalette
|
|
1181
|
+
? chartPaletteColorAt(themeColorPalette, options.seriesIndex)
|
|
1148
1182
|
: undefined);
|
|
1149
1183
|
// Per-data-point color overrides — Gantt charts use `<c:dPt>` to
|
|
1150
1184
|
// give every task its own color. Build a per-point color array
|
|
@@ -1166,7 +1200,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1166
1200
|
// with (or the series' own colour when one was authored).
|
|
1167
1201
|
const existing = Array.isArray(target.marker?.colors)
|
|
1168
1202
|
? target.marker.colors
|
|
1169
|
-
: themeColorPalette;
|
|
1203
|
+
: expandChartPalette(themeColorPalette);
|
|
1170
1204
|
const labelCount = Array.isArray(target.labels)
|
|
1171
1205
|
? target.labels.length
|
|
1172
1206
|
: 0;
|
|
@@ -1584,7 +1618,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1584
1618
|
const ofPieType = resolveOfPieType(spec.ofPieType);
|
|
1585
1619
|
const secondScale = clampSecondPieSize(spec.secondPieSize) / 100;
|
|
1586
1620
|
const halfAngle = otherSliceHalfAngle(otherTotal, total);
|
|
1587
|
-
const paletteAt = (index) => themeColorPalette
|
|
1621
|
+
const paletteAt = (index) => chartPaletteColorAt(themeColorPalette, index);
|
|
1588
1622
|
// Layout in paper coordinates (0-1 across the plot area
|
|
1589
1623
|
// inside the margins). Main pie on the left, secondary plot
|
|
1590
1624
|
// on the right.
|
|
@@ -1714,7 +1748,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1714
1748
|
labels: pieData.labels,
|
|
1715
1749
|
name: dataLabel,
|
|
1716
1750
|
marker: {
|
|
1717
|
-
|
|
1751
|
+
// Unrolled through Excel's luminance variations so slice 7 does
|
|
1752
|
+
// not repeat slice 1 (rowsncolumns/spreadsheet#1045).
|
|
1753
|
+
colors: expandChartPalette(themeColorPalette),
|
|
1718
1754
|
},
|
|
1719
1755
|
};
|
|
1720
1756
|
// Ties the trace back to spec.series for click selection and
|
|
@@ -1831,6 +1867,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1831
1867
|
: valueAxisNameGap({
|
|
1832
1868
|
values: primaryValues,
|
|
1833
1869
|
pattern: primaryAxisNumberFormat,
|
|
1870
|
+
locale,
|
|
1834
1871
|
fontSize: axisFontSize,
|
|
1835
1872
|
minimum: 0,
|
|
1836
1873
|
}) -
|
|
@@ -1846,6 +1883,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1846
1883
|
? trace.y
|
|
1847
1884
|
: []),
|
|
1848
1885
|
pattern: secondaryAxisNumberFormat,
|
|
1886
|
+
locale,
|
|
1849
1887
|
fontSize: axisFontSize,
|
|
1850
1888
|
minimum: 0,
|
|
1851
1889
|
}) -
|
|
@@ -2026,14 +2064,14 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2026
2064
|
const piePull = isOfPie ? undefined : explosionToPlotlyPull(resolveExplosion(spec));
|
|
2027
2065
|
const formattedTraces = traces.map((trace) => {
|
|
2028
2066
|
const pattern = traceNumberFormat(trace);
|
|
2029
|
-
let next = applyDataLabelsToTracePlotly(trace, dlOpts, spec.chartType, pattern);
|
|
2067
|
+
let next = applyDataLabelsToTracePlotly(trace, dlOpts, spec.chartType, pattern, locale);
|
|
2030
2068
|
if (next.type === "pie") {
|
|
2031
2069
|
// Hover values format like the slice's source cell.
|
|
2032
2070
|
if (pattern && !next.text) {
|
|
2033
2071
|
const values = Array.isArray(next.values) ? next.values : [];
|
|
2034
2072
|
next = {
|
|
2035
2073
|
...next,
|
|
2036
|
-
text: values.map((v) => formatAxisValue(pattern, v)),
|
|
2074
|
+
text: values.map((v) => formatAxisValue(pattern, v, locale)),
|
|
2037
2075
|
hoverinfo: "label+text+percent+name",
|
|
2038
2076
|
};
|
|
2039
2077
|
}
|
|
@@ -2060,7 +2098,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2060
2098
|
if (seriesPattern && Array.isArray(values)) {
|
|
2061
2099
|
next = {
|
|
2062
2100
|
...next,
|
|
2063
|
-
customdata: values.map((v) => formatAxisValue(seriesPattern, v)),
|
|
2101
|
+
customdata: values.map((v) => formatAxisValue(seriesPattern, v, locale)),
|
|
2064
2102
|
hovertemplate: `${horizontal ? "%{y}" : "%{x}"}<br>%{customdata}<extra>%{fullData.name}</extra>`,
|
|
2065
2103
|
};
|
|
2066
2104
|
}
|
|
@@ -2102,6 +2140,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2102
2140
|
getDomainValuesFromRange,
|
|
2103
2141
|
getSeriesValuesFromRange,
|
|
2104
2142
|
getFormattingFromRange,
|
|
2143
|
+
locale,
|
|
2105
2144
|
]);
|
|
2106
2145
|
// Update theme
|
|
2107
2146
|
useIsomorphicLayoutEffect(() => {
|
|
@@ -2139,7 +2178,13 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2139
2178
|
scrollZoom: false,
|
|
2140
2179
|
doubleClick: false,
|
|
2141
2180
|
};
|
|
2142
|
-
|
|
2181
|
+
// Ticks and hover values plotly formats itself use the sheet's separators.
|
|
2182
|
+
const separators = useMemo(() => plotlySeparators(locale), [locale]);
|
|
2183
|
+
const plotLayout = useMemo(() => ({
|
|
2184
|
+
...layout,
|
|
2185
|
+
dragmode: false,
|
|
2186
|
+
...(separators ? { separators } : {}),
|
|
2187
|
+
}), [layout, separators]);
|
|
2143
2188
|
// Excel's "Rounded Corners" chart frame (`<c:roundedCorners val="1"/>`
|
|
2144
2189
|
// at the chartSpace root). Default per OOXML §21.2.2.183 is square,
|
|
2145
2190
|
// so only opt in when the workbook explicitly set the flag.
|