chartjs-chart-treemap 3.1.0 → 4.1.0
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/chartjs-chart-treemap.cjs +1130 -1082
- package/dist/chartjs-chart-treemap.esm.js +1124 -1084
- package/dist/chartjs-chart-treemap.min.js +3 -3
- package/dist/chartjs-chart-treemap.min.js.map +1 -1
- package/dist/controller.d.ts +19 -0
- package/dist/element.d.ts +74 -0
- package/dist/geometry.d.ts +38 -0
- package/dist/helpers/index.d.ts +9 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.esm.d.ts +3 -0
- package/dist/options.d.ts +9 -0
- package/dist/rect.d.ts +18 -0
- package/dist/squarify.d.ts +1 -0
- package/dist/statArray.d.ts +18 -0
- package/dist/text.d.ts +20 -0
- package/dist/types.d.ts +149 -0
- package/dist/utils.d.ts +8 -0
- package/package.json +85 -74
- package/types/index.esm.d.ts +0 -159
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-treemap
|
|
2
|
+
* chartjs-chart-treemap v0.0.0-development
|
|
3
3
|
* https://chartjs-chart-treemap.pages.dev/
|
|
4
|
-
* (c)
|
|
4
|
+
* (c) 2026 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
6
6
|
*/
|
|
7
7
|
(function (global, factory) {
|
|
@@ -10,1179 +10,1227 @@ typeof define === 'function' && define.amd ? define(['exports', 'chart.js', 'cha
|
|
|
10
10
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["chartjs-chart-treemap"] = {}, global.Chart, global.Chart.helpers));
|
|
11
11
|
})(this, (function (exports, chart_js, helpers) { 'use strict';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
var version = "0.0.0-development";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
record[k] = obj[k];
|
|
29
|
-
});
|
|
30
|
-
result.push(record);
|
|
31
|
-
} else {
|
|
32
|
-
for (const childKey of Object.keys(obj)) {
|
|
33
|
-
const child = obj[childKey];
|
|
34
|
-
if (helpers.isObject(child)) {
|
|
35
|
-
tree.push(childKey);
|
|
36
|
-
scanTreeObject(keys, treeLeafKey, child, tree, lvl + 1, result);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
tree.splice(objIndex, 1);
|
|
41
|
-
return result;
|
|
15
|
+
function scaleRect(sq, xScale, yScale, sp) {
|
|
16
|
+
const sp2 = sp * 2;
|
|
17
|
+
const x = xScale.getPixelForValue(sq.x);
|
|
18
|
+
const y = yScale.getPixelForValue(sq.y);
|
|
19
|
+
const w = xScale.getPixelForValue(sq.x + sq.w) - x;
|
|
20
|
+
const h = yScale.getPixelForValue(sq.y + sq.h) - y;
|
|
21
|
+
return {
|
|
22
|
+
height: h - sp2,
|
|
23
|
+
hidden: sp2 > w || sp2 > h,
|
|
24
|
+
width: w - sp2,
|
|
25
|
+
x: x + sp,
|
|
26
|
+
y: y + sp
|
|
27
|
+
};
|
|
42
28
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const data = scanTreeObject(keys, treeLeafKey, obj);
|
|
46
|
-
if (!data.length) {
|
|
47
|
-
return data;
|
|
48
|
-
}
|
|
49
|
-
const max = data.reduce(function(maxVal, element) {
|
|
50
|
-
// minus 2 because _leaf and value properties are added
|
|
51
|
-
// on top to groups ones
|
|
52
|
-
const ikeys = Object.keys(element).length - 2;
|
|
53
|
-
return maxVal > ikeys ? maxVal : ikeys;
|
|
54
|
-
});
|
|
55
|
-
data.forEach(function(element) {
|
|
56
|
-
for (let i = 0; i < max; i++) {
|
|
57
|
-
const groupKey = getGroupKey(i);
|
|
58
|
-
if (!element[groupKey]) {
|
|
59
|
-
element[groupKey] = '';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
return data;
|
|
29
|
+
function rectNotEqual(r1, r2) {
|
|
30
|
+
return !r1 || !r2 || r1.x !== r2.x || r1.y !== r2.y || r1.w !== r2.w || r1.h !== r2.h || r1.rtl !== r2.rtl || r1.unsorted !== r2.unsorted;
|
|
64
31
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
while (stack.length) {
|
|
71
|
-
// pop value from stack
|
|
72
|
-
const next = stack.pop();
|
|
73
|
-
if (Array.isArray(next)) {
|
|
74
|
-
// push back array items, won't modify the original input
|
|
75
|
-
stack.push(...next);
|
|
76
|
-
} else {
|
|
77
|
-
res.push(next);
|
|
32
|
+
function arrayNotEqual(a, b) {
|
|
33
|
+
let i;
|
|
34
|
+
let n;
|
|
35
|
+
if (!a || !b) {
|
|
36
|
+
return true;
|
|
78
37
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return res.reverse();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function getPath(groups, value, defaultValue) {
|
|
85
|
-
if (!groups.length) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const path = [];
|
|
89
|
-
for (const grp of groups) {
|
|
90
|
-
const item = value[grp];
|
|
91
|
-
if (item === '') {
|
|
92
|
-
path.push(defaultValue);
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
path.push(item);
|
|
96
|
-
}
|
|
97
|
-
return path.length ? path.join('.') : defaultValue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @param {[]} values
|
|
102
|
-
* @param {string} grp
|
|
103
|
-
* @param {[string]} keys
|
|
104
|
-
* @param {string} treeeLeafKey
|
|
105
|
-
* @param {string} [mainGrp]
|
|
106
|
-
* @param {*} [mainValue]
|
|
107
|
-
* @param {[]} groups
|
|
108
|
-
*/
|
|
109
|
-
function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = []) {
|
|
110
|
-
const key = keys[0];
|
|
111
|
-
const addKeys = keys.slice(1);
|
|
112
|
-
const tmp = Object.create(null);
|
|
113
|
-
const data = Object.create(null);
|
|
114
|
-
const ret = [];
|
|
115
|
-
let g, i, n;
|
|
116
|
-
for (i = 0, n = values.length; i < n; ++i) {
|
|
117
|
-
const v = values[i];
|
|
118
|
-
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
g = v[grp] || v[treeLeafKey] || '';
|
|
122
|
-
if (!(g in tmp)) {
|
|
123
|
-
const tmpRef = tmp[g] = {value: 0};
|
|
124
|
-
addKeys.forEach(function(k) {
|
|
125
|
-
tmpRef[k] = 0;
|
|
126
|
-
});
|
|
127
|
-
data[g] = [];
|
|
128
|
-
}
|
|
129
|
-
tmp[g].value += +v[key];
|
|
130
|
-
tmp[g].label = v[grp] || '';
|
|
131
|
-
const tmpRef = tmp[g];
|
|
132
|
-
addKeys.forEach(function(k) {
|
|
133
|
-
tmpRef[k] += v[k];
|
|
134
|
-
});
|
|
135
|
-
tmp[g].path = getPath(groups, v, g);
|
|
136
|
-
data[g].push(v);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
Object.keys(tmp).forEach((k) => {
|
|
140
|
-
const v = {children: data[k]};
|
|
141
|
-
v[key] = +tmp[k].value;
|
|
142
|
-
addKeys.forEach(function(ak) {
|
|
143
|
-
v[ak] = +tmp[k][ak];
|
|
144
|
-
});
|
|
145
|
-
v[grp] = tmp[k].label;
|
|
146
|
-
v.label = k;
|
|
147
|
-
v.path = tmp[k].path;
|
|
148
|
-
|
|
149
|
-
if (mainGrp) {
|
|
150
|
-
v[mainGrp] = mainValue;
|
|
38
|
+
if (a === b) {
|
|
39
|
+
return false;
|
|
151
40
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return ret;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function index(values, key) {
|
|
159
|
-
let n = values.length;
|
|
160
|
-
let i;
|
|
161
|
-
|
|
162
|
-
if (!n) {
|
|
163
|
-
return key;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const obj = helpers.isObject(values[0]);
|
|
167
|
-
key = obj ? key : 'v';
|
|
168
|
-
|
|
169
|
-
for (i = 0, n = values.length; i < n; ++i) {
|
|
170
|
-
if (obj) {
|
|
171
|
-
values[i]._idx = i;
|
|
172
|
-
} else {
|
|
173
|
-
values[i] = {v: values[i], _idx: i};
|
|
41
|
+
if (a.length !== b.length) {
|
|
42
|
+
return true;
|
|
174
43
|
}
|
|
175
|
-
|
|
176
|
-
|
|
44
|
+
for(i = 0, n = a.length; i < n; ++i){
|
|
45
|
+
if (a[i] !== b[i]) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
177
50
|
}
|
|
178
51
|
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
values.sort((a, b) => +b[key] - +a[key]);
|
|
182
|
-
} else {
|
|
183
|
-
values.sort((a, b) => +b - +a);
|
|
184
|
-
}
|
|
52
|
+
function limit(value, min, max) {
|
|
53
|
+
return Math.max(Math.min(value, max), min);
|
|
185
54
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
55
|
+
function parseBorderWidth(value, maxW, maxH) {
|
|
56
|
+
const o = helpers.toTRBL(value);
|
|
57
|
+
return {
|
|
58
|
+
b: limit(o.bottom, 0, maxH),
|
|
59
|
+
l: limit(o.left, 0, maxW),
|
|
60
|
+
r: limit(o.right, 0, maxW),
|
|
61
|
+
t: limit(o.top, 0, maxH)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function parseBorderRadius(value, maxW, maxH) {
|
|
65
|
+
const o = helpers.toTRBLCorners(value);
|
|
66
|
+
const maxR = Math.min(maxW, maxH);
|
|
67
|
+
return {
|
|
68
|
+
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
69
|
+
bottomRight: limit(o.bottomRight, 0, maxR),
|
|
70
|
+
topLeft: limit(o.topLeft, 0, maxR),
|
|
71
|
+
topRight: limit(o.topRight, 0, maxR)
|
|
72
|
+
};
|
|
195
73
|
}
|
|
196
74
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
*
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
75
|
+
function getDims(itm, w2, s2, key) {
|
|
76
|
+
const a = itm._normalized;
|
|
77
|
+
const ar = w2 * a / s2;
|
|
78
|
+
const d1 = Math.sqrt(a * ar);
|
|
79
|
+
const d2 = a / d1;
|
|
80
|
+
const w = key === '_ix' ? d1 : d2;
|
|
81
|
+
const h = key === '_ix' ? d2 : d1;
|
|
82
|
+
return {
|
|
83
|
+
d1,
|
|
84
|
+
d2,
|
|
85
|
+
h,
|
|
86
|
+
w
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const getX = (rect, w)=>rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
|
|
90
|
+
function buildRow(rect, itm, dims, sum) {
|
|
91
|
+
const r = {
|
|
92
|
+
_data: itm._data,
|
|
93
|
+
a: itm._normalized,
|
|
94
|
+
h: dims.h,
|
|
95
|
+
s: sum,
|
|
96
|
+
v: itm.value,
|
|
97
|
+
vs: itm.values,
|
|
98
|
+
w: dims.w,
|
|
99
|
+
x: getX(rect, dims.w),
|
|
100
|
+
y: rect.y + rect._iy
|
|
101
|
+
};
|
|
102
|
+
if (itm.group) {
|
|
103
|
+
r.g = itm.group;
|
|
104
|
+
r.l = itm.level;
|
|
105
|
+
r.gs = itm.groupSum;
|
|
218
106
|
}
|
|
219
|
-
|
|
220
|
-
return true;
|
|
107
|
+
return r;
|
|
221
108
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
109
|
+
class Rect {
|
|
110
|
+
get area() {
|
|
111
|
+
return this.w * this.h;
|
|
112
|
+
}
|
|
113
|
+
get iw() {
|
|
114
|
+
return this.w - this._ix;
|
|
115
|
+
}
|
|
116
|
+
get ih() {
|
|
117
|
+
return this.h - this._iy;
|
|
118
|
+
}
|
|
119
|
+
get dir() {
|
|
120
|
+
const ih = this.ih;
|
|
121
|
+
return ih <= this.iw && ih > 0 ? 'y' : 'x';
|
|
122
|
+
}
|
|
123
|
+
get side() {
|
|
124
|
+
return this.dir === 'x' ? this.iw : this.ih;
|
|
125
|
+
}
|
|
126
|
+
map(arr) {
|
|
127
|
+
const { dir, side } = this;
|
|
128
|
+
const key = dir === 'x' ? '_ix' : '_iy';
|
|
129
|
+
const sum = arr.nsum;
|
|
130
|
+
const row = arr.get();
|
|
131
|
+
const w2 = side * side;
|
|
132
|
+
const s2 = sum * sum;
|
|
133
|
+
const ret = [];
|
|
134
|
+
let maxd2 = 0;
|
|
135
|
+
let totd1 = 0;
|
|
136
|
+
for (const itm of row){
|
|
137
|
+
const dims = getDims(itm, w2, s2, key);
|
|
138
|
+
totd1 += dims.d1;
|
|
139
|
+
maxd2 = Math.max(maxd2, dims.d2);
|
|
140
|
+
ret.push(buildRow(this, itm, dims, arr.sum));
|
|
141
|
+
this[key] += dims.d1;
|
|
142
|
+
}
|
|
143
|
+
this[dir === 'x' ? '_iy' : '_ix'] += maxd2;
|
|
144
|
+
this[key] -= totd1;
|
|
145
|
+
return ret;
|
|
146
|
+
}
|
|
147
|
+
constructor(r){
|
|
148
|
+
r = r || {
|
|
149
|
+
h: 1,
|
|
150
|
+
w: 1
|
|
151
|
+
};
|
|
152
|
+
this.rtl = !!r.rtl;
|
|
153
|
+
this.unsorted = !!r.unsorted;
|
|
154
|
+
this.x = r.x || r.left || 0;
|
|
155
|
+
this.y = r.y || r.top || 0;
|
|
156
|
+
this._ix = 0;
|
|
157
|
+
this._iy = 0;
|
|
158
|
+
this.w = r.w || r.width || r.right - r.left;
|
|
159
|
+
this.h = r.h || r.height || r.bottom - r.top;
|
|
160
|
+
}
|
|
235
161
|
}
|
|
236
162
|
|
|
237
|
-
|
|
238
|
-
|
|
163
|
+
const min = Math.min;
|
|
164
|
+
const max = Math.max;
|
|
165
|
+
function getStat(sa) {
|
|
166
|
+
return {
|
|
167
|
+
max: sa.max,
|
|
168
|
+
min: sa.min,
|
|
169
|
+
nmax: sa.nmax,
|
|
170
|
+
nmin: sa.nmin,
|
|
171
|
+
nsum: sa.nsum,
|
|
172
|
+
sum: sa.sum
|
|
173
|
+
};
|
|
239
174
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
175
|
+
function getNewStat(sa, o) {
|
|
176
|
+
const v = +o[sa.key];
|
|
177
|
+
const n = v * sa.ratio;
|
|
178
|
+
o._normalized = n;
|
|
179
|
+
return {
|
|
180
|
+
max: max(sa.max, v),
|
|
181
|
+
min: min(sa.min, v),
|
|
182
|
+
nmax: max(sa.nmax, n),
|
|
183
|
+
nmin: min(sa.nmin, n),
|
|
184
|
+
nsum: sa.nsum + n,
|
|
185
|
+
sum: sa.sum + v
|
|
186
|
+
};
|
|
250
187
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const o = helpers.toTRBLCorners(value);
|
|
254
|
-
const maxR = Math.min(maxW, maxH);
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
topLeft: limit(o.topLeft, 0, maxR),
|
|
258
|
-
topRight: limit(o.topRight, 0, maxR),
|
|
259
|
-
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
260
|
-
bottomRight: limit(o.bottomRight, 0, maxR)
|
|
261
|
-
};
|
|
188
|
+
function setStat(sa, stat) {
|
|
189
|
+
Object.assign(sa, stat);
|
|
262
190
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const width = bounds.right - bounds.left;
|
|
267
|
-
const height = bounds.bottom - bounds.top;
|
|
268
|
-
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
269
|
-
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
270
|
-
const outer = {
|
|
271
|
-
x: bounds.left,
|
|
272
|
-
y: bounds.top,
|
|
273
|
-
w: width,
|
|
274
|
-
h: height,
|
|
275
|
-
active: rect.active,
|
|
276
|
-
radius
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
return {
|
|
280
|
-
outer,
|
|
281
|
-
inner: {
|
|
282
|
-
x: outer.x + border.l,
|
|
283
|
-
y: outer.y + border.t,
|
|
284
|
-
w: outer.w - border.l - border.r,
|
|
285
|
-
h: outer.h - border.t - border.b,
|
|
286
|
-
active: rect.active,
|
|
287
|
-
radius: {
|
|
288
|
-
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
289
|
-
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
|
|
290
|
-
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
291
|
-
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
};
|
|
191
|
+
function push(sa, o, stat) {
|
|
192
|
+
sa._arr.push(o);
|
|
193
|
+
setStat(sa, stat);
|
|
295
194
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
195
|
+
class StatArray {
|
|
196
|
+
get length() {
|
|
197
|
+
return this._arr.length;
|
|
198
|
+
}
|
|
199
|
+
reset() {
|
|
200
|
+
this._arr = [];
|
|
201
|
+
this._hist = [];
|
|
202
|
+
this.sum = 0;
|
|
203
|
+
this.nsum = 0;
|
|
204
|
+
this.min = Infinity;
|
|
205
|
+
this.max = -Infinity;
|
|
206
|
+
this.nmin = Infinity;
|
|
207
|
+
this.nmax = -Infinity;
|
|
208
|
+
}
|
|
209
|
+
push(o) {
|
|
210
|
+
push(this, o, getNewStat(this, o));
|
|
211
|
+
}
|
|
212
|
+
pushIf(o, fn, ...args) {
|
|
213
|
+
const nstat = getNewStat(this, o);
|
|
214
|
+
if (!fn(getStat(this), nstat, args)) {
|
|
215
|
+
return o;
|
|
216
|
+
}
|
|
217
|
+
push(this, o, nstat);
|
|
218
|
+
}
|
|
219
|
+
get() {
|
|
220
|
+
return this._arr;
|
|
221
|
+
}
|
|
222
|
+
constructor(key, ratio){
|
|
223
|
+
this._arr = [];
|
|
224
|
+
this._hist = [];
|
|
225
|
+
this.sum = 0;
|
|
226
|
+
this.nsum = 0;
|
|
227
|
+
this.min = Infinity;
|
|
228
|
+
this.max = -Infinity;
|
|
229
|
+
this.nmin = Infinity;
|
|
230
|
+
this.nmax = -Infinity;
|
|
231
|
+
this.key = key;
|
|
232
|
+
this.ratio = ratio;
|
|
233
|
+
this.reset();
|
|
234
|
+
}
|
|
305
235
|
}
|
|
306
236
|
|
|
307
|
-
|
|
308
|
-
|
|
237
|
+
const isOlderPart = (act, req)=>req > act || act.length > req.length && act.startsWith(req);
|
|
238
|
+
const getGroupKey = (lvl)=>String(lvl);
|
|
239
|
+
const hasGroupValue = (value)=>value !== undefined && value !== null && value !== '';
|
|
240
|
+
function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
|
|
241
|
+
const objIndex = lvl - 1;
|
|
242
|
+
if (keys[0] in obj && lvl > 0) {
|
|
243
|
+
const record = tree.reduce((reduced, item, i)=>{
|
|
244
|
+
if (i !== objIndex) {
|
|
245
|
+
reduced[getGroupKey(i)] = item;
|
|
246
|
+
}
|
|
247
|
+
return reduced;
|
|
248
|
+
}, {});
|
|
249
|
+
record[treeLeafKey] = tree[objIndex];
|
|
250
|
+
keys.forEach((k)=>{
|
|
251
|
+
record[k] = obj[k];
|
|
252
|
+
});
|
|
253
|
+
result.push(record);
|
|
254
|
+
} else {
|
|
255
|
+
for (const childKey of Object.keys(obj)){
|
|
256
|
+
const child = obj[childKey];
|
|
257
|
+
if (helpers.isObject(child)) {
|
|
258
|
+
tree.push(childKey);
|
|
259
|
+
scanTreeObject(keys, treeLeafKey, child, tree, lvl + 1, result);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
tree.splice(objIndex, 1);
|
|
264
|
+
return result;
|
|
309
265
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
266
|
+
function normalizeTreeToArray(keys, treeLeafKey, obj) {
|
|
267
|
+
const data = scanTreeObject(keys, treeLeafKey, obj);
|
|
268
|
+
if (!data.length) {
|
|
269
|
+
return data;
|
|
270
|
+
}
|
|
271
|
+
const max = data.reduce((maxVal, element)=>{
|
|
272
|
+
const ikeys = Object.keys(element).length - 2;
|
|
273
|
+
return Math.max(maxVal, ikeys);
|
|
274
|
+
}, 0);
|
|
275
|
+
data.forEach((element)=>{
|
|
276
|
+
for(let i = 0; i < max; i++){
|
|
277
|
+
const groupKey = getGroupKey(i);
|
|
278
|
+
if (!element[groupKey]) {
|
|
279
|
+
element[groupKey] = '';
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
return data;
|
|
318
284
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
285
|
+
function flatten(input) {
|
|
286
|
+
const stack = [
|
|
287
|
+
...input
|
|
288
|
+
];
|
|
289
|
+
const res = [];
|
|
290
|
+
while(stack.length){
|
|
291
|
+
const next = stack.pop();
|
|
292
|
+
if (Array.isArray(next)) {
|
|
293
|
+
stack.push(...next);
|
|
294
|
+
} else {
|
|
295
|
+
res.push(next);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return res.reverse();
|
|
329
299
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
} else if (!isLeaf && shouldDrawCaption(rect, captions)) {
|
|
341
|
-
drawCaption(ctx, rect, options, item);
|
|
342
|
-
}
|
|
343
|
-
ctx.restore();
|
|
300
|
+
function getPath(groups, value, defaultValue) {
|
|
301
|
+
const path = [];
|
|
302
|
+
for (const grp of groups){
|
|
303
|
+
const item = value[grp];
|
|
304
|
+
if (!hasGroupValue(item)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
path.push(item);
|
|
308
|
+
}
|
|
309
|
+
return path.length ? path.join('.') : defaultValue;
|
|
344
310
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
311
|
+
function resolveGroup(value, treeLeafKey, allGroups, groupIndex) {
|
|
312
|
+
for(let idx = groupIndex; idx < allGroups.length; idx++){
|
|
313
|
+
const groupKey = getGroupKey(allGroups[idx]);
|
|
314
|
+
if (hasGroupValue(value[groupKey])) {
|
|
315
|
+
return {
|
|
316
|
+
group: groupKey,
|
|
317
|
+
groupIndex: idx,
|
|
318
|
+
value: value[groupKey]
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (hasGroupValue(value[treeLeafKey])) {
|
|
323
|
+
return {
|
|
324
|
+
group: treeLeafKey,
|
|
325
|
+
groupIndex,
|
|
326
|
+
value: value[treeLeafKey]
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
360
330
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
prev += item.string;
|
|
365
|
-
return prev;
|
|
366
|
-
}, '');
|
|
367
|
-
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
368
|
-
if (!widthCache.has(mapKey)) {
|
|
369
|
-
ctx.save();
|
|
370
|
-
const count = lines.length;
|
|
371
|
-
let width = 0;
|
|
372
|
-
let height = 0;
|
|
373
|
-
for (let i = 0; i < count; i++) {
|
|
374
|
-
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
375
|
-
ctx.font = font.string;
|
|
376
|
-
const text = lines[i];
|
|
377
|
-
width = Math.max(width, ctx.measureText(text).width);
|
|
378
|
-
height += font.lineHeight;
|
|
331
|
+
function ensureGroup(tmp, data, groupValue, addKeys) {
|
|
332
|
+
if (groupValue in tmp) {
|
|
333
|
+
return;
|
|
379
334
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
335
|
+
tmp[groupValue] = {
|
|
336
|
+
value: 0
|
|
337
|
+
};
|
|
338
|
+
const tmpRef = tmp[groupValue];
|
|
339
|
+
addKeys.forEach((k)=>{
|
|
340
|
+
tmpRef[k] = 0;
|
|
341
|
+
});
|
|
342
|
+
data[groupValue] = [];
|
|
384
343
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
344
|
+
function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
|
|
345
|
+
const key = keys[0];
|
|
346
|
+
const addKeys = keys.slice(1);
|
|
347
|
+
const tmp = Object.create(null);
|
|
348
|
+
const data = Object.create(null);
|
|
349
|
+
const ret = [];
|
|
350
|
+
const resolvedAllGroups = allGroups.length ? allGroups : [
|
|
351
|
+
grp
|
|
352
|
+
];
|
|
353
|
+
const resolvedGroupIndex = allGroups.length ? groupIndex : 0;
|
|
354
|
+
let i;
|
|
355
|
+
let n;
|
|
356
|
+
for(i = 0, n = values.length; i < n; ++i){
|
|
357
|
+
const v = values[i];
|
|
358
|
+
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const itemGroup = resolveGroup(v, treeLeafKey, resolvedAllGroups, resolvedGroupIndex);
|
|
362
|
+
if (!itemGroup) {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
const g = itemGroup.value;
|
|
366
|
+
ensureGroup(tmp, data, g, addKeys);
|
|
367
|
+
tmp[g].value += +v[key];
|
|
368
|
+
tmp[g].label = g;
|
|
369
|
+
tmp[g].group = itemGroup.group;
|
|
370
|
+
tmp[g].groupIndex = itemGroup.groupIndex;
|
|
371
|
+
const tmpRef = tmp[g];
|
|
372
|
+
addKeys.forEach((k)=>{
|
|
373
|
+
tmpRef[k] += v[k];
|
|
374
|
+
});
|
|
375
|
+
tmp[g].path = getPath(resolvedAllGroups.slice(0, itemGroup.groupIndex + 1), v, g);
|
|
376
|
+
data[g].push(v);
|
|
377
|
+
}
|
|
378
|
+
Object.keys(tmp).forEach((k)=>{
|
|
379
|
+
const v = {
|
|
380
|
+
children: data[k]
|
|
381
|
+
};
|
|
382
|
+
v[key] = +tmp[k].value;
|
|
383
|
+
addKeys.forEach((ak)=>{
|
|
384
|
+
v[ak] = +tmp[k][ak];
|
|
385
|
+
});
|
|
386
|
+
v[grp] = tmp[k].label;
|
|
387
|
+
v.group = tmp[k].group;
|
|
388
|
+
v.groupIndex = tmp[k].groupIndex;
|
|
389
|
+
v.label = k;
|
|
390
|
+
v.path = tmp[k].path;
|
|
391
|
+
if (mainGrp) {
|
|
392
|
+
v[mainGrp] = mainValue;
|
|
393
|
+
}
|
|
394
|
+
ret.push(v);
|
|
395
|
+
});
|
|
396
|
+
return ret;
|
|
392
397
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
398
|
+
function index(values, key) {
|
|
399
|
+
let n = values.length;
|
|
400
|
+
let i;
|
|
401
|
+
if (!n) {
|
|
402
|
+
return key;
|
|
403
|
+
}
|
|
404
|
+
const obj = helpers.isObject(values[0]);
|
|
405
|
+
key = obj ? key : 'v';
|
|
406
|
+
for(i = 0, n = values.length; i < n; ++i){
|
|
407
|
+
if (obj) {
|
|
408
|
+
values[i]._idx = i;
|
|
409
|
+
} else {
|
|
410
|
+
values[i] = {
|
|
411
|
+
_idx: i,
|
|
412
|
+
v: values[i]
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return key;
|
|
406
417
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
418
|
+
function sort(values, key) {
|
|
419
|
+
if (key) {
|
|
420
|
+
values.sort((a, b)=>+b[key] - +a[key]);
|
|
421
|
+
} else {
|
|
422
|
+
values.sort((a, b)=>+b - +a);
|
|
423
|
+
}
|
|
412
424
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
let fonts = getFontFromOptions(rect, labels);
|
|
422
|
-
let labelSize = measureLabelSize(ctx, contents, fonts);
|
|
423
|
-
const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
|
|
424
|
-
if (!lblToDraw) {
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (helpers.isNumber(lblToDraw)) {
|
|
428
|
-
labelSize = {width: labelSize.width * lblToDraw, height: labelSize.height * lblToDraw};
|
|
429
|
-
fonts = toFonts(fonts, lblToDraw);
|
|
430
|
-
}
|
|
431
|
-
const {color, hoverColor, align} = labels;
|
|
432
|
-
const optColor = (rect.active ? hoverColor : color) || color;
|
|
433
|
-
const colors = helpers.isArray(optColor) ? optColor : [optColor];
|
|
434
|
-
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
435
|
-
ctx.textAlign = align;
|
|
436
|
-
ctx.textBaseline = 'middle';
|
|
437
|
-
let lhs = 0;
|
|
438
|
-
contents.forEach(function(l, i) {
|
|
439
|
-
const c = colors[Math.min(i, colors.length - 1)];
|
|
440
|
-
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
441
|
-
const lh = f.lineHeight;
|
|
442
|
-
ctx.font = f.string;
|
|
443
|
-
ctx.fillStyle = c;
|
|
444
|
-
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
445
|
-
lhs += lh;
|
|
446
|
-
});
|
|
425
|
+
function sum(values, key) {
|
|
426
|
+
let s;
|
|
427
|
+
let i;
|
|
428
|
+
let n;
|
|
429
|
+
for(s = 0, i = 0, n = values.length; i < n; ++i){
|
|
430
|
+
s += key ? +values[i][key] : +values[i];
|
|
431
|
+
}
|
|
432
|
+
return s;
|
|
447
433
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
ctx.moveTo(x + w2, y);
|
|
466
|
-
ctx.lineTo(x + w2, y + h);
|
|
467
|
-
} else {
|
|
468
|
-
const h2 = h / 2;
|
|
469
|
-
ctx.moveTo(x, y + h2);
|
|
470
|
-
ctx.lineTo(x + w, y + h2);
|
|
471
|
-
}
|
|
472
|
-
ctx.stroke();
|
|
473
|
-
ctx.restore();
|
|
434
|
+
function requireVersion(pkg, min, ver, strict = true) {
|
|
435
|
+
const parts = ver.split('.');
|
|
436
|
+
let i = 0;
|
|
437
|
+
for (const req of min.split('.')){
|
|
438
|
+
const act = parts[i++];
|
|
439
|
+
if (Number.parseInt(req, 10) < Number.parseInt(act, 10)) {
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
if (isOlderPart(act, req)) {
|
|
443
|
+
if (strict) {
|
|
444
|
+
throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`);
|
|
445
|
+
} else {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
474
451
|
}
|
|
475
452
|
|
|
476
|
-
function
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return {x, y};
|
|
453
|
+
function compareAspectRatio(oldStat, newStat, args) {
|
|
454
|
+
if (oldStat.sum === 0) {
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
const [length] = args;
|
|
458
|
+
const os2 = oldStat.nsum * oldStat.nsum;
|
|
459
|
+
const ns2 = newStat.nsum * newStat.nsum;
|
|
460
|
+
const l2 = length * length;
|
|
461
|
+
const or = Math.max(l2 * oldStat.nmax / os2, os2 / (l2 * oldStat.nmin));
|
|
462
|
+
const nr = Math.max(l2 * newStat.nmax / ns2, ns2 / (l2 * newStat.nmin));
|
|
463
|
+
return nr <= or;
|
|
488
464
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
465
|
+
function squarify(values, rectangle, keys = [], grp, lvl, gsum) {
|
|
466
|
+
values = values || [];
|
|
467
|
+
const rows = [];
|
|
468
|
+
const rect = new Rect(rectangle);
|
|
469
|
+
const row = new StatArray('value', rect.area / sum(values, keys[0]));
|
|
470
|
+
let length = rect.side;
|
|
471
|
+
const n = values.length;
|
|
472
|
+
let i;
|
|
473
|
+
let o;
|
|
474
|
+
if (!n) {
|
|
475
|
+
return rows;
|
|
476
|
+
}
|
|
477
|
+
const tmp = values.slice();
|
|
478
|
+
const key = index(tmp, keys[0]);
|
|
479
|
+
if (!rectangle?.unsorted) {
|
|
480
|
+
sort(tmp, key);
|
|
481
|
+
}
|
|
482
|
+
const val = (idx)=>key ? +tmp[idx][key] : +tmp[idx];
|
|
483
|
+
const gval = (idx)=>grp && tmp[idx][grp];
|
|
484
|
+
for(i = 0; i < n; ++i){
|
|
485
|
+
o = {
|
|
486
|
+
_data: values[tmp[i]._idx],
|
|
487
|
+
group: undefined,
|
|
488
|
+
groupSum: gsum,
|
|
489
|
+
level: undefined,
|
|
490
|
+
value: val(i)
|
|
491
|
+
};
|
|
492
|
+
if (grp) {
|
|
493
|
+
o.level = tmp[i].groupIndex ?? lvl;
|
|
494
|
+
o.group = gval(i);
|
|
495
|
+
const tmpRef = tmp[i];
|
|
496
|
+
o.values = keys.reduce((obj, k)=>{
|
|
497
|
+
obj[k] = +tmpRef[k];
|
|
498
|
+
return obj;
|
|
499
|
+
}, {});
|
|
500
|
+
}
|
|
501
|
+
o = row.pushIf(o, compareAspectRatio, length);
|
|
502
|
+
if (o) {
|
|
503
|
+
rows.push(rect.map(row));
|
|
504
|
+
length = rect.side;
|
|
505
|
+
row.reset();
|
|
506
|
+
row.push(o);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (row.length) {
|
|
510
|
+
rows.push(rect.map(row));
|
|
511
|
+
}
|
|
512
|
+
return flatten(rows);
|
|
497
513
|
}
|
|
498
514
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.options = undefined;
|
|
505
|
-
this.width = undefined;
|
|
506
|
-
this.height = undefined;
|
|
507
|
-
|
|
508
|
-
if (cfg) {
|
|
509
|
-
Object.assign(this, cfg);
|
|
515
|
+
const widthCache = new Map();
|
|
516
|
+
function shouldDrawCaption(displayMode, rect, options) {
|
|
517
|
+
if (!options || options.display === false) {
|
|
518
|
+
return false;
|
|
510
519
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
draw(ctx, data, levels = 0) {
|
|
514
|
-
if (!data) {
|
|
515
|
-
return;
|
|
520
|
+
if (displayMode === 'headerBoxes') {
|
|
521
|
+
return true;
|
|
516
522
|
}
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (
|
|
525
|
-
|
|
526
|
-
addRectPath(ctx, outer);
|
|
527
|
-
ctx.clip();
|
|
528
|
-
addRectPath(ctx, inner);
|
|
529
|
-
ctx.fillStyle = options.borderColor;
|
|
530
|
-
ctx.fill('evenodd');
|
|
523
|
+
const { w, h } = rect;
|
|
524
|
+
const font = helpers.toFont(options.font || {});
|
|
525
|
+
const min = font.lineHeight;
|
|
526
|
+
const padding = Math.max(Math.min(helpers.valueOrDefault(options.padding, 3) * 2, Math.min(w, h)), 0);
|
|
527
|
+
return w - padding > min && h - padding > min;
|
|
528
|
+
}
|
|
529
|
+
function getCaptionHeight(displayMode, rect, font, padding) {
|
|
530
|
+
if (displayMode !== 'headerBoxes') {
|
|
531
|
+
return font.lineHeight + padding * 2;
|
|
531
532
|
}
|
|
532
|
-
|
|
533
|
+
const captionHeight = font.lineHeight + padding * 2;
|
|
534
|
+
return rect.h < 2 * captionHeight ? rect.h / 3 : captionHeight;
|
|
535
|
+
}
|
|
536
|
+
function drawText(ctx, rect, options, item, element) {
|
|
537
|
+
const { captions, labels, displayMode } = options;
|
|
538
|
+
ctx.save();
|
|
533
539
|
ctx.beginPath();
|
|
534
|
-
|
|
535
|
-
ctx.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
541
|
+
ctx.clip();
|
|
542
|
+
const isLeaf = item && (!helpers.defined(item.l) || item.isLeaf);
|
|
543
|
+
if (isLeaf && labels.display) {
|
|
544
|
+
drawLabel(ctx, rect, options, item, element);
|
|
545
|
+
} else if (!isLeaf && shouldDrawCaption(displayMode, rect, captions)) {
|
|
546
|
+
drawCaption(ctx, rect, options, item, element);
|
|
547
|
+
}
|
|
540
548
|
ctx.restore();
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
inXRange(mouseX, useFinalPosition) {
|
|
548
|
-
return inRange(this, mouseX, null, useFinalPosition);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
inYRange(mouseY, useFinalPosition) {
|
|
552
|
-
return inRange(this, null, mouseY, useFinalPosition);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
getCenterPoint(useFinalPosition) {
|
|
556
|
-
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
557
|
-
return {
|
|
558
|
-
x: x + width / 2,
|
|
559
|
-
y: y + height / 2
|
|
549
|
+
}
|
|
550
|
+
function callbackContext(element, item) {
|
|
551
|
+
const context = element.$context || {
|
|
552
|
+
active: !!element.active,
|
|
553
|
+
raw: item,
|
|
554
|
+
type: 'data'
|
|
560
555
|
};
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
return this.getCenterPoint();
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* @todo: remove this unused function in v3
|
|
569
|
-
*/
|
|
570
|
-
getRange(axis) {
|
|
571
|
-
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
572
|
-
}
|
|
556
|
+
context.active = !!element.active;
|
|
557
|
+
context.raw = item;
|
|
558
|
+
return context;
|
|
573
559
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
TreemapElement.defaults = {
|
|
578
|
-
borderRadius: 0,
|
|
579
|
-
borderWidth: 0,
|
|
580
|
-
captions: {
|
|
581
|
-
align: undefined,
|
|
582
|
-
color: 'black',
|
|
583
|
-
display: true,
|
|
584
|
-
font: {},
|
|
585
|
-
formatter: (ctx) => ctx.raw.g || ctx.raw._data.label || '',
|
|
586
|
-
padding: 3
|
|
587
|
-
},
|
|
588
|
-
dividers: {
|
|
589
|
-
display: false,
|
|
590
|
-
lineCapStyle: 'butt',
|
|
591
|
-
lineColor: 'black',
|
|
592
|
-
lineDash: [],
|
|
593
|
-
lineDashOffset: 0,
|
|
594
|
-
lineWidth: 1,
|
|
595
|
-
},
|
|
596
|
-
label: undefined,
|
|
597
|
-
labels: {
|
|
598
|
-
align: 'center',
|
|
599
|
-
color: 'black',
|
|
600
|
-
display: false,
|
|
601
|
-
font: {},
|
|
602
|
-
formatter(ctx) {
|
|
603
|
-
if (ctx.raw.g) {
|
|
604
|
-
return [ctx.raw.g, ctx.raw.v + ''];
|
|
605
|
-
}
|
|
606
|
-
return ctx.raw._data.label ? [ctx.raw._data.label, ctx.raw.v + ''] : ctx.raw.v + '';
|
|
607
|
-
},
|
|
608
|
-
overflow: 'cut',
|
|
609
|
-
position: 'middle',
|
|
610
|
-
padding: 3
|
|
611
|
-
},
|
|
612
|
-
rtl: false,
|
|
613
|
-
spacing: 0.5,
|
|
614
|
-
unsorted: false,
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
TreemapElement.descriptors = {
|
|
618
|
-
captions: {
|
|
619
|
-
_fallback: true
|
|
620
|
-
},
|
|
621
|
-
labels: {
|
|
622
|
-
_fallback: true
|
|
623
|
-
},
|
|
624
|
-
_scriptable: true,
|
|
625
|
-
_indexable: false
|
|
626
|
-
};
|
|
627
|
-
|
|
628
|
-
TreemapElement.defaultRoutes = {
|
|
629
|
-
backgroundColor: 'backgroundColor',
|
|
630
|
-
borderColor: 'borderColor'
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
function getDims(itm, w2, s2, key) {
|
|
634
|
-
const a = itm._normalized;
|
|
635
|
-
const ar = w2 * a / s2;
|
|
636
|
-
const d1 = Math.sqrt(a * ar);
|
|
637
|
-
const d2 = a / d1;
|
|
638
|
-
const w = key === '_ix' ? d1 : d2;
|
|
639
|
-
const h = key === '_ix' ? d2 : d1;
|
|
640
|
-
|
|
641
|
-
return {d1, d2, w, h};
|
|
560
|
+
function resolveOption(option, context) {
|
|
561
|
+
return typeof option === 'function' ? option(context) : option;
|
|
642
562
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
function buildRow(rect, itm, dims, sum) {
|
|
647
|
-
const r = {
|
|
648
|
-
x: getX(rect, dims.w),
|
|
649
|
-
y: rect.y + rect._iy,
|
|
650
|
-
w: dims.w,
|
|
651
|
-
h: dims.h,
|
|
652
|
-
a: itm._normalized,
|
|
653
|
-
v: itm.value,
|
|
654
|
-
vs: itm.values,
|
|
655
|
-
s: sum,
|
|
656
|
-
_data: itm._data
|
|
657
|
-
};
|
|
658
|
-
if (itm.group) {
|
|
659
|
-
r.g = itm.group;
|
|
660
|
-
r.l = itm.level;
|
|
661
|
-
r.gs = itm.groupSum;
|
|
662
|
-
}
|
|
663
|
-
return r;
|
|
563
|
+
function resolveCaptionText(captions, element, item) {
|
|
564
|
+
return resolveOption(captions.formatter, callbackContext(element, item)) || item.g || '';
|
|
664
565
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
get area() {
|
|
680
|
-
return this.w * this.h;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
get iw() {
|
|
684
|
-
return this.w - this._ix;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
get ih() {
|
|
688
|
-
return this.h - this._iy;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
get dir() {
|
|
692
|
-
const ih = this.ih;
|
|
693
|
-
return ih <= this.iw && ih > 0 ? 'y' : 'x';
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
get side() {
|
|
697
|
-
return this.dir === 'x' ? this.iw : this.ih;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
map(arr) {
|
|
701
|
-
const {dir, side} = this;
|
|
702
|
-
const key = dir === 'x' ? '_ix' : '_iy';
|
|
703
|
-
const sum = arr.nsum;
|
|
704
|
-
const row = arr.get();
|
|
705
|
-
const w2 = side * side;
|
|
706
|
-
const s2 = sum * sum;
|
|
707
|
-
const ret = [];
|
|
708
|
-
let maxd2 = 0;
|
|
709
|
-
let totd1 = 0;
|
|
710
|
-
for (const itm of row) {
|
|
711
|
-
const dims = getDims(itm, w2, s2, key);
|
|
712
|
-
totd1 += dims.d1;
|
|
713
|
-
maxd2 = Math.max(maxd2, dims.d2);
|
|
714
|
-
ret.push(buildRow(this, itm, dims, arr.sum));
|
|
715
|
-
this[key] += dims.d1;
|
|
566
|
+
function drawCaption(ctx, rect, options, item, element) {
|
|
567
|
+
const { captions, spacing, rtl, displayMode } = options;
|
|
568
|
+
const { color, hoverColor, font, hoverFont, padding, align } = captions;
|
|
569
|
+
const oColor = (rect.active ? hoverColor : color) || color;
|
|
570
|
+
const oAlign = align || (rtl ? 'right' : 'left');
|
|
571
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
572
|
+
const oFont = helpers.toFont(optFont);
|
|
573
|
+
const fonts = [
|
|
574
|
+
oFont
|
|
575
|
+
];
|
|
576
|
+
if (oFont.lineHeight > rect.h) {
|
|
577
|
+
return;
|
|
716
578
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
};
|
|
579
|
+
let text = resolveCaptionText(captions, element, item);
|
|
580
|
+
if (!text) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const captionSize = measureLabelSize(ctx, [
|
|
584
|
+
text
|
|
585
|
+
], fonts);
|
|
586
|
+
if (captionSize.width + 2 * padding > rect.w) {
|
|
587
|
+
text = sliceTextToFitWidth(ctx, text, rect.w - 2 * padding, fonts);
|
|
588
|
+
}
|
|
589
|
+
const lh = oFont.lineHeight / 2;
|
|
590
|
+
const x = calculateX(rect, oAlign, padding);
|
|
591
|
+
ctx.fillStyle = oColor;
|
|
592
|
+
ctx.font = oFont.string;
|
|
593
|
+
ctx.textAlign = oAlign;
|
|
594
|
+
ctx.textBaseline = 'middle';
|
|
595
|
+
const y = displayMode === 'headerBoxes' ? rect.y + rect.h / 2 : rect.y + padding + spacing + lh;
|
|
596
|
+
ctx.fillText(text, x, y);
|
|
736
597
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
598
|
+
function sliceTextToFitWidth(ctx, text, width, fonts) {
|
|
599
|
+
const ellipsis = '...';
|
|
600
|
+
const ellipsisWidth = measureLabelSize(ctx, [
|
|
601
|
+
ellipsis
|
|
602
|
+
], fonts).width;
|
|
603
|
+
if (ellipsisWidth >= width) {
|
|
604
|
+
return '';
|
|
605
|
+
}
|
|
606
|
+
let lowerBoundLen = 1;
|
|
607
|
+
let upperBoundLen = text.length;
|
|
608
|
+
let currentWidth;
|
|
609
|
+
while(lowerBoundLen <= upperBoundLen){
|
|
610
|
+
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
611
|
+
const currentText = text.slice(0, currentLen);
|
|
612
|
+
currentWidth = measureLabelSize(ctx, [
|
|
613
|
+
currentText
|
|
614
|
+
], fonts).width;
|
|
615
|
+
if (currentWidth + ellipsisWidth > width) {
|
|
616
|
+
upperBoundLen = currentLen - 1;
|
|
617
|
+
} else {
|
|
618
|
+
lowerBoundLen = currentLen + 1;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
622
|
+
return slicedText ? slicedText + ellipsis : '';
|
|
751
623
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
624
|
+
function measureLabelSize(ctx, lines, fonts) {
|
|
625
|
+
const fontsKey = fonts.reduce((prev, item)=>{
|
|
626
|
+
prev += item.string;
|
|
627
|
+
return prev;
|
|
628
|
+
}, '');
|
|
629
|
+
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
630
|
+
let size = widthCache.get(mapKey);
|
|
631
|
+
if (!size) {
|
|
632
|
+
ctx.save();
|
|
633
|
+
const count = lines.length;
|
|
634
|
+
let width = 0;
|
|
635
|
+
let height = 0;
|
|
636
|
+
for(let i = 0; i < count; i++){
|
|
637
|
+
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
638
|
+
ctx.font = font.string;
|
|
639
|
+
const text = lines[i];
|
|
640
|
+
width = Math.max(width, ctx.measureText(text).width);
|
|
641
|
+
height += font.lineHeight;
|
|
642
|
+
}
|
|
643
|
+
ctx.restore();
|
|
644
|
+
size = {
|
|
645
|
+
height,
|
|
646
|
+
width
|
|
647
|
+
};
|
|
648
|
+
widthCache.set(mapKey, size);
|
|
649
|
+
}
|
|
650
|
+
return size;
|
|
755
651
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
652
|
+
function toFonts(fonts, fitRatio) {
|
|
653
|
+
return fonts.map((f)=>{
|
|
654
|
+
const { lineHeight: _lineHeight, ...font } = f;
|
|
655
|
+
return helpers.toFont({
|
|
656
|
+
...font,
|
|
657
|
+
size: Math.floor(f.size * fitRatio)
|
|
658
|
+
});
|
|
659
|
+
});
|
|
760
660
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
return this._arr.length;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
reset() {
|
|
775
|
-
const me = this;
|
|
776
|
-
me._arr = [];
|
|
777
|
-
me._hist = [];
|
|
778
|
-
me.sum = 0;
|
|
779
|
-
me.nsum = 0;
|
|
780
|
-
me.min = Infinity;
|
|
781
|
-
me.max = -Infinity;
|
|
782
|
-
me.nmin = Infinity;
|
|
783
|
-
me.nmax = -Infinity;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
push(o) {
|
|
787
|
-
push(this, o, getNewStat(this, o));
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
pushIf(o, fn, ...args) {
|
|
791
|
-
const nstat = getNewStat(this, o);
|
|
792
|
-
if (!fn(getStat(this), nstat, args)) {
|
|
793
|
-
return o;
|
|
661
|
+
function labelToDraw(_ctx, rect, options, labelSize) {
|
|
662
|
+
const { overflow, padding } = options;
|
|
663
|
+
const { width, height } = labelSize;
|
|
664
|
+
if (overflow === 'hidden') {
|
|
665
|
+
return !(width + padding * 2 > rect.w || height + padding * 2 > rect.h);
|
|
666
|
+
} else if (overflow === 'fit') {
|
|
667
|
+
const ratio = Math.min(rect.w / (width + padding * 2), rect.h / (height + padding * 2));
|
|
668
|
+
if (ratio < 1) {
|
|
669
|
+
return ratio;
|
|
670
|
+
}
|
|
794
671
|
}
|
|
795
|
-
push(this, o, nstat);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
get() {
|
|
799
|
-
return this._arr;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
function compareAspectRatio(oldStat, newStat, args) {
|
|
804
|
-
if (oldStat.sum === 0) {
|
|
805
672
|
return true;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const [length] = args;
|
|
809
|
-
const os2 = oldStat.nsum * oldStat.nsum;
|
|
810
|
-
const ns2 = newStat.nsum * newStat.nsum;
|
|
811
|
-
const l2 = length * length;
|
|
812
|
-
const or = Math.max(l2 * oldStat.nmax / os2, os2 / (l2 * oldStat.nmin));
|
|
813
|
-
const nr = Math.max(l2 * newStat.nmax / ns2, ns2 / (l2 * newStat.nmin));
|
|
814
|
-
return nr <= or;
|
|
815
673
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
* @param {string} [grp]
|
|
823
|
-
* @param {number} [lvl]
|
|
824
|
-
* @param {number} [gsum]
|
|
825
|
-
*/
|
|
826
|
-
function squarify(values, rectangle, keys = [], grp, lvl, gsum) {
|
|
827
|
-
values = values || [];
|
|
828
|
-
const rows = [];
|
|
829
|
-
const rect = new Rect(rectangle);
|
|
830
|
-
const row = new StatArray('value', rect.area / sum(values, keys[0]));
|
|
831
|
-
let length = rect.side;
|
|
832
|
-
const n = values.length;
|
|
833
|
-
let i, o;
|
|
834
|
-
|
|
835
|
-
if (!n) {
|
|
836
|
-
return rows;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
const tmp = values.slice();
|
|
840
|
-
let key = index(tmp, keys[0]);
|
|
841
|
-
|
|
842
|
-
if (!rectangle?.unsorted) {
|
|
843
|
-
sort(tmp, key);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
const val = (idx) => key ? +tmp[idx][key] : +tmp[idx];
|
|
847
|
-
const gval = (idx) => grp && tmp[idx][grp];
|
|
848
|
-
|
|
849
|
-
for (i = 0; i < n; ++i) {
|
|
850
|
-
o = {value: val(i), groupSum: gsum, _data: values[tmp[i]._idx], level: undefined, group: undefined};
|
|
851
|
-
if (grp) {
|
|
852
|
-
o.level = lvl;
|
|
853
|
-
o.group = gval(i);
|
|
854
|
-
const tmpRef = tmp[i];
|
|
855
|
-
o.values = keys.reduce(function(obj, k) {
|
|
856
|
-
obj[k] = +tmpRef[k];
|
|
857
|
-
return obj;
|
|
858
|
-
}, {});
|
|
859
|
-
}
|
|
860
|
-
o = row.pushIf(o, compareAspectRatio, length);
|
|
861
|
-
if (o) {
|
|
862
|
-
rows.push(rect.map(row));
|
|
863
|
-
length = rect.side;
|
|
864
|
-
row.reset();
|
|
865
|
-
row.push(o);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
if (row.length) {
|
|
869
|
-
rows.push(rect.map(row));
|
|
870
|
-
}
|
|
871
|
-
return flatten(rows);
|
|
674
|
+
function getFontFromOptions(rect, labels) {
|
|
675
|
+
const { font, hoverFont } = labels;
|
|
676
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
677
|
+
return Array.isArray(optFont) ? optFont.map((f)=>helpers.toFont(f)) : [
|
|
678
|
+
helpers.toFont(optFont)
|
|
679
|
+
];
|
|
872
680
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
681
|
+
function drawLabel(ctx, rect, options, item, element) {
|
|
682
|
+
const labels = options.labels;
|
|
683
|
+
const content = resolveOption(labels.formatter, callbackContext(element, item));
|
|
684
|
+
if (!content) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const contents = helpers.isArray(content) ? content : [
|
|
688
|
+
content
|
|
689
|
+
];
|
|
690
|
+
let fonts = getFontFromOptions(rect, labels);
|
|
691
|
+
let labelSize = measureLabelSize(ctx, contents, fonts);
|
|
692
|
+
const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
|
|
693
|
+
if (!lblToDraw) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (helpers.isNumber(lblToDraw)) {
|
|
697
|
+
labelSize = {
|
|
698
|
+
height: labelSize.height * lblToDraw,
|
|
699
|
+
width: labelSize.width * lblToDraw
|
|
700
|
+
};
|
|
701
|
+
fonts = toFonts(fonts, lblToDraw);
|
|
702
|
+
}
|
|
703
|
+
const { color, hoverColor, align } = labels;
|
|
704
|
+
const optColor = (rect.active ? hoverColor : color) || color;
|
|
705
|
+
const colors = helpers.isArray(optColor) ? optColor : [
|
|
706
|
+
optColor
|
|
707
|
+
];
|
|
708
|
+
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
709
|
+
ctx.textAlign = align;
|
|
710
|
+
ctx.textBaseline = 'middle';
|
|
711
|
+
let lhs = 0;
|
|
712
|
+
contents.forEach((l, i)=>{
|
|
713
|
+
const c = colors[Math.min(i, colors.length - 1)];
|
|
714
|
+
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
715
|
+
const lh = f.lineHeight;
|
|
716
|
+
ctx.font = f.string;
|
|
717
|
+
ctx.fillStyle = c;
|
|
718
|
+
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
719
|
+
lhs += lh;
|
|
720
|
+
});
|
|
889
721
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
722
|
+
function drawDivider(ctx, rect, options, item) {
|
|
723
|
+
const dividers = options.dividers;
|
|
724
|
+
const children = item._data?.children;
|
|
725
|
+
if (!dividers.display || !Array.isArray(children) || !children.length) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const { x, y, w, h } = rect;
|
|
729
|
+
const { lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth } = dividers;
|
|
730
|
+
ctx.save();
|
|
731
|
+
ctx.strokeStyle = lineColor;
|
|
732
|
+
ctx.lineCap = lineCapStyle;
|
|
733
|
+
ctx.setLineDash(lineDash);
|
|
734
|
+
ctx.lineDashOffset = lineDashOffset;
|
|
735
|
+
ctx.lineWidth = lineWidth;
|
|
736
|
+
ctx.beginPath();
|
|
737
|
+
if (w > h) {
|
|
738
|
+
const w2 = w / 2;
|
|
739
|
+
ctx.moveTo(x + w2, y);
|
|
740
|
+
ctx.lineTo(x + w2, y + h);
|
|
741
|
+
} else {
|
|
742
|
+
const h2 = h / 2;
|
|
743
|
+
ctx.moveTo(x, y + h2);
|
|
744
|
+
ctx.lineTo(x + w, y + h2);
|
|
745
|
+
}
|
|
746
|
+
ctx.stroke();
|
|
747
|
+
ctx.restore();
|
|
899
748
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
if (a.length !== b.length) {
|
|
913
|
-
return true;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
for (i = 0, n = a.length; i < n; ++i) {
|
|
917
|
-
if (a[i] !== b[i]) {
|
|
918
|
-
return true;
|
|
749
|
+
function calculateXYLabel(rect, options, labelSize) {
|
|
750
|
+
const { align, position, padding } = options;
|
|
751
|
+
const x = calculateX(rect, align, padding);
|
|
752
|
+
let y;
|
|
753
|
+
if (position === 'top') {
|
|
754
|
+
y = rect.y + padding;
|
|
755
|
+
} else if (position === 'bottom') {
|
|
756
|
+
y = rect.y + rect.h - padding - labelSize.height;
|
|
757
|
+
} else {
|
|
758
|
+
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
919
759
|
}
|
|
920
|
-
|
|
921
|
-
|
|
760
|
+
return {
|
|
761
|
+
x,
|
|
762
|
+
y
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function calculateX(rect, align, padding) {
|
|
766
|
+
if (align === 'left') {
|
|
767
|
+
return rect.x + padding;
|
|
768
|
+
} else if (align === 'right') {
|
|
769
|
+
return rect.x + rect.w - padding;
|
|
770
|
+
}
|
|
771
|
+
return rect.x + rect.w / 2;
|
|
922
772
|
}
|
|
923
773
|
|
|
924
774
|
function buildData(tree, dataset, keys, mainRect) {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if (gidx < glen - 1) {
|
|
943
|
-
gsq.forEach((sq) => {
|
|
944
|
-
const bw = parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
775
|
+
const treeLeafKey = dataset.treeLeafKey || '_leaf';
|
|
776
|
+
if (helpers.isObject(tree)) {
|
|
777
|
+
tree = normalizeTreeToArray(keys, treeLeafKey, tree);
|
|
778
|
+
}
|
|
779
|
+
const groups = dataset.groups || [];
|
|
780
|
+
const glen = groups.length;
|
|
781
|
+
const sp = dataset.displayMode === 'headerBoxes' ? 0 : helpers.valueOrDefault(dataset.spacing, 0);
|
|
782
|
+
const captions = dataset.captions || {};
|
|
783
|
+
const font = helpers.toFont(captions.font);
|
|
784
|
+
const padding = helpers.valueOrDefault(captions.padding, 3);
|
|
785
|
+
function getSubRect(sq, rect) {
|
|
786
|
+
const bw = dataset.displayMode === 'headerBoxes' ? {
|
|
787
|
+
b: 0,
|
|
788
|
+
l: 0,
|
|
789
|
+
r: 0,
|
|
790
|
+
t: 0
|
|
791
|
+
} : parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
945
792
|
const subRect = {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
793
|
+
...rect,
|
|
794
|
+
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
795
|
+
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
796
|
+
x: sq.x + sp + bw.l,
|
|
797
|
+
y: sq.y + sp + bw.t
|
|
951
798
|
};
|
|
952
|
-
if (shouldDrawCaption(subRect, captions)) {
|
|
953
|
-
|
|
954
|
-
|
|
799
|
+
if (shouldDrawCaption(dataset.displayMode, subRect, captions)) {
|
|
800
|
+
const captionHeight = getCaptionHeight(dataset.displayMode, subRect, font, padding);
|
|
801
|
+
subRect.y += captionHeight;
|
|
802
|
+
subRect.h -= captionHeight;
|
|
955
803
|
}
|
|
956
|
-
|
|
957
|
-
ret.push(...recur(gEl.children, gidx + 1, subRect, sq.g, sq.s));
|
|
958
|
-
});
|
|
959
|
-
});
|
|
804
|
+
return subRect;
|
|
960
805
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
806
|
+
function recur(treeElements, gidx, rect, parent, gs) {
|
|
807
|
+
const g = getGroupKey(groups[gidx]);
|
|
808
|
+
const gdata = group(treeElements, g, keys, treeLeafKey, undefined, parent, groups.filter((_item, index)=>index <= gidx), groups, gidx);
|
|
809
|
+
const gsq = squarify(gdata, rect, keys, g, gidx, gs);
|
|
810
|
+
const ret = gsq.slice();
|
|
811
|
+
if (gidx < glen - 1) {
|
|
812
|
+
gsq.forEach((sq)=>{
|
|
813
|
+
const subRect = getSubRect(sq, rect);
|
|
814
|
+
const children = [];
|
|
815
|
+
const nextGroupIndex = sq._data.groupIndex + 1;
|
|
816
|
+
if (nextGroupIndex < glen) {
|
|
817
|
+
children.push(...recur(sq._data.children, nextGroupIndex, subRect, sq.g, sq.s));
|
|
818
|
+
}
|
|
819
|
+
ret.push(...children);
|
|
820
|
+
sq.isLeaf = !children.length;
|
|
821
|
+
});
|
|
822
|
+
} else {
|
|
823
|
+
gsq.forEach((sq)=>{
|
|
824
|
+
sq.isLeaf = true;
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
return ret;
|
|
828
|
+
}
|
|
829
|
+
const result = glen ? recur(tree, 0, mainRect) : squarify(tree, mainRect, keys);
|
|
830
|
+
return result.map((d)=>{
|
|
831
|
+
if (dataset.displayMode !== 'headerBoxes' || d.isLeaf) {
|
|
832
|
+
return d;
|
|
833
|
+
}
|
|
834
|
+
if (!shouldDrawCaption(dataset.displayMode, d, captions)) {
|
|
835
|
+
return undefined;
|
|
836
|
+
}
|
|
837
|
+
const captionHeight = getCaptionHeight(dataset.displayMode, d, font, padding);
|
|
838
|
+
return {
|
|
839
|
+
...d,
|
|
840
|
+
h: captionHeight
|
|
841
|
+
};
|
|
842
|
+
}).filter(Boolean);
|
|
967
843
|
}
|
|
968
|
-
|
|
969
844
|
class TreemapController extends chart_js.DatasetController {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
this._groups = undefined;
|
|
974
|
-
this._keys = undefined;
|
|
975
|
-
this._rect = undefined;
|
|
976
|
-
this._rectChanged = true;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
initialize() {
|
|
980
|
-
this.enableOptionSharing = true;
|
|
981
|
-
super.initialize();
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
getMinMax(scale) {
|
|
985
|
-
return {
|
|
986
|
-
min: 0,
|
|
987
|
-
max: scale.axis === 'x' ? scale.right - scale.left : scale.bottom - scale.top
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
configure() {
|
|
992
|
-
super.configure();
|
|
993
|
-
const {xScale, yScale} = this.getMeta();
|
|
994
|
-
if (!xScale || !yScale) {
|
|
995
|
-
// configure is called once before `linkScales`, and at that call we don't have any scales linked yet
|
|
996
|
-
return;
|
|
845
|
+
initialize() {
|
|
846
|
+
this.enableOptionSharing = true;
|
|
847
|
+
super.initialize();
|
|
997
848
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
if (rectNotEqual(this._rect, rect)) {
|
|
1004
|
-
this._rect = rect;
|
|
1005
|
-
this._rectChanged = true;
|
|
849
|
+
getMinMax(scale) {
|
|
850
|
+
return {
|
|
851
|
+
max: scale.axis === 'x' ? scale.right - scale.left : scale.bottom - scale.top,
|
|
852
|
+
min: 0
|
|
853
|
+
};
|
|
1006
854
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
855
|
+
configure() {
|
|
856
|
+
super.configure();
|
|
857
|
+
const { xScale, yScale } = this.getMeta();
|
|
858
|
+
if (!xScale || !yScale) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const w = xScale.right - xScale.left;
|
|
862
|
+
const h = yScale.bottom - yScale.top;
|
|
863
|
+
const rect = {
|
|
864
|
+
h,
|
|
865
|
+
rtl: !!this.options.rtl,
|
|
866
|
+
unsorted: !!this.options.unsorted,
|
|
867
|
+
w,
|
|
868
|
+
x: 0,
|
|
869
|
+
y: 0
|
|
870
|
+
};
|
|
871
|
+
if (rectNotEqual(this._rect, rect)) {
|
|
872
|
+
this._rect = rect;
|
|
873
|
+
this._rectChanged = true;
|
|
874
|
+
}
|
|
875
|
+
if (this._rectChanged) {
|
|
876
|
+
xScale.max = w;
|
|
877
|
+
xScale.configure();
|
|
878
|
+
yScale.max = h;
|
|
879
|
+
yScale.configure();
|
|
880
|
+
}
|
|
1013
881
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
882
|
+
update(mode) {
|
|
883
|
+
const dataset = this.getDataset();
|
|
884
|
+
const { data } = this.getMeta();
|
|
885
|
+
const groups = dataset.groups || [];
|
|
886
|
+
const keys = [
|
|
887
|
+
dataset.key || ''
|
|
888
|
+
].concat(dataset.sumKeys || []);
|
|
889
|
+
dataset.tree = dataset.tree || dataset.data || [];
|
|
890
|
+
const tree = dataset.tree;
|
|
891
|
+
if (mode === 'reset') {
|
|
892
|
+
this.configure();
|
|
893
|
+
}
|
|
894
|
+
if (this._rectChanged || arrayNotEqual(this._keys || [], keys) || arrayNotEqual(this._groups || [], groups) || this._prevTree !== tree) {
|
|
895
|
+
this._groups = groups.slice();
|
|
896
|
+
this._keys = keys.slice();
|
|
897
|
+
this._prevTree = tree;
|
|
898
|
+
this._rectChanged = false;
|
|
899
|
+
dataset.data = buildData(tree, dataset, this._keys, this._rect);
|
|
900
|
+
this._dataCheck();
|
|
901
|
+
this._resyncElements();
|
|
902
|
+
}
|
|
903
|
+
this.updateElements(data, 0, data.length, mode);
|
|
1026
904
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
905
|
+
updateElements(rects, start, count, mode) {
|
|
906
|
+
const reset = mode === 'reset';
|
|
907
|
+
const dataset = this.getDataset();
|
|
908
|
+
const firstOpts = this.resolveDataElementOptions(start, mode);
|
|
909
|
+
this._rect.options = firstOpts;
|
|
910
|
+
const sharedOptions = this.getSharedOptions(firstOpts);
|
|
911
|
+
const includeOptions = this.includeOptions(mode, sharedOptions || {});
|
|
912
|
+
const { xScale, yScale } = this.getMeta();
|
|
913
|
+
for(let i = start; i < start + count; i++){
|
|
914
|
+
const options = sharedOptions || this.resolveDataElementOptions(i, mode);
|
|
915
|
+
const properties = scaleRect(dataset.data[i], xScale, yScale, options.spacing);
|
|
916
|
+
if (reset) {
|
|
917
|
+
properties.width = 0;
|
|
918
|
+
properties.height = 0;
|
|
919
|
+
}
|
|
920
|
+
if (includeOptions) {
|
|
921
|
+
properties.options = options;
|
|
922
|
+
}
|
|
923
|
+
this.updateElement(rects[i], i, properties, mode);
|
|
924
|
+
}
|
|
925
|
+
this.updateSharedOptions(sharedOptions || {}, mode, firstOpts);
|
|
1039
926
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
if (includeOptions) {
|
|
1061
|
-
properties.options = options;
|
|
1062
|
-
}
|
|
1063
|
-
this.updateElement(rects[i], i, properties, mode);
|
|
927
|
+
draw() {
|
|
928
|
+
const { ctx, chartArea } = this.chart;
|
|
929
|
+
const metadata = this.getMeta().data || [];
|
|
930
|
+
const dataset = this.getDataset();
|
|
931
|
+
const data = dataset.data;
|
|
932
|
+
helpers.clipArea(ctx, chartArea);
|
|
933
|
+
for(let i = 0, ilen = metadata.length; i < ilen; ++i){
|
|
934
|
+
const rect = metadata[i];
|
|
935
|
+
if (!rect.hidden) {
|
|
936
|
+
rect.draw(ctx, data[i]);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
helpers.unclipArea(ctx);
|
|
940
|
+
}
|
|
941
|
+
constructor(chart, datasetIndex){
|
|
942
|
+
super(chart, datasetIndex);
|
|
943
|
+
this._groups = undefined;
|
|
944
|
+
this._keys = undefined;
|
|
945
|
+
this._rect = undefined;
|
|
946
|
+
this._rectChanged = true;
|
|
1064
947
|
}
|
|
1065
|
-
|
|
1066
|
-
this.updateSharedOptions(sharedOptions, mode, firstOpts);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
draw() {
|
|
1070
|
-
const {ctx, chartArea} = this.chart;
|
|
1071
|
-
const metadata = this.getMeta().data || [];
|
|
1072
|
-
const dataset = this.getDataset();
|
|
1073
|
-
const levels = (dataset.groups || []).length - 1;
|
|
1074
|
-
const data = dataset.data;
|
|
1075
|
-
|
|
1076
|
-
helpers.clipArea(ctx, chartArea);
|
|
1077
|
-
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
1078
|
-
const rect = metadata[i];
|
|
1079
|
-
if (!rect.hidden) {
|
|
1080
|
-
rect.draw(ctx, data[i], levels);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
helpers.unclipArea(ctx);
|
|
1084
|
-
}
|
|
1085
948
|
}
|
|
1086
|
-
|
|
1087
949
|
TreemapController.id = 'treemap';
|
|
1088
|
-
|
|
1089
950
|
TreemapController.version = version;
|
|
1090
|
-
|
|
1091
951
|
TreemapController.defaults = {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
952
|
+
animations: {
|
|
953
|
+
numbers: {
|
|
954
|
+
properties: [
|
|
955
|
+
'x',
|
|
956
|
+
'y',
|
|
957
|
+
'width',
|
|
958
|
+
'height'
|
|
959
|
+
],
|
|
960
|
+
type: 'number'
|
|
961
|
+
}
|
|
1098
962
|
},
|
|
1099
|
-
|
|
1100
|
-
|
|
963
|
+
dataElementType: 'treemap'
|
|
1101
964
|
};
|
|
1102
|
-
|
|
1103
965
|
TreemapController.descriptors = {
|
|
1104
|
-
|
|
1105
|
-
|
|
966
|
+
_indexable: false,
|
|
967
|
+
_scriptable: true,
|
|
968
|
+
captions: {
|
|
969
|
+
_fallback: true,
|
|
970
|
+
_scriptable: (name)=>name !== 'formatter'
|
|
971
|
+
},
|
|
972
|
+
labels: {
|
|
973
|
+
_fallback: true,
|
|
974
|
+
_scriptable: (name)=>name !== 'formatter'
|
|
975
|
+
}
|
|
1106
976
|
};
|
|
1107
|
-
|
|
1108
977
|
TreemapController.overrides = {
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
hover: {},
|
|
1116
|
-
|
|
1117
|
-
plugins: {
|
|
1118
|
-
tooltip: {
|
|
1119
|
-
position: 'treemap',
|
|
1120
|
-
intersect: true,
|
|
1121
|
-
callbacks: {
|
|
1122
|
-
title(items) {
|
|
1123
|
-
if (items.length) {
|
|
1124
|
-
const item = items[0];
|
|
1125
|
-
return item.dataset.key || '';
|
|
1126
|
-
}
|
|
1127
|
-
return '';
|
|
1128
|
-
},
|
|
1129
|
-
label(item) {
|
|
1130
|
-
const dataset = item.dataset;
|
|
1131
|
-
const dataItem = dataset.data[item.dataIndex];
|
|
1132
|
-
const label = dataItem.g || dataItem._data.label || dataset.label;
|
|
1133
|
-
return (label ? label + ': ' : '') + dataItem.v;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
978
|
+
hover: {},
|
|
979
|
+
interaction: {
|
|
980
|
+
includeInvisible: true,
|
|
981
|
+
intersect: true,
|
|
982
|
+
mode: 'point'
|
|
1136
983
|
},
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
984
|
+
plugins: {
|
|
985
|
+
tooltip: {
|
|
986
|
+
callbacks: {
|
|
987
|
+
label (item) {
|
|
988
|
+
const dataset = item.dataset;
|
|
989
|
+
const dataItem = dataset.data[item.dataIndex];
|
|
990
|
+
const label = dataItem.g || dataItem._data.label || dataset.label;
|
|
991
|
+
return (label ? `${label}: ` : '') + dataItem.v;
|
|
992
|
+
},
|
|
993
|
+
title (items) {
|
|
994
|
+
if (items.length) {
|
|
995
|
+
const item = items[0];
|
|
996
|
+
return item.dataset.key || '';
|
|
997
|
+
}
|
|
998
|
+
return '';
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
intersect: true,
|
|
1002
|
+
position: 'treemap'
|
|
1003
|
+
}
|
|
1144
1004
|
},
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1005
|
+
scales: {
|
|
1006
|
+
x: {
|
|
1007
|
+
alignToPixels: true,
|
|
1008
|
+
bounds: 'data',
|
|
1009
|
+
display: false,
|
|
1010
|
+
type: 'linear'
|
|
1011
|
+
},
|
|
1012
|
+
y: {
|
|
1013
|
+
alignToPixels: true,
|
|
1014
|
+
bounds: 'data',
|
|
1015
|
+
display: false,
|
|
1016
|
+
reverse: true,
|
|
1017
|
+
type: 'linear'
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1153
1020
|
};
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1021
|
+
TreemapController.beforeRegister = ()=>{
|
|
1022
|
+
requireVersion('chart.js', '3.8', chart_js.Chart.version);
|
|
1023
|
+
};
|
|
1024
|
+
TreemapController.afterRegister = ()=>{
|
|
1025
|
+
const tooltipPlugin = chart_js.registry.plugins.get('tooltip');
|
|
1026
|
+
if (tooltipPlugin) {
|
|
1027
|
+
tooltipPlugin.positioners.treemap = (active)=>{
|
|
1028
|
+
if (!active.length) {
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
const item = active.at(-1);
|
|
1032
|
+
const el = item.element;
|
|
1033
|
+
return el.tooltipPosition();
|
|
1034
|
+
};
|
|
1035
|
+
} else {
|
|
1036
|
+
console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
TreemapController.afterUnregister = ()=>{
|
|
1040
|
+
const tooltipPlugin = chart_js.registry.plugins.get('tooltip');
|
|
1041
|
+
if (tooltipPlugin) {
|
|
1042
|
+
delete tooltipPlugin.positioners.treemap;
|
|
1043
|
+
}
|
|
1157
1044
|
};
|
|
1158
1045
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1046
|
+
function getBounds(rect, useFinalPosition) {
|
|
1047
|
+
const { x, y, width, height } = rect.getProps([
|
|
1048
|
+
'x',
|
|
1049
|
+
'y',
|
|
1050
|
+
'width',
|
|
1051
|
+
'height'
|
|
1052
|
+
], useFinalPosition);
|
|
1053
|
+
return {
|
|
1054
|
+
bottom: y + height,
|
|
1055
|
+
left: x,
|
|
1056
|
+
right: x + width,
|
|
1057
|
+
top: y
|
|
1171
1058
|
};
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1059
|
+
}
|
|
1060
|
+
function boundingRects(rect) {
|
|
1061
|
+
const bounds = getBounds(rect);
|
|
1062
|
+
const width = bounds.right - bounds.left;
|
|
1063
|
+
const height = bounds.bottom - bounds.top;
|
|
1064
|
+
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
1065
|
+
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
1066
|
+
const outer = {
|
|
1067
|
+
active: rect.active,
|
|
1068
|
+
h: height,
|
|
1069
|
+
radius,
|
|
1070
|
+
w: width,
|
|
1071
|
+
x: bounds.left,
|
|
1072
|
+
y: bounds.top
|
|
1073
|
+
};
|
|
1074
|
+
return {
|
|
1075
|
+
inner: {
|
|
1076
|
+
active: rect.active,
|
|
1077
|
+
h: outer.h - border.t - border.b,
|
|
1078
|
+
radius: {
|
|
1079
|
+
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
1080
|
+
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
1081
|
+
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
1082
|
+
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r))
|
|
1083
|
+
},
|
|
1084
|
+
w: outer.w - border.l - border.r,
|
|
1085
|
+
x: outer.x + border.l,
|
|
1086
|
+
y: outer.y + border.t
|
|
1087
|
+
},
|
|
1088
|
+
outer
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function inRange(rect, x, y, useFinalPosition) {
|
|
1092
|
+
const skipX = x === null;
|
|
1093
|
+
const skipY = y === null;
|
|
1094
|
+
const bounds = !rect || skipX && skipY ? false : getBounds(rect, useFinalPosition);
|
|
1095
|
+
return bounds && (skipX || x >= bounds.left && x <= bounds.right) && (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
1096
|
+
}
|
|
1097
|
+
function hasRadius(radius) {
|
|
1098
|
+
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
1099
|
+
}
|
|
1100
|
+
function addNormalRectPath(ctx, rect) {
|
|
1101
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
1102
|
+
}
|
|
1176
1103
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1104
|
+
class TreemapElement extends chart_js.Element {
|
|
1105
|
+
draw(ctx, data) {
|
|
1106
|
+
if (!data) {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const options = this.options;
|
|
1110
|
+
const { inner, outer } = boundingRects(this);
|
|
1111
|
+
const addRectPath = hasRadius(outer.radius) ? helpers.addRoundedRectPath : addNormalRectPath;
|
|
1112
|
+
ctx.save();
|
|
1113
|
+
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
1114
|
+
ctx.beginPath();
|
|
1115
|
+
addRectPath(ctx, outer);
|
|
1116
|
+
ctx.clip();
|
|
1117
|
+
addRectPath(ctx, inner);
|
|
1118
|
+
ctx.fillStyle = options.borderColor;
|
|
1119
|
+
ctx.fill('evenodd');
|
|
1120
|
+
}
|
|
1121
|
+
ctx.beginPath();
|
|
1122
|
+
addRectPath(ctx, inner);
|
|
1123
|
+
ctx.fillStyle = options.backgroundColor;
|
|
1124
|
+
ctx.fill();
|
|
1125
|
+
drawDivider(ctx, inner, options, data);
|
|
1126
|
+
drawText(ctx, inner, options, data, this);
|
|
1127
|
+
ctx.restore();
|
|
1128
|
+
}
|
|
1129
|
+
inRange(mouseX, mouseY, useFinalPosition) {
|
|
1130
|
+
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
1131
|
+
}
|
|
1132
|
+
inXRange(mouseX, useFinalPosition) {
|
|
1133
|
+
return inRange(this, mouseX, null, useFinalPosition);
|
|
1134
|
+
}
|
|
1135
|
+
inYRange(mouseY, useFinalPosition) {
|
|
1136
|
+
return inRange(this, null, mouseY, useFinalPosition);
|
|
1137
|
+
}
|
|
1138
|
+
getCenterPoint(useFinalPosition) {
|
|
1139
|
+
const { x, y, width, height } = this.getProps([
|
|
1140
|
+
'x',
|
|
1141
|
+
'y',
|
|
1142
|
+
'width',
|
|
1143
|
+
'height'
|
|
1144
|
+
], useFinalPosition);
|
|
1145
|
+
return {
|
|
1146
|
+
x: x + width / 2,
|
|
1147
|
+
y: y + height / 2
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
tooltipPosition() {
|
|
1151
|
+
return this.getCenterPoint();
|
|
1152
|
+
}
|
|
1153
|
+
constructor(cfg){
|
|
1154
|
+
super();
|
|
1155
|
+
this.width = undefined;
|
|
1156
|
+
this.height = undefined;
|
|
1157
|
+
if (cfg) {
|
|
1158
|
+
Object.assign(this, cfg);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
TreemapElement.id = 'treemap';
|
|
1163
|
+
TreemapElement.defaults = {
|
|
1164
|
+
borderRadius: 0,
|
|
1165
|
+
borderWidth: 0,
|
|
1166
|
+
captions: {
|
|
1167
|
+
align: undefined,
|
|
1168
|
+
color: 'black',
|
|
1169
|
+
display: true,
|
|
1170
|
+
font: {},
|
|
1171
|
+
formatter: (ctx)=>{
|
|
1172
|
+
const label = ctx.raw._data?.label;
|
|
1173
|
+
return ctx.raw.g || (typeof label === 'string' ? label : '');
|
|
1174
|
+
},
|
|
1175
|
+
padding: 3
|
|
1176
|
+
},
|
|
1177
|
+
displayMode: 'containerBoxes',
|
|
1178
|
+
dividers: {
|
|
1179
|
+
display: false,
|
|
1180
|
+
lineCapStyle: 'butt',
|
|
1181
|
+
lineColor: 'black',
|
|
1182
|
+
lineDash: [],
|
|
1183
|
+
lineDashOffset: 0,
|
|
1184
|
+
lineWidth: 1
|
|
1185
|
+
},
|
|
1186
|
+
label: undefined,
|
|
1187
|
+
labels: {
|
|
1188
|
+
align: 'center',
|
|
1189
|
+
color: 'black',
|
|
1190
|
+
display: false,
|
|
1191
|
+
font: {},
|
|
1192
|
+
formatter (ctx) {
|
|
1193
|
+
const label = ctx.raw._data?.label;
|
|
1194
|
+
if (ctx.raw.g) {
|
|
1195
|
+
return [
|
|
1196
|
+
ctx.raw.g,
|
|
1197
|
+
`${ctx.raw.v}`
|
|
1198
|
+
];
|
|
1199
|
+
}
|
|
1200
|
+
return typeof label === 'string' ? [
|
|
1201
|
+
label,
|
|
1202
|
+
`${ctx.raw.v}`
|
|
1203
|
+
] : `${ctx.raw.v}`;
|
|
1204
|
+
},
|
|
1205
|
+
overflow: 'cut',
|
|
1206
|
+
padding: 3,
|
|
1207
|
+
position: 'middle'
|
|
1208
|
+
},
|
|
1209
|
+
rtl: false,
|
|
1210
|
+
spacing: 0.5,
|
|
1211
|
+
unsorted: false
|
|
1212
|
+
};
|
|
1213
|
+
TreemapElement.descriptors = {
|
|
1214
|
+
_indexable: false,
|
|
1215
|
+
_scriptable: true,
|
|
1216
|
+
captions: {
|
|
1217
|
+
_fallback: true,
|
|
1218
|
+
_scriptable: (name)=>name !== 'formatter'
|
|
1219
|
+
},
|
|
1220
|
+
labels: {
|
|
1221
|
+
_fallback: true,
|
|
1222
|
+
_scriptable: (name)=>name !== 'formatter'
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
TreemapElement.defaultRoutes = {
|
|
1226
|
+
backgroundColor: 'backgroundColor',
|
|
1227
|
+
borderColor: 'borderColor'
|
|
1182
1228
|
};
|
|
1183
1229
|
|
|
1184
1230
|
chart_js.Chart.register(TreemapController, TreemapElement);
|
|
1185
1231
|
|
|
1232
|
+
exports.TreemapController = TreemapController;
|
|
1233
|
+
exports.TreemapElement = TreemapElement;
|
|
1186
1234
|
exports.flatten = flatten;
|
|
1187
1235
|
exports.getGroupKey = getGroupKey;
|
|
1188
1236
|
exports.group = group;
|