@rowsncolumns/charts 28.1.2 → 28.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/cjs/basic-chart/basic-chart.js +152 -88
- package/dist/cjs/basic-chart/helpers.d.ts +9 -0
- package/dist/cjs/basic-chart/helpers.d.ts.map +1 -1
- package/dist/cjs/basic-chart/helpers.js +55 -1
- package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/cjs/plotly/basic-chart/basic-chart.js +225 -140
- package/dist/cjs/plotly/plot.d.ts +12 -0
- package/dist/cjs/plotly/plot.d.ts.map +1 -0
- package/dist/cjs/plotly/plot.js +8 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/utils/axis-number-format.d.ts +14 -0
- package/dist/cjs/utils/axis-number-format.d.ts.map +1 -1
- package/dist/cjs/utils/axis-number-format.js +41 -1
- package/dist/cjs/utils/empty-cells.d.ts +9 -0
- package/dist/cjs/utils/empty-cells.d.ts.map +1 -1
- package/dist/cjs/utils/empty-cells.js +26 -1
- package/dist/cjs/utils/legend-layout.d.ts +168 -0
- package/dist/cjs/utils/legend-layout.d.ts.map +1 -0
- package/dist/cjs/utils/legend-layout.js +338 -0
- package/dist/cjs/utils/trendline.d.ts +18 -5
- package/dist/cjs/utils/trendline.d.ts.map +1 -1
- package/dist/cjs/utils/trendline.js +38 -16
- package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/basic-chart/basic-chart.js +118 -47
- package/dist/esm/basic-chart/helpers.d.ts +9 -0
- package/dist/esm/basic-chart/helpers.d.ts.map +1 -1
- package/dist/esm/basic-chart/helpers.js +30 -0
- package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/plotly/basic-chart/basic-chart.js +140 -13
- package/dist/esm/plotly/plot.d.ts +12 -0
- package/dist/esm/plotly/plot.d.ts.map +1 -0
- package/dist/esm/plotly/plot.js +12 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/utils/axis-number-format.d.ts +14 -0
- package/dist/esm/utils/axis-number-format.d.ts.map +1 -1
- package/dist/esm/utils/axis-number-format.js +36 -0
- package/dist/esm/utils/empty-cells.d.ts +9 -0
- package/dist/esm/utils/empty-cells.d.ts.map +1 -1
- package/dist/esm/utils/empty-cells.js +21 -0
- package/dist/esm/utils/legend-layout.d.ts +168 -0
- package/dist/esm/utils/legend-layout.d.ts.map +1 -0
- package/dist/esm/utils/legend-layout.js +299 -0
- package/dist/esm/utils/trendline.d.ts +18 -5
- package/dist/esm/utils/trendline.d.ts.map +1 -1
- package/dist/esm/utils/trendline.js +36 -16
- package/package.json +9 -9
|
@@ -10,7 +10,7 @@ import * as echarts from "echarts/core";
|
|
|
10
10
|
import { CanvasRenderer } from "echarts/renderers";
|
|
11
11
|
import React, { useCallback, useMemo, useRef, useState } from "react";
|
|
12
12
|
import { aggregateSeriesByDomain, getDomainValuesForAxis, sanitizeDomainValues, shouldAggregateSeries, } from "../utils/aggregation";
|
|
13
|
-
import { circularTooltipFormatter, formatAxisValue, resolveSeriesNumberFormats, resolveSourceNumberFormat, resolveValueAxisNumberFormat, tooltipValueFormatter, valueAxisLabelFormatter, } from "../utils/axis-number-format";
|
|
13
|
+
import { circularTooltipFormatter, formatAxisValue, resolveSeriesNumberFormats, resolveSourceNumberFormat, resolveValueAxisNumberFormat, tooltipValueFormatter, valueAxisLabelFormatter, valueAxisNameGap, } from "../utils/axis-number-format";
|
|
14
14
|
import { gapWidthToBarCategoryGap, overlapToBarGap, } from "../utils/bar-layout";
|
|
15
15
|
import { computeBoxplotSummary } from "../utils/boxplot";
|
|
16
16
|
import { createBubbleSizeScale, prepareBubbleSeriesData, } from "../utils/bubble";
|
|
@@ -18,13 +18,14 @@ import { coerceCalculatedLabel, getChartDataRanges, } from "../utils/chart-spec-
|
|
|
18
18
|
import { canSwitchRowColumn, cellLabelGetterFromRangeReader, switchRowColumn, } from "../utils/chart-orientation";
|
|
19
19
|
import { dataUrlToBlob } from "../utils/chart-image";
|
|
20
20
|
import { getChartPalette } from "../utils/chart-styles";
|
|
21
|
-
import { applyEmptyCellsPolicy, connectsAcrossEmptyCells, isLineLikeChartType, } from "../utils/empty-cells";
|
|
21
|
+
import { applyEmptyCellsPolicy, connectsAcrossEmptyCells, isolatedPointIndexes, isLineLikeChartType, } from "../utils/empty-cells";
|
|
22
22
|
import { getEchartsGlState, loadEchartsGl, } from "../utils/echarts-gl-loader";
|
|
23
23
|
import { computeErrorBars, resolveCustomErrorBarValues, } from "../utils/error-bars";
|
|
24
24
|
import { echartsGroupedCategoryAxes, resolveCategoryLevels, } from "../utils/category-levels";
|
|
25
25
|
import { gradientToEcharts } from "../utils/gradient";
|
|
26
26
|
import { buildHierarchyTree, extractHierarchyRows, getHierarchyHeaderTitle, } from "../utils/hierarchy";
|
|
27
27
|
import { binHistogram, computePareto, isParetoSpec, } from "../utils/histogram";
|
|
28
|
+
import { categoryAxisNameGap, legendLabelsFrom, reserveEchartsLegendBand, } from "../utils/legend-layout";
|
|
28
29
|
import { clampSecondPieSize, isOfPieChartSpec, otherSliceHalfAngle, resolveOfPieType, splitOfPie, } from "../utils/of-pie";
|
|
29
30
|
import { applyAxisOverrides, applyLegendOverrides, buildSeriesLabelOption, } from "../utils/overrides";
|
|
30
31
|
import { buildRadarIndicators, buildRadarSeriesData } from "../utils/radar";
|
|
@@ -37,7 +38,7 @@ import { buildBar3DData, buildContourData, buildLine3DSeries, buildSurfaceData,
|
|
|
37
38
|
import { computeTrendline } from "../utils/trendline";
|
|
38
39
|
import { computeWaterfall } from "../utils/waterfall";
|
|
39
40
|
import { isWebGLAvailable } from "../utils/webgl";
|
|
40
|
-
import { chartTypeToEchartsType, getEchartsSeriesStyle, mapMarkerSymbolToEcharts, showXAxis, } from "./helpers";
|
|
41
|
+
import { chartTypeToEchartsType, circularLegendEntries, getEchartsSeriesStyle, isCircularChartType, mapMarkerSymbolToEcharts, showXAxis, } from "./helpers";
|
|
41
42
|
// Excel error values that should be treated as null in charts
|
|
42
43
|
const EXCEL_ERROR_VALUES = new Set([
|
|
43
44
|
"#N/A",
|
|
@@ -180,6 +181,8 @@ const defaultLegendConfig = {
|
|
|
180
181
|
itemHeight: 4,
|
|
181
182
|
top: 20,
|
|
182
183
|
};
|
|
184
|
+
/** Category-axis title gap: clears one row of tick labels (#986). */
|
|
185
|
+
const CATEGORY_AXIS_NAME_GAP = categoryAxisNameGap();
|
|
183
186
|
export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, readonly, getDomainValuesFromRange, getSeriesValuesFromRange, getFormattingFromRange, onDeleteChart, onUpdateChart, onRequestEdit, onRequestCalculate, isSelected, fieldButtons, }) => {
|
|
184
187
|
const spec = chart.spec;
|
|
185
188
|
const explicitChartTitle = chart.spec.title ?? undefined;
|
|
@@ -306,7 +309,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
306
309
|
rawDomainValues.length === seriesValues.length;
|
|
307
310
|
if (canAggregate) {
|
|
308
311
|
const aggregatedValues = aggregateSeriesByDomain(rawDomainValues, seriesValues, aggregateType, orderedDomainValues);
|
|
309
|
-
if (spec.chartType
|
|
312
|
+
if (isCircularChartType(spec.chartType)) {
|
|
310
313
|
return orderedDomainValues.map((name, idx) => ({
|
|
311
314
|
value: aggregatedValues[idx] ?? 0,
|
|
312
315
|
name,
|
|
@@ -315,7 +318,9 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
315
318
|
return aggregatedValues;
|
|
316
319
|
}
|
|
317
320
|
const numericValues = seriesValues;
|
|
318
|
-
|
|
321
|
+
// Pie and doughnut points are slices keyed by their category name;
|
|
322
|
+
// a doughnut is a pie series with a hole, so it labels the same way.
|
|
323
|
+
if (isCircularChartType(spec.chartType)) {
|
|
319
324
|
if (options.pieLabels) {
|
|
320
325
|
const { domainLabels, offset } = options.pieLabels;
|
|
321
326
|
const labels = offset === undefined
|
|
@@ -1458,7 +1463,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1458
1463
|
? spec.verticalAxisTitle ?? ""
|
|
1459
1464
|
: spec.horizontalAxisTitle ?? "",
|
|
1460
1465
|
nameLocation: "middle",
|
|
1461
|
-
nameGap:
|
|
1466
|
+
nameGap: CATEGORY_AXIS_NAME_GAP + 24 * categoryLevels.outer.length,
|
|
1462
1467
|
}));
|
|
1463
1468
|
}
|
|
1464
1469
|
// One category axis per domain (Excel): a domain drawn from several
|
|
@@ -1485,7 +1490,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1485
1490
|
splitLine: { show: !isCircularChart },
|
|
1486
1491
|
name: axisTitle,
|
|
1487
1492
|
nameLocation: "middle",
|
|
1488
|
-
nameGap:
|
|
1493
|
+
nameGap: CATEGORY_AXIS_NAME_GAP,
|
|
1489
1494
|
});
|
|
1490
1495
|
});
|
|
1491
1496
|
if (chartDomains.length === 0 && !isCircularChart) {
|
|
@@ -1500,7 +1505,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1500
1505
|
? spec.verticalAxisTitle ?? ""
|
|
1501
1506
|
: spec.horizontalAxisTitle ?? "",
|
|
1502
1507
|
nameLocation: "middle",
|
|
1503
|
-
nameGap:
|
|
1508
|
+
nameGap: CATEGORY_AXIS_NAME_GAP,
|
|
1504
1509
|
});
|
|
1505
1510
|
}
|
|
1506
1511
|
// Trendline + error-bar overlays are extra series appended after the
|
|
@@ -1670,6 +1675,20 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1670
1675
|
seriesCount: sideBySideBarCount,
|
|
1671
1676
|
overlap: isStacked ? 0 : spec.overlap,
|
|
1672
1677
|
});
|
|
1678
|
+
// The colour a series without its own fill gets: the chart palette entry
|
|
1679
|
+
// for its position, the same cycle the option-level `color` gives echarts'
|
|
1680
|
+
// default path. Excel keeps that colour whatever else is authored on the
|
|
1681
|
+
// series — `<c:dPt>` overrides recolour only their points and
|
|
1682
|
+
// `invertIfNegative` only the negative bars (rowsncolumns/spreadsheet#1030).
|
|
1683
|
+
const seriesPalette = getChartPalette(spec, [
|
|
1684
|
+
theme.themeColors.accent1,
|
|
1685
|
+
theme.themeColors.accent2,
|
|
1686
|
+
theme.themeColors.accent3,
|
|
1687
|
+
theme.themeColors.accent4,
|
|
1688
|
+
theme.themeColors.accent5,
|
|
1689
|
+
theme.themeColors.accent6,
|
|
1690
|
+
]);
|
|
1691
|
+
const paletteColorAt = (index) => seriesPalette[index % seriesPalette.length];
|
|
1673
1692
|
for (const [specSeriesIndex, series] of (spec.series ?? []).entries()) {
|
|
1674
1693
|
const dataLabel = !isNil(series.dataLabel) && isAFormula(series.dataLabel)
|
|
1675
1694
|
? coerceCalculatedLabel(await onRequestCalculate(series.dataLabel, position.sheetId ?? sheetId, position.overlayPosition.anchorCell.rowIndex, position.overlayPosition.anchorCell.columnIndex))
|
|
@@ -1689,7 +1708,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1689
1708
|
// domain range's labels in sequence, each series range taking its own
|
|
1690
1709
|
// window — not the primary domain range alone (C-78).
|
|
1691
1710
|
const domainSources = spec.domains?.[0]?.sources ?? [];
|
|
1692
|
-
const pieDomainLabels =
|
|
1711
|
+
const pieDomainLabels = isCircularChart &&
|
|
1693
1712
|
(domainSources.length > 1 || series.sources.length > 1)
|
|
1694
1713
|
? domainSources.flatMap((sheetRange) => getDomainValuesFromRange(sheetRange, spec.hiddenDimensionStrategy) ?? [])
|
|
1695
1714
|
: undefined;
|
|
@@ -1766,6 +1785,22 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1766
1785
|
mapMarkerSymbolToEcharts(markerSymbol) ?? "circle";
|
|
1767
1786
|
seriesEntry.symbolSize = seriesAny.marker?.size ?? 6;
|
|
1768
1787
|
}
|
|
1788
|
+
else if (Array.isArray(data)) {
|
|
1789
|
+
// A marker-less line draws nothing for a point with a gap on both
|
|
1790
|
+
// sides (a data row followed by a blank row), so that row vanishes
|
|
1791
|
+
// from the chart. Excel plots an isolated marker there: keep the
|
|
1792
|
+
// series symbol-less and mark only those points.
|
|
1793
|
+
const isolated = isolatedPointIndexes(data, connectsAcrossEmptyCells(spec, lineLike));
|
|
1794
|
+
if (isolated.length) {
|
|
1795
|
+
seriesEntry.showSymbol = true;
|
|
1796
|
+
seriesEntry.symbol = "none";
|
|
1797
|
+
const marked = [...data];
|
|
1798
|
+
for (const i of isolated) {
|
|
1799
|
+
marked[i] = { value: marked[i], symbol: "circle", symbolSize: 6 };
|
|
1800
|
+
}
|
|
1801
|
+
seriesEntry.data = marked;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1769
1804
|
}
|
|
1770
1805
|
// Gradient fill — overrides solid color when present. echarts
|
|
1771
1806
|
// paints it natively via a LinearGradient on itemStyle.color
|
|
@@ -1801,14 +1836,16 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1801
1836
|
// Per-data-point color overrides — Gantt-style charts colour
|
|
1802
1837
|
// each task differently via `<c:dPt>`. Echarts accepts an
|
|
1803
1838
|
// `itemStyle.color` function that receives the data index;
|
|
1804
|
-
// we look up the override or fall back to the series
|
|
1839
|
+
// we look up the override or fall back to the series colour —
|
|
1840
|
+
// its own, else its palette slot (never a fixed accent1, which
|
|
1841
|
+
// painted every such series alike).
|
|
1805
1842
|
const overrides = new Map();
|
|
1806
1843
|
for (const dp of seriesAny.dataPointColors) {
|
|
1807
1844
|
if (typeof dp.idx === "number" && typeof dp.color === "string") {
|
|
1808
1845
|
overrides.set(dp.idx, dp.color);
|
|
1809
1846
|
}
|
|
1810
1847
|
}
|
|
1811
|
-
const fallback = series.color ??
|
|
1848
|
+
const fallback = series.color ?? paletteColorAt(specSeriesIndex);
|
|
1812
1849
|
seriesEntry.itemStyle = {
|
|
1813
1850
|
...(seriesEntry.itemStyle ?? {}),
|
|
1814
1851
|
color: (params) => overrides.get(params.dataIndex) ?? fallback,
|
|
@@ -1826,25 +1863,18 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1826
1863
|
overrides.set(dp.idx, dp.color);
|
|
1827
1864
|
}
|
|
1828
1865
|
}
|
|
1829
|
-
const slicePalette = getChartPalette(spec, [
|
|
1830
|
-
theme.themeColors.accent1,
|
|
1831
|
-
theme.themeColors.accent2,
|
|
1832
|
-
theme.themeColors.accent3,
|
|
1833
|
-
theme.themeColors.accent4,
|
|
1834
|
-
theme.themeColors.accent5,
|
|
1835
|
-
theme.themeColors.accent6,
|
|
1836
|
-
]);
|
|
1837
1866
|
seriesEntry.itemStyle = {
|
|
1838
1867
|
...(seriesEntry.itemStyle ?? {}),
|
|
1839
1868
|
color: (params) => overrides.get(params.dataIndex) ??
|
|
1840
1869
|
series.color ??
|
|
1841
|
-
|
|
1870
|
+
paletteColorAt(params.dataIndex),
|
|
1842
1871
|
};
|
|
1843
1872
|
}
|
|
1844
1873
|
else if (isBarOrColumn && series.invertIfNegative) {
|
|
1845
|
-
|
|
1874
|
+
// Positive bars keep the series colour (own, else palette slot);
|
|
1875
|
+
// only negative bars take the inverted colour.
|
|
1876
|
+
const positiveColor = series.color ?? paletteColorAt(specSeriesIndex);
|
|
1846
1877
|
const negativeColor = series.negativeColor ?? "#FF0000";
|
|
1847
|
-
// Use a color function that returns different colors based on value
|
|
1848
1878
|
seriesEntry.itemStyle = {
|
|
1849
1879
|
...(seriesEntry.itemStyle ?? {}),
|
|
1850
1880
|
color: (params) => {
|
|
@@ -1876,6 +1906,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1876
1906
|
chartSeries.push(seriesEntry);
|
|
1877
1907
|
pushSeriesOverlays(series, data, dataLabel);
|
|
1878
1908
|
}
|
|
1909
|
+
if (isCircularChart && !isOfPie) {
|
|
1910
|
+
// Excel's pie / doughnut legend lists the categories, one entry per
|
|
1911
|
+
// slice — not the series name (the loop above collected those). The
|
|
1912
|
+
// of-pie branch below builds its own category legend.
|
|
1913
|
+
legend.data = circularLegendEntries(chartSeries);
|
|
1914
|
+
}
|
|
1879
1915
|
if (isOfPie) {
|
|
1880
1916
|
// Pie-of-pie / bar-of-pie — Excel splits the minor slices into a
|
|
1881
1917
|
// secondary pie or stacked bar beside the main pie, replaces them
|
|
@@ -2298,27 +2334,6 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2298
2334
|
previousSeriesCount.current !== renderedSeriesCount
|
|
2299
2335
|
? ["series", "xAxis", "yAxis", "legend", "grid", "tooltip"]
|
|
2300
2336
|
: [];
|
|
2301
|
-
// Of-pie uses a full-container grid (for the connector overlay's
|
|
2302
|
-
// hidden 0-1 axes) plus, for bar-of-pie, a second grid hosting the
|
|
2303
|
-
// stacked secondary bar.
|
|
2304
|
-
const gridConfig = isOfPie
|
|
2305
|
-
? [
|
|
2306
|
-
{ left: 0, right: 0, top: 0, bottom: 0 },
|
|
2307
|
-
...(ofPieLayout?.barGrid ? [ofPieLayout.barGrid] : []),
|
|
2308
|
-
]
|
|
2309
|
-
: isCircularChart
|
|
2310
|
-
? {
|
|
2311
|
-
left: 0,
|
|
2312
|
-
right: 0,
|
|
2313
|
-
top: 0,
|
|
2314
|
-
bottom: 0,
|
|
2315
|
-
}
|
|
2316
|
-
: {
|
|
2317
|
-
left: "3%",
|
|
2318
|
-
right: "4%",
|
|
2319
|
-
bottom: "3%",
|
|
2320
|
-
containLabel: true,
|
|
2321
|
-
};
|
|
2322
2337
|
const valueAxisBase = {
|
|
2323
2338
|
type: "value",
|
|
2324
2339
|
show: !isCircularChart,
|
|
@@ -2380,6 +2395,49 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2380
2395
|
};
|
|
2381
2396
|
// Phase 5.1: apply legend / axis / dataLabels / doughnut overrides.
|
|
2382
2397
|
const finalLegend = applyLegendOverrides(legend, spec.legend);
|
|
2398
|
+
// Reserve a band for the legend outside the axis-title band, outside
|
|
2399
|
+
// the tick labels — Excel shrinks the plot instead of painting the
|
|
2400
|
+
// legend over the category labels / axis title (#986). The bottom
|
|
2401
|
+
// axis is the value axis on a horizontal bar chart, else the (first)
|
|
2402
|
+
// category axis with whatever nameGap its levels needed.
|
|
2403
|
+
const bottomAxisNameGap = isHorizontalBarChart
|
|
2404
|
+
? valueAxisBase.nameGap
|
|
2405
|
+
: Number(chartDomains[0]?.nameGap ?? CATEGORY_AXIS_NAME_GAP);
|
|
2406
|
+
const legendBand = reserveEchartsLegendBand({
|
|
2407
|
+
position: spec.legend?.position,
|
|
2408
|
+
visible: finalLegend.show !== false,
|
|
2409
|
+
labels: legendLabelsFrom(finalLegend.data, chartSeries.map((s) => s?.name)),
|
|
2410
|
+
legendFontSize: finalLegend.textStyle?.fontSize ??
|
|
2411
|
+
spec.legend?.textFormat?.fontSize ??
|
|
2412
|
+
12,
|
|
2413
|
+
legendItemWidth: defaultLegendConfig.itemWidth,
|
|
2414
|
+
width: width ?? 0,
|
|
2415
|
+
height: height ?? 0,
|
|
2416
|
+
hasTitle: Boolean(title),
|
|
2417
|
+
titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
|
|
2418
|
+
hasBottomAxisTitle: !isCircularChart && Boolean(spec.horizontalAxisTitle),
|
|
2419
|
+
bottomAxisNameGap,
|
|
2420
|
+
hasLeftAxisTitle: !isCircularChart && Boolean(spec.verticalAxisTitle),
|
|
2421
|
+
});
|
|
2422
|
+
const positionedLegend = isCircularChart
|
|
2423
|
+
? finalLegend
|
|
2424
|
+
: { ...finalLegend, ...legendBand.legend };
|
|
2425
|
+
// Of-pie uses a full-container grid (for the connector overlay's
|
|
2426
|
+
// hidden 0-1 axes) plus, for bar-of-pie, a second grid hosting the
|
|
2427
|
+
// stacked secondary bar.
|
|
2428
|
+
const gridConfig = isOfPie
|
|
2429
|
+
? [
|
|
2430
|
+
{ left: 0, right: 0, top: 0, bottom: 0 },
|
|
2431
|
+
...(ofPieLayout?.barGrid ? [ofPieLayout.barGrid] : []),
|
|
2432
|
+
]
|
|
2433
|
+
: isCircularChart
|
|
2434
|
+
? {
|
|
2435
|
+
left: 0,
|
|
2436
|
+
right: 0,
|
|
2437
|
+
top: 0,
|
|
2438
|
+
bottom: 0,
|
|
2439
|
+
}
|
|
2440
|
+
: legendBand.grid;
|
|
2383
2441
|
// Data labels fall back to the series' axis format when the spec has
|
|
2384
2442
|
// no explicit `<c:dLbls><c:numFmt>` — Excel's "Linked to source".
|
|
2385
2443
|
const dataLabelOptions = spec.dataLabels;
|
|
@@ -2540,7 +2598,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2540
2598
|
splitLine: { show: true },
|
|
2541
2599
|
name: spec.horizontalAxisTitle ?? "",
|
|
2542
2600
|
nameLocation: "middle",
|
|
2543
|
-
nameGap:
|
|
2601
|
+
nameGap: CATEGORY_AXIS_NAME_GAP,
|
|
2544
2602
|
},
|
|
2545
2603
|
];
|
|
2546
2604
|
return base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions));
|
|
@@ -2595,13 +2653,26 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2595
2653
|
splitLine: { show: true },
|
|
2596
2654
|
name: spec.verticalAxisTitle ?? "",
|
|
2597
2655
|
nameLocation: "middle",
|
|
2598
|
-
nameGap:
|
|
2656
|
+
nameGap: CATEGORY_AXIS_NAME_GAP,
|
|
2599
2657
|
},
|
|
2600
2658
|
];
|
|
2601
2659
|
return base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions));
|
|
2602
2660
|
}
|
|
2603
2661
|
const axes = [
|
|
2604
|
-
applyAxisOverrides({
|
|
2662
|
+
applyAxisOverrides({
|
|
2663
|
+
...valueAxisBase,
|
|
2664
|
+
name: spec.verticalAxisTitle ?? "",
|
|
2665
|
+
// Clear the tick labels: "$2,000.00" is wider than the 50px
|
|
2666
|
+
// default gap, and the title painted over its own labels.
|
|
2667
|
+
nameGap: valueAxisNameGap({
|
|
2668
|
+
values: chartSeries
|
|
2669
|
+
.filter((s) => s?.yAxisIndex !== 1)
|
|
2670
|
+
.flatMap((s) => Array.isArray(s?.data)
|
|
2671
|
+
? s.data.map((d) => d !== null && typeof d === "object" ? d.value : d)
|
|
2672
|
+
: []),
|
|
2673
|
+
pattern: primaryAxisNumberFormat,
|
|
2674
|
+
}),
|
|
2675
|
+
}, spec.valueAxisOptions, primaryAxisNumberFormat),
|
|
2605
2676
|
];
|
|
2606
2677
|
if (hasSecondaryAxis) {
|
|
2607
2678
|
axes.push(applyAxisOverrides({
|
|
@@ -2621,7 +2692,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2621
2692
|
return axes;
|
|
2622
2693
|
})(),
|
|
2623
2694
|
series: seriesWithLabels,
|
|
2624
|
-
legend:
|
|
2695
|
+
legend: positionedLegend,
|
|
2625
2696
|
grid: gridConfig,
|
|
2626
2697
|
tooltip: tooltipConfig,
|
|
2627
2698
|
}, { replaceMerge, lazyUpdate: true });
|
|
@@ -5,6 +5,15 @@ export declare const chartTypeToEchartsType: (type: EmbeddedChart["spec"]["chart
|
|
|
5
5
|
* Returns undefined for "auto" / unknown — caller defaults to "circle".
|
|
6
6
|
*/
|
|
7
7
|
export declare const mapMarkerSymbolToEcharts: (symbol: string | undefined | null) => string | undefined;
|
|
8
|
+
/** Pie and doughnut — the pie-family types whose points are slices. */
|
|
9
|
+
export declare const isCircularChartType: (type: EmbeddedChart["spec"]["chartType"] | undefined) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Legend entries of a pie / doughnut: the slice names (the categories), in
|
|
12
|
+
* order, each once across every pie series — Excel's pie legend lists the
|
|
13
|
+
* categories, not the series name. echarts keys pie legend items by data
|
|
14
|
+
* item name, so an entry toggles that slice in every series.
|
|
15
|
+
*/
|
|
16
|
+
export declare const circularLegendEntries: (series: ReadonlyArray<unknown>) => string[];
|
|
8
17
|
export declare const getEchartsSeriesStyle: (type: EmbeddedChart["spec"]["chartType"]) => {
|
|
9
18
|
areaStyle: {};
|
|
10
19
|
} | undefined;
|
|
@@ -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,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,YAejE,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;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,YAejE,CAAC"}
|
|
@@ -74,6 +74,36 @@ export const mapMarkerSymbolToEcharts = (symbol) => {
|
|
|
74
74
|
return undefined;
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
|
+
/** Pie and doughnut — the pie-family types whose points are slices. */
|
|
78
|
+
export const isCircularChartType = (type) => type === "pie" || type === "doughnut";
|
|
79
|
+
/**
|
|
80
|
+
* Legend entries of a pie / doughnut: the slice names (the categories), in
|
|
81
|
+
* order, each once across every pie series — Excel's pie legend lists the
|
|
82
|
+
* categories, not the series name. echarts keys pie legend items by data
|
|
83
|
+
* item name, so an entry toggles that slice in every series.
|
|
84
|
+
*/
|
|
85
|
+
export const circularLegendEntries = (series) => {
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const entries = [];
|
|
88
|
+
for (const raw of series) {
|
|
89
|
+
const entry = raw;
|
|
90
|
+
if (entry?.type !== "pie" || !Array.isArray(entry.data))
|
|
91
|
+
continue;
|
|
92
|
+
for (const item of entry.data) {
|
|
93
|
+
const name = item !== null && typeof item === "object" && "name" in item
|
|
94
|
+
? item.name
|
|
95
|
+
: undefined;
|
|
96
|
+
if (name === undefined || name === null)
|
|
97
|
+
continue;
|
|
98
|
+
const label = String(name);
|
|
99
|
+
if (label === "" || seen.has(label))
|
|
100
|
+
continue;
|
|
101
|
+
seen.add(label);
|
|
102
|
+
entries.push(label);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return entries;
|
|
106
|
+
};
|
|
77
107
|
export const getEchartsSeriesStyle = (type) => {
|
|
78
108
|
switch (type) {
|
|
79
109
|
case "area":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAiD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../../plotly/basic-chart/basic-chart.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAgXxD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AA0FnD,eAAO,MAAM,UAAU,GAAI,wOAiBxB,eAAe,sBA4zEjB,CAAC"}
|