chartjs-chart-treemap 2.0.2 → 2.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.esm.js +548 -362
- package/dist/chartjs-chart-treemap.js +551 -362
- package/dist/chartjs-chart-treemap.min.js +3 -3
- package/package.json +18 -14
- package/types/index.esm.d.ts +19 -10
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-treemap v2.0
|
|
2
|
+
* chartjs-chart-treemap v2.1.0
|
|
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,6 +195,387 @@ function requireVersion(min, ver) {
|
|
|
118
195
|
}
|
|
119
196
|
}
|
|
120
197
|
|
|
198
|
+
const widthCache = new Map();
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Helper function to get the bounds of the rect
|
|
202
|
+
* @param {TreemapElement} rect the rect
|
|
203
|
+
* @param {boolean} [useFinalPosition]
|
|
204
|
+
* @return {object} bounds of the rect
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
function getBounds(rect, useFinalPosition) {
|
|
208
|
+
const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
209
|
+
return {left: x, top: y, right: x + width, bottom: y + height};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function limit(value, min, max) {
|
|
213
|
+
return Math.max(Math.min(value, max), min);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseBorderWidth(value, maxW, maxH) {
|
|
217
|
+
const o = helpers.toTRBL(value);
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
t: limit(o.top, 0, maxH),
|
|
221
|
+
r: limit(o.right, 0, maxW),
|
|
222
|
+
b: limit(o.bottom, 0, maxH),
|
|
223
|
+
l: limit(o.left, 0, maxW)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function parseBorderRadius(value, maxW, maxH) {
|
|
228
|
+
const o = helpers.toTRBLCorners(value);
|
|
229
|
+
const maxR = Math.min(maxW, maxH);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
topLeft: limit(o.topLeft, 0, maxR),
|
|
233
|
+
topRight: limit(o.topRight, 0, maxR),
|
|
234
|
+
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
235
|
+
bottomRight: limit(o.bottomRight, 0, maxR)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function boundingRects(rect) {
|
|
240
|
+
const bounds = getBounds(rect);
|
|
241
|
+
const width = bounds.right - bounds.left;
|
|
242
|
+
const height = bounds.bottom - bounds.top;
|
|
243
|
+
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
244
|
+
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
outer: {
|
|
248
|
+
x: bounds.left,
|
|
249
|
+
y: bounds.top,
|
|
250
|
+
w: width,
|
|
251
|
+
h: height,
|
|
252
|
+
radius
|
|
253
|
+
},
|
|
254
|
+
inner: {
|
|
255
|
+
x: bounds.left + border.l,
|
|
256
|
+
y: bounds.top + border.t,
|
|
257
|
+
w: width - border.l - border.r,
|
|
258
|
+
h: height - border.t - border.b,
|
|
259
|
+
radius: {
|
|
260
|
+
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
261
|
+
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
|
|
262
|
+
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
263
|
+
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function inRange(rect, x, y, useFinalPosition) {
|
|
270
|
+
const skipX = x === null;
|
|
271
|
+
const skipY = y === null;
|
|
272
|
+
const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
|
|
273
|
+
|
|
274
|
+
return bounds
|
|
275
|
+
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
276
|
+
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function hasRadius(radius) {
|
|
280
|
+
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Add a path of a rectangle to the current sub-path
|
|
285
|
+
* @param {CanvasRenderingContext2D} ctx Context
|
|
286
|
+
* @param {*} rect Bounding rect
|
|
287
|
+
*/
|
|
288
|
+
function addNormalRectPath(ctx, rect) {
|
|
289
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function shouldDrawCaption(rect, options) {
|
|
293
|
+
if (!options || !options.display) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
const {w, h} = rect;
|
|
297
|
+
const font = helpers.toFont(options.font);
|
|
298
|
+
const min = font.lineHeight;
|
|
299
|
+
const padding = limit(helpers.valueOrDefault(options.padding, 3) * 2, 0, Math.min(w, h));
|
|
300
|
+
return (w - padding) > min && (h - padding) > min;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function drawText(ctx, rect, options, item, levels) {
|
|
304
|
+
const {captions, labels} = options;
|
|
305
|
+
ctx.save();
|
|
306
|
+
ctx.beginPath();
|
|
307
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
308
|
+
ctx.clip();
|
|
309
|
+
const isLeaf = (!('l' in item) || item.l === levels);
|
|
310
|
+
if (isLeaf && labels.display) {
|
|
311
|
+
drawLabel(ctx, rect, options);
|
|
312
|
+
} else if (!isLeaf && shouldDrawCaption(rect, captions)) {
|
|
313
|
+
drawCaption(ctx, rect, options, item);
|
|
314
|
+
}
|
|
315
|
+
ctx.restore();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function drawCaption(ctx, rect, options, item) {
|
|
319
|
+
const {captions, spacing, rtl} = options;
|
|
320
|
+
const {color, hoverColor, font, hoverFont, padding, align, formatter} = captions;
|
|
321
|
+
const oColor = (rect.active ? hoverColor : color) || color;
|
|
322
|
+
const oAlign = align || (rtl ? 'right' : 'left');
|
|
323
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
324
|
+
const oFont = helpers.toFont(optFont);
|
|
325
|
+
const lh = oFont.lineHeight / 2;
|
|
326
|
+
const x = calculateX(rect, oAlign, padding);
|
|
327
|
+
ctx.fillStyle = oColor;
|
|
328
|
+
ctx.font = oFont.string;
|
|
329
|
+
ctx.textAlign = oAlign;
|
|
330
|
+
ctx.textBaseline = 'middle';
|
|
331
|
+
ctx.fillText(formatter || item.g, x, rect.y + padding + spacing + lh);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function measureLabelSize(ctx, lines, fonts) {
|
|
335
|
+
const fontsKey = fonts.reduce(function(prev, item) {
|
|
336
|
+
prev += item.string;
|
|
337
|
+
return prev;
|
|
338
|
+
}, '');
|
|
339
|
+
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
340
|
+
if (!widthCache.has(mapKey)) {
|
|
341
|
+
ctx.save();
|
|
342
|
+
const count = lines.length;
|
|
343
|
+
let width = 0;
|
|
344
|
+
let height = 0;
|
|
345
|
+
for (let i = 0; i < count; i++) {
|
|
346
|
+
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
347
|
+
ctx.font = font.string;
|
|
348
|
+
const text = lines[i];
|
|
349
|
+
width = Math.max(width, ctx.measureText(text).width);
|
|
350
|
+
height += font.lineHeight;
|
|
351
|
+
}
|
|
352
|
+
ctx.restore();
|
|
353
|
+
widthCache.set(mapKey, {width, height});
|
|
354
|
+
}
|
|
355
|
+
return widthCache.get(mapKey);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function labelToDraw(ctx, rect, options, labelSize) {
|
|
359
|
+
const {overflow, padding} = options;
|
|
360
|
+
const {width, height} = labelSize;
|
|
361
|
+
if (overflow === 'hidden') {
|
|
362
|
+
return !((width + padding * 2) > rect.w || (height + padding * 2) > rect.h);
|
|
363
|
+
}
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function drawLabel(ctx, rect, options) {
|
|
368
|
+
const labels = options.labels;
|
|
369
|
+
const content = labels.formatter;
|
|
370
|
+
if (!content) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const contents = helpers.isArray(content) ? content : [content];
|
|
374
|
+
const {font, hoverFont} = labels;
|
|
375
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
376
|
+
const fonts = helpers.isArray(optFont) ? optFont.map(f => helpers.toFont(f)) : [helpers.toFont(optFont)];
|
|
377
|
+
const labelSize = measureLabelSize(ctx, contents, fonts);
|
|
378
|
+
if (!labelToDraw(ctx, rect, labels, labelSize)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const {color, hoverColor, align} = labels;
|
|
382
|
+
const optColor = (rect.active ? hoverColor : color) || color;
|
|
383
|
+
const colors = helpers.isArray(optColor) ? optColor : [optColor];
|
|
384
|
+
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
385
|
+
ctx.textAlign = align;
|
|
386
|
+
ctx.textBaseline = 'middle';
|
|
387
|
+
let lhs = 0;
|
|
388
|
+
contents.forEach(function(l, i) {
|
|
389
|
+
const c = colors[Math.min(i, colors.length - 1)];
|
|
390
|
+
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
391
|
+
const lh = f.lineHeight;
|
|
392
|
+
ctx.font = f.string;
|
|
393
|
+
ctx.fillStyle = c;
|
|
394
|
+
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
395
|
+
lhs += lh;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function drawDivider(ctx, rect, options, item) {
|
|
400
|
+
const dividers = options.dividers;
|
|
401
|
+
if (!dividers.display || !item._data.children.length) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const {x, y, w, h} = rect;
|
|
405
|
+
const {lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth} = dividers;
|
|
406
|
+
ctx.save();
|
|
407
|
+
ctx.strokeStyle = lineColor;
|
|
408
|
+
ctx.lineCap = lineCapStyle;
|
|
409
|
+
ctx.setLineDash(lineDash);
|
|
410
|
+
ctx.lineDashOffset = lineDashOffset;
|
|
411
|
+
ctx.lineWidth = lineWidth;
|
|
412
|
+
ctx.beginPath();
|
|
413
|
+
if (w > h) {
|
|
414
|
+
const w2 = w / 2;
|
|
415
|
+
ctx.moveTo(x + w2, y);
|
|
416
|
+
ctx.lineTo(x + w2, y + h);
|
|
417
|
+
} else {
|
|
418
|
+
const h2 = h / 2;
|
|
419
|
+
ctx.moveTo(x, y + h2);
|
|
420
|
+
ctx.lineTo(x + w, y + h2);
|
|
421
|
+
}
|
|
422
|
+
ctx.stroke();
|
|
423
|
+
ctx.restore();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function calculateXYLabel(rect, options, labelSize) {
|
|
427
|
+
const {align, position, padding} = options;
|
|
428
|
+
let x, y;
|
|
429
|
+
x = calculateX(rect, align, padding);
|
|
430
|
+
if (position === 'top') {
|
|
431
|
+
y = rect.y + padding;
|
|
432
|
+
} else if (position === 'bottom') {
|
|
433
|
+
y = rect.y + rect.h - padding - labelSize.height;
|
|
434
|
+
} else {
|
|
435
|
+
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
436
|
+
}
|
|
437
|
+
return {x, y};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function calculateX(rect, align, padding) {
|
|
441
|
+
if (align === 'left') {
|
|
442
|
+
return rect.x + padding;
|
|
443
|
+
} else if (align === 'right') {
|
|
444
|
+
return rect.x + rect.w - padding;
|
|
445
|
+
}
|
|
446
|
+
return rect.x + rect.w / 2;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
class TreemapElement extends chart_js.Element {
|
|
450
|
+
|
|
451
|
+
constructor(cfg) {
|
|
452
|
+
super();
|
|
453
|
+
|
|
454
|
+
this.options = undefined;
|
|
455
|
+
this.width = undefined;
|
|
456
|
+
this.height = undefined;
|
|
457
|
+
|
|
458
|
+
if (cfg) {
|
|
459
|
+
Object.assign(this, cfg);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
draw(ctx, data, levels = 0) {
|
|
464
|
+
if (!data) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const options = this.options;
|
|
468
|
+
const {inner, outer} = boundingRects(this);
|
|
469
|
+
|
|
470
|
+
const addRectPath = hasRadius(outer.radius) ? helpers.addRoundedRectPath : addNormalRectPath;
|
|
471
|
+
|
|
472
|
+
ctx.save();
|
|
473
|
+
|
|
474
|
+
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
475
|
+
ctx.beginPath();
|
|
476
|
+
addRectPath(ctx, outer);
|
|
477
|
+
ctx.clip();
|
|
478
|
+
addRectPath(ctx, inner);
|
|
479
|
+
ctx.fillStyle = options.borderColor;
|
|
480
|
+
ctx.fill('evenodd');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
ctx.beginPath();
|
|
484
|
+
addRectPath(ctx, inner);
|
|
485
|
+
ctx.fillStyle = options.backgroundColor;
|
|
486
|
+
ctx.fill();
|
|
487
|
+
|
|
488
|
+
drawDivider(ctx, inner, options, data);
|
|
489
|
+
drawText(ctx, inner, options, data, levels);
|
|
490
|
+
ctx.restore();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
inRange(mouseX, mouseY, useFinalPosition) {
|
|
494
|
+
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
inXRange(mouseX, useFinalPosition) {
|
|
498
|
+
return inRange(this, mouseX, null, useFinalPosition);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
inYRange(mouseY, useFinalPosition) {
|
|
502
|
+
return inRange(this, null, mouseY, useFinalPosition);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
getCenterPoint(useFinalPosition) {
|
|
506
|
+
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
507
|
+
return {
|
|
508
|
+
x: x + width / 2,
|
|
509
|
+
y: y + height / 2
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
tooltipPosition() {
|
|
514
|
+
return this.getCenterPoint();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
getRange(axis) {
|
|
518
|
+
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
TreemapElement.id = 'treemap';
|
|
523
|
+
|
|
524
|
+
TreemapElement.defaults = {
|
|
525
|
+
label: undefined,
|
|
526
|
+
borderRadius: 0,
|
|
527
|
+
borderWidth: 0,
|
|
528
|
+
captions: {
|
|
529
|
+
align: undefined,
|
|
530
|
+
color: 'black',
|
|
531
|
+
display: true,
|
|
532
|
+
font: {},
|
|
533
|
+
formatter: (ctx) => ctx.raw.g || ctx.raw._data.label || '',
|
|
534
|
+
padding: 3
|
|
535
|
+
},
|
|
536
|
+
dividers: {
|
|
537
|
+
display: false,
|
|
538
|
+
lineCapStyle: 'butt',
|
|
539
|
+
lineColor: 'black',
|
|
540
|
+
lineDash: [],
|
|
541
|
+
lineDashOffset: 0,
|
|
542
|
+
lineWidth: 1,
|
|
543
|
+
},
|
|
544
|
+
labels: {
|
|
545
|
+
align: 'center',
|
|
546
|
+
color: 'black',
|
|
547
|
+
display: false,
|
|
548
|
+
font: {},
|
|
549
|
+
formatter(ctx) {
|
|
550
|
+
if (ctx.raw.g) {
|
|
551
|
+
return [ctx.raw.g, ctx.raw.v + ''];
|
|
552
|
+
}
|
|
553
|
+
return ctx.raw._data.label ? [ctx.raw._data.label, ctx.raw.v + ''] : ctx.raw.v + '';
|
|
554
|
+
},
|
|
555
|
+
overflow: 'cut',
|
|
556
|
+
position: 'middle',
|
|
557
|
+
padding: 3
|
|
558
|
+
},
|
|
559
|
+
rtl: false,
|
|
560
|
+
spacing: 0.5
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
TreemapElement.descriptors = {
|
|
564
|
+
labels: {
|
|
565
|
+
_fallback: true
|
|
566
|
+
},
|
|
567
|
+
captions: {
|
|
568
|
+
_fallback: true
|
|
569
|
+
},
|
|
570
|
+
_scriptable: true,
|
|
571
|
+
_indexable: false
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
TreemapElement.defaultRoutes = {
|
|
575
|
+
backgroundColor: 'backgroundColor',
|
|
576
|
+
borderColor: 'borderColor'
|
|
577
|
+
};
|
|
578
|
+
|
|
121
579
|
function round(v, n) {
|
|
122
580
|
// @ts-ignore
|
|
123
581
|
return (+(Math.round(v + 'e+' + n) + 'e-' + n)) || 0;
|
|
@@ -356,7 +814,7 @@ function squarify(values, rectangle, key, grp, lvl, gsum) {
|
|
|
356
814
|
return flatten(rows);
|
|
357
815
|
}
|
|
358
816
|
|
|
359
|
-
var version = "2.0
|
|
817
|
+
var version = "2.1.0";
|
|
360
818
|
|
|
361
819
|
function rectNotEqual(r1, r2) {
|
|
362
820
|
return !r1 || !r2
|
|
@@ -381,94 +839,38 @@ function arrayNotEqual(a1, a2) {
|
|
|
381
839
|
return false;
|
|
382
840
|
}
|
|
383
841
|
|
|
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) {
|
|
842
|
+
function buildData(dataset, mainRect) {
|
|
452
843
|
const key = dataset.key || '';
|
|
844
|
+
const treeLeafKey = dataset.treeLeafKey || '_leaf';
|
|
453
845
|
let tree = dataset.tree || [];
|
|
846
|
+
if (helpers.isObject(tree)) {
|
|
847
|
+
tree = normalizeTreeToArray(key, treeLeafKey, tree);
|
|
848
|
+
}
|
|
454
849
|
const groups = dataset.groups || [];
|
|
455
850
|
const glen = groups.length;
|
|
456
|
-
const sp = helpers.valueOrDefault(dataset.spacing, 0)
|
|
457
|
-
const
|
|
458
|
-
const font = helpers.toFont(
|
|
851
|
+
const sp = helpers.valueOrDefault(dataset.spacing, 0);
|
|
852
|
+
const captions = dataset.captions || {display: true};
|
|
853
|
+
const font = helpers.toFont(captions.font);
|
|
459
854
|
const padding = helpers.valueOrDefault(captions.padding, 3);
|
|
460
855
|
|
|
461
856
|
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);
|
|
857
|
+
const g = getGroupKey(groups[gidx]);
|
|
858
|
+
const pg = (gidx > 0) && getGroupKey(groups[gidx - 1]);
|
|
859
|
+
const gdata = group(tree, g, key, treeLeafKey, pg, parent, groups.filter((item, index) => index <= gidx));
|
|
465
860
|
const gsq = squarify(gdata, rect, key, g, gidx, gs);
|
|
466
861
|
const ret = gsq.slice();
|
|
467
862
|
let subRect;
|
|
468
863
|
if (gidx < glen - 1) {
|
|
469
864
|
gsq.forEach((sq) => {
|
|
470
|
-
|
|
471
|
-
|
|
865
|
+
const bw = parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
866
|
+
subRect = {
|
|
867
|
+
x: sq.x + sp + bw.l,
|
|
868
|
+
y: sq.y + sp + bw.t,
|
|
869
|
+
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
870
|
+
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
871
|
+
rtl: rect.rtl
|
|
872
|
+
};
|
|
873
|
+
if (shouldDrawCaption(subRect, captions)) {
|
|
472
874
|
subRect.y += font.lineHeight + padding * 2;
|
|
473
875
|
subRect.h -= font.lineHeight + padding * 2;
|
|
474
876
|
}
|
|
@@ -487,51 +889,19 @@ function buildData(dataset, mainRect, captions) {
|
|
|
487
889
|
: squarify(tree, mainRect, key);
|
|
488
890
|
}
|
|
489
891
|
|
|
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};
|
|
892
|
+
function getMinMax(data, useTree) {
|
|
893
|
+
const vMax = useTree ? 1 : maxValue(data);
|
|
894
|
+
const vMin = useTree ? 0 : minValue(data, vMax);
|
|
895
|
+
return {vMin, vMax};
|
|
526
896
|
}
|
|
527
897
|
|
|
528
|
-
function
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return
|
|
898
|
+
function getArea({xScale, yScale}, data, rtl, useTree) {
|
|
899
|
+
const {vMin, vMax} = getMinMax(data, useTree);
|
|
900
|
+
const xMin = xScale.getPixelForValue(0);
|
|
901
|
+
const xMax = xScale.getPixelForValue(1);
|
|
902
|
+
const yMin = yScale.getPixelForValue(vMin);
|
|
903
|
+
const yMax = yScale.getPixelForValue(vMax);
|
|
904
|
+
return {x: xMin, y: yMax, w: xMax - xMin, h: yMin - yMax, rtl};
|
|
535
905
|
}
|
|
536
906
|
|
|
537
907
|
class TreemapController extends chart_js.DatasetController {
|
|
@@ -541,6 +911,7 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
541
911
|
this._rect = undefined;
|
|
542
912
|
this._key = undefined;
|
|
543
913
|
this._groups = undefined;
|
|
914
|
+
this._useTree = undefined;
|
|
544
915
|
}
|
|
545
916
|
|
|
546
917
|
initialize() {
|
|
@@ -548,24 +919,46 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
548
919
|
super.initialize();
|
|
549
920
|
}
|
|
550
921
|
|
|
922
|
+
/**
|
|
923
|
+
* TODO: to be removed when https://github.com/kurkle/chartjs-chart-treemap/issues/137
|
|
924
|
+
* will be implemented
|
|
925
|
+
*/
|
|
926
|
+
updateRangeFromParsed(range, scale) {
|
|
927
|
+
if (range.updated) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
range.updated = true;
|
|
931
|
+
if (scale.axis === 'x') {
|
|
932
|
+
range.min = 0;
|
|
933
|
+
range.max = 1;
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const me = this;
|
|
937
|
+
const dataset = me.getDataset();
|
|
938
|
+
const {vMin, vMax} = getMinMax(dataset.data, me._useTree);
|
|
939
|
+
range.min = vMin;
|
|
940
|
+
range.max = vMax;
|
|
941
|
+
}
|
|
942
|
+
|
|
551
943
|
update(mode) {
|
|
552
944
|
const me = this;
|
|
553
945
|
const meta = me.getMeta();
|
|
554
946
|
const dataset = me.getDataset();
|
|
947
|
+
if (!helpers.defined(me._useTree)) {
|
|
948
|
+
me._useTree = !!dataset.tree;
|
|
949
|
+
}
|
|
555
950
|
const groups = dataset.groups || (dataset.groups = []);
|
|
556
|
-
const captions = dataset.captions ? dataset.captions : {};
|
|
557
|
-
const area = me.chart.chartArea;
|
|
558
951
|
const key = dataset.key || '';
|
|
559
952
|
const rtl = !!dataset.rtl;
|
|
560
953
|
|
|
561
|
-
const mainRect =
|
|
954
|
+
const mainRect = getArea(meta, dataset.data, rtl, me._useTree);
|
|
562
955
|
|
|
563
956
|
if (mode === 'reset' || rectNotEqual(me._rect, mainRect) || me._key !== key || arrayNotEqual(me._groups, groups)) {
|
|
564
957
|
me._rect = mainRect;
|
|
565
958
|
me._groups = groups.slice();
|
|
566
959
|
me._key = key;
|
|
567
960
|
|
|
568
|
-
dataset.data = buildData(dataset, mainRect
|
|
961
|
+
dataset.data = buildData(dataset, mainRect);
|
|
569
962
|
// @ts-ignore using private stuff
|
|
570
963
|
me._dataCheck();
|
|
571
964
|
// @ts-ignore using private stuff
|
|
@@ -575,13 +968,6 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
575
968
|
me.updateElements(meta.data, 0, meta.data.length, mode);
|
|
576
969
|
}
|
|
577
970
|
|
|
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;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
971
|
updateElements(rects, start, count, mode) {
|
|
586
972
|
const me = this;
|
|
587
973
|
const reset = mode === 'reset';
|
|
@@ -612,41 +998,23 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
612
998
|
me.updateSharedOptions(sharedOptions, mode, firstOpts);
|
|
613
999
|
}
|
|
614
1000
|
|
|
615
|
-
_drawDividers(ctx, data, metadata) {
|
|
616
|
-
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
617
|
-
const rect = metadata[i];
|
|
618
|
-
const item = data[i];
|
|
619
|
-
const dividersOpts = rect.options.dividers || {};
|
|
620
|
-
if (dividersOpts.display && item._data.children.length > 1) {
|
|
621
|
-
drawDivider(ctx, rect);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
_drawRects(ctx, data, metadata, levels) {
|
|
627
|
-
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
628
|
-
const rect = metadata[i];
|
|
629
|
-
const item = data[i];
|
|
630
|
-
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
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
1001
|
draw() {
|
|
641
1002
|
const me = this;
|
|
642
1003
|
const ctx = me.chart.ctx;
|
|
1004
|
+
const area = me.chart.chartArea;
|
|
643
1005
|
const metadata = me.getMeta().data || [];
|
|
644
1006
|
const dataset = me.getDataset();
|
|
645
1007
|
const levels = (dataset.groups || []).length - 1;
|
|
646
|
-
const data = dataset.data
|
|
1008
|
+
const data = dataset.data;
|
|
647
1009
|
|
|
648
|
-
|
|
649
|
-
|
|
1010
|
+
helpers.clipArea(ctx, area);
|
|
1011
|
+
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
1012
|
+
const rect = metadata[i];
|
|
1013
|
+
if (!rect.hidden) {
|
|
1014
|
+
rect.draw(ctx, data[i], levels);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
helpers.unclipArea(ctx);
|
|
650
1018
|
}
|
|
651
1019
|
}
|
|
652
1020
|
|
|
@@ -664,13 +1032,6 @@ TreemapController.defaults = {
|
|
|
664
1032
|
},
|
|
665
1033
|
},
|
|
666
1034
|
|
|
667
|
-
borderWidth: 0,
|
|
668
|
-
spacing: 0.5,
|
|
669
|
-
dividers: {
|
|
670
|
-
display: false,
|
|
671
|
-
lineWidth: 1,
|
|
672
|
-
}
|
|
673
|
-
|
|
674
1035
|
};
|
|
675
1036
|
|
|
676
1037
|
TreemapController.descriptors = {
|
|
@@ -701,7 +1062,7 @@ TreemapController.overrides = {
|
|
|
701
1062
|
label(item) {
|
|
702
1063
|
const dataset = item.dataset;
|
|
703
1064
|
const dataItem = dataset.data[item.dataIndex];
|
|
704
|
-
const label = dataItem.g || dataset.label;
|
|
1065
|
+
const label = dataItem.g || dataItem._data.label || dataset.label;
|
|
705
1066
|
return (label ? label + ': ' : '') + dataItem.v;
|
|
706
1067
|
}
|
|
707
1068
|
}
|
|
@@ -736,6 +1097,8 @@ TreemapController.afterRegister = function() {
|
|
|
736
1097
|
|
|
737
1098
|
return el.tooltipPosition();
|
|
738
1099
|
};
|
|
1100
|
+
} else {
|
|
1101
|
+
console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
|
|
739
1102
|
}
|
|
740
1103
|
};
|
|
741
1104
|
|
|
@@ -746,189 +1109,15 @@ TreemapController.afterUnregister = function() {
|
|
|
746
1109
|
}
|
|
747
1110
|
};
|
|
748
1111
|
|
|
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
1112
|
chart_js.Chart.register(TreemapController, TreemapElement);
|
|
927
1113
|
|
|
928
1114
|
exports.flatten = flatten;
|
|
1115
|
+
exports.getGroupKey = getGroupKey;
|
|
929
1116
|
exports.group = group;
|
|
930
1117
|
exports.index = index;
|
|
931
|
-
exports.
|
|
1118
|
+
exports.maxValue = maxValue;
|
|
1119
|
+
exports.minValue = minValue;
|
|
1120
|
+
exports.normalizeTreeToArray = normalizeTreeToArray;
|
|
932
1121
|
exports.requireVersion = requireVersion;
|
|
933
1122
|
exports.sort = sort;
|
|
934
1123
|
exports.sum = sum;
|