@rowsncolumns/charts 28.1.2 → 28.1.4
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 +138 -76
- 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 +218 -128
- 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/esm/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/basic-chart/basic-chart.js +96 -34
- 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 +136 -6
- 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/package.json +7 -7
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Legend band reservation (rowsncolumns/spreadsheet#986).
|
|
3
|
+
//
|
|
4
|
+
// Excel and Google Sheets never let the legend paint over the plot: for
|
|
5
|
+
// every legend position the chart reserves a band of its own — legend
|
|
6
|
+
// outermost, then the axis title, then the tick labels, then the plot
|
|
7
|
+
// area shrinks to whatever is left. Both renderers here used to pin the
|
|
8
|
+
// legend a fixed distance from the frame edge and leave the plot margin
|
|
9
|
+
// constant, so a bottom legend sat on the category labels and hid the
|
|
10
|
+
// horizontal axis title (and a top legend crowded the chart title).
|
|
11
|
+
//
|
|
12
|
+
// This module estimates the legend's rendered size from its actual items
|
|
13
|
+
// (count, label widths, font size, wrapping into rows / columns) and turns
|
|
14
|
+
// that into renderer geometry: the echarts `grid` + legend offsets +
|
|
15
|
+
// category-axis `nameGap`, and the plotly `margin` + paper-relative legend
|
|
16
|
+
// anchor. The estimate is deliberately generous — a few px of slack beats
|
|
17
|
+
// a legend on top of the labels.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.reservePlotlyLegendBand = exports.reserveEchartsLegendBand = exports.echartsTitleBand = exports.categoryAxisNameGap = exports.echartsAxisLabelDepth = exports.legendLabelsFrom = exports.estimateLegendBand = exports.measureTextWidth = exports.LEGEND_ITEM_GAP = exports.LEGEND_PADDING = exports.EDGE_PAD = exports.BAND_GAP = void 0;
|
|
20
|
+
var tslib_1 = require("tslib");
|
|
21
|
+
/** Space kept between neighbouring bands (legend ↔ axis title ↔ labels). */
|
|
22
|
+
exports.BAND_GAP = 8;
|
|
23
|
+
/** Inset of the outermost band from the chart frame. */
|
|
24
|
+
exports.EDGE_PAD = 10;
|
|
25
|
+
/** echarts `legend.padding` default used by both adapters' legend boxes. */
|
|
26
|
+
exports.LEGEND_PADDING = 10;
|
|
27
|
+
/** echarts `legend.itemGap` default. */
|
|
28
|
+
exports.LEGEND_ITEM_GAP = 10;
|
|
29
|
+
/** Gap echarts leaves between a legend icon and its text. */
|
|
30
|
+
var LEGEND_ICON_TEXT_GAP = 5;
|
|
31
|
+
/** Approximate rendered width of `text` at `fontSize` (canvas when available). */
|
|
32
|
+
var measureTextWidth = function (text, fontSize) {
|
|
33
|
+
if (typeof document !== "undefined") {
|
|
34
|
+
var ctx = document.createElement("canvas").getContext("2d");
|
|
35
|
+
if (ctx) {
|
|
36
|
+
ctx.font = "".concat(fontSize, "px sans-serif");
|
|
37
|
+
var width = ctx.measureText(text).width;
|
|
38
|
+
if (Number.isFinite(width) && width > 0)
|
|
39
|
+
return width;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Headless fallback: ~0.6em per character.
|
|
43
|
+
return text.length * fontSize * 0.6;
|
|
44
|
+
};
|
|
45
|
+
exports.measureTextWidth = measureTextWidth;
|
|
46
|
+
/**
|
|
47
|
+
* Estimate the legend's rendered box from its items. Horizontal legends
|
|
48
|
+
* pack items greedily into rows no wider than the frame; vertical legends
|
|
49
|
+
* stack items and spill into a new column when taller than the frame.
|
|
50
|
+
*/
|
|
51
|
+
var estimateLegendBand = function (_a) {
|
|
52
|
+
var e_1, _b;
|
|
53
|
+
var labels = _a.labels, _c = _a.fontSize, fontSize = _c === void 0 ? 12 : _c, _d = _a.itemWidth, itemWidth = _d === void 0 ? 10 : _d, _e = _a.itemGap, itemGap = _e === void 0 ? exports.LEGEND_ITEM_GAP : _e, _f = _a.padding, padding = _f === void 0 ? exports.LEGEND_PADDING : _f, orientation = _a.orientation, availableWidth = _a.availableWidth, availableHeight = _a.availableHeight;
|
|
54
|
+
if (!labels.length)
|
|
55
|
+
return { rows: 0, columns: 0, width: 0, height: 0 };
|
|
56
|
+
var lineHeight = Math.ceil(fontSize * 1.3);
|
|
57
|
+
var itemWidths = labels.map(function (label) {
|
|
58
|
+
return itemWidth + LEGEND_ICON_TEXT_GAP + Math.ceil((0, exports.measureTextWidth)(label, fontSize));
|
|
59
|
+
});
|
|
60
|
+
if (orientation === "horizontal") {
|
|
61
|
+
var maxRowWidth = Math.max(1, availableWidth - padding * 2);
|
|
62
|
+
var rows_1 = 1;
|
|
63
|
+
var rowWidth = 0;
|
|
64
|
+
var widestRow = 0;
|
|
65
|
+
try {
|
|
66
|
+
for (var itemWidths_1 = tslib_1.__values(itemWidths), itemWidths_1_1 = itemWidths_1.next(); !itemWidths_1_1.done; itemWidths_1_1 = itemWidths_1.next()) {
|
|
67
|
+
var w = itemWidths_1_1.value;
|
|
68
|
+
var next = rowWidth === 0 ? w : rowWidth + itemGap + w;
|
|
69
|
+
if (next > maxRowWidth && rowWidth > 0) {
|
|
70
|
+
widestRow = Math.max(widestRow, rowWidth);
|
|
71
|
+
rows_1 += 1;
|
|
72
|
+
rowWidth = w;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
rowWidth = next;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
80
|
+
finally {
|
|
81
|
+
try {
|
|
82
|
+
if (itemWidths_1_1 && !itemWidths_1_1.done && (_b = itemWidths_1.return)) _b.call(itemWidths_1);
|
|
83
|
+
}
|
|
84
|
+
finally { if (e_1) throw e_1.error; }
|
|
85
|
+
}
|
|
86
|
+
widestRow = Math.max(widestRow, rowWidth);
|
|
87
|
+
return {
|
|
88
|
+
rows: rows_1,
|
|
89
|
+
columns: 1,
|
|
90
|
+
width: padding * 2 + widestRow,
|
|
91
|
+
height: padding * 2 + rows_1 * lineHeight + (rows_1 - 1) * itemGap,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
var maxColumnHeight = Math.max(1, availableHeight - padding * 2);
|
|
95
|
+
var perColumn = Math.max(1, Math.floor((maxColumnHeight + itemGap) / (lineHeight + itemGap)));
|
|
96
|
+
var columns = Math.ceil(labels.length / perColumn);
|
|
97
|
+
var rows = Math.min(labels.length, perColumn);
|
|
98
|
+
var widestItem = Math.max.apply(Math, tslib_1.__spreadArray([], tslib_1.__read(itemWidths), false));
|
|
99
|
+
return {
|
|
100
|
+
rows: rows,
|
|
101
|
+
columns: columns,
|
|
102
|
+
width: padding * 2 + columns * widestItem + (columns - 1) * itemGap,
|
|
103
|
+
height: padding * 2 + rows * lineHeight + (rows - 1) * itemGap,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
exports.estimateLegendBand = estimateLegendBand;
|
|
107
|
+
/**
|
|
108
|
+
* Pull the legend entry labels out of an echarts `legend.data` (strings or
|
|
109
|
+
* `{ name }` items); falls back to the series names, which is what echarts
|
|
110
|
+
* itself lists when `data` is empty.
|
|
111
|
+
*/
|
|
112
|
+
var legendLabelsFrom = function (legendData, seriesNames) {
|
|
113
|
+
var fromData = (legendData !== null && legendData !== void 0 ? legendData : [])
|
|
114
|
+
.map(function (d) {
|
|
115
|
+
return typeof d === "string"
|
|
116
|
+
? d
|
|
117
|
+
: d && typeof d === "object" && typeof d.name === "string"
|
|
118
|
+
? d.name
|
|
119
|
+
: "";
|
|
120
|
+
})
|
|
121
|
+
.filter(function (s) { return s.length > 0; });
|
|
122
|
+
if (fromData.length)
|
|
123
|
+
return fromData;
|
|
124
|
+
return seriesNames
|
|
125
|
+
.map(function (n) { return (typeof n === "string" ? n : n === undefined || n === null ? "" : String(n)); })
|
|
126
|
+
.filter(function (s) { return s.length > 0; });
|
|
127
|
+
};
|
|
128
|
+
exports.legendLabelsFrom = legendLabelsFrom;
|
|
129
|
+
/**
|
|
130
|
+
* Depth (px) of one row of echarts axis tick labels measured from the axis
|
|
131
|
+
* line: 5px tick + 8px label margin + one 12px line, rounded up. With
|
|
132
|
+
* `grid.containLabel` this is the part the grid already accounts for; the
|
|
133
|
+
* axis title sits beyond it.
|
|
134
|
+
*/
|
|
135
|
+
var echartsAxisLabelDepth = function (fontSize) {
|
|
136
|
+
if (fontSize === void 0) { fontSize = 12; }
|
|
137
|
+
return 5 + 8 + Math.ceil(fontSize * 1.1);
|
|
138
|
+
};
|
|
139
|
+
exports.echartsAxisLabelDepth = echartsAxisLabelDepth;
|
|
140
|
+
/**
|
|
141
|
+
* echarts `nameGap` for a category axis whose title sits in the middle
|
|
142
|
+
* below the tick labels: clear one row of labels plus a small gap, measured
|
|
143
|
+
* to the title's centre line.
|
|
144
|
+
*/
|
|
145
|
+
var categoryAxisNameGap = function (fontSize) {
|
|
146
|
+
if (fontSize === void 0) { fontSize = 12; }
|
|
147
|
+
return (0, exports.echartsAxisLabelDepth)(fontSize) + exports.BAND_GAP / 2 + Math.ceil(fontSize / 2);
|
|
148
|
+
};
|
|
149
|
+
exports.categoryAxisNameGap = categoryAxisNameGap;
|
|
150
|
+
/** Height of the echarts chart-title band (padding 10 each side + one line). */
|
|
151
|
+
var echartsTitleBand = function (titleFontSize) {
|
|
152
|
+
if (titleFontSize === void 0) { titleFontSize = 16; }
|
|
153
|
+
return exports.LEGEND_PADDING * 2 + Math.ceil(titleFontSize * 1.25);
|
|
154
|
+
};
|
|
155
|
+
exports.echartsTitleBand = echartsTitleBand;
|
|
156
|
+
/**
|
|
157
|
+
* Echarts cartesian geometry with a band reserved for the legend in its
|
|
158
|
+
* position, outside the axis-title band, outside the tick labels.
|
|
159
|
+
*/
|
|
160
|
+
var reserveEchartsLegendBand = function (_a) {
|
|
161
|
+
var position = _a.position, visible = _a.visible, labels = _a.labels, _b = _a.legendFontSize, legendFontSize = _b === void 0 ? 12 : _b, _c = _a.legendItemWidth, legendItemWidth = _c === void 0 ? 10 : _c, width = _a.width, height = _a.height, hasTitle = _a.hasTitle, _d = _a.titleFontSize, titleFontSize = _d === void 0 ? 16 : _d, hasBottomAxisTitle = _a.hasBottomAxisTitle, bottomAxisNameGap = _a.bottomAxisNameGap, hasLeftAxisTitle = _a.hasLeftAxisTitle, _e = _a.axisFontSize, axisFontSize = _e === void 0 ? 12 : _e;
|
|
162
|
+
var pos = visible ? (position !== null && position !== void 0 ? position : "top") : "none";
|
|
163
|
+
var horizontal = pos === "top" || pos === "bottom";
|
|
164
|
+
var band = pos === "none"
|
|
165
|
+
? { rows: 0, columns: 0, width: 0, height: 0 }
|
|
166
|
+
: (0, exports.estimateLegendBand)({
|
|
167
|
+
labels: labels,
|
|
168
|
+
fontSize: legendFontSize,
|
|
169
|
+
itemWidth: legendItemWidth,
|
|
170
|
+
orientation: horizontal ? "horizontal" : "vertical",
|
|
171
|
+
availableWidth: width,
|
|
172
|
+
availableHeight: height,
|
|
173
|
+
});
|
|
174
|
+
var titleBand = hasTitle ? (0, exports.echartsTitleBand)(titleFontSize) : 0;
|
|
175
|
+
var labelDepth = (0, exports.echartsAxisLabelDepth)(axisFontSize);
|
|
176
|
+
// The bottom axis title's centre sits `nameGap` below the axis line and
|
|
177
|
+
// its line box extends half a font below that; the contained labels
|
|
178
|
+
// already cover `labelDepth` of it.
|
|
179
|
+
var bottomTitleOverhang = hasBottomAxisTitle
|
|
180
|
+
? Math.max(0, bottomAxisNameGap + Math.ceil(axisFontSize / 2) - labelDepth) + exports.BAND_GAP
|
|
181
|
+
: 0;
|
|
182
|
+
// The rotated left axis title clears its labels by `fontSize + 6` (see
|
|
183
|
+
// valueAxisNameGap) and its own line box extends half a font further.
|
|
184
|
+
var leftTitleOverhang = hasLeftAxisTitle
|
|
185
|
+
? Math.ceil(axisFontSize * 1.5) + 6 + exports.BAND_GAP / 2
|
|
186
|
+
: 0;
|
|
187
|
+
// Half a tick label pokes past the top / right end of an axis.
|
|
188
|
+
var topHeadroom = Math.ceil(axisFontSize / 2) + 4;
|
|
189
|
+
var rightHeadroom = Math.ceil(axisFontSize / 2) + 10;
|
|
190
|
+
var legend = {};
|
|
191
|
+
var top = (hasTitle ? titleBand : exports.EDGE_PAD) + topHeadroom;
|
|
192
|
+
var bottom = exports.EDGE_PAD + bottomTitleOverhang;
|
|
193
|
+
var left = exports.EDGE_PAD + leftTitleOverhang;
|
|
194
|
+
var right = rightHeadroom;
|
|
195
|
+
switch (pos) {
|
|
196
|
+
case "top": {
|
|
197
|
+
legend.top = hasTitle ? titleBand : exports.EDGE_PAD;
|
|
198
|
+
legend.bottom = undefined;
|
|
199
|
+
top = legend.top + band.height + exports.BAND_GAP + topHeadroom;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "bottom": {
|
|
203
|
+
legend.bottom = exports.EDGE_PAD;
|
|
204
|
+
legend.top = undefined;
|
|
205
|
+
bottom = exports.EDGE_PAD + band.height + exports.BAND_GAP + bottomTitleOverhang;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case "left": {
|
|
209
|
+
legend.left = exports.EDGE_PAD;
|
|
210
|
+
legend.right = undefined;
|
|
211
|
+
left = exports.EDGE_PAD + band.width + exports.BAND_GAP + leftTitleOverhang;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "right": {
|
|
215
|
+
legend.right = exports.EDGE_PAD;
|
|
216
|
+
legend.left = undefined;
|
|
217
|
+
right = exports.EDGE_PAD + band.width + exports.BAND_GAP;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case "topRight": {
|
|
221
|
+
legend.right = exports.EDGE_PAD;
|
|
222
|
+
legend.left = undefined;
|
|
223
|
+
legend.top = hasTitle ? titleBand : exports.EDGE_PAD;
|
|
224
|
+
legend.bottom = undefined;
|
|
225
|
+
right = exports.EDGE_PAD + band.width + exports.BAND_GAP;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
default:
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
band: band,
|
|
233
|
+
grid: { left: left, right: right, top: top, bottom: bottom, containLabel: true },
|
|
234
|
+
legend: legend,
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
exports.reserveEchartsLegendBand = reserveEchartsLegendBand;
|
|
238
|
+
/** Plotly text line height (its LINE_SPACING is 1.3em). */
|
|
239
|
+
var plotlyLineHeight = function (fontSize) { return Math.ceil(fontSize * 1.3); };
|
|
240
|
+
/** Plotly default `legend.itemwidth`. */
|
|
241
|
+
var PLOTLY_LEGEND_ITEM_WIDTH = 30;
|
|
242
|
+
/**
|
|
243
|
+
* Plotly geometry with a band reserved for the legend in its position.
|
|
244
|
+
* Legend coordinates are paper-relative (fractions of the plot area), so
|
|
245
|
+
* they are derived from the margins this returns; `automargin` on the
|
|
246
|
+
* axes may still grow a margin further for unusually deep tick labels.
|
|
247
|
+
*/
|
|
248
|
+
var reservePlotlyLegendBand = function (_a) {
|
|
249
|
+
var position = _a.position, visible = _a.visible, labels = _a.labels, _b = _a.legendFontSize, legendFontSize = _b === void 0 ? 12 : _b, width = _a.width, height = _a.height, hasTitle = _a.hasTitle, _c = _a.titleFontSize, titleFontSize = _c === void 0 ? 16 : _c, hasBottomAxisTitle = _a.hasBottomAxisTitle, hasLeftAxisTitle = _a.hasLeftAxisTitle, leftAxisDepth = _a.leftAxisDepth, _d = _a.rightAxisDepth, rightAxisDepth = _d === void 0 ? 0 : _d, _e = _a.bottomAxisTitleStandoff, bottomAxisTitleStandoff = _e === void 0 ? exports.BAND_GAP : _e, _f = _a.axisFontSize, axisFontSize = _f === void 0 ? 12 : _f;
|
|
250
|
+
var pos = visible ? (position !== null && position !== void 0 ? position : "right") : "none";
|
|
251
|
+
var horizontal = pos === "top" || pos === "bottom";
|
|
252
|
+
var band = pos === "none"
|
|
253
|
+
? { rows: 0, columns: 0, width: 0, height: 0 }
|
|
254
|
+
: (0, exports.estimateLegendBand)({
|
|
255
|
+
labels: labels,
|
|
256
|
+
fontSize: legendFontSize,
|
|
257
|
+
itemWidth: PLOTLY_LEGEND_ITEM_WIDTH,
|
|
258
|
+
itemGap: 5,
|
|
259
|
+
padding: 5,
|
|
260
|
+
orientation: horizontal ? "horizontal" : "vertical",
|
|
261
|
+
availableWidth: width,
|
|
262
|
+
availableHeight: height,
|
|
263
|
+
});
|
|
264
|
+
var titleBand = hasTitle
|
|
265
|
+
? exports.EDGE_PAD + plotlyLineHeight(titleFontSize) + exports.BAND_GAP
|
|
266
|
+
: 0;
|
|
267
|
+
// One row of bottom tick labels: plotly pads them ~3px off the axis.
|
|
268
|
+
var bottomLabelDepth = 3 + plotlyLineHeight(axisFontSize);
|
|
269
|
+
var bottomTitleBand = hasBottomAxisTitle
|
|
270
|
+
? bottomAxisTitleStandoff + plotlyLineHeight(axisFontSize)
|
|
271
|
+
: 0;
|
|
272
|
+
var bottomAxisDepth = bottomLabelDepth + bottomTitleBand;
|
|
273
|
+
var topHeadroom = Math.ceil(axisFontSize / 2) + 4;
|
|
274
|
+
var t = Math.max(exports.EDGE_PAD + topHeadroom, titleBand + topHeadroom);
|
|
275
|
+
var b = bottomAxisDepth + exports.EDGE_PAD;
|
|
276
|
+
var l = leftAxisDepth + (hasLeftAxisTitle ? exports.BAND_GAP : 0) + exports.EDGE_PAD;
|
|
277
|
+
var r = Math.max(exports.EDGE_PAD + Math.ceil(axisFontSize / 2), rightAxisDepth + exports.EDGE_PAD);
|
|
278
|
+
var legend = null;
|
|
279
|
+
switch (pos) {
|
|
280
|
+
case "top": {
|
|
281
|
+
t = (hasTitle ? titleBand : exports.EDGE_PAD) + band.height + exports.BAND_GAP + topHeadroom;
|
|
282
|
+
legend = { orientation: "h", x: 0.5, xanchor: "center", y: 1, yanchor: "bottom" };
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case "bottom": {
|
|
286
|
+
b = bottomAxisDepth + exports.BAND_GAP + band.height + exports.EDGE_PAD;
|
|
287
|
+
legend = { orientation: "h", x: 0.5, xanchor: "center", y: 0, yanchor: "top" };
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case "left": {
|
|
291
|
+
l = leftAxisDepth + exports.BAND_GAP + band.width + exports.EDGE_PAD;
|
|
292
|
+
legend = { orientation: "v", x: 0, xanchor: "right", y: 0.5, yanchor: "middle" };
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
case "right": {
|
|
296
|
+
r = rightAxisDepth + exports.BAND_GAP + band.width + exports.EDGE_PAD;
|
|
297
|
+
legend = { orientation: "v", x: 1, xanchor: "left", y: 0.5, yanchor: "middle" };
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case "topRight": {
|
|
301
|
+
r = rightAxisDepth + exports.BAND_GAP + band.width + exports.EDGE_PAD;
|
|
302
|
+
legend = { orientation: "v", x: 1, xanchor: "left", y: 1, yanchor: "top" };
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
default:
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
// Paper coordinates: offsets outside the plot area are fractions of it.
|
|
309
|
+
var plotWidth = Math.max(1, width - l - r);
|
|
310
|
+
var plotHeight = Math.max(1, height - t - b);
|
|
311
|
+
if (legend) {
|
|
312
|
+
switch (pos) {
|
|
313
|
+
case "top":
|
|
314
|
+
legend.y = 1 + (exports.BAND_GAP + topHeadroom) / plotHeight;
|
|
315
|
+
break;
|
|
316
|
+
case "bottom":
|
|
317
|
+
legend.y = -(bottomAxisDepth + exports.BAND_GAP) / plotHeight;
|
|
318
|
+
break;
|
|
319
|
+
case "left":
|
|
320
|
+
legend.x = -(leftAxisDepth + exports.BAND_GAP) / plotWidth;
|
|
321
|
+
break;
|
|
322
|
+
case "right":
|
|
323
|
+
case "topRight":
|
|
324
|
+
legend.x = 1 + (rightAxisDepth + exports.BAND_GAP) / plotWidth;
|
|
325
|
+
break;
|
|
326
|
+
default:
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
band: band,
|
|
332
|
+
margin: { l: l, r: r, t: t, b: b },
|
|
333
|
+
legend: legend,
|
|
334
|
+
bottomAxisTitleStandoff: bottomAxisTitleStandoff,
|
|
335
|
+
title: { yref: "container", y: 1, yanchor: "top", pad: { t: exports.EDGE_PAD } },
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
exports.reservePlotlyLegendBand = reservePlotlyLegendBand;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../basic-chart/basic-chart.tsx"],"names":[],"mappings":"AA4DA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"basic-chart.d.ts","sourceRoot":"","sources":["../../../basic-chart/basic-chart.tsx"],"names":[],"mappings":"AA4DA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAmQrD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAqEnD,eAAO,MAAM,UAAU,GAAI,wOAiBxB,eAAe,sBAwyGjB,CAAC"}
|
|
@@ -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
|
|
@@ -1689,7 +1694,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1689
1694
|
// domain range's labels in sequence, each series range taking its own
|
|
1690
1695
|
// window — not the primary domain range alone (C-78).
|
|
1691
1696
|
const domainSources = spec.domains?.[0]?.sources ?? [];
|
|
1692
|
-
const pieDomainLabels =
|
|
1697
|
+
const pieDomainLabels = isCircularChart &&
|
|
1693
1698
|
(domainSources.length > 1 || series.sources.length > 1)
|
|
1694
1699
|
? domainSources.flatMap((sheetRange) => getDomainValuesFromRange(sheetRange, spec.hiddenDimensionStrategy) ?? [])
|
|
1695
1700
|
: undefined;
|
|
@@ -1766,6 +1771,22 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1766
1771
|
mapMarkerSymbolToEcharts(markerSymbol) ?? "circle";
|
|
1767
1772
|
seriesEntry.symbolSize = seriesAny.marker?.size ?? 6;
|
|
1768
1773
|
}
|
|
1774
|
+
else if (Array.isArray(data)) {
|
|
1775
|
+
// A marker-less line draws nothing for a point with a gap on both
|
|
1776
|
+
// sides (a data row followed by a blank row), so that row vanishes
|
|
1777
|
+
// from the chart. Excel plots an isolated marker there: keep the
|
|
1778
|
+
// series symbol-less and mark only those points.
|
|
1779
|
+
const isolated = isolatedPointIndexes(data, connectsAcrossEmptyCells(spec, lineLike));
|
|
1780
|
+
if (isolated.length) {
|
|
1781
|
+
seriesEntry.showSymbol = true;
|
|
1782
|
+
seriesEntry.symbol = "none";
|
|
1783
|
+
const marked = [...data];
|
|
1784
|
+
for (const i of isolated) {
|
|
1785
|
+
marked[i] = { value: marked[i], symbol: "circle", symbolSize: 6 };
|
|
1786
|
+
}
|
|
1787
|
+
seriesEntry.data = marked;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1769
1790
|
}
|
|
1770
1791
|
// Gradient fill — overrides solid color when present. echarts
|
|
1771
1792
|
// paints it natively via a LinearGradient on itemStyle.color
|
|
@@ -1876,6 +1897,12 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
1876
1897
|
chartSeries.push(seriesEntry);
|
|
1877
1898
|
pushSeriesOverlays(series, data, dataLabel);
|
|
1878
1899
|
}
|
|
1900
|
+
if (isCircularChart && !isOfPie) {
|
|
1901
|
+
// Excel's pie / doughnut legend lists the categories, one entry per
|
|
1902
|
+
// slice — not the series name (the loop above collected those). The
|
|
1903
|
+
// of-pie branch below builds its own category legend.
|
|
1904
|
+
legend.data = circularLegendEntries(chartSeries);
|
|
1905
|
+
}
|
|
1879
1906
|
if (isOfPie) {
|
|
1880
1907
|
// Pie-of-pie / bar-of-pie — Excel splits the minor slices into a
|
|
1881
1908
|
// secondary pie or stacked bar beside the main pie, replaces them
|
|
@@ -2298,27 +2325,6 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2298
2325
|
previousSeriesCount.current !== renderedSeriesCount
|
|
2299
2326
|
? ["series", "xAxis", "yAxis", "legend", "grid", "tooltip"]
|
|
2300
2327
|
: [];
|
|
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
2328
|
const valueAxisBase = {
|
|
2323
2329
|
type: "value",
|
|
2324
2330
|
show: !isCircularChart,
|
|
@@ -2380,6 +2386,49 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2380
2386
|
};
|
|
2381
2387
|
// Phase 5.1: apply legend / axis / dataLabels / doughnut overrides.
|
|
2382
2388
|
const finalLegend = applyLegendOverrides(legend, spec.legend);
|
|
2389
|
+
// Reserve a band for the legend outside the axis-title band, outside
|
|
2390
|
+
// the tick labels — Excel shrinks the plot instead of painting the
|
|
2391
|
+
// legend over the category labels / axis title (#986). The bottom
|
|
2392
|
+
// axis is the value axis on a horizontal bar chart, else the (first)
|
|
2393
|
+
// category axis with whatever nameGap its levels needed.
|
|
2394
|
+
const bottomAxisNameGap = isHorizontalBarChart
|
|
2395
|
+
? valueAxisBase.nameGap
|
|
2396
|
+
: Number(chartDomains[0]?.nameGap ?? CATEGORY_AXIS_NAME_GAP);
|
|
2397
|
+
const legendBand = reserveEchartsLegendBand({
|
|
2398
|
+
position: spec.legend?.position,
|
|
2399
|
+
visible: finalLegend.show !== false,
|
|
2400
|
+
labels: legendLabelsFrom(finalLegend.data, chartSeries.map((s) => s?.name)),
|
|
2401
|
+
legendFontSize: finalLegend.textStyle?.fontSize ??
|
|
2402
|
+
spec.legend?.textFormat?.fontSize ??
|
|
2403
|
+
12,
|
|
2404
|
+
legendItemWidth: defaultLegendConfig.itemWidth,
|
|
2405
|
+
width: width ?? 0,
|
|
2406
|
+
height: height ?? 0,
|
|
2407
|
+
hasTitle: Boolean(title),
|
|
2408
|
+
titleFontSize: spec.titleTextFormat?.fontSize ?? 16,
|
|
2409
|
+
hasBottomAxisTitle: !isCircularChart && Boolean(spec.horizontalAxisTitle),
|
|
2410
|
+
bottomAxisNameGap,
|
|
2411
|
+
hasLeftAxisTitle: !isCircularChart && Boolean(spec.verticalAxisTitle),
|
|
2412
|
+
});
|
|
2413
|
+
const positionedLegend = isCircularChart
|
|
2414
|
+
? finalLegend
|
|
2415
|
+
: { ...finalLegend, ...legendBand.legend };
|
|
2416
|
+
// Of-pie uses a full-container grid (for the connector overlay's
|
|
2417
|
+
// hidden 0-1 axes) plus, for bar-of-pie, a second grid hosting the
|
|
2418
|
+
// stacked secondary bar.
|
|
2419
|
+
const gridConfig = isOfPie
|
|
2420
|
+
? [
|
|
2421
|
+
{ left: 0, right: 0, top: 0, bottom: 0 },
|
|
2422
|
+
...(ofPieLayout?.barGrid ? [ofPieLayout.barGrid] : []),
|
|
2423
|
+
]
|
|
2424
|
+
: isCircularChart
|
|
2425
|
+
? {
|
|
2426
|
+
left: 0,
|
|
2427
|
+
right: 0,
|
|
2428
|
+
top: 0,
|
|
2429
|
+
bottom: 0,
|
|
2430
|
+
}
|
|
2431
|
+
: legendBand.grid;
|
|
2383
2432
|
// Data labels fall back to the series' axis format when the spec has
|
|
2384
2433
|
// no explicit `<c:dLbls><c:numFmt>` — Excel's "Linked to source".
|
|
2385
2434
|
const dataLabelOptions = spec.dataLabels;
|
|
@@ -2540,7 +2589,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2540
2589
|
splitLine: { show: true },
|
|
2541
2590
|
name: spec.horizontalAxisTitle ?? "",
|
|
2542
2591
|
nameLocation: "middle",
|
|
2543
|
-
nameGap:
|
|
2592
|
+
nameGap: CATEGORY_AXIS_NAME_GAP,
|
|
2544
2593
|
},
|
|
2545
2594
|
];
|
|
2546
2595
|
return base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions));
|
|
@@ -2595,13 +2644,26 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2595
2644
|
splitLine: { show: true },
|
|
2596
2645
|
name: spec.verticalAxisTitle ?? "",
|
|
2597
2646
|
nameLocation: "middle",
|
|
2598
|
-
nameGap:
|
|
2647
|
+
nameGap: CATEGORY_AXIS_NAME_GAP,
|
|
2599
2648
|
},
|
|
2600
2649
|
];
|
|
2601
2650
|
return base.map((axis) => applyAxisOverrides(axis, spec.categoryAxisOptions));
|
|
2602
2651
|
}
|
|
2603
2652
|
const axes = [
|
|
2604
|
-
applyAxisOverrides({
|
|
2653
|
+
applyAxisOverrides({
|
|
2654
|
+
...valueAxisBase,
|
|
2655
|
+
name: spec.verticalAxisTitle ?? "",
|
|
2656
|
+
// Clear the tick labels: "$2,000.00" is wider than the 50px
|
|
2657
|
+
// default gap, and the title painted over its own labels.
|
|
2658
|
+
nameGap: valueAxisNameGap({
|
|
2659
|
+
values: chartSeries
|
|
2660
|
+
.filter((s) => s?.yAxisIndex !== 1)
|
|
2661
|
+
.flatMap((s) => Array.isArray(s?.data)
|
|
2662
|
+
? s.data.map((d) => d !== null && typeof d === "object" ? d.value : d)
|
|
2663
|
+
: []),
|
|
2664
|
+
pattern: primaryAxisNumberFormat,
|
|
2665
|
+
}),
|
|
2666
|
+
}, spec.valueAxisOptions, primaryAxisNumberFormat),
|
|
2605
2667
|
];
|
|
2606
2668
|
if (hasSecondaryAxis) {
|
|
2607
2669
|
axes.push(applyAxisOverrides({
|
|
@@ -2621,7 +2683,7 @@ export const BasicChart = ({ width, height, chart, theme, isDarkMode, sheetId, r
|
|
|
2621
2683
|
return axes;
|
|
2622
2684
|
})(),
|
|
2623
2685
|
series: seriesWithLabels,
|
|
2624
|
-
legend:
|
|
2686
|
+
legend: positionedLegend,
|
|
2625
2687
|
grid: gridConfig,
|
|
2626
2688
|
tooltip: tooltipConfig,
|
|
2627
2689
|
}, { 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;AAGtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,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;AAGtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA+WxD,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AA0FnD,eAAO,MAAM,UAAU,GAAI,wOAiBxB,eAAe,sBAg0EjB,CAAC"}
|