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,11 +1,70 @@
|
|
|
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
|
-
import { Chart, registry, DatasetController
|
|
8
|
-
import { toFont, valueOrDefault,
|
|
7
|
+
import { Element, Chart, registry, DatasetController } from 'chart.js';
|
|
8
|
+
import { isObject, addRoundedRectPath, toFont, isArray, toTRBL, toTRBLCorners, valueOrDefault, defined, clipArea, unclipArea } from 'chart.js/helpers';
|
|
9
|
+
|
|
10
|
+
const getValue = (item) => isObject(item) ? item.v : item;
|
|
11
|
+
|
|
12
|
+
const maxValue = (data) => data.reduce(function(m, v) {
|
|
13
|
+
return (m > getValue(v) ? m : getValue(v));
|
|
14
|
+
}, 0);
|
|
15
|
+
|
|
16
|
+
const minValue = (data, mx) => data.reduce(function(m, v) {
|
|
17
|
+
return (m < getValue(v) ? m : getValue(v));
|
|
18
|
+
}, mx);
|
|
19
|
+
|
|
20
|
+
const getGroupKey = (lvl) => '' + lvl;
|
|
21
|
+
|
|
22
|
+
function scanTreeObject(key, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
|
|
23
|
+
const objIndex = lvl - 1;
|
|
24
|
+
if (key in obj && lvl > 0) {
|
|
25
|
+
const record = tree.reduce(function(reduced, item, i) {
|
|
26
|
+
if (i !== objIndex) {
|
|
27
|
+
reduced[getGroupKey(i)] = item;
|
|
28
|
+
}
|
|
29
|
+
return reduced;
|
|
30
|
+
}, {});
|
|
31
|
+
record[treeLeafKey] = tree[objIndex];
|
|
32
|
+
record[key] = obj[key];
|
|
33
|
+
result.push(record);
|
|
34
|
+
} else {
|
|
35
|
+
for (const childKey of Object.keys(obj)) {
|
|
36
|
+
const child = obj[childKey];
|
|
37
|
+
if (isObject(child)) {
|
|
38
|
+
tree.push(childKey);
|
|
39
|
+
scanTreeObject(key, treeLeafKey, child, tree, lvl + 1, result);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
tree.splice(objIndex, 1);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeTreeToArray(key, treeLeafKey, obj) {
|
|
48
|
+
const data = scanTreeObject(key, treeLeafKey, obj);
|
|
49
|
+
if (!data.length) {
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
const max = data.reduce(function(maxVal, element) {
|
|
53
|
+
// minus 2 because _leaf and value properties are added
|
|
54
|
+
// on top to groups ones
|
|
55
|
+
const keys = Object.keys(element).length - 2;
|
|
56
|
+
return maxVal > keys ? maxVal : keys;
|
|
57
|
+
});
|
|
58
|
+
data.forEach(function(element) {
|
|
59
|
+
for (let i = 0; i < max; i++) {
|
|
60
|
+
const groupKey = getGroupKey(i);
|
|
61
|
+
if (!element[groupKey]) {
|
|
62
|
+
element[groupKey] = '';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return data;
|
|
67
|
+
}
|
|
9
68
|
|
|
10
69
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
|
|
11
70
|
function flatten(input) {
|
|
@@ -25,36 +84,59 @@ function flatten(input) {
|
|
|
25
84
|
return res.reverse();
|
|
26
85
|
}
|
|
27
86
|
|
|
87
|
+
function getPath(groups, value, defaultValue) {
|
|
88
|
+
if (!groups.length) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const path = [];
|
|
92
|
+
for (const grp of groups) {
|
|
93
|
+
const item = value[grp];
|
|
94
|
+
if (item === '') {
|
|
95
|
+
path.push(defaultValue);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
path.push(item);
|
|
99
|
+
}
|
|
100
|
+
return path.length ? path.join('.') : defaultValue;
|
|
101
|
+
}
|
|
102
|
+
|
|
28
103
|
/**
|
|
29
104
|
* @param {[]} values
|
|
30
105
|
* @param {string} grp
|
|
31
106
|
* @param {string} key
|
|
107
|
+
* @param {string} treeeLeafKey
|
|
32
108
|
* @param {string} [mainGrp]
|
|
33
109
|
* @param {*} [mainValue]
|
|
110
|
+
* @param {[]} groups
|
|
34
111
|
*/
|
|
35
|
-
function group(values, grp, key, mainGrp, mainValue) {
|
|
112
|
+
function group(values, grp, key, treeLeafKey, mainGrp, mainValue, groups = []) {
|
|
36
113
|
const tmp = Object.create(null);
|
|
37
114
|
const data = Object.create(null);
|
|
38
115
|
const ret = [];
|
|
39
|
-
let g, i, n
|
|
116
|
+
let g, i, n;
|
|
40
117
|
for (i = 0, n = values.length; i < n; ++i) {
|
|
41
|
-
v = values[i];
|
|
118
|
+
const v = values[i];
|
|
42
119
|
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
43
120
|
continue;
|
|
44
121
|
}
|
|
45
|
-
g = v[grp] || '';
|
|
122
|
+
g = v[grp] || v[treeLeafKey] || '';
|
|
46
123
|
if (!(g in tmp)) {
|
|
47
|
-
tmp[g] = 0;
|
|
124
|
+
tmp[g] = {value: 0};
|
|
48
125
|
data[g] = [];
|
|
49
126
|
}
|
|
50
|
-
tmp[g] += +v[key];
|
|
127
|
+
tmp[g].value += +v[key];
|
|
128
|
+
tmp[g].label = v[grp] || '';
|
|
129
|
+
tmp[g].path = getPath(groups, v, g);
|
|
51
130
|
data[g].push(v);
|
|
52
131
|
}
|
|
53
132
|
|
|
54
133
|
Object.keys(tmp).forEach((k) => {
|
|
55
|
-
v = {children: data[k]};
|
|
56
|
-
v[key] = +tmp[k];
|
|
57
|
-
v[grp] = k;
|
|
134
|
+
const v = {children: data[k]};
|
|
135
|
+
v[key] = +tmp[k].value;
|
|
136
|
+
v[grp] = tmp[k].label;
|
|
137
|
+
v.label = k;
|
|
138
|
+
v.path = tmp[k].path;
|
|
139
|
+
|
|
58
140
|
if (mainGrp) {
|
|
59
141
|
v[mainGrp] = mainValue;
|
|
60
142
|
}
|
|
@@ -64,11 +146,6 @@ function group(values, grp, key, mainGrp, mainValue) {
|
|
|
64
146
|
return ret;
|
|
65
147
|
}
|
|
66
148
|
|
|
67
|
-
function isObject(obj) {
|
|
68
|
-
const type = typeof obj;
|
|
69
|
-
return type === 'function' || type === 'object' && !!obj;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
149
|
function index(values, key) {
|
|
73
150
|
let n = values.length;
|
|
74
151
|
let i;
|
|
@@ -115,6 +192,387 @@ function requireVersion(min, ver) {
|
|
|
115
192
|
}
|
|
116
193
|
}
|
|
117
194
|
|
|
195
|
+
const widthCache = new Map();
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Helper function to get the bounds of the rect
|
|
199
|
+
* @param {TreemapElement} rect the rect
|
|
200
|
+
* @param {boolean} [useFinalPosition]
|
|
201
|
+
* @return {object} bounds of the rect
|
|
202
|
+
* @private
|
|
203
|
+
*/
|
|
204
|
+
function getBounds(rect, useFinalPosition) {
|
|
205
|
+
const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
206
|
+
return {left: x, top: y, right: x + width, bottom: y + height};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function limit(value, min, max) {
|
|
210
|
+
return Math.max(Math.min(value, max), min);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseBorderWidth(value, maxW, maxH) {
|
|
214
|
+
const o = toTRBL(value);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
t: limit(o.top, 0, maxH),
|
|
218
|
+
r: limit(o.right, 0, maxW),
|
|
219
|
+
b: limit(o.bottom, 0, maxH),
|
|
220
|
+
l: limit(o.left, 0, maxW)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function parseBorderRadius(value, maxW, maxH) {
|
|
225
|
+
const o = toTRBLCorners(value);
|
|
226
|
+
const maxR = Math.min(maxW, maxH);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
topLeft: limit(o.topLeft, 0, maxR),
|
|
230
|
+
topRight: limit(o.topRight, 0, maxR),
|
|
231
|
+
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
232
|
+
bottomRight: limit(o.bottomRight, 0, maxR)
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function boundingRects(rect) {
|
|
237
|
+
const bounds = getBounds(rect);
|
|
238
|
+
const width = bounds.right - bounds.left;
|
|
239
|
+
const height = bounds.bottom - bounds.top;
|
|
240
|
+
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
241
|
+
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
outer: {
|
|
245
|
+
x: bounds.left,
|
|
246
|
+
y: bounds.top,
|
|
247
|
+
w: width,
|
|
248
|
+
h: height,
|
|
249
|
+
radius
|
|
250
|
+
},
|
|
251
|
+
inner: {
|
|
252
|
+
x: bounds.left + border.l,
|
|
253
|
+
y: bounds.top + border.t,
|
|
254
|
+
w: width - border.l - border.r,
|
|
255
|
+
h: height - border.t - border.b,
|
|
256
|
+
radius: {
|
|
257
|
+
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
258
|
+
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
|
|
259
|
+
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
260
|
+
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function inRange(rect, x, y, useFinalPosition) {
|
|
267
|
+
const skipX = x === null;
|
|
268
|
+
const skipY = y === null;
|
|
269
|
+
const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
|
|
270
|
+
|
|
271
|
+
return bounds
|
|
272
|
+
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
273
|
+
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function hasRadius(radius) {
|
|
277
|
+
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Add a path of a rectangle to the current sub-path
|
|
282
|
+
* @param {CanvasRenderingContext2D} ctx Context
|
|
283
|
+
* @param {*} rect Bounding rect
|
|
284
|
+
*/
|
|
285
|
+
function addNormalRectPath(ctx, rect) {
|
|
286
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function shouldDrawCaption(rect, options) {
|
|
290
|
+
if (!options || !options.display) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const {w, h} = rect;
|
|
294
|
+
const font = toFont(options.font);
|
|
295
|
+
const min = font.lineHeight;
|
|
296
|
+
const padding = limit(valueOrDefault(options.padding, 3) * 2, 0, Math.min(w, h));
|
|
297
|
+
return (w - padding) > min && (h - padding) > min;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function drawText(ctx, rect, options, item, levels) {
|
|
301
|
+
const {captions, labels} = options;
|
|
302
|
+
ctx.save();
|
|
303
|
+
ctx.beginPath();
|
|
304
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
305
|
+
ctx.clip();
|
|
306
|
+
const isLeaf = (!('l' in item) || item.l === levels);
|
|
307
|
+
if (isLeaf && labels.display) {
|
|
308
|
+
drawLabel(ctx, rect, options);
|
|
309
|
+
} else if (!isLeaf && shouldDrawCaption(rect, captions)) {
|
|
310
|
+
drawCaption(ctx, rect, options, item);
|
|
311
|
+
}
|
|
312
|
+
ctx.restore();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function drawCaption(ctx, rect, options, item) {
|
|
316
|
+
const {captions, spacing, rtl} = options;
|
|
317
|
+
const {color, hoverColor, font, hoverFont, padding, align, formatter} = captions;
|
|
318
|
+
const oColor = (rect.active ? hoverColor : color) || color;
|
|
319
|
+
const oAlign = align || (rtl ? 'right' : 'left');
|
|
320
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
321
|
+
const oFont = toFont(optFont);
|
|
322
|
+
const lh = oFont.lineHeight / 2;
|
|
323
|
+
const x = calculateX(rect, oAlign, padding);
|
|
324
|
+
ctx.fillStyle = oColor;
|
|
325
|
+
ctx.font = oFont.string;
|
|
326
|
+
ctx.textAlign = oAlign;
|
|
327
|
+
ctx.textBaseline = 'middle';
|
|
328
|
+
ctx.fillText(formatter || item.g, x, rect.y + padding + spacing + lh);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function measureLabelSize(ctx, lines, fonts) {
|
|
332
|
+
const fontsKey = fonts.reduce(function(prev, item) {
|
|
333
|
+
prev += item.string;
|
|
334
|
+
return prev;
|
|
335
|
+
}, '');
|
|
336
|
+
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
337
|
+
if (!widthCache.has(mapKey)) {
|
|
338
|
+
ctx.save();
|
|
339
|
+
const count = lines.length;
|
|
340
|
+
let width = 0;
|
|
341
|
+
let height = 0;
|
|
342
|
+
for (let i = 0; i < count; i++) {
|
|
343
|
+
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
344
|
+
ctx.font = font.string;
|
|
345
|
+
const text = lines[i];
|
|
346
|
+
width = Math.max(width, ctx.measureText(text).width);
|
|
347
|
+
height += font.lineHeight;
|
|
348
|
+
}
|
|
349
|
+
ctx.restore();
|
|
350
|
+
widthCache.set(mapKey, {width, height});
|
|
351
|
+
}
|
|
352
|
+
return widthCache.get(mapKey);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function labelToDraw(ctx, rect, options, labelSize) {
|
|
356
|
+
const {overflow, padding} = options;
|
|
357
|
+
const {width, height} = labelSize;
|
|
358
|
+
if (overflow === 'hidden') {
|
|
359
|
+
return !((width + padding * 2) > rect.w || (height + padding * 2) > rect.h);
|
|
360
|
+
}
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function drawLabel(ctx, rect, options) {
|
|
365
|
+
const labels = options.labels;
|
|
366
|
+
const content = labels.formatter;
|
|
367
|
+
if (!content) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const contents = isArray(content) ? content : [content];
|
|
371
|
+
const {font, hoverFont} = labels;
|
|
372
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
373
|
+
const fonts = isArray(optFont) ? optFont.map(f => toFont(f)) : [toFont(optFont)];
|
|
374
|
+
const labelSize = measureLabelSize(ctx, contents, fonts);
|
|
375
|
+
if (!labelToDraw(ctx, rect, labels, labelSize)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const {color, hoverColor, align} = labels;
|
|
379
|
+
const optColor = (rect.active ? hoverColor : color) || color;
|
|
380
|
+
const colors = isArray(optColor) ? optColor : [optColor];
|
|
381
|
+
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
382
|
+
ctx.textAlign = align;
|
|
383
|
+
ctx.textBaseline = 'middle';
|
|
384
|
+
let lhs = 0;
|
|
385
|
+
contents.forEach(function(l, i) {
|
|
386
|
+
const c = colors[Math.min(i, colors.length - 1)];
|
|
387
|
+
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
388
|
+
const lh = f.lineHeight;
|
|
389
|
+
ctx.font = f.string;
|
|
390
|
+
ctx.fillStyle = c;
|
|
391
|
+
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
392
|
+
lhs += lh;
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function drawDivider(ctx, rect, options, item) {
|
|
397
|
+
const dividers = options.dividers;
|
|
398
|
+
if (!dividers.display || !item._data.children.length) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const {x, y, w, h} = rect;
|
|
402
|
+
const {lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth} = dividers;
|
|
403
|
+
ctx.save();
|
|
404
|
+
ctx.strokeStyle = lineColor;
|
|
405
|
+
ctx.lineCap = lineCapStyle;
|
|
406
|
+
ctx.setLineDash(lineDash);
|
|
407
|
+
ctx.lineDashOffset = lineDashOffset;
|
|
408
|
+
ctx.lineWidth = lineWidth;
|
|
409
|
+
ctx.beginPath();
|
|
410
|
+
if (w > h) {
|
|
411
|
+
const w2 = w / 2;
|
|
412
|
+
ctx.moveTo(x + w2, y);
|
|
413
|
+
ctx.lineTo(x + w2, y + h);
|
|
414
|
+
} else {
|
|
415
|
+
const h2 = h / 2;
|
|
416
|
+
ctx.moveTo(x, y + h2);
|
|
417
|
+
ctx.lineTo(x + w, y + h2);
|
|
418
|
+
}
|
|
419
|
+
ctx.stroke();
|
|
420
|
+
ctx.restore();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function calculateXYLabel(rect, options, labelSize) {
|
|
424
|
+
const {align, position, padding} = options;
|
|
425
|
+
let x, y;
|
|
426
|
+
x = calculateX(rect, align, padding);
|
|
427
|
+
if (position === 'top') {
|
|
428
|
+
y = rect.y + padding;
|
|
429
|
+
} else if (position === 'bottom') {
|
|
430
|
+
y = rect.y + rect.h - padding - labelSize.height;
|
|
431
|
+
} else {
|
|
432
|
+
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
433
|
+
}
|
|
434
|
+
return {x, y};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function calculateX(rect, align, padding) {
|
|
438
|
+
if (align === 'left') {
|
|
439
|
+
return rect.x + padding;
|
|
440
|
+
} else if (align === 'right') {
|
|
441
|
+
return rect.x + rect.w - padding;
|
|
442
|
+
}
|
|
443
|
+
return rect.x + rect.w / 2;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
class TreemapElement extends Element {
|
|
447
|
+
|
|
448
|
+
constructor(cfg) {
|
|
449
|
+
super();
|
|
450
|
+
|
|
451
|
+
this.options = undefined;
|
|
452
|
+
this.width = undefined;
|
|
453
|
+
this.height = undefined;
|
|
454
|
+
|
|
455
|
+
if (cfg) {
|
|
456
|
+
Object.assign(this, cfg);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
draw(ctx, data, levels = 0) {
|
|
461
|
+
if (!data) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const options = this.options;
|
|
465
|
+
const {inner, outer} = boundingRects(this);
|
|
466
|
+
|
|
467
|
+
const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
|
|
468
|
+
|
|
469
|
+
ctx.save();
|
|
470
|
+
|
|
471
|
+
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
472
|
+
ctx.beginPath();
|
|
473
|
+
addRectPath(ctx, outer);
|
|
474
|
+
ctx.clip();
|
|
475
|
+
addRectPath(ctx, inner);
|
|
476
|
+
ctx.fillStyle = options.borderColor;
|
|
477
|
+
ctx.fill('evenodd');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
ctx.beginPath();
|
|
481
|
+
addRectPath(ctx, inner);
|
|
482
|
+
ctx.fillStyle = options.backgroundColor;
|
|
483
|
+
ctx.fill();
|
|
484
|
+
|
|
485
|
+
drawDivider(ctx, inner, options, data);
|
|
486
|
+
drawText(ctx, inner, options, data, levels);
|
|
487
|
+
ctx.restore();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
inRange(mouseX, mouseY, useFinalPosition) {
|
|
491
|
+
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
inXRange(mouseX, useFinalPosition) {
|
|
495
|
+
return inRange(this, mouseX, null, useFinalPosition);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
inYRange(mouseY, useFinalPosition) {
|
|
499
|
+
return inRange(this, null, mouseY, useFinalPosition);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
getCenterPoint(useFinalPosition) {
|
|
503
|
+
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
504
|
+
return {
|
|
505
|
+
x: x + width / 2,
|
|
506
|
+
y: y + height / 2
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
tooltipPosition() {
|
|
511
|
+
return this.getCenterPoint();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
getRange(axis) {
|
|
515
|
+
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
TreemapElement.id = 'treemap';
|
|
520
|
+
|
|
521
|
+
TreemapElement.defaults = {
|
|
522
|
+
label: undefined,
|
|
523
|
+
borderRadius: 0,
|
|
524
|
+
borderWidth: 0,
|
|
525
|
+
captions: {
|
|
526
|
+
align: undefined,
|
|
527
|
+
color: 'black',
|
|
528
|
+
display: true,
|
|
529
|
+
font: {},
|
|
530
|
+
formatter: (ctx) => ctx.raw.g || ctx.raw._data.label || '',
|
|
531
|
+
padding: 3
|
|
532
|
+
},
|
|
533
|
+
dividers: {
|
|
534
|
+
display: false,
|
|
535
|
+
lineCapStyle: 'butt',
|
|
536
|
+
lineColor: 'black',
|
|
537
|
+
lineDash: [],
|
|
538
|
+
lineDashOffset: 0,
|
|
539
|
+
lineWidth: 1,
|
|
540
|
+
},
|
|
541
|
+
labels: {
|
|
542
|
+
align: 'center',
|
|
543
|
+
color: 'black',
|
|
544
|
+
display: false,
|
|
545
|
+
font: {},
|
|
546
|
+
formatter(ctx) {
|
|
547
|
+
if (ctx.raw.g) {
|
|
548
|
+
return [ctx.raw.g, ctx.raw.v + ''];
|
|
549
|
+
}
|
|
550
|
+
return ctx.raw._data.label ? [ctx.raw._data.label, ctx.raw.v + ''] : ctx.raw.v + '';
|
|
551
|
+
},
|
|
552
|
+
overflow: 'cut',
|
|
553
|
+
position: 'middle',
|
|
554
|
+
padding: 3
|
|
555
|
+
},
|
|
556
|
+
rtl: false,
|
|
557
|
+
spacing: 0.5
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
TreemapElement.descriptors = {
|
|
561
|
+
labels: {
|
|
562
|
+
_fallback: true
|
|
563
|
+
},
|
|
564
|
+
captions: {
|
|
565
|
+
_fallback: true
|
|
566
|
+
},
|
|
567
|
+
_scriptable: true,
|
|
568
|
+
_indexable: false
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
TreemapElement.defaultRoutes = {
|
|
572
|
+
backgroundColor: 'backgroundColor',
|
|
573
|
+
borderColor: 'borderColor'
|
|
574
|
+
};
|
|
575
|
+
|
|
118
576
|
function round(v, n) {
|
|
119
577
|
// @ts-ignore
|
|
120
578
|
return (+(Math.round(v + 'e+' + n) + 'e-' + n)) || 0;
|
|
@@ -353,7 +811,7 @@ function squarify(values, rectangle, key, grp, lvl, gsum) {
|
|
|
353
811
|
return flatten(rows);
|
|
354
812
|
}
|
|
355
813
|
|
|
356
|
-
var version = "2.0
|
|
814
|
+
var version = "2.1.0";
|
|
357
815
|
|
|
358
816
|
function rectNotEqual(r1, r2) {
|
|
359
817
|
return !r1 || !r2
|
|
@@ -378,94 +836,38 @@ function arrayNotEqual(a1, a2) {
|
|
|
378
836
|
return false;
|
|
379
837
|
}
|
|
380
838
|
|
|
381
|
-
function
|
|
382
|
-
if (!font) {
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
|
-
const w = rect.width || rect.w;
|
|
386
|
-
const h = rect.height || rect.h;
|
|
387
|
-
const min = font.lineHeight * 2;
|
|
388
|
-
return w > min && h > min;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function drawCaption(ctx, rect, item, opts, levels) {
|
|
392
|
-
ctx.save();
|
|
393
|
-
ctx.beginPath();
|
|
394
|
-
ctx.rect(rect.x, rect.y, rect.width, rect.height);
|
|
395
|
-
ctx.clip();
|
|
396
|
-
if (!('l' in item) || item.l === levels) {
|
|
397
|
-
drawLabels(ctx, item, rect);
|
|
398
|
-
} else if (opts.captions && opts.captions.display) {
|
|
399
|
-
drawCaptionLabel(ctx, item, rect);
|
|
400
|
-
}
|
|
401
|
-
ctx.restore();
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function drawCaptionLabel(ctx, item, rect) {
|
|
405
|
-
const opts = rect.options;
|
|
406
|
-
const captionsOpts = opts.captions || {};
|
|
407
|
-
const borderWidth = opts.borderWidth || 0;
|
|
408
|
-
const spacing = valueOrDefault(opts.spacing, 0) + borderWidth;
|
|
409
|
-
const color = (rect.active ? captionsOpts.hoverColor : captionsOpts.color) || captionsOpts.color;
|
|
410
|
-
const padding = captionsOpts.padding;
|
|
411
|
-
const align = captionsOpts.align || (opts.rtl ? 'right' : 'left');
|
|
412
|
-
const optFont = (rect.active ? captionsOpts.hoverFont : captionsOpts.font) || captionsOpts.font;
|
|
413
|
-
const font = toFont(optFont);
|
|
414
|
-
const x = calculateX(rect, align, padding, borderWidth);
|
|
415
|
-
ctx.fillStyle = color;
|
|
416
|
-
ctx.font = font.string;
|
|
417
|
-
ctx.textAlign = align;
|
|
418
|
-
ctx.textBaseline = 'middle';
|
|
419
|
-
ctx.fillText(captionsOpts.formatter || item.g, x, rect.y + padding + spacing + (font.lineHeight / 2));
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function drawDivider(ctx, rect) {
|
|
423
|
-
const opts = rect.options;
|
|
424
|
-
const dividersOpts = opts.dividers || {};
|
|
425
|
-
const w = rect.width || rect.w;
|
|
426
|
-
const h = rect.height || rect.h;
|
|
427
|
-
|
|
428
|
-
ctx.save();
|
|
429
|
-
ctx.strokeStyle = dividersOpts.lineColor || 'black';
|
|
430
|
-
ctx.lineCap = dividersOpts.lineCapStyle;
|
|
431
|
-
ctx.setLineDash(dividersOpts.lineDash || []);
|
|
432
|
-
ctx.lineDashOffset = dividersOpts.lineDashOffset;
|
|
433
|
-
ctx.lineWidth = dividersOpts.lineWidth;
|
|
434
|
-
ctx.beginPath();
|
|
435
|
-
if (w > h) {
|
|
436
|
-
const w2 = w / 2;
|
|
437
|
-
ctx.moveTo(rect.x + w2, rect.y);
|
|
438
|
-
ctx.lineTo(rect.x + w2, rect.y + h);
|
|
439
|
-
} else {
|
|
440
|
-
const h2 = h / 2;
|
|
441
|
-
ctx.moveTo(rect.x, rect.y + h2);
|
|
442
|
-
ctx.lineTo(rect.x + w, rect.y + h2);
|
|
443
|
-
}
|
|
444
|
-
ctx.stroke();
|
|
445
|
-
ctx.restore();
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function buildData(dataset, mainRect, captions) {
|
|
839
|
+
function buildData(dataset, mainRect) {
|
|
449
840
|
const key = dataset.key || '';
|
|
841
|
+
const treeLeafKey = dataset.treeLeafKey || '_leaf';
|
|
450
842
|
let tree = dataset.tree || [];
|
|
843
|
+
if (isObject(tree)) {
|
|
844
|
+
tree = normalizeTreeToArray(key, treeLeafKey, tree);
|
|
845
|
+
}
|
|
451
846
|
const groups = dataset.groups || [];
|
|
452
847
|
const glen = groups.length;
|
|
453
|
-
const sp = valueOrDefault(dataset.spacing, 0)
|
|
454
|
-
const
|
|
455
|
-
const font = toFont(
|
|
848
|
+
const sp = valueOrDefault(dataset.spacing, 0);
|
|
849
|
+
const captions = dataset.captions || {display: true};
|
|
850
|
+
const font = toFont(captions.font);
|
|
456
851
|
const padding = valueOrDefault(captions.padding, 3);
|
|
457
852
|
|
|
458
853
|
function recur(gidx, rect, parent, gs) {
|
|
459
|
-
const g = groups[gidx];
|
|
460
|
-
const pg = (gidx > 0) && groups[gidx - 1];
|
|
461
|
-
const gdata = group(tree, g, key, pg, parent);
|
|
854
|
+
const g = getGroupKey(groups[gidx]);
|
|
855
|
+
const pg = (gidx > 0) && getGroupKey(groups[gidx - 1]);
|
|
856
|
+
const gdata = group(tree, g, key, treeLeafKey, pg, parent, groups.filter((item, index) => index <= gidx));
|
|
462
857
|
const gsq = squarify(gdata, rect, key, g, gidx, gs);
|
|
463
858
|
const ret = gsq.slice();
|
|
464
859
|
let subRect;
|
|
465
860
|
if (gidx < glen - 1) {
|
|
466
861
|
gsq.forEach((sq) => {
|
|
467
|
-
|
|
468
|
-
|
|
862
|
+
const bw = parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
863
|
+
subRect = {
|
|
864
|
+
x: sq.x + sp + bw.l,
|
|
865
|
+
y: sq.y + sp + bw.t,
|
|
866
|
+
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
867
|
+
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
868
|
+
rtl: rect.rtl
|
|
869
|
+
};
|
|
870
|
+
if (shouldDrawCaption(subRect, captions)) {
|
|
469
871
|
subRect.y += font.lineHeight + padding * 2;
|
|
470
872
|
subRect.h -= font.lineHeight + padding * 2;
|
|
471
873
|
}
|
|
@@ -484,51 +886,19 @@ function buildData(dataset, mainRect, captions) {
|
|
|
484
886
|
: squarify(tree, mainRect, key);
|
|
485
887
|
}
|
|
486
888
|
|
|
487
|
-
function
|
|
488
|
-
const
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
const optColor = (rect.active ? labelsOpts.hoverColor : labelsOpts.color) || labelsOpts.color;
|
|
494
|
-
const optFont = (rect.active ? labelsOpts.hoverFont : labelsOpts.font) || labelsOpts.font;
|
|
495
|
-
const font = toFont(optFont);
|
|
496
|
-
const lh = font.lineHeight;
|
|
497
|
-
const label = labelsOpts.formatter;
|
|
498
|
-
if (label) {
|
|
499
|
-
const labels = isArray(label) ? label : [label];
|
|
500
|
-
const xyPoint = calculateXYLabel(opts, rect, labels, lh);
|
|
501
|
-
ctx.font = font.string;
|
|
502
|
-
ctx.textAlign = labelsOpts.align;
|
|
503
|
-
ctx.textBaseline = labelsOpts.position;
|
|
504
|
-
ctx.fillStyle = optColor;
|
|
505
|
-
labels.forEach((l, i) => ctx.fillText(l, xyPoint.x, xyPoint.y + i * lh));
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function calculateXYLabel(options, rect, labels, lineHeight) {
|
|
510
|
-
const labelsOpts = options.labels;
|
|
511
|
-
const borderWidth = options.borderWidth || 0;
|
|
512
|
-
const {align, position, padding} = labelsOpts;
|
|
513
|
-
let x, y;
|
|
514
|
-
x = calculateX(rect, align, padding, borderWidth);
|
|
515
|
-
if (position === 'top') {
|
|
516
|
-
y = rect.y + padding + borderWidth;
|
|
517
|
-
} else if (position === 'bottom') {
|
|
518
|
-
y = rect.y + rect.height - padding - borderWidth - (labels.length - 1) * lineHeight;
|
|
519
|
-
} else {
|
|
520
|
-
y = rect.y + rect.height / 2 - labels.length * lineHeight / 4;
|
|
521
|
-
}
|
|
522
|
-
return {x, y};
|
|
889
|
+
function getMinMax(data, useTree) {
|
|
890
|
+
const vMax = useTree ? 1 : maxValue(data);
|
|
891
|
+
const vMin = useTree ? 0 : minValue(data, vMax);
|
|
892
|
+
return {vMin, vMax};
|
|
523
893
|
}
|
|
524
894
|
|
|
525
|
-
function
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
return
|
|
895
|
+
function getArea({xScale, yScale}, data, rtl, useTree) {
|
|
896
|
+
const {vMin, vMax} = getMinMax(data, useTree);
|
|
897
|
+
const xMin = xScale.getPixelForValue(0);
|
|
898
|
+
const xMax = xScale.getPixelForValue(1);
|
|
899
|
+
const yMin = yScale.getPixelForValue(vMin);
|
|
900
|
+
const yMax = yScale.getPixelForValue(vMax);
|
|
901
|
+
return {x: xMin, y: yMax, w: xMax - xMin, h: yMin - yMax, rtl};
|
|
532
902
|
}
|
|
533
903
|
|
|
534
904
|
class TreemapController extends DatasetController {
|
|
@@ -538,6 +908,7 @@ class TreemapController extends DatasetController {
|
|
|
538
908
|
this._rect = undefined;
|
|
539
909
|
this._key = undefined;
|
|
540
910
|
this._groups = undefined;
|
|
911
|
+
this._useTree = undefined;
|
|
541
912
|
}
|
|
542
913
|
|
|
543
914
|
initialize() {
|
|
@@ -545,24 +916,46 @@ class TreemapController extends DatasetController {
|
|
|
545
916
|
super.initialize();
|
|
546
917
|
}
|
|
547
918
|
|
|
919
|
+
/**
|
|
920
|
+
* TODO: to be removed when https://github.com/kurkle/chartjs-chart-treemap/issues/137
|
|
921
|
+
* will be implemented
|
|
922
|
+
*/
|
|
923
|
+
updateRangeFromParsed(range, scale) {
|
|
924
|
+
if (range.updated) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
range.updated = true;
|
|
928
|
+
if (scale.axis === 'x') {
|
|
929
|
+
range.min = 0;
|
|
930
|
+
range.max = 1;
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const me = this;
|
|
934
|
+
const dataset = me.getDataset();
|
|
935
|
+
const {vMin, vMax} = getMinMax(dataset.data, me._useTree);
|
|
936
|
+
range.min = vMin;
|
|
937
|
+
range.max = vMax;
|
|
938
|
+
}
|
|
939
|
+
|
|
548
940
|
update(mode) {
|
|
549
941
|
const me = this;
|
|
550
942
|
const meta = me.getMeta();
|
|
551
943
|
const dataset = me.getDataset();
|
|
944
|
+
if (!defined(me._useTree)) {
|
|
945
|
+
me._useTree = !!dataset.tree;
|
|
946
|
+
}
|
|
552
947
|
const groups = dataset.groups || (dataset.groups = []);
|
|
553
|
-
const captions = dataset.captions ? dataset.captions : {};
|
|
554
|
-
const area = me.chart.chartArea;
|
|
555
948
|
const key = dataset.key || '';
|
|
556
949
|
const rtl = !!dataset.rtl;
|
|
557
950
|
|
|
558
|
-
const mainRect =
|
|
951
|
+
const mainRect = getArea(meta, dataset.data, rtl, me._useTree);
|
|
559
952
|
|
|
560
953
|
if (mode === 'reset' || rectNotEqual(me._rect, mainRect) || me._key !== key || arrayNotEqual(me._groups, groups)) {
|
|
561
954
|
me._rect = mainRect;
|
|
562
955
|
me._groups = groups.slice();
|
|
563
956
|
me._key = key;
|
|
564
957
|
|
|
565
|
-
dataset.data = buildData(dataset, mainRect
|
|
958
|
+
dataset.data = buildData(dataset, mainRect);
|
|
566
959
|
// @ts-ignore using private stuff
|
|
567
960
|
me._dataCheck();
|
|
568
961
|
// @ts-ignore using private stuff
|
|
@@ -572,13 +965,6 @@ class TreemapController extends DatasetController {
|
|
|
572
965
|
me.updateElements(meta.data, 0, meta.data.length, mode);
|
|
573
966
|
}
|
|
574
967
|
|
|
575
|
-
resolveDataElementOptions(index, mode) {
|
|
576
|
-
const options = super.resolveDataElementOptions(index, mode);
|
|
577
|
-
const result = Object.isFrozen(options) ? Object.assign({}, options) : options;
|
|
578
|
-
result.font = toFont(options.captions.font);
|
|
579
|
-
return result;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
968
|
updateElements(rects, start, count, mode) {
|
|
583
969
|
const me = this;
|
|
584
970
|
const reset = mode === 'reset';
|
|
@@ -609,41 +995,23 @@ class TreemapController extends DatasetController {
|
|
|
609
995
|
me.updateSharedOptions(sharedOptions, mode, firstOpts);
|
|
610
996
|
}
|
|
611
997
|
|
|
612
|
-
_drawDividers(ctx, data, metadata) {
|
|
613
|
-
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
614
|
-
const rect = metadata[i];
|
|
615
|
-
const item = data[i];
|
|
616
|
-
const dividersOpts = rect.options.dividers || {};
|
|
617
|
-
if (dividersOpts.display && item._data.children.length > 1) {
|
|
618
|
-
drawDivider(ctx, rect);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
_drawRects(ctx, data, metadata, levels) {
|
|
624
|
-
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
625
|
-
const rect = metadata[i];
|
|
626
|
-
const item = data[i];
|
|
627
|
-
if (!rect.hidden) {
|
|
628
|
-
rect.draw(ctx);
|
|
629
|
-
const opts = rect.options;
|
|
630
|
-
if (shouldDrawCaption(rect, opts.captions.font)) {
|
|
631
|
-
drawCaption(ctx, rect, item, opts, levels);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
998
|
draw() {
|
|
638
999
|
const me = this;
|
|
639
1000
|
const ctx = me.chart.ctx;
|
|
1001
|
+
const area = me.chart.chartArea;
|
|
640
1002
|
const metadata = me.getMeta().data || [];
|
|
641
1003
|
const dataset = me.getDataset();
|
|
642
1004
|
const levels = (dataset.groups || []).length - 1;
|
|
643
|
-
const data = dataset.data
|
|
1005
|
+
const data = dataset.data;
|
|
644
1006
|
|
|
645
|
-
|
|
646
|
-
|
|
1007
|
+
clipArea(ctx, area);
|
|
1008
|
+
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
1009
|
+
const rect = metadata[i];
|
|
1010
|
+
if (!rect.hidden) {
|
|
1011
|
+
rect.draw(ctx, data[i], levels);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
unclipArea(ctx);
|
|
647
1015
|
}
|
|
648
1016
|
}
|
|
649
1017
|
|
|
@@ -661,13 +1029,6 @@ TreemapController.defaults = {
|
|
|
661
1029
|
},
|
|
662
1030
|
},
|
|
663
1031
|
|
|
664
|
-
borderWidth: 0,
|
|
665
|
-
spacing: 0.5,
|
|
666
|
-
dividers: {
|
|
667
|
-
display: false,
|
|
668
|
-
lineWidth: 1,
|
|
669
|
-
}
|
|
670
|
-
|
|
671
1032
|
};
|
|
672
1033
|
|
|
673
1034
|
TreemapController.descriptors = {
|
|
@@ -698,7 +1059,7 @@ TreemapController.overrides = {
|
|
|
698
1059
|
label(item) {
|
|
699
1060
|
const dataset = item.dataset;
|
|
700
1061
|
const dataItem = dataset.data[item.dataIndex];
|
|
701
|
-
const label = dataItem.g || dataset.label;
|
|
1062
|
+
const label = dataItem.g || dataItem._data.label || dataset.label;
|
|
702
1063
|
return (label ? label + ': ' : '') + dataItem.v;
|
|
703
1064
|
}
|
|
704
1065
|
}
|
|
@@ -733,6 +1094,8 @@ TreemapController.afterRegister = function() {
|
|
|
733
1094
|
|
|
734
1095
|
return el.tooltipPosition();
|
|
735
1096
|
};
|
|
1097
|
+
} else {
|
|
1098
|
+
console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
|
|
736
1099
|
}
|
|
737
1100
|
};
|
|
738
1101
|
|
|
@@ -743,181 +1106,4 @@ TreemapController.afterUnregister = function() {
|
|
|
743
1106
|
}
|
|
744
1107
|
};
|
|
745
1108
|
|
|
746
|
-
/**
|
|
747
|
-
* Helper function to get the bounds of the rect
|
|
748
|
-
* @param {TreemapElement} rect the rect
|
|
749
|
-
* @param {boolean} [useFinalPosition]
|
|
750
|
-
* @return {object} bounds of the rect
|
|
751
|
-
* @private
|
|
752
|
-
*/
|
|
753
|
-
function getBounds(rect, useFinalPosition) {
|
|
754
|
-
const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
755
|
-
return {left: x, top: y, right: x + width, bottom: y + height};
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function limit(value, min, max) {
|
|
759
|
-
return Math.max(Math.min(value, max), min);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
function parseBorderWidth(value, maxW, maxH) {
|
|
763
|
-
let t, r, b, l;
|
|
764
|
-
|
|
765
|
-
if (isObject(value)) {
|
|
766
|
-
t = +value.top || 0;
|
|
767
|
-
r = +value.right || 0;
|
|
768
|
-
b = +value.bottom || 0;
|
|
769
|
-
l = +value.left || 0;
|
|
770
|
-
} else {
|
|
771
|
-
t = r = b = l = +value || 0;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
return {
|
|
775
|
-
t: limit(t, 0, maxH),
|
|
776
|
-
r: limit(r, 0, maxW),
|
|
777
|
-
b: limit(b, 0, maxH),
|
|
778
|
-
l: limit(l, 0, maxW)
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
function boundingRects(rect) {
|
|
783
|
-
const bounds = getBounds(rect);
|
|
784
|
-
const width = bounds.right - bounds.left;
|
|
785
|
-
const height = bounds.bottom - bounds.top;
|
|
786
|
-
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
787
|
-
|
|
788
|
-
return {
|
|
789
|
-
outer: {
|
|
790
|
-
x: bounds.left,
|
|
791
|
-
y: bounds.top,
|
|
792
|
-
w: width,
|
|
793
|
-
h: height
|
|
794
|
-
},
|
|
795
|
-
inner: {
|
|
796
|
-
x: bounds.left + border.l,
|
|
797
|
-
y: bounds.top + border.t,
|
|
798
|
-
w: width - border.l - border.r,
|
|
799
|
-
h: height - border.t - border.b
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function inRange(rect, x, y, useFinalPosition) {
|
|
805
|
-
const skipX = x === null;
|
|
806
|
-
const skipY = y === null;
|
|
807
|
-
const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
|
|
808
|
-
|
|
809
|
-
return bounds
|
|
810
|
-
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
811
|
-
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
class TreemapElement extends Element {
|
|
815
|
-
|
|
816
|
-
constructor(cfg) {
|
|
817
|
-
super();
|
|
818
|
-
|
|
819
|
-
this.options = undefined;
|
|
820
|
-
this.width = undefined;
|
|
821
|
-
this.height = undefined;
|
|
822
|
-
|
|
823
|
-
if (cfg) {
|
|
824
|
-
Object.assign(this, cfg);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
draw(ctx) {
|
|
829
|
-
const options = this.options;
|
|
830
|
-
const {inner, outer} = boundingRects(this);
|
|
831
|
-
|
|
832
|
-
ctx.save();
|
|
833
|
-
|
|
834
|
-
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
835
|
-
ctx.beginPath();
|
|
836
|
-
ctx.rect(outer.x, outer.y, outer.w, outer.h);
|
|
837
|
-
ctx.clip();
|
|
838
|
-
ctx.rect(inner.x, inner.y, inner.w, inner.h);
|
|
839
|
-
ctx.fillStyle = options.backgroundColor;
|
|
840
|
-
ctx.fill();
|
|
841
|
-
ctx.fillStyle = options.borderColor;
|
|
842
|
-
ctx.fill('evenodd');
|
|
843
|
-
} else {
|
|
844
|
-
ctx.fillStyle = options.backgroundColor;
|
|
845
|
-
ctx.fillRect(inner.x, inner.y, inner.w, inner.h);
|
|
846
|
-
}
|
|
847
|
-
ctx.restore();
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
inRange(mouseX, mouseY, useFinalPosition) {
|
|
851
|
-
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
inXRange(mouseX, useFinalPosition) {
|
|
855
|
-
return inRange(this, mouseX, null, useFinalPosition);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
inYRange(mouseY, useFinalPosition) {
|
|
859
|
-
return inRange(this, null, mouseY, useFinalPosition);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
getCenterPoint(useFinalPosition) {
|
|
863
|
-
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
864
|
-
return {
|
|
865
|
-
x: x + width / 2,
|
|
866
|
-
y: y + height / 2
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
tooltipPosition() {
|
|
871
|
-
return this.getCenterPoint();
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
getRange(axis) {
|
|
875
|
-
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
TreemapElement.id = 'treemap';
|
|
880
|
-
|
|
881
|
-
TreemapElement.defaults = {
|
|
882
|
-
borderWidth: undefined,
|
|
883
|
-
spacing: undefined,
|
|
884
|
-
label: undefined,
|
|
885
|
-
rtl: undefined,
|
|
886
|
-
dividers: {
|
|
887
|
-
display: false,
|
|
888
|
-
lineCapStyle: 'butt',
|
|
889
|
-
lineColor: 'black',
|
|
890
|
-
lineDash: undefined,
|
|
891
|
-
lineDashOffset: 0,
|
|
892
|
-
lineWidth: 0,
|
|
893
|
-
},
|
|
894
|
-
captions: {
|
|
895
|
-
align: undefined,
|
|
896
|
-
color: undefined,
|
|
897
|
-
display: true,
|
|
898
|
-
formatter: (ctx) => ctx.raw.g || '',
|
|
899
|
-
font: {},
|
|
900
|
-
padding: 3
|
|
901
|
-
},
|
|
902
|
-
labels: {
|
|
903
|
-
align: 'center',
|
|
904
|
-
color: undefined,
|
|
905
|
-
display: false,
|
|
906
|
-
formatter: (ctx) => ctx.raw.g ? [ctx.raw.g, ctx.raw.v] : ctx.raw.v,
|
|
907
|
-
font: {},
|
|
908
|
-
position: 'middle',
|
|
909
|
-
padding: 3
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
|
|
913
|
-
TreemapElement.descriptors = {
|
|
914
|
-
_scriptable: true,
|
|
915
|
-
_indexable: false
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
TreemapElement.defaultRoutes = {
|
|
919
|
-
backgroundColor: 'backgroundColor',
|
|
920
|
-
borderColor: 'borderColor'
|
|
921
|
-
};
|
|
922
|
-
|
|
923
1109
|
export { TreemapController, TreemapElement };
|