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