@rowsncolumns/charts 28.1.10 → 28.1.12
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 +268 -173
- package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/cjs/plotly/basic-chart/basic-chart.js +140 -95
- package/dist/cjs/utils/hierarchy.d.ts +6 -0
- package/dist/cjs/utils/hierarchy.d.ts.map +1 -1
- package/dist/cjs/utils/hierarchy.js +23 -1
- package/dist/cjs/utils/histogram.d.ts +42 -3
- package/dist/cjs/utils/histogram.d.ts.map +1 -1
- package/dist/cjs/utils/histogram.js +193 -5
- package/dist/cjs/utils/overrides.d.ts +1 -1
- package/dist/cjs/utils/overrides.d.ts.map +1 -1
- package/dist/cjs/utils/overrides.js +44 -34
- package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/basic-chart/basic-chart.js +168 -40
- package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -1
- package/dist/esm/plotly/basic-chart/basic-chart.js +63 -6
- package/dist/esm/utils/hierarchy.d.ts +6 -0
- package/dist/esm/utils/hierarchy.d.ts.map +1 -1
- package/dist/esm/utils/hierarchy.js +21 -0
- package/dist/esm/utils/histogram.d.ts +42 -3
- package/dist/esm/utils/histogram.d.ts.map +1 -1
- package/dist/esm/utils/histogram.js +171 -4
- package/dist/esm/utils/overrides.d.ts +1 -1
- package/dist/esm/utils/overrides.d.ts.map +1 -1
- package/dist/esm/utils/overrides.js +10 -3
- package/package.json +2 -2
|
@@ -45,17 +45,58 @@ export const computePareto = (bins) => {
|
|
|
45
45
|
const formatBinBound = (value) => {
|
|
46
46
|
if (!Number.isFinite(value))
|
|
47
47
|
return String(value);
|
|
48
|
+
if (value === 0)
|
|
49
|
+
return "0";
|
|
48
50
|
const abs = Math.abs(value);
|
|
49
51
|
if (abs >= 1000 || abs < 0.01)
|
|
50
52
|
return value.toExponential(2);
|
|
51
53
|
return Number.parseFloat(value.toFixed(2)).toString();
|
|
52
54
|
};
|
|
55
|
+
const isFinite_ = (v) => typeof v === "number" && Number.isFinite(v);
|
|
56
|
+
/** True when the spec authored any Excel binning option. */
|
|
57
|
+
export const hasExcelBinning = (options) => Boolean(options &&
|
|
58
|
+
(isFinite_(options.binSize) ||
|
|
59
|
+
options.intervalClosed === "l" ||
|
|
60
|
+
options.intervalClosed === "r" ||
|
|
61
|
+
isFinite_(options.overflow) ||
|
|
62
|
+
isFinite_(options.underflow)));
|
|
63
|
+
/** Round up to two significant digits (31 for 30.77, 0.038 for 0.0377). */
|
|
64
|
+
const ceilToTwoSignificant = (value) => {
|
|
65
|
+
if (!(value > 0))
|
|
66
|
+
return value;
|
|
67
|
+
const magnitude = 10 ** (Math.floor(Math.log10(value)) - 1);
|
|
68
|
+
return Math.ceil(value / magnitude - 1e-9) * magnitude;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Excel's automatic bin width: Scott's normal reference rule,
|
|
72
|
+
* 3.5 · σ / ∛n with the sample standard deviation, rounded up to two
|
|
73
|
+
* significant digits. For the 20 values 12 … 95 of #1087 (σ ≈ 23.86) that
|
|
74
|
+
* is 30.77 → 31, i.e. Excel's three bins [12, 43], (43, 74], (74, 105].
|
|
75
|
+
*/
|
|
76
|
+
export const excelAutoBinWidth = (numeric) => {
|
|
77
|
+
const n = numeric.length;
|
|
78
|
+
if (n < 2)
|
|
79
|
+
return 1;
|
|
80
|
+
const mean = numeric.reduce((sum, v) => sum + v, 0) / n;
|
|
81
|
+
const variance = numeric.reduce((sum, v) => sum + (v - mean) ** 2, 0) / (n - 1);
|
|
82
|
+
const sigma = Math.sqrt(variance);
|
|
83
|
+
if (!(sigma > 0))
|
|
84
|
+
return 1;
|
|
85
|
+
return ceilToTwoSignificant((3.5 * sigma) / Math.cbrt(n));
|
|
86
|
+
};
|
|
53
87
|
/**
|
|
54
|
-
* Bin a set of numeric values into
|
|
55
|
-
*
|
|
88
|
+
* Bin a set of numeric values into equal-width bins.
|
|
89
|
+
*
|
|
90
|
+
* Default (no Excel options): `binCount` bins over [min, max], closed on
|
|
91
|
+
* the left — or Sturges' rule, ceil(log2(n) + 1), when `binCount` is
|
|
92
|
+
* omitted. With Excel binning options (#1087): bins start at the minimum
|
|
93
|
+
* (or the underflow bound), are `binSize` wide — else (max − min) /
|
|
94
|
+
* `binCount`, else Excel's automatic width — closed on the right unless
|
|
95
|
+
* `intervalClosed` is `l`, and values beyond `overflow` / `underflow`
|
|
96
|
+
* collect in their own end bins.
|
|
56
97
|
*/
|
|
57
|
-
export const binHistogram = (values, binCount) => {
|
|
58
|
-
const numeric = values.filter(
|
|
98
|
+
export const binHistogram = (values, binCount, options) => {
|
|
99
|
+
const numeric = values.filter(isFinite_);
|
|
59
100
|
if (numeric.length === 0) {
|
|
60
101
|
return { labels: [], counts: [] };
|
|
61
102
|
}
|
|
@@ -67,6 +108,9 @@ export const binHistogram = (values, binCount) => {
|
|
|
67
108
|
if (v > max)
|
|
68
109
|
max = v;
|
|
69
110
|
}
|
|
111
|
+
if (hasExcelBinning(options)) {
|
|
112
|
+
return binExcel(numeric, min, max, binCount, options);
|
|
113
|
+
}
|
|
70
114
|
const sturges = Math.max(1, Math.ceil(Math.log2(numeric.length) + 1));
|
|
71
115
|
const bins = typeof binCount === "number" && binCount > 0
|
|
72
116
|
? Math.floor(binCount)
|
|
@@ -93,3 +137,126 @@ export const binHistogram = (values, binCount) => {
|
|
|
93
137
|
}
|
|
94
138
|
return { labels, counts };
|
|
95
139
|
};
|
|
140
|
+
const binExcel = (numeric, min, max, binCount, options) => {
|
|
141
|
+
const rightClosed = options.intervalClosed !== "l";
|
|
142
|
+
const underflow = isFinite_(options.underflow) ? options.underflow : null;
|
|
143
|
+
const overflow = isFinite_(options.overflow) ? options.overflow : null;
|
|
144
|
+
// The regular bins span the values between the end bins.
|
|
145
|
+
const inRange = numeric.filter((v) => (underflow === null || v >= underflow) &&
|
|
146
|
+
(overflow === null || v <= overflow));
|
|
147
|
+
const start = underflow ?? (inRange.length ? Math.min(...inRange) : min);
|
|
148
|
+
const end = overflow ?? (inRange.length ? Math.max(...inRange) : max);
|
|
149
|
+
const span = end - start;
|
|
150
|
+
let width;
|
|
151
|
+
if (isFinite_(options.binSize) && options.binSize > 0) {
|
|
152
|
+
width = options.binSize;
|
|
153
|
+
}
|
|
154
|
+
else if (typeof binCount === "number" && binCount > 0) {
|
|
155
|
+
width = span > 0 ? span / Math.floor(binCount) : 1;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
width = excelAutoBinWidth(inRange.length ? inRange : numeric);
|
|
159
|
+
}
|
|
160
|
+
// Enough bins to reach the last value; a right-closed layout needs one
|
|
161
|
+
// more when the span is an exact multiple (the boundary value belongs to
|
|
162
|
+
// the bin below, so the top bin stays).
|
|
163
|
+
let bins = span > 0 ? Math.ceil(span / width - 1e-9) : 1;
|
|
164
|
+
bins = Math.max(1, bins);
|
|
165
|
+
const counts = new Array(bins).fill(0);
|
|
166
|
+
const edges = Array.from({ length: bins + 1 }, (_, i) => start + width * i);
|
|
167
|
+
let under = 0;
|
|
168
|
+
let over = 0;
|
|
169
|
+
for (const v of numeric) {
|
|
170
|
+
if (underflow !== null && v < underflow) {
|
|
171
|
+
under++;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (overflow !== null && v > overflow) {
|
|
175
|
+
over++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
let idx;
|
|
179
|
+
if (rightClosed) {
|
|
180
|
+
// (lo, hi]: a value on a boundary belongs to the bin below it; the
|
|
181
|
+
// first bin also takes its own lower bound.
|
|
182
|
+
idx = Math.ceil((v - start) / width - 1e-9) - 1;
|
|
183
|
+
if (idx < 0)
|
|
184
|
+
idx = 0;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
idx = Math.floor((v - start) / width + 1e-9);
|
|
188
|
+
}
|
|
189
|
+
if (idx >= bins)
|
|
190
|
+
idx = bins - 1;
|
|
191
|
+
counts[idx]++;
|
|
192
|
+
}
|
|
193
|
+
const labels = edges.slice(0, -1).map((lo, i) => {
|
|
194
|
+
const hi = edges[i + 1];
|
|
195
|
+
if (rightClosed) {
|
|
196
|
+
const opening = i === 0 ? "[" : "(";
|
|
197
|
+
return `${opening}${formatBinBound(lo)}, ${formatBinBound(hi)}]`;
|
|
198
|
+
}
|
|
199
|
+
const closing = i === bins - 1 ? "]" : ")";
|
|
200
|
+
return `[${formatBinBound(lo)}, ${formatBinBound(hi)}${closing}`;
|
|
201
|
+
});
|
|
202
|
+
if (underflow !== null) {
|
|
203
|
+
labels.unshift(`< ${formatBinBound(underflow)}`);
|
|
204
|
+
counts.unshift(under);
|
|
205
|
+
}
|
|
206
|
+
if (overflow !== null) {
|
|
207
|
+
labels.push(`> ${formatBinBound(overflow)}`);
|
|
208
|
+
counts.push(over);
|
|
209
|
+
}
|
|
210
|
+
return { labels, counts };
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Excel's histogram "By Category" / Pareto over categories
|
|
214
|
+
* (`<cx:aggregation/>`, #1087): one column per distinct category in order
|
|
215
|
+
* of first appearance, its value the aggregate (sum by default) of the
|
|
216
|
+
* series values of that category — instead of binning the values.
|
|
217
|
+
*/
|
|
218
|
+
export const aggregateByCategory = (categories, values, aggregateType = "SUM") => {
|
|
219
|
+
const groups = new Map();
|
|
220
|
+
const order = [];
|
|
221
|
+
const count = Math.max(categories.length, values.length);
|
|
222
|
+
for (let i = 0; i < count; i++) {
|
|
223
|
+
const raw = categories[i];
|
|
224
|
+
const label = raw === null || raw === undefined ? "" : `${raw}`.trim();
|
|
225
|
+
if (label === "")
|
|
226
|
+
continue;
|
|
227
|
+
if (!groups.has(label)) {
|
|
228
|
+
groups.set(label, []);
|
|
229
|
+
order.push(label);
|
|
230
|
+
}
|
|
231
|
+
const v = values[i];
|
|
232
|
+
if (isFinite_(v))
|
|
233
|
+
groups.get(label).push(v);
|
|
234
|
+
}
|
|
235
|
+
const reduce = (nums) => {
|
|
236
|
+
if (nums.length === 0)
|
|
237
|
+
return 0;
|
|
238
|
+
switch (aggregateType ?? "SUM") {
|
|
239
|
+
case "COUNT":
|
|
240
|
+
return nums.length;
|
|
241
|
+
case "AVERAGE":
|
|
242
|
+
return nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
243
|
+
case "MAX":
|
|
244
|
+
return Math.max(...nums);
|
|
245
|
+
case "MIN":
|
|
246
|
+
return Math.min(...nums);
|
|
247
|
+
case "MEDIAN": {
|
|
248
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
249
|
+
const mid = Math.floor(sorted.length / 2);
|
|
250
|
+
return sorted.length % 2
|
|
251
|
+
? sorted[mid]
|
|
252
|
+
: (sorted[mid - 1] + sorted[mid]) / 2;
|
|
253
|
+
}
|
|
254
|
+
default:
|
|
255
|
+
return nums.reduce((a, b) => a + b, 0);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
return {
|
|
259
|
+
labels: order,
|
|
260
|
+
counts: order.map((label) => reduce(groups.get(label) ?? [])),
|
|
261
|
+
};
|
|
262
|
+
};
|
|
@@ -8,7 +8,7 @@ export declare const applyLegendOverrides: (legend: LegendComponentOption, opts:
|
|
|
8
8
|
* becomes the tick-label formatter.
|
|
9
9
|
*/
|
|
10
10
|
export declare const applyAxisOverrides: (axis: any, opts: any, numberFormatPattern?: string, locale?: string) => any;
|
|
11
|
-
export declare const dataLabelFormatter: (opts: any, locale?: string) => (params: any) => string;
|
|
11
|
+
export declare const dataLabelFormatter: (opts: any, locale?: string, joiner?: string) => (params: any) => string;
|
|
12
12
|
/**
|
|
13
13
|
* The data-label options one series renders with: its own
|
|
14
14
|
* `ChartData.dataLabels` when it has them (OOXML's `<c:ser><c:dLbls>`
|
|
@@ -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,
|
|
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,GAC7B,MAAM,GAAG,EACT,SAAS,MAAM,EAEf,eAAa,MACT,QAAQ,GAAG,WAoDhB,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;;;iCAlGH,GAAG;aA+GhB,CAAC"}
|
|
@@ -117,7 +117,9 @@ export const applyAxisOverrides = (axis, opts, numberFormatPattern, locale) => {
|
|
|
117
117
|
* — Excel's behaviour.
|
|
118
118
|
*/
|
|
119
119
|
const formatLabelValue = (rawValue, originalValue, pattern, locale) => formatAxisValue(pattern, originalValue ?? rawValue, locale);
|
|
120
|
-
export const dataLabelFormatter = (opts, locale
|
|
120
|
+
export const dataLabelFormatter = (opts, locale,
|
|
121
|
+
// A treemap block stacks the parts (name, value) on their own lines.
|
|
122
|
+
joiner = ": ") => (params) => {
|
|
121
123
|
const parts = [];
|
|
122
124
|
if (opts.showSeriesName && params.seriesName)
|
|
123
125
|
parts.push(params.seriesName);
|
|
@@ -126,10 +128,15 @@ export const dataLabelFormatter = (opts, locale) => (params) => {
|
|
|
126
128
|
if (opts.showPercent && params.percent !== undefined) {
|
|
127
129
|
parts.push(`${params.percent}%`);
|
|
128
130
|
}
|
|
131
|
+
// The value shows by default unless another field was asked for instead:
|
|
132
|
+
// an importer records only the flags that are ON, so `<c:showVal val="0"/>`
|
|
133
|
+
// + `<c:showBubbleSize val="1"/>` arrives as `{ showBubbleSize: true }` and
|
|
134
|
+
// must label the bubble with its size alone (#1068 / #1079).
|
|
129
135
|
const showVal = opts.showValue !== false &&
|
|
130
136
|
!opts.showCategoryName &&
|
|
131
137
|
!opts.showPercent &&
|
|
132
|
-
!opts.showSeriesName
|
|
138
|
+
!opts.showSeriesName &&
|
|
139
|
+
!opts.showBubbleSize
|
|
133
140
|
? true
|
|
134
141
|
: !!opts.showValue;
|
|
135
142
|
// A scatter / bubble point's value is its [x, y, size] tuple: the label's
|
|
@@ -149,7 +156,7 @@ export const dataLabelFormatter = (opts, locale) => (params) => {
|
|
|
149
156
|
if (opts.showBubbleSize && isPointTuple && params.value.length > 2) {
|
|
150
157
|
parts.push(formatLabelValue(params.value[2], undefined, opts.numberFormatPattern, locale));
|
|
151
158
|
}
|
|
152
|
-
return parts.join(
|
|
159
|
+
return parts.join(joiner);
|
|
153
160
|
};
|
|
154
161
|
const ECHARTS_DLBL_POS = {
|
|
155
162
|
center: "inside",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowsncolumns/charts",
|
|
3
|
-
"version": "28.1.
|
|
3
|
+
"version": "28.1.12",
|
|
4
4
|
"author": "Vinay M",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -70,7 +70,7 @@
|
|
|
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.
|
|
73
|
+
"@rowsncolumns/spreadsheet": ">=10.4.10",
|
|
74
74
|
"@rowsncolumns/spreadsheet-state": ">=29.1.8",
|
|
75
75
|
"@rowsncolumns/toolkit": ">=24.5.10",
|
|
76
76
|
"@rowsncolumns/ui": ">=16.0.4",
|