chartjs-chart-treemap 2.0.2 → 2.1.1
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/README.md +1 -1
- package/dist/chartjs-chart-treemap.esm.js +641 -419
- package/dist/chartjs-chart-treemap.js +644 -419
- package/dist/chartjs-chart-treemap.min.js +3 -3
- package/package.json +21 -16
- package/types/index.esm.d.ts +19 -10
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-treemap v2.
|
|
2
|
+
* chartjs-chart-treemap v2.1.1
|
|
3
3
|
* https://chartjs-chart-treemap.pages.dev/
|
|
4
|
-
* (c)
|
|
4
|
+
* (c) 2022 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
6
6
|
*/
|
|
7
7
|
(function (global, factory) {
|
|
@@ -10,6 +10,65 @@ 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
|
+
const getValue = (item) => helpers.isObject(item) ? item.v : item;
|
|
14
|
+
|
|
15
|
+
const maxValue = (data) => data.reduce(function(m, v) {
|
|
16
|
+
return (m > getValue(v) ? m : getValue(v));
|
|
17
|
+
}, 0);
|
|
18
|
+
|
|
19
|
+
const minValue = (data, mx) => data.reduce(function(m, v) {
|
|
20
|
+
return (m < getValue(v) ? m : getValue(v));
|
|
21
|
+
}, mx);
|
|
22
|
+
|
|
23
|
+
const getGroupKey = (lvl) => '' + lvl;
|
|
24
|
+
|
|
25
|
+
function scanTreeObject(key, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
|
|
26
|
+
const objIndex = lvl - 1;
|
|
27
|
+
if (key in obj && lvl > 0) {
|
|
28
|
+
const record = tree.reduce(function(reduced, item, i) {
|
|
29
|
+
if (i !== objIndex) {
|
|
30
|
+
reduced[getGroupKey(i)] = item;
|
|
31
|
+
}
|
|
32
|
+
return reduced;
|
|
33
|
+
}, {});
|
|
34
|
+
record[treeLeafKey] = tree[objIndex];
|
|
35
|
+
record[key] = obj[key];
|
|
36
|
+
result.push(record);
|
|
37
|
+
} else {
|
|
38
|
+
for (const childKey of Object.keys(obj)) {
|
|
39
|
+
const child = obj[childKey];
|
|
40
|
+
if (helpers.isObject(child)) {
|
|
41
|
+
tree.push(childKey);
|
|
42
|
+
scanTreeObject(key, treeLeafKey, child, tree, lvl + 1, result);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
tree.splice(objIndex, 1);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeTreeToArray(key, treeLeafKey, obj) {
|
|
51
|
+
const data = scanTreeObject(key, treeLeafKey, obj);
|
|
52
|
+
if (!data.length) {
|
|
53
|
+
return data;
|
|
54
|
+
}
|
|
55
|
+
const max = data.reduce(function(maxVal, element) {
|
|
56
|
+
// minus 2 because _leaf and value properties are added
|
|
57
|
+
// on top to groups ones
|
|
58
|
+
const keys = Object.keys(element).length - 2;
|
|
59
|
+
return maxVal > keys ? maxVal : keys;
|
|
60
|
+
});
|
|
61
|
+
data.forEach(function(element) {
|
|
62
|
+
for (let i = 0; i < max; i++) {
|
|
63
|
+
const groupKey = getGroupKey(i);
|
|
64
|
+
if (!element[groupKey]) {
|
|
65
|
+
element[groupKey] = '';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
|
|
13
72
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
|
|
14
73
|
function flatten(input) {
|
|
15
74
|
const stack = [...input];
|
|
@@ -28,36 +87,59 @@ function flatten(input) {
|
|
|
28
87
|
return res.reverse();
|
|
29
88
|
}
|
|
30
89
|
|
|
90
|
+
function getPath(groups, value, defaultValue) {
|
|
91
|
+
if (!groups.length) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const path = [];
|
|
95
|
+
for (const grp of groups) {
|
|
96
|
+
const item = value[grp];
|
|
97
|
+
if (item === '') {
|
|
98
|
+
path.push(defaultValue);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
path.push(item);
|
|
102
|
+
}
|
|
103
|
+
return path.length ? path.join('.') : defaultValue;
|
|
104
|
+
}
|
|
105
|
+
|
|
31
106
|
/**
|
|
32
107
|
* @param {[]} values
|
|
33
108
|
* @param {string} grp
|
|
34
109
|
* @param {string} key
|
|
110
|
+
* @param {string} treeeLeafKey
|
|
35
111
|
* @param {string} [mainGrp]
|
|
36
112
|
* @param {*} [mainValue]
|
|
113
|
+
* @param {[]} groups
|
|
37
114
|
*/
|
|
38
|
-
function group(values, grp, key, mainGrp, mainValue) {
|
|
115
|
+
function group(values, grp, key, treeLeafKey, mainGrp, mainValue, groups = []) {
|
|
39
116
|
const tmp = Object.create(null);
|
|
40
117
|
const data = Object.create(null);
|
|
41
118
|
const ret = [];
|
|
42
|
-
let g, i, n
|
|
119
|
+
let g, i, n;
|
|
43
120
|
for (i = 0, n = values.length; i < n; ++i) {
|
|
44
|
-
v = values[i];
|
|
121
|
+
const v = values[i];
|
|
45
122
|
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
46
123
|
continue;
|
|
47
124
|
}
|
|
48
|
-
g = v[grp] || '';
|
|
125
|
+
g = v[grp] || v[treeLeafKey] || '';
|
|
49
126
|
if (!(g in tmp)) {
|
|
50
|
-
tmp[g] = 0;
|
|
127
|
+
tmp[g] = {value: 0};
|
|
51
128
|
data[g] = [];
|
|
52
129
|
}
|
|
53
|
-
tmp[g] += +v[key];
|
|
130
|
+
tmp[g].value += +v[key];
|
|
131
|
+
tmp[g].label = v[grp] || '';
|
|
132
|
+
tmp[g].path = getPath(groups, v, g);
|
|
54
133
|
data[g].push(v);
|
|
55
134
|
}
|
|
56
135
|
|
|
57
136
|
Object.keys(tmp).forEach((k) => {
|
|
58
|
-
v = {children: data[k]};
|
|
59
|
-
v[key] = +tmp[k];
|
|
60
|
-
v[grp] = k;
|
|
137
|
+
const v = {children: data[k]};
|
|
138
|
+
v[key] = +tmp[k].value;
|
|
139
|
+
v[grp] = tmp[k].label;
|
|
140
|
+
v.label = k;
|
|
141
|
+
v.path = tmp[k].path;
|
|
142
|
+
|
|
61
143
|
if (mainGrp) {
|
|
62
144
|
v[mainGrp] = mainValue;
|
|
63
145
|
}
|
|
@@ -67,11 +149,6 @@ function group(values, grp, key, mainGrp, mainValue) {
|
|
|
67
149
|
return ret;
|
|
68
150
|
}
|
|
69
151
|
|
|
70
|
-
function isObject(obj) {
|
|
71
|
-
const type = typeof obj;
|
|
72
|
-
return type === 'function' || type === 'object' && !!obj;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
152
|
function index(values, key) {
|
|
76
153
|
let n = values.length;
|
|
77
154
|
let i;
|
|
@@ -80,7 +157,7 @@ function index(values, key) {
|
|
|
80
157
|
return key;
|
|
81
158
|
}
|
|
82
159
|
|
|
83
|
-
const obj = isObject(values[0]);
|
|
160
|
+
const obj = helpers.isObject(values[0]);
|
|
84
161
|
key = obj ? key : 'v';
|
|
85
162
|
|
|
86
163
|
for (i = 0, n = values.length; i < n; ++i) {
|
|
@@ -118,35 +195,427 @@ function requireVersion(min, ver) {
|
|
|
118
195
|
}
|
|
119
196
|
}
|
|
120
197
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
198
|
+
const rasterize = (v, dpr) => Math.round(v * dpr) / dpr;
|
|
199
|
+
|
|
200
|
+
function rasterizeRect(rect, dpr) {
|
|
201
|
+
return {
|
|
202
|
+
...rect,
|
|
203
|
+
x: rasterize(rect.x, dpr),
|
|
204
|
+
y: rasterize(rect.y, dpr),
|
|
205
|
+
w: rasterize(rect.w, dpr),
|
|
206
|
+
h: rasterize(rect.h, dpr),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const widthCache = new Map();
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Helper function to get the bounds of the rect
|
|
214
|
+
* @param {TreemapElement} rect the rect
|
|
215
|
+
* @param {boolean} [useFinalPosition]
|
|
216
|
+
* @return {object} bounds of the rect
|
|
217
|
+
* @private
|
|
218
|
+
*/
|
|
219
|
+
function getBounds(rect, useFinalPosition) {
|
|
220
|
+
const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
221
|
+
return {left: x, top: y, right: x + width, bottom: y + height};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function limit(value, min, max) {
|
|
225
|
+
return Math.max(Math.min(value, max), min);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function parseBorderWidth(value, maxW, maxH) {
|
|
229
|
+
const o = helpers.toTRBL(value);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
t: limit(o.top, 0, maxH),
|
|
233
|
+
r: limit(o.right, 0, maxW),
|
|
234
|
+
b: limit(o.bottom, 0, maxH),
|
|
235
|
+
l: limit(o.left, 0, maxW)
|
|
236
|
+
};
|
|
124
237
|
}
|
|
125
238
|
|
|
126
|
-
function
|
|
239
|
+
function parseBorderRadius(value, maxW, maxH) {
|
|
240
|
+
const o = helpers.toTRBLCorners(value);
|
|
241
|
+
const maxR = Math.min(maxW, maxH);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
topLeft: limit(o.topLeft, 0, maxR),
|
|
245
|
+
topRight: limit(o.topRight, 0, maxR),
|
|
246
|
+
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
247
|
+
bottomRight: limit(o.bottomRight, 0, maxR)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function boundingRects(rect, dpr) {
|
|
252
|
+
const bounds = getBounds(rect);
|
|
253
|
+
const width = bounds.right - bounds.left;
|
|
254
|
+
const height = bounds.bottom - bounds.top;
|
|
255
|
+
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
256
|
+
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
257
|
+
const outer = rasterizeRect({
|
|
258
|
+
x: bounds.left,
|
|
259
|
+
y: bounds.top,
|
|
260
|
+
w: width,
|
|
261
|
+
h: height,
|
|
262
|
+
radius
|
|
263
|
+
}, dpr);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
outer,
|
|
267
|
+
inner: {
|
|
268
|
+
x: outer.x + border.l,
|
|
269
|
+
y: outer.y + border.t,
|
|
270
|
+
w: outer.w - border.l - border.r,
|
|
271
|
+
h: outer.h - border.t - border.b,
|
|
272
|
+
radius: {
|
|
273
|
+
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
274
|
+
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
|
|
275
|
+
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
276
|
+
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function inRange(rect, x, y, useFinalPosition) {
|
|
283
|
+
const skipX = x === null;
|
|
284
|
+
const skipY = y === null;
|
|
285
|
+
const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
|
|
286
|
+
|
|
287
|
+
return bounds
|
|
288
|
+
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
289
|
+
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function hasRadius(radius) {
|
|
293
|
+
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Add a path of a rectangle to the current sub-path
|
|
298
|
+
* @param {CanvasRenderingContext2D} ctx Context
|
|
299
|
+
* @param {*} rect Bounding rect
|
|
300
|
+
*/
|
|
301
|
+
function addNormalRectPath(ctx, rect) {
|
|
302
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function shouldDrawCaption(rect, options) {
|
|
306
|
+
if (!options || options.display === false) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
const {w, h} = rect;
|
|
310
|
+
const font = helpers.toFont(options.font);
|
|
311
|
+
const min = font.lineHeight;
|
|
312
|
+
const padding = limit(helpers.valueOrDefault(options.padding, 3) * 2, 0, Math.min(w, h));
|
|
313
|
+
return (w - padding) > min && (h - padding) > min;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function drawText(ctx, rect, options, item, levels) {
|
|
317
|
+
const {captions, labels} = options;
|
|
318
|
+
ctx.save();
|
|
319
|
+
ctx.beginPath();
|
|
320
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
321
|
+
ctx.clip();
|
|
322
|
+
const isLeaf = (!('l' in item) || item.l === levels);
|
|
323
|
+
if (isLeaf && labels.display) {
|
|
324
|
+
drawLabel(ctx, rect, options);
|
|
325
|
+
} else if (!isLeaf && shouldDrawCaption(rect, captions)) {
|
|
326
|
+
drawCaption(ctx, rect, options, item);
|
|
327
|
+
}
|
|
328
|
+
ctx.restore();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function drawCaption(ctx, rect, options, item) {
|
|
332
|
+
const {captions, spacing, rtl} = options;
|
|
333
|
+
const {color, hoverColor, font, hoverFont, padding, align, formatter} = captions;
|
|
334
|
+
const oColor = (rect.active ? hoverColor : color) || color;
|
|
335
|
+
const oAlign = align || (rtl ? 'right' : 'left');
|
|
336
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
337
|
+
const oFont = helpers.toFont(optFont);
|
|
338
|
+
const lh = oFont.lineHeight / 2;
|
|
339
|
+
const x = calculateX(rect, oAlign, padding);
|
|
340
|
+
ctx.fillStyle = oColor;
|
|
341
|
+
ctx.font = oFont.string;
|
|
342
|
+
ctx.textAlign = oAlign;
|
|
343
|
+
ctx.textBaseline = 'middle';
|
|
344
|
+
ctx.fillText(formatter || item.g, x, rect.y + padding + spacing + lh);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function measureLabelSize(ctx, lines, fonts) {
|
|
348
|
+
const fontsKey = fonts.reduce(function(prev, item) {
|
|
349
|
+
prev += item.string;
|
|
350
|
+
return prev;
|
|
351
|
+
}, '');
|
|
352
|
+
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
353
|
+
if (!widthCache.has(mapKey)) {
|
|
354
|
+
ctx.save();
|
|
355
|
+
const count = lines.length;
|
|
356
|
+
let width = 0;
|
|
357
|
+
let height = 0;
|
|
358
|
+
for (let i = 0; i < count; i++) {
|
|
359
|
+
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
360
|
+
ctx.font = font.string;
|
|
361
|
+
const text = lines[i];
|
|
362
|
+
width = Math.max(width, ctx.measureText(text).width);
|
|
363
|
+
height += font.lineHeight;
|
|
364
|
+
}
|
|
365
|
+
ctx.restore();
|
|
366
|
+
widthCache.set(mapKey, {width, height});
|
|
367
|
+
}
|
|
368
|
+
return widthCache.get(mapKey);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function labelToDraw(ctx, rect, options, labelSize) {
|
|
372
|
+
const {overflow, padding} = options;
|
|
373
|
+
const {width, height} = labelSize;
|
|
374
|
+
if (overflow === 'hidden') {
|
|
375
|
+
return !((width + padding * 2) > rect.w || (height + padding * 2) > rect.h);
|
|
376
|
+
}
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function drawLabel(ctx, rect, options) {
|
|
381
|
+
const labels = options.labels;
|
|
382
|
+
const content = labels.formatter;
|
|
383
|
+
if (!content) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const contents = helpers.isArray(content) ? content : [content];
|
|
387
|
+
const {font, hoverFont} = labels;
|
|
388
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
389
|
+
const fonts = helpers.isArray(optFont) ? optFont.map(f => helpers.toFont(f)) : [helpers.toFont(optFont)];
|
|
390
|
+
const labelSize = measureLabelSize(ctx, contents, fonts);
|
|
391
|
+
if (!labelToDraw(ctx, rect, labels, labelSize)) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const {color, hoverColor, align} = labels;
|
|
395
|
+
const optColor = (rect.active ? hoverColor : color) || color;
|
|
396
|
+
const colors = helpers.isArray(optColor) ? optColor : [optColor];
|
|
397
|
+
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
398
|
+
ctx.textAlign = align;
|
|
399
|
+
ctx.textBaseline = 'middle';
|
|
400
|
+
let lhs = 0;
|
|
401
|
+
contents.forEach(function(l, i) {
|
|
402
|
+
const c = colors[Math.min(i, colors.length - 1)];
|
|
403
|
+
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
404
|
+
const lh = f.lineHeight;
|
|
405
|
+
ctx.font = f.string;
|
|
406
|
+
ctx.fillStyle = c;
|
|
407
|
+
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
408
|
+
lhs += lh;
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function drawDivider(ctx, rect, options, item) {
|
|
413
|
+
const dividers = options.dividers;
|
|
414
|
+
if (!dividers.display || !item._data.children.length) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const {x, y, w, h} = rect;
|
|
418
|
+
const {lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth} = dividers;
|
|
419
|
+
ctx.save();
|
|
420
|
+
ctx.strokeStyle = lineColor;
|
|
421
|
+
ctx.lineCap = lineCapStyle;
|
|
422
|
+
ctx.setLineDash(lineDash);
|
|
423
|
+
ctx.lineDashOffset = lineDashOffset;
|
|
424
|
+
ctx.lineWidth = lineWidth;
|
|
425
|
+
ctx.beginPath();
|
|
426
|
+
if (w > h) {
|
|
427
|
+
const w2 = w / 2;
|
|
428
|
+
ctx.moveTo(x + w2, y);
|
|
429
|
+
ctx.lineTo(x + w2, y + h);
|
|
430
|
+
} else {
|
|
431
|
+
const h2 = h / 2;
|
|
432
|
+
ctx.moveTo(x, y + h2);
|
|
433
|
+
ctx.lineTo(x + w, y + h2);
|
|
434
|
+
}
|
|
435
|
+
ctx.stroke();
|
|
436
|
+
ctx.restore();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function calculateXYLabel(rect, options, labelSize) {
|
|
440
|
+
const {align, position, padding} = options;
|
|
441
|
+
let x, y;
|
|
442
|
+
x = calculateX(rect, align, padding);
|
|
443
|
+
if (position === 'top') {
|
|
444
|
+
y = rect.y + padding;
|
|
445
|
+
} else if (position === 'bottom') {
|
|
446
|
+
y = rect.y + rect.h - padding - labelSize.height;
|
|
447
|
+
} else {
|
|
448
|
+
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
449
|
+
}
|
|
450
|
+
return {x, y};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function calculateX(rect, align, padding) {
|
|
454
|
+
if (align === 'left') {
|
|
455
|
+
return rect.x + padding;
|
|
456
|
+
} else if (align === 'right') {
|
|
457
|
+
return rect.x + rect.w - padding;
|
|
458
|
+
}
|
|
459
|
+
return rect.x + rect.w / 2;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
class TreemapElement extends chart_js.Element {
|
|
463
|
+
|
|
464
|
+
constructor(cfg) {
|
|
465
|
+
super();
|
|
466
|
+
|
|
467
|
+
this.options = undefined;
|
|
468
|
+
this.width = undefined;
|
|
469
|
+
this.height = undefined;
|
|
470
|
+
|
|
471
|
+
if (cfg) {
|
|
472
|
+
Object.assign(this, cfg);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
draw(ctx, data, levels = 0, dpr) {
|
|
477
|
+
if (!data) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const options = this.options;
|
|
481
|
+
const {inner, outer} = boundingRects(this, dpr);
|
|
482
|
+
|
|
483
|
+
const addRectPath = hasRadius(outer.radius) ? helpers.addRoundedRectPath : addNormalRectPath;
|
|
484
|
+
|
|
485
|
+
ctx.save();
|
|
486
|
+
|
|
487
|
+
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
488
|
+
ctx.beginPath();
|
|
489
|
+
addRectPath(ctx, outer);
|
|
490
|
+
ctx.clip();
|
|
491
|
+
addRectPath(ctx, inner);
|
|
492
|
+
ctx.fillStyle = options.borderColor;
|
|
493
|
+
ctx.fill('evenodd');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
ctx.beginPath();
|
|
497
|
+
addRectPath(ctx, inner);
|
|
498
|
+
ctx.fillStyle = options.backgroundColor;
|
|
499
|
+
ctx.fill();
|
|
500
|
+
|
|
501
|
+
drawDivider(ctx, inner, options, data);
|
|
502
|
+
drawText(ctx, inner, options, data, levels);
|
|
503
|
+
ctx.restore();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
inRange(mouseX, mouseY, useFinalPosition) {
|
|
507
|
+
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
inXRange(mouseX, useFinalPosition) {
|
|
511
|
+
return inRange(this, mouseX, null, useFinalPosition);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
inYRange(mouseY, useFinalPosition) {
|
|
515
|
+
return inRange(this, null, mouseY, useFinalPosition);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
getCenterPoint(useFinalPosition) {
|
|
519
|
+
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
520
|
+
return {
|
|
521
|
+
x: x + width / 2,
|
|
522
|
+
y: y + height / 2
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
tooltipPosition() {
|
|
527
|
+
return this.getCenterPoint();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* @todo: remove this unused function in v3
|
|
532
|
+
*/
|
|
533
|
+
getRange(axis) {
|
|
534
|
+
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
TreemapElement.id = 'treemap';
|
|
539
|
+
|
|
540
|
+
TreemapElement.defaults = {
|
|
541
|
+
label: undefined,
|
|
542
|
+
borderRadius: 0,
|
|
543
|
+
borderWidth: 0,
|
|
544
|
+
captions: {
|
|
545
|
+
align: undefined,
|
|
546
|
+
color: 'black',
|
|
547
|
+
display: true,
|
|
548
|
+
font: {},
|
|
549
|
+
formatter: (ctx) => ctx.raw.g || ctx.raw._data.label || '',
|
|
550
|
+
padding: 3
|
|
551
|
+
},
|
|
552
|
+
dividers: {
|
|
553
|
+
display: false,
|
|
554
|
+
lineCapStyle: 'butt',
|
|
555
|
+
lineColor: 'black',
|
|
556
|
+
lineDash: [],
|
|
557
|
+
lineDashOffset: 0,
|
|
558
|
+
lineWidth: 1,
|
|
559
|
+
},
|
|
560
|
+
labels: {
|
|
561
|
+
align: 'center',
|
|
562
|
+
color: 'black',
|
|
563
|
+
display: false,
|
|
564
|
+
font: {},
|
|
565
|
+
formatter(ctx) {
|
|
566
|
+
if (ctx.raw.g) {
|
|
567
|
+
return [ctx.raw.g, ctx.raw.v + ''];
|
|
568
|
+
}
|
|
569
|
+
return ctx.raw._data.label ? [ctx.raw._data.label, ctx.raw.v + ''] : ctx.raw.v + '';
|
|
570
|
+
},
|
|
571
|
+
overflow: 'cut',
|
|
572
|
+
position: 'middle',
|
|
573
|
+
padding: 3
|
|
574
|
+
},
|
|
575
|
+
rtl: false,
|
|
576
|
+
spacing: 0.5
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
TreemapElement.descriptors = {
|
|
580
|
+
labels: {
|
|
581
|
+
_fallback: true
|
|
582
|
+
},
|
|
583
|
+
captions: {
|
|
584
|
+
_fallback: true
|
|
585
|
+
},
|
|
586
|
+
_scriptable: true,
|
|
587
|
+
_indexable: false
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
TreemapElement.defaultRoutes = {
|
|
591
|
+
backgroundColor: 'backgroundColor',
|
|
592
|
+
borderColor: 'borderColor'
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
function getDims(itm, w2, s2, key, dpr, maxd2) {
|
|
127
596
|
const a = itm._normalized;
|
|
128
597
|
const ar = w2 * a / s2;
|
|
129
|
-
const d1 = Math.sqrt(a * ar);
|
|
130
|
-
const d2 = a / d1;
|
|
598
|
+
const d1 = rasterize(Math.sqrt(a * ar), dpr);
|
|
599
|
+
const d2 = Math.min(rasterize(a / d1, dpr), maxd2);
|
|
131
600
|
const w = key === '_ix' ? d1 : d2;
|
|
132
601
|
const h = key === '_ix' ? d2 : d1;
|
|
133
602
|
|
|
134
603
|
return {d1, d2, w, h};
|
|
135
604
|
}
|
|
136
605
|
|
|
137
|
-
const getX = (rect, w) =>
|
|
606
|
+
const getX = (rect, w) => rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
|
|
138
607
|
|
|
139
|
-
function buildRow(rect, itm, dims, sum) {
|
|
140
|
-
const r = {
|
|
608
|
+
function buildRow(rect, itm, dims, sum, dpr) {
|
|
609
|
+
const r = rasterizeRect({
|
|
141
610
|
x: getX(rect, dims.w),
|
|
142
|
-
y:
|
|
143
|
-
w:
|
|
144
|
-
h:
|
|
145
|
-
a:
|
|
611
|
+
y: rect.y + rect._iy,
|
|
612
|
+
w: dims.w,
|
|
613
|
+
h: dims.h,
|
|
614
|
+
a: itm._normalized,
|
|
146
615
|
v: itm.value,
|
|
147
616
|
s: sum,
|
|
148
617
|
_data: itm._data
|
|
149
|
-
};
|
|
618
|
+
}, dpr);
|
|
150
619
|
if (itm.group) {
|
|
151
620
|
r.g = itm.group;
|
|
152
621
|
r.l = itm.level;
|
|
@@ -156,16 +625,16 @@ function buildRow(rect, itm, dims, sum) {
|
|
|
156
625
|
}
|
|
157
626
|
|
|
158
627
|
class Rect {
|
|
159
|
-
constructor(r) {
|
|
160
|
-
|
|
628
|
+
constructor(r, dpr) {
|
|
629
|
+
this.dpr = dpr;
|
|
161
630
|
r = r || {w: 1, h: 1};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
631
|
+
this.rtl = !!r.rtl;
|
|
632
|
+
this.x = r.x || r.left || 0;
|
|
633
|
+
this.y = r.y || r.top || 0;
|
|
634
|
+
this._ix = 0;
|
|
635
|
+
this._iy = 0;
|
|
636
|
+
this.w = r.w || r.width || (r.right - r.left);
|
|
637
|
+
this.h = r.h || r.height || (r.bottom - r.top);
|
|
169
638
|
}
|
|
170
639
|
|
|
171
640
|
get area() {
|
|
@@ -186,34 +655,61 @@ class Rect {
|
|
|
186
655
|
}
|
|
187
656
|
|
|
188
657
|
get side() {
|
|
189
|
-
return this.dir === 'x' ? this.iw : this.ih;
|
|
658
|
+
return rasterize(this.dir === 'x' ? this.iw : this.ih, this.dpr);
|
|
190
659
|
}
|
|
191
660
|
|
|
192
661
|
map(arr) {
|
|
193
|
-
const
|
|
194
|
-
const
|
|
662
|
+
const {dir, side, dpr} = this;
|
|
663
|
+
const {key, d2prop, id2prop, d2key} = getPropNames(dir);
|
|
195
664
|
const sum = arr.nsum;
|
|
196
665
|
const row = arr.get();
|
|
197
|
-
const dir = me.dir;
|
|
198
|
-
const side = me.side;
|
|
199
666
|
const w2 = side * side;
|
|
200
|
-
const
|
|
667
|
+
const availd2 = rasterize(this[id2prop], dpr);
|
|
201
668
|
const s2 = sum * sum;
|
|
669
|
+
const ret = [];
|
|
202
670
|
let maxd2 = 0;
|
|
203
671
|
let totd1 = 0;
|
|
204
672
|
for (const itm of row) {
|
|
205
|
-
const dims = getDims(itm, w2, s2, key);
|
|
673
|
+
const dims = getDims(itm, w2, s2, key, dpr, availd2);
|
|
206
674
|
totd1 += dims.d1;
|
|
207
|
-
maxd2 = Math.max(maxd2, dims.d2);
|
|
208
|
-
ret.push(buildRow(
|
|
209
|
-
|
|
675
|
+
maxd2 = Math.min(Math.max(maxd2, dims.d2), availd2);
|
|
676
|
+
ret.push(buildRow(this, itm, dims, arr.sum, dpr));
|
|
677
|
+
this[key] += dims.d1;
|
|
210
678
|
}
|
|
211
|
-
|
|
212
|
-
|
|
679
|
+
// make sure all rects are same size in d2 direction
|
|
680
|
+
for (const r of ret) {
|
|
681
|
+
const d2 = r[d2prop];
|
|
682
|
+
if (d2 !== maxd2) {
|
|
683
|
+
if (this.rtl && dir === 'y') {
|
|
684
|
+
// for RTL, x-coordinate needs to be adjusted too
|
|
685
|
+
r.x -= maxd2 - d2;
|
|
686
|
+
}
|
|
687
|
+
r[d2prop] = maxd2;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
this[d2key] += maxd2;
|
|
691
|
+
this[key] -= totd1;
|
|
213
692
|
return ret;
|
|
214
693
|
}
|
|
215
694
|
}
|
|
216
695
|
|
|
696
|
+
function getPropNames(dir) {
|
|
697
|
+
if (dir === 'x') {
|
|
698
|
+
return {
|
|
699
|
+
key: '_ix',
|
|
700
|
+
d2prop: 'h',
|
|
701
|
+
id2prop: 'ih',
|
|
702
|
+
d2key: '_iy'
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
key: '_iy',
|
|
707
|
+
d2prop: 'w',
|
|
708
|
+
id2prop: 'iw',
|
|
709
|
+
d2key: '_ix'
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
217
713
|
const min = Math.min;
|
|
218
714
|
const max = Math.max;
|
|
219
715
|
|
|
@@ -311,15 +807,16 @@ function compareAspectRatio(oldStat, newStat, args) {
|
|
|
311
807
|
*
|
|
312
808
|
* @param {number[]|object[]} values
|
|
313
809
|
* @param {object} rectangle
|
|
314
|
-
* @param {string} key
|
|
315
|
-
* @param {
|
|
316
|
-
* @param {*}
|
|
317
|
-
* @param {*}
|
|
810
|
+
* @param {string} [key]
|
|
811
|
+
* @param {number} [dpr=1]
|
|
812
|
+
* @param {*} [grp]
|
|
813
|
+
* @param {*} [lvl]
|
|
814
|
+
* @param {*} [gsum]
|
|
318
815
|
*/
|
|
319
|
-
function squarify(values, rectangle, key, grp, lvl, gsum) {
|
|
816
|
+
function squarify(values, rectangle, key, dpr = 1, grp, lvl, gsum) {
|
|
320
817
|
values = values || [];
|
|
321
818
|
const rows = [];
|
|
322
|
-
const rect = new Rect(rectangle);
|
|
819
|
+
const rect = new Rect(rectangle, dpr);
|
|
323
820
|
const row = new StatArray('value', rect.area / sum(values, key));
|
|
324
821
|
let length = rect.side;
|
|
325
822
|
const n = values.length;
|
|
@@ -356,7 +853,7 @@ function squarify(values, rectangle, key, grp, lvl, gsum) {
|
|
|
356
853
|
return flatten(rows);
|
|
357
854
|
}
|
|
358
855
|
|
|
359
|
-
var version = "2.
|
|
856
|
+
var version = "2.1.1";
|
|
360
857
|
|
|
361
858
|
function rectNotEqual(r1, r2) {
|
|
362
859
|
return !r1 || !r2
|
|
@@ -381,98 +878,41 @@ function arrayNotEqual(a1, a2) {
|
|
|
381
878
|
return false;
|
|
382
879
|
}
|
|
383
880
|
|
|
384
|
-
function
|
|
385
|
-
if (!font) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
const w = rect.width || rect.w;
|
|
389
|
-
const h = rect.height || rect.h;
|
|
390
|
-
const min = font.lineHeight * 2;
|
|
391
|
-
return w > min && h > min;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function drawCaption(ctx, rect, item, opts, levels) {
|
|
395
|
-
ctx.save();
|
|
396
|
-
ctx.beginPath();
|
|
397
|
-
ctx.rect(rect.x, rect.y, rect.width, rect.height);
|
|
398
|
-
ctx.clip();
|
|
399
|
-
if (!('l' in item) || item.l === levels) {
|
|
400
|
-
drawLabels(ctx, item, rect);
|
|
401
|
-
} else if (opts.captions && opts.captions.display) {
|
|
402
|
-
drawCaptionLabel(ctx, item, rect);
|
|
403
|
-
}
|
|
404
|
-
ctx.restore();
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function drawCaptionLabel(ctx, item, rect) {
|
|
408
|
-
const opts = rect.options;
|
|
409
|
-
const captionsOpts = opts.captions || {};
|
|
410
|
-
const borderWidth = opts.borderWidth || 0;
|
|
411
|
-
const spacing = helpers.valueOrDefault(opts.spacing, 0) + borderWidth;
|
|
412
|
-
const color = (rect.active ? captionsOpts.hoverColor : captionsOpts.color) || captionsOpts.color;
|
|
413
|
-
const padding = captionsOpts.padding;
|
|
414
|
-
const align = captionsOpts.align || (opts.rtl ? 'right' : 'left');
|
|
415
|
-
const optFont = (rect.active ? captionsOpts.hoverFont : captionsOpts.font) || captionsOpts.font;
|
|
416
|
-
const font = helpers.toFont(optFont);
|
|
417
|
-
const x = calculateX(rect, align, padding, borderWidth);
|
|
418
|
-
ctx.fillStyle = color;
|
|
419
|
-
ctx.font = font.string;
|
|
420
|
-
ctx.textAlign = align;
|
|
421
|
-
ctx.textBaseline = 'middle';
|
|
422
|
-
ctx.fillText(captionsOpts.formatter || item.g, x, rect.y + padding + spacing + (font.lineHeight / 2));
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function drawDivider(ctx, rect) {
|
|
426
|
-
const opts = rect.options;
|
|
427
|
-
const dividersOpts = opts.dividers || {};
|
|
428
|
-
const w = rect.width || rect.w;
|
|
429
|
-
const h = rect.height || rect.h;
|
|
430
|
-
|
|
431
|
-
ctx.save();
|
|
432
|
-
ctx.strokeStyle = dividersOpts.lineColor || 'black';
|
|
433
|
-
ctx.lineCap = dividersOpts.lineCapStyle;
|
|
434
|
-
ctx.setLineDash(dividersOpts.lineDash || []);
|
|
435
|
-
ctx.lineDashOffset = dividersOpts.lineDashOffset;
|
|
436
|
-
ctx.lineWidth = dividersOpts.lineWidth;
|
|
437
|
-
ctx.beginPath();
|
|
438
|
-
if (w > h) {
|
|
439
|
-
const w2 = w / 2;
|
|
440
|
-
ctx.moveTo(rect.x + w2, rect.y);
|
|
441
|
-
ctx.lineTo(rect.x + w2, rect.y + h);
|
|
442
|
-
} else {
|
|
443
|
-
const h2 = h / 2;
|
|
444
|
-
ctx.moveTo(rect.x, rect.y + h2);
|
|
445
|
-
ctx.lineTo(rect.x + w, rect.y + h2);
|
|
446
|
-
}
|
|
447
|
-
ctx.stroke();
|
|
448
|
-
ctx.restore();
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function buildData(dataset, mainRect, captions) {
|
|
881
|
+
function buildData(dataset, mainRect, dpr) {
|
|
452
882
|
const key = dataset.key || '';
|
|
883
|
+
const treeLeafKey = dataset.treeLeafKey || '_leaf';
|
|
453
884
|
let tree = dataset.tree || [];
|
|
885
|
+
if (helpers.isObject(tree)) {
|
|
886
|
+
tree = normalizeTreeToArray(key, treeLeafKey, tree);
|
|
887
|
+
}
|
|
454
888
|
const groups = dataset.groups || [];
|
|
455
889
|
const glen = groups.length;
|
|
456
|
-
const sp = helpers.valueOrDefault(dataset.spacing, 0)
|
|
457
|
-
const
|
|
458
|
-
const font = helpers.toFont(
|
|
890
|
+
const sp = helpers.valueOrDefault(dataset.spacing, 0);
|
|
891
|
+
const captions = dataset.captions || {};
|
|
892
|
+
const font = helpers.toFont(captions.font);
|
|
459
893
|
const padding = helpers.valueOrDefault(captions.padding, 3);
|
|
460
894
|
|
|
461
895
|
function recur(gidx, rect, parent, gs) {
|
|
462
|
-
const g = groups[gidx];
|
|
463
|
-
const pg = (gidx > 0) && groups[gidx - 1];
|
|
464
|
-
const gdata = group(tree, g, key, pg, parent);
|
|
465
|
-
const gsq = squarify(gdata, rect, key, g, gidx, gs);
|
|
896
|
+
const g = getGroupKey(groups[gidx]);
|
|
897
|
+
const pg = (gidx > 0) && getGroupKey(groups[gidx - 1]);
|
|
898
|
+
const gdata = group(tree, g, key, treeLeafKey, pg, parent, groups.filter((item, index) => index <= gidx));
|
|
899
|
+
const gsq = squarify(gdata, rect, key, dpr, g, gidx, gs);
|
|
466
900
|
const ret = gsq.slice();
|
|
467
|
-
let subRect;
|
|
468
901
|
if (gidx < glen - 1) {
|
|
469
902
|
gsq.forEach((sq) => {
|
|
470
|
-
|
|
471
|
-
|
|
903
|
+
const bw = parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
904
|
+
const subRect = {
|
|
905
|
+
...rect,
|
|
906
|
+
x: sq.x + sp + bw.l,
|
|
907
|
+
y: sq.y + sp + bw.t,
|
|
908
|
+
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
909
|
+
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
910
|
+
};
|
|
911
|
+
if (shouldDrawCaption(subRect, captions)) {
|
|
472
912
|
subRect.y += font.lineHeight + padding * 2;
|
|
473
913
|
subRect.h -= font.lineHeight + padding * 2;
|
|
474
914
|
}
|
|
475
|
-
ret.push(...recur(gidx + 1, subRect, sq.g, sq.s));
|
|
915
|
+
ret.push(...recur(gidx + 1, rasterizeRect(subRect, dpr), sq.g, sq.s));
|
|
476
916
|
});
|
|
477
917
|
}
|
|
478
918
|
return ret;
|
|
@@ -484,54 +924,22 @@ function buildData(dataset, mainRect, captions) {
|
|
|
484
924
|
|
|
485
925
|
return glen
|
|
486
926
|
? recur(0, mainRect)
|
|
487
|
-
: squarify(tree, mainRect, key);
|
|
927
|
+
: squarify(tree, mainRect, key, dpr);
|
|
488
928
|
}
|
|
489
929
|
|
|
490
|
-
function
|
|
491
|
-
const
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
const optColor = (rect.active ? labelsOpts.hoverColor : labelsOpts.color) || labelsOpts.color;
|
|
497
|
-
const optFont = (rect.active ? labelsOpts.hoverFont : labelsOpts.font) || labelsOpts.font;
|
|
498
|
-
const font = helpers.toFont(optFont);
|
|
499
|
-
const lh = font.lineHeight;
|
|
500
|
-
const label = labelsOpts.formatter;
|
|
501
|
-
if (label) {
|
|
502
|
-
const labels = helpers.isArray(label) ? label : [label];
|
|
503
|
-
const xyPoint = calculateXYLabel(opts, rect, labels, lh);
|
|
504
|
-
ctx.font = font.string;
|
|
505
|
-
ctx.textAlign = labelsOpts.align;
|
|
506
|
-
ctx.textBaseline = labelsOpts.position;
|
|
507
|
-
ctx.fillStyle = optColor;
|
|
508
|
-
labels.forEach((l, i) => ctx.fillText(l, xyPoint.x, xyPoint.y + i * lh));
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function calculateXYLabel(options, rect, labels, lineHeight) {
|
|
513
|
-
const labelsOpts = options.labels;
|
|
514
|
-
const borderWidth = options.borderWidth || 0;
|
|
515
|
-
const {align, position, padding} = labelsOpts;
|
|
516
|
-
let x, y;
|
|
517
|
-
x = calculateX(rect, align, padding, borderWidth);
|
|
518
|
-
if (position === 'top') {
|
|
519
|
-
y = rect.y + padding + borderWidth;
|
|
520
|
-
} else if (position === 'bottom') {
|
|
521
|
-
y = rect.y + rect.height - padding - borderWidth - (labels.length - 1) * lineHeight;
|
|
522
|
-
} else {
|
|
523
|
-
y = rect.y + rect.height / 2 - labels.length * lineHeight / 4;
|
|
524
|
-
}
|
|
525
|
-
return {x, y};
|
|
930
|
+
function getMinMax(data, useTree) {
|
|
931
|
+
const vMax = useTree ? 1 : maxValue(data);
|
|
932
|
+
const vMin = useTree ? 0 : minValue(data, vMax);
|
|
933
|
+
return {vMin, vMax};
|
|
526
934
|
}
|
|
527
935
|
|
|
528
|
-
function
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return
|
|
936
|
+
function getArea({xScale, yScale}, data, rtl, useTree) {
|
|
937
|
+
const {vMin, vMax} = getMinMax(data, useTree);
|
|
938
|
+
const xMin = xScale.getPixelForValue(0);
|
|
939
|
+
const xMax = xScale.getPixelForValue(1);
|
|
940
|
+
const yMin = yScale.getPixelForValue(vMin);
|
|
941
|
+
const yMax = yScale.getPixelForValue(vMax);
|
|
942
|
+
return {x: xMin, y: yMax, w: xMax - xMin, h: yMin - yMax, rtl};
|
|
535
943
|
}
|
|
536
944
|
|
|
537
945
|
class TreemapController extends chart_js.DatasetController {
|
|
@@ -541,6 +949,7 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
541
949
|
this._rect = undefined;
|
|
542
950
|
this._key = undefined;
|
|
543
951
|
this._groups = undefined;
|
|
952
|
+
this._useTree = undefined;
|
|
544
953
|
}
|
|
545
954
|
|
|
546
955
|
initialize() {
|
|
@@ -548,38 +957,52 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
548
957
|
super.initialize();
|
|
549
958
|
}
|
|
550
959
|
|
|
960
|
+
/**
|
|
961
|
+
* @todo: Remove with https://github.com/kurkle/chartjs-chart-treemap/issues/137
|
|
962
|
+
*/
|
|
963
|
+
updateRangeFromParsed(range, scale) {
|
|
964
|
+
if (range.updated) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
range.updated = true;
|
|
968
|
+
if (scale.axis === 'x') {
|
|
969
|
+
range.min = 0;
|
|
970
|
+
range.max = 1;
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const dataset = this.getDataset();
|
|
974
|
+
const {vMin, vMax} = getMinMax(dataset.data, this._useTree);
|
|
975
|
+
range.min = vMin;
|
|
976
|
+
range.max = vMax;
|
|
977
|
+
}
|
|
978
|
+
|
|
551
979
|
update(mode) {
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
const
|
|
980
|
+
const meta = this.getMeta();
|
|
981
|
+
const dataset = this.getDataset();
|
|
982
|
+
const dpr = this.chart.currentDevicePixelRatio;
|
|
983
|
+
|
|
984
|
+
if (!helpers.defined(this._useTree)) {
|
|
985
|
+
this._useTree = !!dataset.tree;
|
|
986
|
+
}
|
|
555
987
|
const groups = dataset.groups || (dataset.groups = []);
|
|
556
|
-
const captions = dataset.captions ? dataset.captions : {};
|
|
557
|
-
const area = me.chart.chartArea;
|
|
558
988
|
const key = dataset.key || '';
|
|
559
989
|
const rtl = !!dataset.rtl;
|
|
560
990
|
|
|
561
|
-
const mainRect =
|
|
991
|
+
const mainRect = rasterizeRect(getArea(meta, dataset.data, rtl, this._useTree), dpr);
|
|
562
992
|
|
|
563
|
-
if (mode === 'reset' || rectNotEqual(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
993
|
+
if (mode === 'reset' || rectNotEqual(this._rect, mainRect) || this._key !== key || arrayNotEqual(this._groups, groups)) {
|
|
994
|
+
this._rect = mainRect;
|
|
995
|
+
this._groups = groups.slice();
|
|
996
|
+
this._key = key;
|
|
567
997
|
|
|
568
|
-
dataset.data = buildData(dataset, mainRect,
|
|
998
|
+
dataset.data = buildData(dataset, mainRect, dpr);
|
|
569
999
|
// @ts-ignore using private stuff
|
|
570
|
-
|
|
1000
|
+
this._dataCheck();
|
|
571
1001
|
// @ts-ignore using private stuff
|
|
572
|
-
|
|
1002
|
+
this._resyncElements();
|
|
573
1003
|
}
|
|
574
1004
|
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
resolveDataElementOptions(index, mode) {
|
|
579
|
-
const options = super.resolveDataElementOptions(index, mode);
|
|
580
|
-
const result = Object.isFrozen(options) ? Object.assign({}, options) : options;
|
|
581
|
-
result.font = helpers.toFont(options.captions.font);
|
|
582
|
-
return result;
|
|
1005
|
+
this.updateElements(meta.data, 0, meta.data.length, mode);
|
|
583
1006
|
}
|
|
584
1007
|
|
|
585
1008
|
updateElements(rects, start, count, mode) {
|
|
@@ -612,41 +1035,21 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
612
1035
|
me.updateSharedOptions(sharedOptions, mode, firstOpts);
|
|
613
1036
|
}
|
|
614
1037
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
drawDivider(ctx, rect);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
1038
|
+
draw() {
|
|
1039
|
+
const {ctx, chartArea, currentDevicePixelRatio: dpr} = this.chart;
|
|
1040
|
+
const metadata = this.getMeta().data || [];
|
|
1041
|
+
const dataset = this.getDataset();
|
|
1042
|
+
const levels = (dataset.groups || []).length - 1;
|
|
1043
|
+
const data = dataset.data;
|
|
625
1044
|
|
|
626
|
-
|
|
1045
|
+
helpers.clipArea(ctx, chartArea);
|
|
627
1046
|
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
628
1047
|
const rect = metadata[i];
|
|
629
|
-
const item = data[i];
|
|
630
1048
|
if (!rect.hidden) {
|
|
631
|
-
rect.draw(ctx);
|
|
632
|
-
const opts = rect.options;
|
|
633
|
-
if (shouldDrawCaption(rect, opts.captions.font)) {
|
|
634
|
-
drawCaption(ctx, rect, item, opts, levels);
|
|
635
|
-
}
|
|
1049
|
+
rect.draw(ctx, data[i], levels, dpr);
|
|
636
1050
|
}
|
|
637
1051
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
draw() {
|
|
641
|
-
const me = this;
|
|
642
|
-
const ctx = me.chart.ctx;
|
|
643
|
-
const metadata = me.getMeta().data || [];
|
|
644
|
-
const dataset = me.getDataset();
|
|
645
|
-
const levels = (dataset.groups || []).length - 1;
|
|
646
|
-
const data = dataset.data || [];
|
|
647
|
-
|
|
648
|
-
me._drawRects(ctx, data, metadata, levels);
|
|
649
|
-
me._drawDividers(ctx, data, metadata);
|
|
1052
|
+
helpers.unclipArea(ctx);
|
|
650
1053
|
}
|
|
651
1054
|
}
|
|
652
1055
|
|
|
@@ -664,13 +1067,6 @@ TreemapController.defaults = {
|
|
|
664
1067
|
},
|
|
665
1068
|
},
|
|
666
1069
|
|
|
667
|
-
borderWidth: 0,
|
|
668
|
-
spacing: 0.5,
|
|
669
|
-
dividers: {
|
|
670
|
-
display: false,
|
|
671
|
-
lineWidth: 1,
|
|
672
|
-
}
|
|
673
|
-
|
|
674
1070
|
};
|
|
675
1071
|
|
|
676
1072
|
TreemapController.descriptors = {
|
|
@@ -681,6 +1077,7 @@ TreemapController.descriptors = {
|
|
|
681
1077
|
TreemapController.overrides = {
|
|
682
1078
|
interaction: {
|
|
683
1079
|
mode: 'point',
|
|
1080
|
+
includeInvisible: true,
|
|
684
1081
|
intersect: true
|
|
685
1082
|
},
|
|
686
1083
|
|
|
@@ -701,7 +1098,7 @@ TreemapController.overrides = {
|
|
|
701
1098
|
label(item) {
|
|
702
1099
|
const dataset = item.dataset;
|
|
703
1100
|
const dataItem = dataset.data[item.dataIndex];
|
|
704
|
-
const label = dataItem.g || dataset.label;
|
|
1101
|
+
const label = dataItem.g || dataItem._data.label || dataset.label;
|
|
705
1102
|
return (label ? label + ': ' : '') + dataItem.v;
|
|
706
1103
|
}
|
|
707
1104
|
}
|
|
@@ -720,7 +1117,7 @@ TreemapController.overrides = {
|
|
|
720
1117
|
};
|
|
721
1118
|
|
|
722
1119
|
TreemapController.beforeRegister = function() {
|
|
723
|
-
requireVersion('3.
|
|
1120
|
+
requireVersion('3.8', chart_js.Chart.version);
|
|
724
1121
|
};
|
|
725
1122
|
|
|
726
1123
|
TreemapController.afterRegister = function() {
|
|
@@ -736,6 +1133,8 @@ TreemapController.afterRegister = function() {
|
|
|
736
1133
|
|
|
737
1134
|
return el.tooltipPosition();
|
|
738
1135
|
};
|
|
1136
|
+
} else {
|
|
1137
|
+
console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
|
|
739
1138
|
}
|
|
740
1139
|
};
|
|
741
1140
|
|
|
@@ -746,189 +1145,15 @@ TreemapController.afterUnregister = function() {
|
|
|
746
1145
|
}
|
|
747
1146
|
};
|
|
748
1147
|
|
|
749
|
-
/**
|
|
750
|
-
* Helper function to get the bounds of the rect
|
|
751
|
-
* @param {TreemapElement} rect the rect
|
|
752
|
-
* @param {boolean} [useFinalPosition]
|
|
753
|
-
* @return {object} bounds of the rect
|
|
754
|
-
* @private
|
|
755
|
-
*/
|
|
756
|
-
function getBounds(rect, useFinalPosition) {
|
|
757
|
-
const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
758
|
-
return {left: x, top: y, right: x + width, bottom: y + height};
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function limit(value, min, max) {
|
|
762
|
-
return Math.max(Math.min(value, max), min);
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
function parseBorderWidth(value, maxW, maxH) {
|
|
766
|
-
let t, r, b, l;
|
|
767
|
-
|
|
768
|
-
if (isObject(value)) {
|
|
769
|
-
t = +value.top || 0;
|
|
770
|
-
r = +value.right || 0;
|
|
771
|
-
b = +value.bottom || 0;
|
|
772
|
-
l = +value.left || 0;
|
|
773
|
-
} else {
|
|
774
|
-
t = r = b = l = +value || 0;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
return {
|
|
778
|
-
t: limit(t, 0, maxH),
|
|
779
|
-
r: limit(r, 0, maxW),
|
|
780
|
-
b: limit(b, 0, maxH),
|
|
781
|
-
l: limit(l, 0, maxW)
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function boundingRects(rect) {
|
|
786
|
-
const bounds = getBounds(rect);
|
|
787
|
-
const width = bounds.right - bounds.left;
|
|
788
|
-
const height = bounds.bottom - bounds.top;
|
|
789
|
-
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
790
|
-
|
|
791
|
-
return {
|
|
792
|
-
outer: {
|
|
793
|
-
x: bounds.left,
|
|
794
|
-
y: bounds.top,
|
|
795
|
-
w: width,
|
|
796
|
-
h: height
|
|
797
|
-
},
|
|
798
|
-
inner: {
|
|
799
|
-
x: bounds.left + border.l,
|
|
800
|
-
y: bounds.top + border.t,
|
|
801
|
-
w: width - border.l - border.r,
|
|
802
|
-
h: height - border.t - border.b
|
|
803
|
-
}
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
function inRange(rect, x, y, useFinalPosition) {
|
|
808
|
-
const skipX = x === null;
|
|
809
|
-
const skipY = y === null;
|
|
810
|
-
const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
|
|
811
|
-
|
|
812
|
-
return bounds
|
|
813
|
-
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
814
|
-
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
class TreemapElement extends chart_js.Element {
|
|
818
|
-
|
|
819
|
-
constructor(cfg) {
|
|
820
|
-
super();
|
|
821
|
-
|
|
822
|
-
this.options = undefined;
|
|
823
|
-
this.width = undefined;
|
|
824
|
-
this.height = undefined;
|
|
825
|
-
|
|
826
|
-
if (cfg) {
|
|
827
|
-
Object.assign(this, cfg);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
draw(ctx) {
|
|
832
|
-
const options = this.options;
|
|
833
|
-
const {inner, outer} = boundingRects(this);
|
|
834
|
-
|
|
835
|
-
ctx.save();
|
|
836
|
-
|
|
837
|
-
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
838
|
-
ctx.beginPath();
|
|
839
|
-
ctx.rect(outer.x, outer.y, outer.w, outer.h);
|
|
840
|
-
ctx.clip();
|
|
841
|
-
ctx.rect(inner.x, inner.y, inner.w, inner.h);
|
|
842
|
-
ctx.fillStyle = options.backgroundColor;
|
|
843
|
-
ctx.fill();
|
|
844
|
-
ctx.fillStyle = options.borderColor;
|
|
845
|
-
ctx.fill('evenodd');
|
|
846
|
-
} else {
|
|
847
|
-
ctx.fillStyle = options.backgroundColor;
|
|
848
|
-
ctx.fillRect(inner.x, inner.y, inner.w, inner.h);
|
|
849
|
-
}
|
|
850
|
-
ctx.restore();
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
inRange(mouseX, mouseY, useFinalPosition) {
|
|
854
|
-
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
inXRange(mouseX, useFinalPosition) {
|
|
858
|
-
return inRange(this, mouseX, null, useFinalPosition);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
inYRange(mouseY, useFinalPosition) {
|
|
862
|
-
return inRange(this, null, mouseY, useFinalPosition);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
getCenterPoint(useFinalPosition) {
|
|
866
|
-
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
867
|
-
return {
|
|
868
|
-
x: x + width / 2,
|
|
869
|
-
y: y + height / 2
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
tooltipPosition() {
|
|
874
|
-
return this.getCenterPoint();
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
getRange(axis) {
|
|
878
|
-
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
TreemapElement.id = 'treemap';
|
|
883
|
-
|
|
884
|
-
TreemapElement.defaults = {
|
|
885
|
-
borderWidth: undefined,
|
|
886
|
-
spacing: undefined,
|
|
887
|
-
label: undefined,
|
|
888
|
-
rtl: undefined,
|
|
889
|
-
dividers: {
|
|
890
|
-
display: false,
|
|
891
|
-
lineCapStyle: 'butt',
|
|
892
|
-
lineColor: 'black',
|
|
893
|
-
lineDash: undefined,
|
|
894
|
-
lineDashOffset: 0,
|
|
895
|
-
lineWidth: 0,
|
|
896
|
-
},
|
|
897
|
-
captions: {
|
|
898
|
-
align: undefined,
|
|
899
|
-
color: undefined,
|
|
900
|
-
display: true,
|
|
901
|
-
formatter: (ctx) => ctx.raw.g || '',
|
|
902
|
-
font: {},
|
|
903
|
-
padding: 3
|
|
904
|
-
},
|
|
905
|
-
labels: {
|
|
906
|
-
align: 'center',
|
|
907
|
-
color: undefined,
|
|
908
|
-
display: false,
|
|
909
|
-
formatter: (ctx) => ctx.raw.g ? [ctx.raw.g, ctx.raw.v] : ctx.raw.v,
|
|
910
|
-
font: {},
|
|
911
|
-
position: 'middle',
|
|
912
|
-
padding: 3
|
|
913
|
-
}
|
|
914
|
-
};
|
|
915
|
-
|
|
916
|
-
TreemapElement.descriptors = {
|
|
917
|
-
_scriptable: true,
|
|
918
|
-
_indexable: false
|
|
919
|
-
};
|
|
920
|
-
|
|
921
|
-
TreemapElement.defaultRoutes = {
|
|
922
|
-
backgroundColor: 'backgroundColor',
|
|
923
|
-
borderColor: 'borderColor'
|
|
924
|
-
};
|
|
925
|
-
|
|
926
1148
|
chart_js.Chart.register(TreemapController, TreemapElement);
|
|
927
1149
|
|
|
928
1150
|
exports.flatten = flatten;
|
|
1151
|
+
exports.getGroupKey = getGroupKey;
|
|
929
1152
|
exports.group = group;
|
|
930
1153
|
exports.index = index;
|
|
931
|
-
exports.
|
|
1154
|
+
exports.maxValue = maxValue;
|
|
1155
|
+
exports.minValue = minValue;
|
|
1156
|
+
exports.normalizeTreeToArray = normalizeTreeToArray;
|
|
932
1157
|
exports.requireVersion = requireVersion;
|
|
933
1158
|
exports.sort = sort;
|
|
934
1159
|
exports.sum = sum;
|