@rowsncolumns/charts 28.1.7 → 28.1.10
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 +525 -419
- package/dist/cjs/basic-chart/helpers.d.ts.map +1 -1
- package/dist/cjs/basic-chart/helpers.js +2 -0
- package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/cjs/plotly/basic-chart/basic-chart.js +351 -192
- package/dist/cjs/plotly/basic-chart/helpers.d.ts.map +1 -1
- package/dist/cjs/plotly/basic-chart/helpers.js +2 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/utils/bubble.d.ts +34 -1
- package/dist/cjs/utils/bubble.d.ts.map +1 -1
- package/dist/cjs/utils/bubble.js +39 -13
- package/dist/cjs/utils/doughnut.d.ts +19 -0
- package/dist/cjs/utils/doughnut.d.ts.map +1 -0
- package/dist/cjs/utils/doughnut.js +28 -0
- package/dist/cjs/utils/echarts-overlays.d.ts +46 -0
- package/dist/cjs/utils/echarts-overlays.d.ts.map +1 -0
- package/dist/cjs/utils/echarts-overlays.js +134 -0
- package/dist/cjs/utils/hierarchy.d.ts +23 -1
- package/dist/cjs/utils/hierarchy.d.ts.map +1 -1
- package/dist/cjs/utils/hierarchy.js +179 -55
- package/dist/cjs/utils/overrides.d.ts +9 -0
- package/dist/cjs/utils/overrides.d.ts.map +1 -1
- package/dist/cjs/utils/overrides.js +18 -3
- package/dist/cjs/utils/radar.d.ts +31 -5
- package/dist/cjs/utils/radar.d.ts.map +1 -1
- package/dist/cjs/utils/radar.js +67 -27
- package/dist/cjs/utils/trendline.d.ts +7 -1
- package/dist/cjs/utils/trendline.d.ts.map +1 -1
- package/dist/cjs/utils/trendline.js +82 -18
- package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/basic-chart/basic-chart.js +376 -248
- package/dist/esm/basic-chart/helpers.d.ts.map +1 -1
- package/dist/esm/basic-chart/helpers.js +2 -0
- package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/plotly/basic-chart/basic-chart.js +279 -69
- package/dist/esm/plotly/basic-chart/helpers.d.ts.map +1 -1
- package/dist/esm/plotly/basic-chart/helpers.js +2 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/utils/bubble.d.ts +34 -1
- package/dist/esm/utils/bubble.d.ts.map +1 -1
- package/dist/esm/utils/bubble.js +35 -11
- package/dist/esm/utils/doughnut.d.ts +19 -0
- package/dist/esm/utils/doughnut.d.ts.map +1 -0
- package/dist/esm/utils/doughnut.js +21 -0
- package/dist/esm/utils/echarts-overlays.d.ts +46 -0
- package/dist/esm/utils/echarts-overlays.d.ts.map +1 -0
- package/dist/esm/utils/echarts-overlays.js +135 -0
- package/dist/esm/utils/hierarchy.d.ts +23 -1
- package/dist/esm/utils/hierarchy.d.ts.map +1 -1
- package/dist/esm/utils/hierarchy.js +168 -50
- package/dist/esm/utils/overrides.d.ts +9 -0
- package/dist/esm/utils/overrides.d.ts.map +1 -1
- package/dist/esm/utils/overrides.js +16 -2
- package/dist/esm/utils/radar.d.ts +31 -5
- package/dist/esm/utils/radar.d.ts.map +1 -1
- package/dist/esm/utils/radar.js +44 -16
- package/dist/esm/utils/trendline.d.ts +7 -1
- package/dist/esm/utils/trendline.d.ts.map +1 -1
- package/dist/esm/utils/trendline.js +67 -14
- package/package.json +4 -4
|
@@ -16,10 +16,6 @@ const toLabelString = (value) => {
|
|
|
16
16
|
return "";
|
|
17
17
|
return `${value}`.trim();
|
|
18
18
|
};
|
|
19
|
-
const toParentLabel = (value) => {
|
|
20
|
-
const label = toLabelString(value);
|
|
21
|
-
return label === "" ? null : label;
|
|
22
|
-
};
|
|
23
19
|
const toNumericValue = (value) => {
|
|
24
20
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
25
21
|
return value;
|
|
@@ -50,71 +46,178 @@ export const getHierarchyHeaderTitle = (spec, options) => {
|
|
|
50
46
|
}
|
|
51
47
|
return undefined;
|
|
52
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Lay the rows out as Excel does: one column per level (`parentLabels`
|
|
51
|
+
* entries, top level first) and the label column as the innermost level.
|
|
52
|
+
* Returns, per row, the filled level cells up to the row's leaf.
|
|
53
|
+
*
|
|
54
|
+
* A blank level cell BEFORE the row's first filled cell means "same as the
|
|
55
|
+
* row above" (Excel writes a parent only on the first row of its group).
|
|
56
|
+
* A blank AFTER the last filled cell ends the branch there — that cell is
|
|
57
|
+
* the row's leaf, so a shorter branch (`Services | Training | <blank>`)
|
|
58
|
+
* is a leaf of the second level, not `Services > Training > <previous
|
|
59
|
+
* item>` as plain per-column fill-down would make it.
|
|
60
|
+
*/
|
|
61
|
+
const layoutHierarchyLevels = (levelColumns, rowCount) => {
|
|
62
|
+
const filled = [];
|
|
63
|
+
let above = levelColumns.map(() => "");
|
|
64
|
+
for (let row = 0; row < rowCount; row++) {
|
|
65
|
+
const cells = levelColumns.map((column) => column[row] ?? "");
|
|
66
|
+
const firstFilled = cells.findIndex((cell) => cell !== "");
|
|
67
|
+
if (firstFilled === -1) {
|
|
68
|
+
// An entirely blank row defines nothing (and inherits nothing).
|
|
69
|
+
filled.push([]);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const effective = cells.map((cell, level) => level < firstFilled ? above[level] ?? "" : cell);
|
|
73
|
+
let lastFilled = effective.length - 1;
|
|
74
|
+
while (lastFilled >= 0 && effective[lastFilled] === "")
|
|
75
|
+
lastFilled--;
|
|
76
|
+
// The row's own levels end at its last filled cell; the cells past it
|
|
77
|
+
// stay blank for the rows below (they inherit up to the leaf only).
|
|
78
|
+
above = effective.map((cell, level) => (level <= lastFilled ? cell : ""));
|
|
79
|
+
filled.push(effective.slice(0, lastFilled + 1).filter((cell) => cell !== ""));
|
|
80
|
+
}
|
|
81
|
+
return filled;
|
|
82
|
+
};
|
|
53
83
|
export const extractHierarchyRows = (spec, options) => {
|
|
54
84
|
const { getDomainValuesFromRange, getSeriesValuesFromRange } = options;
|
|
55
85
|
const hiddenStrategy = options.hiddenDimensionStrategy;
|
|
56
86
|
const skipHeaders = spec.useFirstRowAsHeaders !== false;
|
|
57
87
|
const labels = flattenChartData(spec.labels, getDomainValuesFromRange, hiddenStrategy, skipHeaders).map(toLabelString);
|
|
58
|
-
|
|
88
|
+
// Each `parentLabels` entry is one hierarchy level (top level first).
|
|
89
|
+
const parentLevels = (spec.parentLabels ?? []).map((entry) => flattenChartData([entry], getDomainValuesFromRange, hiddenStrategy, skipHeaders).map(toLabelString));
|
|
59
90
|
const sizes = flattenChartData(spec.sizeData, getSeriesValuesFromRange, hiddenStrategy, skipHeaders);
|
|
91
|
+
// A single parent column whose values name other rows' labels is a flat
|
|
92
|
+
// adjacency list (Google Sheets' label / parent-label model): every row
|
|
93
|
+
// is its own node and a blank parent means a root. Excel's level layout
|
|
94
|
+
// never lists a level name in the label column.
|
|
95
|
+
const labelSet = new Set(labels.filter((label) => label !== ""));
|
|
96
|
+
const isAdjacencyList = parentLevels.length === 1 &&
|
|
97
|
+
parentLevels[0].some((parent) => parent !== "" && labelSet.has(parent));
|
|
60
98
|
const rows = [];
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
99
|
+
const seenPaths = new Set();
|
|
100
|
+
if (isAdjacencyList) {
|
|
101
|
+
const parents = parentLevels[0];
|
|
102
|
+
for (let i = 0; i < labels.length; i++) {
|
|
103
|
+
const label = labels[i];
|
|
104
|
+
if (!label)
|
|
105
|
+
continue;
|
|
106
|
+
if (seenPaths.has(label)) {
|
|
107
|
+
throw new Error(`Duplicate hierarchy label detected: ${label}`);
|
|
108
|
+
}
|
|
109
|
+
seenPaths.add(label);
|
|
110
|
+
const parent = parents[i] ?? "";
|
|
111
|
+
rows.push({
|
|
112
|
+
label,
|
|
113
|
+
parent: parent === "" ? null : parent,
|
|
114
|
+
value: toNumericValue(sizes[i]),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return rows;
|
|
118
|
+
}
|
|
119
|
+
const rowCount = Math.max(labels.length, ...parentLevels.map((level) => level.length));
|
|
120
|
+
const levels = layoutHierarchyLevels([...parentLevels, labels], rowCount);
|
|
121
|
+
for (let i = 0; i < rowCount; i++) {
|
|
122
|
+
const cells = levels[i];
|
|
123
|
+
if (!cells || cells.length === 0)
|
|
65
124
|
continue;
|
|
66
|
-
|
|
125
|
+
const label = cells[cells.length - 1];
|
|
126
|
+
const path = cells.slice(0, -1);
|
|
127
|
+
const key = [...path, label].join("\u0000");
|
|
128
|
+
if (seenPaths.has(key)) {
|
|
67
129
|
throw new Error(`Duplicate hierarchy label detected: ${label}`);
|
|
68
130
|
}
|
|
69
|
-
|
|
131
|
+
seenPaths.add(key);
|
|
70
132
|
rows.push({
|
|
71
133
|
label,
|
|
72
|
-
parent:
|
|
134
|
+
parent: path.length ? path[path.length - 1] : null,
|
|
73
135
|
value: toNumericValue(sizes[i]),
|
|
136
|
+
path,
|
|
74
137
|
});
|
|
75
138
|
}
|
|
76
139
|
return rows;
|
|
77
140
|
};
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
141
|
+
const PATH_SEPARATOR = "/";
|
|
142
|
+
/**
|
|
143
|
+
* The full path of every row, top level first, ending with the row's own
|
|
144
|
+
* label. A row carrying `path` (Excel's level layout) is placed under it;
|
|
145
|
+
* a legacy adjacency row names its parent, which is another row's label,
|
|
146
|
+
* so its path is that row's path plus itself (chained up to a root).
|
|
147
|
+
*/
|
|
148
|
+
const resolveRowPaths = (rows) => {
|
|
149
|
+
const byLabel = new Map();
|
|
150
|
+
rows.forEach((row) => {
|
|
151
|
+
if (row.label && !row.path && !byLabel.has(row.label)) {
|
|
152
|
+
byLabel.set(row.label, row);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const memo = new Map();
|
|
156
|
+
const resolve = (row, visiting) => {
|
|
157
|
+
const cached = memo.get(row);
|
|
158
|
+
if (cached)
|
|
159
|
+
return cached;
|
|
160
|
+
let path;
|
|
161
|
+
if (row.path) {
|
|
162
|
+
path = [...row.path, row.label];
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const parentName = row.parent && row.parent !== row.label ? row.parent : null;
|
|
166
|
+
const parentRow = parentName ? byLabel.get(parentName) : undefined;
|
|
167
|
+
if (parentRow && parentRow !== row && !visiting.has(parentRow)) {
|
|
168
|
+
visiting.add(row);
|
|
169
|
+
path = [...resolve(parentRow, visiting), row.label];
|
|
170
|
+
visiting.delete(row);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
path = parentName ? [parentName, row.label] : [row.label];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
memo.set(row, path);
|
|
177
|
+
return path;
|
|
87
178
|
};
|
|
88
|
-
|
|
89
|
-
return node;
|
|
179
|
+
return rows.map((row) => resolve(row, new Set()));
|
|
90
180
|
};
|
|
91
181
|
export const buildHierarchyTree = (rows) => {
|
|
92
|
-
const
|
|
93
|
-
|
|
182
|
+
const roots = new Map();
|
|
183
|
+
const nodeAt = (path) => {
|
|
184
|
+
let level = roots;
|
|
185
|
+
let node;
|
|
186
|
+
path.forEach((name, depth) => {
|
|
187
|
+
const existing = level.get(name);
|
|
188
|
+
if (existing) {
|
|
189
|
+
node = existing;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
node = {
|
|
193
|
+
name,
|
|
194
|
+
id: path.slice(0, depth + 1).join(PATH_SEPARATOR),
|
|
195
|
+
value: 0,
|
|
196
|
+
children: new Map(),
|
|
197
|
+
};
|
|
198
|
+
level.set(name, node);
|
|
199
|
+
}
|
|
200
|
+
level = node.children;
|
|
201
|
+
});
|
|
202
|
+
return node;
|
|
203
|
+
};
|
|
204
|
+
const paths = resolveRowPaths(rows);
|
|
205
|
+
rows.forEach((row, index) => {
|
|
94
206
|
if (!row.label)
|
|
95
207
|
return;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
labelNode.value += numericValue;
|
|
99
|
-
const parentName = row.parent && row.parent !== row.label ? row.parent : null;
|
|
100
|
-
if (parentName) {
|
|
101
|
-
const parentNode = getOrCreateNode(nodes, parentName);
|
|
102
|
-
parentNode.children.add(row.label);
|
|
103
|
-
if (labelNode.parent == null) {
|
|
104
|
-
labelNode.parent = parentName;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
208
|
+
const node = nodeAt(paths[index]);
|
|
209
|
+
node.value += row.value ?? 0;
|
|
107
210
|
});
|
|
108
211
|
const buildNode = (internal) => {
|
|
109
212
|
const children = internal.children.size > 0
|
|
110
|
-
? Array.from(internal.children).map(
|
|
213
|
+
? Array.from(internal.children.values()).map(buildNode)
|
|
111
214
|
: undefined;
|
|
112
215
|
const value = children && children.length > 0
|
|
113
216
|
? children.reduce((sum, child) => sum + (child.value ?? 0), 0)
|
|
114
217
|
: internal.value;
|
|
115
218
|
const node = {
|
|
116
219
|
name: internal.name,
|
|
117
|
-
id: internal.
|
|
220
|
+
id: internal.id,
|
|
118
221
|
value,
|
|
119
222
|
};
|
|
120
223
|
if (children && children.length > 0) {
|
|
@@ -122,29 +225,44 @@ export const buildHierarchyTree = (rows) => {
|
|
|
122
225
|
}
|
|
123
226
|
return node;
|
|
124
227
|
};
|
|
125
|
-
|
|
126
|
-
nodes.forEach((internal) => {
|
|
127
|
-
if (internal.parent == null) {
|
|
128
|
-
roots.push(buildNode(internal));
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
return roots;
|
|
228
|
+
return Array.from(roots.values()).map(buildNode);
|
|
132
229
|
};
|
|
133
230
|
export const toPlotlyHierarchy = (rows) => {
|
|
134
231
|
const tree = buildHierarchyTree(rows);
|
|
135
232
|
const labels = [];
|
|
233
|
+
const ids = [];
|
|
136
234
|
const parents = [];
|
|
137
235
|
const values = [];
|
|
138
|
-
const
|
|
236
|
+
const depths = [];
|
|
237
|
+
const traverse = (nodes, parentId, depth) => {
|
|
139
238
|
nodes.forEach((node) => {
|
|
140
239
|
labels.push(node.name);
|
|
141
|
-
|
|
240
|
+
ids.push(node.id ?? node.name);
|
|
241
|
+
parents.push(parentId);
|
|
142
242
|
values.push(node.value ?? 0);
|
|
243
|
+
depths.push(depth);
|
|
143
244
|
if (node.children) {
|
|
144
|
-
traverse(node.children, node.name);
|
|
245
|
+
traverse(node.children, node.id ?? node.name, depth + 1);
|
|
145
246
|
}
|
|
146
247
|
});
|
|
147
248
|
};
|
|
148
|
-
traverse(tree,
|
|
149
|
-
return { labels, parents, values };
|
|
249
|
+
traverse(tree, "", 0);
|
|
250
|
+
return { labels, ids, parents, values, depths };
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* One solid colour per top-level category, inherited by every descendant
|
|
254
|
+
* (Excel colours a treemap / sunburst by its first level), in tree order.
|
|
255
|
+
* Returns the colour per node of `toPlotlyHierarchy`'s flat arrays.
|
|
256
|
+
*/
|
|
257
|
+
export const hierarchyNodeColors = (hierarchy, palette) => {
|
|
258
|
+
const colorById = new Map();
|
|
259
|
+
let rootIndex = 0;
|
|
260
|
+
return hierarchy.ids.map((id, index) => {
|
|
261
|
+
const parentId = hierarchy.parents[index];
|
|
262
|
+
const color = parentId === ""
|
|
263
|
+
? palette[rootIndex++ % Math.max(1, palette.length)] ?? ""
|
|
264
|
+
: colorById.get(parentId) ?? "";
|
|
265
|
+
colorById.set(id, color);
|
|
266
|
+
return color;
|
|
267
|
+
});
|
|
150
268
|
};
|
|
@@ -9,6 +9,15 @@ export declare const applyLegendOverrides: (legend: LegendComponentOption, opts:
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const applyAxisOverrides: (axis: any, opts: any, numberFormatPattern?: string, locale?: string) => any;
|
|
11
11
|
export declare const dataLabelFormatter: (opts: any, locale?: string) => (params: any) => string;
|
|
12
|
+
/**
|
|
13
|
+
* The data-label options one series renders with: its own
|
|
14
|
+
* `ChartData.dataLabels` when it has them (OOXML's `<c:ser><c:dLbls>`
|
|
15
|
+
* override — `{ show: false }` turns a labelled chart's labels off for
|
|
16
|
+
* that series alone), else the chart-level `ChartSpec.dataLabels`.
|
|
17
|
+
*/
|
|
18
|
+
export declare const resolveSeriesDataLabels: <T>(chartLevel: T | null | undefined, series: {
|
|
19
|
+
dataLabels?: T | null;
|
|
20
|
+
} | null | undefined) => T | null | undefined;
|
|
12
21
|
export declare const buildSeriesLabelOption: (opts: any, seriesType?: string, locale?: string) => {
|
|
13
22
|
readonly show: true;
|
|
14
23
|
readonly position: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../../../utils/overrides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAWrD,oEAAoE;AACpE,eAAO,MAAM,4BAA4B,GAAI,KAAK,GAAG,oCASpD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,qBAAqB,EAC7B,MAAM,GAAG,KACR,qBA6CF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,GAAG,EACT,MAAM,GAAG,EACT,sBAAsB,MAAM,EAC5B,SAAS,MAAM,QA4BhB,CAAC;AAgBF,eAAO,MAAM,kBAAkB,GAAI,MAAM,GAAG,EAAE,SAAS,MAAM,MAAM,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../../../utils/overrides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAWrD,oEAAoE;AACpE,eAAO,MAAM,4BAA4B,GAAI,KAAK,GAAG,oCASpD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,qBAAqB,EAC7B,MAAM,GAAG,KACR,qBA6CF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,GAAG,EACT,MAAM,GAAG,EACT,sBAAsB,MAAM,EAC5B,SAAS,MAAM,QA4BhB,CAAC;AAgBF,eAAO,MAAM,kBAAkB,GAAI,MAAM,GAAG,EAAE,SAAS,MAAM,MAAM,QAAQ,GAAG,WA+C7E,CAAC;AAgCF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,CAAC,EACvC,YAAY,CAAC,GAAG,IAAI,GAAG,SAAS,EAChC,QAAQ;IAAE,UAAU,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,KACnD,CAAC,GAAG,IAAI,GAAG,SAA6C,CAAC;AAE5D,eAAO,MAAM,sBAAsB,GACjC,MAAM,GAAG,EACT,aAAa,MAAM,EACnB,SAAS,MAAM;;;iCA7F0D,GAAG;aA0G7E,CAAC"}
|
|
@@ -132,7 +132,11 @@ export const dataLabelFormatter = (opts, locale) => (params) => {
|
|
|
132
132
|
!opts.showSeriesName
|
|
133
133
|
? true
|
|
134
134
|
: !!opts.showValue;
|
|
135
|
-
|
|
135
|
+
// A scatter / bubble point's value is its [x, y, size] tuple: the label's
|
|
136
|
+
// value is Y and the bubble size is the third entry (#1068).
|
|
137
|
+
const isPointTuple = Array.isArray(params.value);
|
|
138
|
+
const pointValue = isPointTuple ? params.value[1] : params.value;
|
|
139
|
+
if (showVal && pointValue !== undefined) {
|
|
136
140
|
// PERCENT_STACKED stores the raw value on the data object so the
|
|
137
141
|
// label can still surface the dollar amount instead of the
|
|
138
142
|
// normalized percentage. Plain stacked / clustered have just a
|
|
@@ -140,7 +144,10 @@ export const dataLabelFormatter = (opts, locale) => (params) => {
|
|
|
140
144
|
const originalValue = typeof params.data === "object" && params.data
|
|
141
145
|
? params.data.originalValue
|
|
142
146
|
: undefined;
|
|
143
|
-
parts.push(formatLabelValue(
|
|
147
|
+
parts.push(formatLabelValue(pointValue, originalValue, opts.numberFormatPattern, locale));
|
|
148
|
+
}
|
|
149
|
+
if (opts.showBubbleSize && isPointTuple && params.value.length > 2) {
|
|
150
|
+
parts.push(formatLabelValue(params.value[2], undefined, opts.numberFormatPattern, locale));
|
|
144
151
|
}
|
|
145
152
|
return parts.join(": ");
|
|
146
153
|
};
|
|
@@ -173,6 +180,13 @@ const ECHARTS_POINT_DLBL_POS = {
|
|
|
173
180
|
right: "right",
|
|
174
181
|
};
|
|
175
182
|
const POINT_SERIES_TYPES = new Set(["line", "scatter", "effectScatter"]);
|
|
183
|
+
/**
|
|
184
|
+
* The data-label options one series renders with: its own
|
|
185
|
+
* `ChartData.dataLabels` when it has them (OOXML's `<c:ser><c:dLbls>`
|
|
186
|
+
* override — `{ show: false }` turns a labelled chart's labels off for
|
|
187
|
+
* that series alone), else the chart-level `ChartSpec.dataLabels`.
|
|
188
|
+
*/
|
|
189
|
+
export const resolveSeriesDataLabels = (chartLevel, series) => series?.dataLabels ?? chartLevel;
|
|
176
190
|
export const buildSeriesLabelOption = (opts, seriesType, locale) => {
|
|
177
191
|
if (!opts?.show)
|
|
178
192
|
return undefined;
|
|
@@ -16,15 +16,41 @@ export type RadarSeriesInput = {
|
|
|
16
16
|
};
|
|
17
17
|
export type RadarIndicator = {
|
|
18
18
|
name: string;
|
|
19
|
+
min: number;
|
|
19
20
|
max: number;
|
|
20
21
|
};
|
|
22
|
+
export type RadarAxisScale = {
|
|
23
|
+
min: number;
|
|
24
|
+
max: number;
|
|
25
|
+
};
|
|
26
|
+
/** The value-axis options a radar's common scale reads (`ChartAxisOptions`). */
|
|
27
|
+
export type RadarAxisOptions = {
|
|
28
|
+
min?: number | null;
|
|
29
|
+
max?: number | null;
|
|
30
|
+
visible?: boolean | null;
|
|
31
|
+
} | null | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The next "nice" axis bound at or above `value` — the top tick a value
|
|
34
|
+
* axis would auto-scale to: the step is 1 / 2 / 5 × a power of ten sized
|
|
35
|
+
* for about five divisions, and the bound is the first multiple of that
|
|
36
|
+
* step at or above the value (9 → 10, 33 → 40, 0.7 → 0.8).
|
|
37
|
+
*/
|
|
38
|
+
export declare const niceAxisCeiling: (value: number) => number;
|
|
39
|
+
/**
|
|
40
|
+
* One value scale shared by every radar axis, as Excel draws it: `max` is
|
|
41
|
+
* the spec's `valueAxisOptions.max` when set, else the largest value across
|
|
42
|
+
* all series and categories rounded up to a nice bound (never below 1);
|
|
43
|
+
* `min` is `valueAxisOptions.min` when set (and below `max`), else 0.
|
|
44
|
+
*/
|
|
45
|
+
export declare const radarValueScale: (series: RadarSeriesInput[], axisOptions?: RadarAxisOptions) => RadarAxisScale;
|
|
21
46
|
/**
|
|
22
|
-
* Build echarts radar indicators from category labels and series.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
47
|
+
* Build echarts radar indicators from category labels and series. Every
|
|
48
|
+
* indicator carries the SAME `min` / `max` — the common scale of
|
|
49
|
+
* {@link radarValueScale} — so values on different axes compare, and a
|
|
50
|
+
* single series draws the shape of its data rather than a regular polygon
|
|
51
|
+
* (rowsncolumns/spreadsheet#1066).
|
|
26
52
|
*/
|
|
27
|
-
export declare const buildRadarIndicators: (categories: (string | number | boolean | null | undefined)[], series: RadarSeriesInput[]) => RadarIndicator[];
|
|
53
|
+
export declare const buildRadarIndicators: (categories: (string | number | boolean | null | undefined)[], series: RadarSeriesInput[], axisOptions?: RadarAxisOptions) => RadarIndicator[];
|
|
28
54
|
/**
|
|
29
55
|
* Shape series into echarts radar data: one entry per series with its
|
|
30
56
|
* value vector aligned to the indicator order. Missing/non-numeric
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radar.d.ts","sourceRoot":"","sources":["../../../utils/radar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"radar.d.ts","sourceRoot":"","sources":["../../../utils/radar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAQxE,MAAM,MAAM,cAAc,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,gFAAgF;AAChF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B,GAAG,IAAI,GAAG,SAAS,CAAC;AAErB;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,MAW/C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,gBAAgB,EAAE,EAC1B,cAAc,gBAAgB,KAC7B,cAaF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC/B,YAAY,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EAC5D,QAAQ,gBAAgB,EAAE,EAC1B,cAAc,gBAAgB,KAC7B,cAAc,EAGhB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,gBAAgB,EAAE,EAC1B,gBAAgB,MAAM,KACrB,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAQzC,CAAC"}
|
package/dist/esm/utils/radar.js
CHANGED
|
@@ -13,25 +13,53 @@
|
|
|
13
13
|
const isFiniteNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
14
14
|
const toNumber = (v) => isFiniteNum(v) ? v : 0;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* The next "nice" axis bound at or above `value` — the top tick a value
|
|
17
|
+
* axis would auto-scale to: the step is 1 / 2 / 5 × a power of ten sized
|
|
18
|
+
* for about five divisions, and the bound is the first multiple of that
|
|
19
|
+
* step at or above the value (9 → 10, 33 → 40, 0.7 → 0.8).
|
|
20
20
|
*/
|
|
21
|
-
export const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
export const niceAxisCeiling = (value) => {
|
|
22
|
+
if (!isFiniteNum(value) || value <= 0)
|
|
23
|
+
return 1;
|
|
24
|
+
const rough = value / 5;
|
|
25
|
+
const magnitude = 10 ** Math.floor(Math.log10(rough));
|
|
26
|
+
const residual = rough / magnitude;
|
|
27
|
+
const step = (residual <= 1 ? 1 : residual <= 2 ? 2 : residual <= 5 ? 5 : 10) *
|
|
28
|
+
magnitude;
|
|
29
|
+
const ceiling = Math.ceil(value / step - 1e-9) * step;
|
|
30
|
+
// Avoid 0.30000000000000004-style bounds from the float arithmetic.
|
|
31
|
+
return Number(ceiling.toPrecision(12));
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* One value scale shared by every radar axis, as Excel draws it: `max` is
|
|
35
|
+
* the spec's `valueAxisOptions.max` when set, else the largest value across
|
|
36
|
+
* all series and categories rounded up to a nice bound (never below 1);
|
|
37
|
+
* `min` is `valueAxisOptions.min` when set (and below `max`), else 0.
|
|
38
|
+
*/
|
|
39
|
+
export const radarValueScale = (series, axisOptions) => {
|
|
40
|
+
let largest = 0;
|
|
41
|
+
for (const s of series) {
|
|
42
|
+
for (const v of s.values) {
|
|
26
43
|
if (isFiniteNum(v))
|
|
27
|
-
|
|
44
|
+
largest = Math.max(largest, v);
|
|
28
45
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
46
|
+
}
|
|
47
|
+
const max = isFiniteNum(axisOptions?.max)
|
|
48
|
+
? axisOptions.max
|
|
49
|
+
: niceAxisCeiling(largest);
|
|
50
|
+
const min = isFiniteNum(axisOptions?.min) && axisOptions.min < max ? axisOptions.min : 0;
|
|
51
|
+
return { min, max };
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Build echarts radar indicators from category labels and series. Every
|
|
55
|
+
* indicator carries the SAME `min` / `max` — the common scale of
|
|
56
|
+
* {@link radarValueScale} — so values on different axes compare, and a
|
|
57
|
+
* single series draws the shape of its data rather than a regular polygon
|
|
58
|
+
* (rowsncolumns/spreadsheet#1066).
|
|
59
|
+
*/
|
|
60
|
+
export const buildRadarIndicators = (categories, series, axisOptions) => {
|
|
61
|
+
const { min, max } = radarValueScale(series, axisOptions);
|
|
62
|
+
return categories.map((label) => ({ name: String(label ?? ""), min, max }));
|
|
35
63
|
};
|
|
36
64
|
/**
|
|
37
65
|
* Shape series into echarts radar data: one entry per series with its
|
|
@@ -43,6 +43,12 @@ export type TrendlineResult = {
|
|
|
43
43
|
* @param values Raw y-values; the 1-based category position is used as x
|
|
44
44
|
* for the fit, the 0-based index is reported in the points.
|
|
45
45
|
* @param spec Trendline parameters (type/order/period/forecast).
|
|
46
|
+
* @param xValues Scatter / bubble only — the series' real X values, index-
|
|
47
|
+
* aligned with `values`. The fit then runs on (x, y) pairs
|
|
48
|
+
* (a regression of Y on X, what Excel draws on an XY chart;
|
|
49
|
+
* the point index is wrong for unevenly spaced X), the
|
|
50
|
+
* forecast extends the X range by that many X units, and the
|
|
51
|
+
* points carry real X coordinates sampled across the range.
|
|
46
52
|
*/
|
|
47
53
|
export declare const computeTrendline: (values: (number | null | undefined)[], spec?: {
|
|
48
54
|
type?: TrendlineType | null;
|
|
@@ -50,5 +56,5 @@ export declare const computeTrendline: (values: (number | null | undefined)[], s
|
|
|
50
56
|
period?: number | null;
|
|
51
57
|
forecastForward?: number | null;
|
|
52
58
|
forecastBackward?: number | null;
|
|
53
|
-
}) => TrendlineResult | null;
|
|
59
|
+
}, xValues?: (number | null | undefined)[] | null) => TrendlineResult | null;
|
|
54
60
|
//# sourceMappingURL=trendline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trendline.d.ts","sourceRoot":"","sources":["../../../utils/trendline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,KAAK,GACL,KAAK,GACL,MAAM,GACN,OAAO,GACP,WAAW,CAAC;AAEhB,6FAA6F;AAC7F,MAAM,MAAM,cAAc,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG;IAC5B,sEAAsE;IACtE,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;
|
|
1
|
+
{"version":3,"file":"trendline.d.ts","sourceRoot":"","sources":["../../../utils/trendline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,KAAK,GACL,KAAK,GACL,MAAM,GACN,OAAO,GACP,WAAW,CAAC;AAEhB,6FAA6F;AAC7F,MAAM,MAAM,cAAc,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG;IAC5B,sEAAsE;IACtE,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAkKF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EACrC,OAAM;IACJ,IAAI,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,EACN,UAAU,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,IAAI,KAC7C,eAAe,GAAG,IA+HpB,CAAC"}
|
|
@@ -29,15 +29,25 @@ const isFiniteNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
|
29
29
|
* position as x (the first category is x = 1, see the module comment).
|
|
30
30
|
* Non-finite y-values are dropped (they cannot be fitted).
|
|
31
31
|
*/
|
|
32
|
-
const toPairs = (values) => {
|
|
32
|
+
const toPairs = (values, xValues) => {
|
|
33
33
|
const pairs = [];
|
|
34
34
|
for (let i = 0; i < values.length; i++) {
|
|
35
35
|
const y = values[i];
|
|
36
|
-
if (isFiniteNum(y))
|
|
36
|
+
if (!isFiniteNum(y))
|
|
37
|
+
continue;
|
|
38
|
+
if (xValues) {
|
|
39
|
+
const x = xValues[i];
|
|
40
|
+
if (isFiniteNum(x))
|
|
41
|
+
pairs.push({ x, y });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
37
44
|
pairs.push({ x: i + 1, y });
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
47
|
return pairs;
|
|
40
48
|
};
|
|
49
|
+
/** Samples along a scatter trendline's x range: enough for the curved fits to read as curves. */
|
|
50
|
+
const XY_TRENDLINE_SAMPLES = 64;
|
|
41
51
|
/** Compute R² between observed y and predicted f(x) over the fit pairs. */
|
|
42
52
|
const rSquaredOf = (pairs, predict) => {
|
|
43
53
|
if (pairs.length < 2)
|
|
@@ -177,14 +187,22 @@ const fitPower = (pairs) => {
|
|
|
177
187
|
* @param values Raw y-values; the 1-based category position is used as x
|
|
178
188
|
* for the fit, the 0-based index is reported in the points.
|
|
179
189
|
* @param spec Trendline parameters (type/order/period/forecast).
|
|
190
|
+
* @param xValues Scatter / bubble only — the series' real X values, index-
|
|
191
|
+
* aligned with `values`. The fit then runs on (x, y) pairs
|
|
192
|
+
* (a regression of Y on X, what Excel draws on an XY chart;
|
|
193
|
+
* the point index is wrong for unevenly spaced X), the
|
|
194
|
+
* forecast extends the X range by that many X units, and the
|
|
195
|
+
* points carry real X coordinates sampled across the range.
|
|
180
196
|
*/
|
|
181
|
-
export const computeTrendline = (values, spec = {}) => {
|
|
182
|
-
const pairs = toPairs(values);
|
|
197
|
+
export const computeTrendline = (values, spec = {}, xValues) => {
|
|
198
|
+
const pairs = toPairs(values, xValues);
|
|
183
199
|
if (pairs.length < 2)
|
|
184
200
|
return null;
|
|
185
201
|
const type = spec.type ?? "linear";
|
|
186
|
-
const
|
|
187
|
-
const
|
|
202
|
+
const forwardRaw = Math.max(0, spec.forecastForward ?? 0);
|
|
203
|
+
const backwardRaw = Math.max(0, spec.forecastBackward ?? 0);
|
|
204
|
+
const forward = xValues ? forwardRaw : Math.floor(forwardRaw);
|
|
205
|
+
const backward = xValues ? backwardRaw : Math.floor(backwardRaw);
|
|
188
206
|
// Moving average is a smoothing of the observed values, not a fitted
|
|
189
207
|
// function, so it is handled separately and ignores forecasting.
|
|
190
208
|
if (type === "movingAvg") {
|
|
@@ -200,17 +218,51 @@ export const computeTrendline = (values, spec = {}) => {
|
|
|
200
218
|
count++;
|
|
201
219
|
}
|
|
202
220
|
}
|
|
203
|
-
if (count
|
|
221
|
+
if (count === 0)
|
|
222
|
+
continue;
|
|
223
|
+
if (xValues) {
|
|
224
|
+
const x = xValues[i];
|
|
225
|
+
if (isFiniteNum(x))
|
|
226
|
+
points.push({ x, y: sum / count });
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
204
229
|
points.push({ x: i, y: sum / count });
|
|
230
|
+
}
|
|
205
231
|
}
|
|
232
|
+
if (xValues)
|
|
233
|
+
points.sort((a, b) => a.x - b.x);
|
|
206
234
|
return { points, rSquared: null, equation: null };
|
|
207
235
|
}
|
|
208
|
-
// Fit domain is 1..n; a forecast of k periods extends it to 1−k / n+k.
|
|
209
|
-
const xStart = 1 - backward;
|
|
210
|
-
const xEnd = values.length + forward;
|
|
211
236
|
const xs = [];
|
|
212
|
-
|
|
213
|
-
|
|
237
|
+
if (xValues) {
|
|
238
|
+
// Sample the fitted curve across the data's X range, extended by the
|
|
239
|
+
// forecast in X units.
|
|
240
|
+
let minX = Infinity;
|
|
241
|
+
let maxX = -Infinity;
|
|
242
|
+
for (const p of pairs) {
|
|
243
|
+
if (p.x < minX)
|
|
244
|
+
minX = p.x;
|
|
245
|
+
if (p.x > maxX)
|
|
246
|
+
maxX = p.x;
|
|
247
|
+
}
|
|
248
|
+
const xStart = minX - backward;
|
|
249
|
+
const xEnd = maxX + forward;
|
|
250
|
+
if (xEnd > xStart) {
|
|
251
|
+
for (let i = 0; i <= XY_TRENDLINE_SAMPLES; i++) {
|
|
252
|
+
xs.push(xStart + ((xEnd - xStart) * i) / XY_TRENDLINE_SAMPLES);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
xs.push(xStart);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// Fit domain is 1..n; a forecast of k periods extends it to 1−k / n+k.
|
|
261
|
+
const xStart = 1 - backward;
|
|
262
|
+
const xEnd = values.length + forward;
|
|
263
|
+
for (let x = xStart; x <= xEnd; x++)
|
|
264
|
+
xs.push(x);
|
|
265
|
+
}
|
|
214
266
|
let predict = null;
|
|
215
267
|
let equation = null;
|
|
216
268
|
switch (type) {
|
|
@@ -263,9 +315,10 @@ export const computeTrendline = (values, spec = {}) => {
|
|
|
263
315
|
default:
|
|
264
316
|
return null;
|
|
265
317
|
}
|
|
266
|
-
// Report the renderer-facing 0-based category index (see module comment)
|
|
318
|
+
// Report the renderer-facing 0-based category index (see module comment);
|
|
319
|
+
// an XY fit reports the real X it was sampled at.
|
|
267
320
|
const points = xs
|
|
268
|
-
.map((x) => ({ x: x - 1, y: predict(x) }))
|
|
321
|
+
.map((x) => ({ x: xValues ? x : x - 1, y: predict(x) }))
|
|
269
322
|
.filter((p) => Number.isFinite(p.y));
|
|
270
323
|
if (points.length < 2)
|
|
271
324
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowsncolumns/charts",
|
|
3
|
-
"version": "28.1.
|
|
3
|
+
"version": "28.1.10",
|
|
4
4
|
"author": "Vinay M",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"@rowsncolumns/common-types": ">=5.3.2",
|
|
71
71
|
"@rowsncolumns/grid": ">=13.2.35",
|
|
72
72
|
"@rowsncolumns/icons": ">=7.2.33",
|
|
73
|
-
"@rowsncolumns/spreadsheet": ">=10.4.
|
|
74
|
-
"@rowsncolumns/spreadsheet-state": ">=29.1.
|
|
75
|
-
"@rowsncolumns/toolkit": ">=24.5.
|
|
73
|
+
"@rowsncolumns/spreadsheet": ">=10.4.8",
|
|
74
|
+
"@rowsncolumns/spreadsheet-state": ">=29.1.8",
|
|
75
|
+
"@rowsncolumns/toolkit": ">=24.5.10",
|
|
76
76
|
"@rowsncolumns/ui": ">=16.0.4",
|
|
77
77
|
"@rowsncolumns/utils": ">=9.5.5",
|
|
78
78
|
"@types/react-plotly.js": "^2.6.3",
|