chartjs-chart-treemap 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chartjs-chart-treemap.cjs +586 -513
- package/dist/chartjs-chart-treemap.esm.js +588 -521
- package/dist/chartjs-chart-treemap.min.js +1 -1
- package/dist/chartjs-chart-treemap.min.js.map +1 -1
- package/dist/element.d.ts +63 -16
- package/dist/geometry.d.ts +38 -0
- package/dist/options.d.ts +9 -0
- package/dist/text.d.ts +20 -0
- package/dist/types.d.ts +57 -21
- package/dist/utils.d.ts +1 -1
- package/package.json +1 -1
|
@@ -12,440 +12,6 @@ typeof define === 'function' && define.amd ? define(['exports', 'chart.js', 'cha
|
|
|
12
12
|
|
|
13
13
|
var version = "0.0.0-development";
|
|
14
14
|
|
|
15
|
-
const widthCache = new Map();
|
|
16
|
-
function getBounds(rect, useFinalPosition) {
|
|
17
|
-
const { x, y, width, height } = rect.getProps([
|
|
18
|
-
'x',
|
|
19
|
-
'y',
|
|
20
|
-
'width',
|
|
21
|
-
'height'
|
|
22
|
-
], useFinalPosition);
|
|
23
|
-
return {
|
|
24
|
-
bottom: y + height,
|
|
25
|
-
left: x,
|
|
26
|
-
right: x + width,
|
|
27
|
-
top: y
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function limit(value, min, max) {
|
|
31
|
-
return Math.max(Math.min(value, max), min);
|
|
32
|
-
}
|
|
33
|
-
function parseBorderWidth(value, maxW, maxH) {
|
|
34
|
-
const o = helpers.toTRBL(value);
|
|
35
|
-
return {
|
|
36
|
-
b: limit(o.bottom, 0, maxH),
|
|
37
|
-
l: limit(o.left, 0, maxW),
|
|
38
|
-
r: limit(o.right, 0, maxW),
|
|
39
|
-
t: limit(o.top, 0, maxH)
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function parseBorderRadius(value, maxW, maxH) {
|
|
43
|
-
const o = helpers.toTRBLCorners(value);
|
|
44
|
-
const maxR = Math.min(maxW, maxH);
|
|
45
|
-
return {
|
|
46
|
-
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
47
|
-
bottomRight: limit(o.bottomRight, 0, maxR),
|
|
48
|
-
topLeft: limit(o.topLeft, 0, maxR),
|
|
49
|
-
topRight: limit(o.topRight, 0, maxR)
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function boundingRects(rect) {
|
|
53
|
-
const bounds = getBounds(rect);
|
|
54
|
-
const width = bounds.right - bounds.left;
|
|
55
|
-
const height = bounds.bottom - bounds.top;
|
|
56
|
-
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
57
|
-
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
58
|
-
const outer = {
|
|
59
|
-
active: rect.active,
|
|
60
|
-
h: height,
|
|
61
|
-
radius,
|
|
62
|
-
w: width,
|
|
63
|
-
x: bounds.left,
|
|
64
|
-
y: bounds.top
|
|
65
|
-
};
|
|
66
|
-
return {
|
|
67
|
-
inner: {
|
|
68
|
-
active: rect.active,
|
|
69
|
-
h: outer.h - border.t - border.b,
|
|
70
|
-
radius: {
|
|
71
|
-
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
72
|
-
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
73
|
-
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
74
|
-
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r))
|
|
75
|
-
},
|
|
76
|
-
w: outer.w - border.l - border.r,
|
|
77
|
-
x: outer.x + border.l,
|
|
78
|
-
y: outer.y + border.t
|
|
79
|
-
},
|
|
80
|
-
outer
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function inRange(rect, x, y, useFinalPosition) {
|
|
84
|
-
const skipX = x === null;
|
|
85
|
-
const skipY = y === null;
|
|
86
|
-
const bounds = !rect || skipX && skipY ? false : getBounds(rect, useFinalPosition);
|
|
87
|
-
return bounds && (skipX || x >= bounds.left && x <= bounds.right) && (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
88
|
-
}
|
|
89
|
-
function hasRadius(radius) {
|
|
90
|
-
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
91
|
-
}
|
|
92
|
-
function addNormalRectPath(ctx, rect) {
|
|
93
|
-
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
94
|
-
}
|
|
95
|
-
function shouldDrawCaption(displayMode, rect, options) {
|
|
96
|
-
if (!options || options.display === false) {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
if (displayMode === 'headerBoxes') {
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
const { w, h } = rect;
|
|
103
|
-
const font = helpers.toFont(options.font);
|
|
104
|
-
const min = font.lineHeight;
|
|
105
|
-
const padding = limit(helpers.valueOrDefault(options.padding, 3) * 2, 0, Math.min(w, h));
|
|
106
|
-
return w - padding > min && h - padding > min;
|
|
107
|
-
}
|
|
108
|
-
function getCaptionHeight(displayMode, rect, font, padding) {
|
|
109
|
-
if (displayMode !== 'headerBoxes') {
|
|
110
|
-
return font.lineHeight + padding * 2;
|
|
111
|
-
}
|
|
112
|
-
const captionHeight = font.lineHeight + padding * 2;
|
|
113
|
-
return rect.h < 2 * captionHeight ? rect.h / 3 : captionHeight;
|
|
114
|
-
}
|
|
115
|
-
function drawText(ctx, rect, options, item) {
|
|
116
|
-
const { captions, labels, displayMode } = options;
|
|
117
|
-
ctx.save();
|
|
118
|
-
ctx.beginPath();
|
|
119
|
-
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
120
|
-
ctx.clip();
|
|
121
|
-
const isLeaf = item && (!helpers.defined(item.l) || item.isLeaf);
|
|
122
|
-
if (isLeaf && labels.display) {
|
|
123
|
-
drawLabel(ctx, rect, options);
|
|
124
|
-
} else if (!isLeaf && shouldDrawCaption(displayMode, rect, captions)) {
|
|
125
|
-
drawCaption(ctx, rect, options, item);
|
|
126
|
-
}
|
|
127
|
-
ctx.restore();
|
|
128
|
-
}
|
|
129
|
-
function drawCaption(ctx, rect, options, item) {
|
|
130
|
-
const { captions, spacing, rtl, displayMode } = options;
|
|
131
|
-
const { color, hoverColor, font, hoverFont, padding, align, formatter } = captions;
|
|
132
|
-
const oColor = (rect.active ? hoverColor : color) || color;
|
|
133
|
-
const oAlign = align || (rtl ? 'right' : 'left');
|
|
134
|
-
const optFont = (rect.active ? hoverFont : font) || font;
|
|
135
|
-
const oFont = helpers.toFont(optFont);
|
|
136
|
-
const fonts = [
|
|
137
|
-
oFont
|
|
138
|
-
];
|
|
139
|
-
if (oFont.lineHeight > rect.h) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
let text = formatter || item.g;
|
|
143
|
-
const captionSize = measureLabelSize(ctx, [
|
|
144
|
-
formatter
|
|
145
|
-
], fonts);
|
|
146
|
-
if (captionSize.width + 2 * padding > rect.w) {
|
|
147
|
-
text = sliceTextToFitWidth(ctx, text, rect.w - 2 * padding, fonts);
|
|
148
|
-
}
|
|
149
|
-
const lh = oFont.lineHeight / 2;
|
|
150
|
-
const x = calculateX(rect, oAlign, padding);
|
|
151
|
-
ctx.fillStyle = oColor;
|
|
152
|
-
ctx.font = oFont.string;
|
|
153
|
-
ctx.textAlign = oAlign;
|
|
154
|
-
ctx.textBaseline = 'middle';
|
|
155
|
-
const y = displayMode === 'headerBoxes' ? rect.y + rect.h / 2 : rect.y + padding + spacing + lh;
|
|
156
|
-
ctx.fillText(text, x, y);
|
|
157
|
-
}
|
|
158
|
-
function sliceTextToFitWidth(ctx, text, width, fonts) {
|
|
159
|
-
const ellipsis = '...';
|
|
160
|
-
const ellipsisWidth = measureLabelSize(ctx, [
|
|
161
|
-
ellipsis
|
|
162
|
-
], fonts).width;
|
|
163
|
-
if (ellipsisWidth >= width) {
|
|
164
|
-
return '';
|
|
165
|
-
}
|
|
166
|
-
let lowerBoundLen = 1;
|
|
167
|
-
let upperBoundLen = text.length;
|
|
168
|
-
let currentWidth;
|
|
169
|
-
while(lowerBoundLen <= upperBoundLen){
|
|
170
|
-
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
171
|
-
const currentText = text.slice(0, currentLen);
|
|
172
|
-
currentWidth = measureLabelSize(ctx, [
|
|
173
|
-
currentText
|
|
174
|
-
], fonts).width;
|
|
175
|
-
if (currentWidth + ellipsisWidth > width) {
|
|
176
|
-
upperBoundLen = currentLen - 1;
|
|
177
|
-
} else {
|
|
178
|
-
lowerBoundLen = currentLen + 1;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
182
|
-
return slicedText ? slicedText + ellipsis : '';
|
|
183
|
-
}
|
|
184
|
-
function measureLabelSize(ctx, lines, fonts) {
|
|
185
|
-
const fontsKey = fonts.reduce((prev, item)=>{
|
|
186
|
-
prev += item.string;
|
|
187
|
-
return prev;
|
|
188
|
-
}, '');
|
|
189
|
-
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
190
|
-
if (!widthCache.has(mapKey)) {
|
|
191
|
-
ctx.save();
|
|
192
|
-
const count = lines.length;
|
|
193
|
-
let width = 0;
|
|
194
|
-
let height = 0;
|
|
195
|
-
for(let i = 0; i < count; i++){
|
|
196
|
-
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
197
|
-
ctx.font = font.string;
|
|
198
|
-
const text = lines[i];
|
|
199
|
-
width = Math.max(width, ctx.measureText(text).width);
|
|
200
|
-
height += font.lineHeight;
|
|
201
|
-
}
|
|
202
|
-
ctx.restore();
|
|
203
|
-
widthCache.set(mapKey, {
|
|
204
|
-
height,
|
|
205
|
-
width
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
return widthCache.get(mapKey);
|
|
209
|
-
}
|
|
210
|
-
function toFonts(fonts, fitRatio) {
|
|
211
|
-
return fonts.map((f)=>{
|
|
212
|
-
f.size = Math.floor(f.size * fitRatio);
|
|
213
|
-
f.lineHeight = undefined;
|
|
214
|
-
return helpers.toFont(f);
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
function labelToDraw(_ctx, rect, options, labelSize) {
|
|
218
|
-
const { overflow, padding } = options;
|
|
219
|
-
const { width, height } = labelSize;
|
|
220
|
-
if (overflow === 'hidden') {
|
|
221
|
-
return !(width + padding * 2 > rect.w || height + padding * 2 > rect.h);
|
|
222
|
-
} else if (overflow === 'fit') {
|
|
223
|
-
const ratio = Math.min(rect.w / (width + padding * 2), rect.h / (height + padding * 2));
|
|
224
|
-
if (ratio < 1) {
|
|
225
|
-
return ratio;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
function getFontFromOptions(rect, labels) {
|
|
231
|
-
const { font, hoverFont } = labels;
|
|
232
|
-
const optFont = (rect.active ? hoverFont : font) || font;
|
|
233
|
-
return Array.isArray(optFont) ? optFont.map((f)=>helpers.toFont(f)) : [
|
|
234
|
-
helpers.toFont(optFont)
|
|
235
|
-
];
|
|
236
|
-
}
|
|
237
|
-
function drawLabel(ctx, rect, options) {
|
|
238
|
-
const labels = options.labels;
|
|
239
|
-
const content = labels.formatter;
|
|
240
|
-
if (!content) {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
const contents = helpers.isArray(content) ? content : [
|
|
244
|
-
content
|
|
245
|
-
];
|
|
246
|
-
let fonts = getFontFromOptions(rect, labels);
|
|
247
|
-
let labelSize = measureLabelSize(ctx, contents, fonts);
|
|
248
|
-
const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
|
|
249
|
-
if (!lblToDraw) {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (helpers.isNumber(lblToDraw)) {
|
|
253
|
-
labelSize = {
|
|
254
|
-
height: labelSize.height * lblToDraw,
|
|
255
|
-
width: labelSize.width * lblToDraw
|
|
256
|
-
};
|
|
257
|
-
fonts = toFonts(fonts, lblToDraw);
|
|
258
|
-
}
|
|
259
|
-
const { color, hoverColor, align } = labels;
|
|
260
|
-
const optColor = (rect.active ? hoverColor : color) || color;
|
|
261
|
-
const colors = helpers.isArray(optColor) ? optColor : [
|
|
262
|
-
optColor
|
|
263
|
-
];
|
|
264
|
-
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
265
|
-
ctx.textAlign = align;
|
|
266
|
-
ctx.textBaseline = 'middle';
|
|
267
|
-
let lhs = 0;
|
|
268
|
-
contents.forEach((l, i)=>{
|
|
269
|
-
const c = colors[Math.min(i, colors.length - 1)];
|
|
270
|
-
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
271
|
-
const lh = f.lineHeight;
|
|
272
|
-
ctx.font = f.string;
|
|
273
|
-
ctx.fillStyle = c;
|
|
274
|
-
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
275
|
-
lhs += lh;
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
function drawDivider(ctx, rect, options, item) {
|
|
279
|
-
const dividers = options.dividers;
|
|
280
|
-
if (!dividers.display || !item._data.children.length) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
const { x, y, w, h } = rect;
|
|
284
|
-
const { lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth } = dividers;
|
|
285
|
-
ctx.save();
|
|
286
|
-
ctx.strokeStyle = lineColor;
|
|
287
|
-
ctx.lineCap = lineCapStyle;
|
|
288
|
-
ctx.setLineDash(lineDash);
|
|
289
|
-
ctx.lineDashOffset = lineDashOffset;
|
|
290
|
-
ctx.lineWidth = lineWidth;
|
|
291
|
-
ctx.beginPath();
|
|
292
|
-
if (w > h) {
|
|
293
|
-
const w2 = w / 2;
|
|
294
|
-
ctx.moveTo(x + w2, y);
|
|
295
|
-
ctx.lineTo(x + w2, y + h);
|
|
296
|
-
} else {
|
|
297
|
-
const h2 = h / 2;
|
|
298
|
-
ctx.moveTo(x, y + h2);
|
|
299
|
-
ctx.lineTo(x + w, y + h2);
|
|
300
|
-
}
|
|
301
|
-
ctx.stroke();
|
|
302
|
-
ctx.restore();
|
|
303
|
-
}
|
|
304
|
-
function calculateXYLabel(rect, options, labelSize) {
|
|
305
|
-
const { align, position, padding } = options;
|
|
306
|
-
const x = calculateX(rect, align, padding);
|
|
307
|
-
let y;
|
|
308
|
-
if (position === 'top') {
|
|
309
|
-
y = rect.y + padding;
|
|
310
|
-
} else if (position === 'bottom') {
|
|
311
|
-
y = rect.y + rect.h - padding - labelSize.height;
|
|
312
|
-
} else {
|
|
313
|
-
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
314
|
-
}
|
|
315
|
-
return {
|
|
316
|
-
x,
|
|
317
|
-
y
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
function calculateX(rect, align, padding) {
|
|
321
|
-
if (align === 'left') {
|
|
322
|
-
return rect.x + padding;
|
|
323
|
-
} else if (align === 'right') {
|
|
324
|
-
return rect.x + rect.w - padding;
|
|
325
|
-
}
|
|
326
|
-
return rect.x + rect.w / 2;
|
|
327
|
-
}
|
|
328
|
-
class TreemapElement extends chart_js.Element {
|
|
329
|
-
draw(ctx, data) {
|
|
330
|
-
if (!data) {
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
const options = this.options;
|
|
334
|
-
const { inner, outer } = boundingRects(this);
|
|
335
|
-
const addRectPath = hasRadius(outer.radius) ? helpers.addRoundedRectPath : addNormalRectPath;
|
|
336
|
-
ctx.save();
|
|
337
|
-
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
338
|
-
ctx.beginPath();
|
|
339
|
-
addRectPath(ctx, outer);
|
|
340
|
-
ctx.clip();
|
|
341
|
-
addRectPath(ctx, inner);
|
|
342
|
-
ctx.fillStyle = options.borderColor;
|
|
343
|
-
ctx.fill('evenodd');
|
|
344
|
-
}
|
|
345
|
-
ctx.beginPath();
|
|
346
|
-
addRectPath(ctx, inner);
|
|
347
|
-
ctx.fillStyle = options.backgroundColor;
|
|
348
|
-
ctx.fill();
|
|
349
|
-
drawDivider(ctx, inner, options, data);
|
|
350
|
-
drawText(ctx, inner, options, data);
|
|
351
|
-
ctx.restore();
|
|
352
|
-
}
|
|
353
|
-
inRange(mouseX, mouseY, useFinalPosition) {
|
|
354
|
-
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
355
|
-
}
|
|
356
|
-
inXRange(mouseX, useFinalPosition) {
|
|
357
|
-
return inRange(this, mouseX, null, useFinalPosition);
|
|
358
|
-
}
|
|
359
|
-
inYRange(mouseY, useFinalPosition) {
|
|
360
|
-
return inRange(this, null, mouseY, useFinalPosition);
|
|
361
|
-
}
|
|
362
|
-
getCenterPoint(useFinalPosition) {
|
|
363
|
-
const { x, y, width, height } = this.getProps([
|
|
364
|
-
'x',
|
|
365
|
-
'y',
|
|
366
|
-
'width',
|
|
367
|
-
'height'
|
|
368
|
-
], useFinalPosition);
|
|
369
|
-
return {
|
|
370
|
-
x: x + width / 2,
|
|
371
|
-
y: y + height / 2
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
tooltipPosition() {
|
|
375
|
-
return this.getCenterPoint();
|
|
376
|
-
}
|
|
377
|
-
constructor(cfg){
|
|
378
|
-
super();
|
|
379
|
-
this.options = undefined;
|
|
380
|
-
this.width = undefined;
|
|
381
|
-
this.height = undefined;
|
|
382
|
-
if (cfg) {
|
|
383
|
-
Object.assign(this, cfg);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
TreemapElement.id = 'treemap';
|
|
388
|
-
TreemapElement.defaults = {
|
|
389
|
-
borderRadius: 0,
|
|
390
|
-
borderWidth: 0,
|
|
391
|
-
captions: {
|
|
392
|
-
align: undefined,
|
|
393
|
-
color: 'black',
|
|
394
|
-
display: true,
|
|
395
|
-
font: {},
|
|
396
|
-
formatter: (ctx)=>ctx.raw.g || ctx.raw._data.label || '',
|
|
397
|
-
padding: 3
|
|
398
|
-
},
|
|
399
|
-
displayMode: 'containerBoxes',
|
|
400
|
-
dividers: {
|
|
401
|
-
display: false,
|
|
402
|
-
lineCapStyle: 'butt',
|
|
403
|
-
lineColor: 'black',
|
|
404
|
-
lineDash: [],
|
|
405
|
-
lineDashOffset: 0,
|
|
406
|
-
lineWidth: 1
|
|
407
|
-
},
|
|
408
|
-
label: undefined,
|
|
409
|
-
labels: {
|
|
410
|
-
align: 'center',
|
|
411
|
-
color: 'black',
|
|
412
|
-
display: false,
|
|
413
|
-
font: {},
|
|
414
|
-
formatter (ctx) {
|
|
415
|
-
if (ctx.raw.g) {
|
|
416
|
-
return [
|
|
417
|
-
ctx.raw.g,
|
|
418
|
-
`${ctx.raw.v}`
|
|
419
|
-
];
|
|
420
|
-
}
|
|
421
|
-
return ctx.raw._data.label ? [
|
|
422
|
-
ctx.raw._data.label,
|
|
423
|
-
`${ctx.raw.v}`
|
|
424
|
-
] : `${ctx.raw.v}`;
|
|
425
|
-
},
|
|
426
|
-
overflow: 'cut',
|
|
427
|
-
padding: 3,
|
|
428
|
-
position: 'middle'
|
|
429
|
-
},
|
|
430
|
-
rtl: false,
|
|
431
|
-
spacing: 0.5,
|
|
432
|
-
unsorted: false
|
|
433
|
-
};
|
|
434
|
-
TreemapElement.descriptors = {
|
|
435
|
-
_indexable: false,
|
|
436
|
-
_scriptable: true,
|
|
437
|
-
captions: {
|
|
438
|
-
_fallback: true
|
|
439
|
-
},
|
|
440
|
-
labels: {
|
|
441
|
-
_fallback: true
|
|
442
|
-
}
|
|
443
|
-
};
|
|
444
|
-
TreemapElement.defaultRoutes = {
|
|
445
|
-
backgroundColor: 'backgroundColor',
|
|
446
|
-
borderColor: 'borderColor'
|
|
447
|
-
};
|
|
448
|
-
|
|
449
15
|
function scaleRect(sq, xScale, yScale, sp) {
|
|
450
16
|
const sp2 = sp * 2;
|
|
451
17
|
const x = xScale.getPixelForValue(sq.x);
|
|
@@ -483,6 +49,29 @@ function arrayNotEqual(a, b) {
|
|
|
483
49
|
return false;
|
|
484
50
|
}
|
|
485
51
|
|
|
52
|
+
function limit(value, min, max) {
|
|
53
|
+
return Math.max(Math.min(value, max), min);
|
|
54
|
+
}
|
|
55
|
+
function parseBorderWidth(value, maxW, maxH) {
|
|
56
|
+
const o = helpers.toTRBL(value);
|
|
57
|
+
return {
|
|
58
|
+
b: limit(o.bottom, 0, maxH),
|
|
59
|
+
l: limit(o.left, 0, maxW),
|
|
60
|
+
r: limit(o.right, 0, maxW),
|
|
61
|
+
t: limit(o.top, 0, maxH)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function parseBorderRadius(value, maxW, maxH) {
|
|
65
|
+
const o = helpers.toTRBLCorners(value);
|
|
66
|
+
const maxR = Math.min(maxW, maxH);
|
|
67
|
+
return {
|
|
68
|
+
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
69
|
+
bottomRight: limit(o.bottomRight, 0, maxR),
|
|
70
|
+
topLeft: limit(o.topLeft, 0, maxR),
|
|
71
|
+
topRight: limit(o.topRight, 0, maxR)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
486
75
|
function getDims(itm, w2, s2, key) {
|
|
487
76
|
const a = itm._normalized;
|
|
488
77
|
const ar = w2 * a / s2;
|
|
@@ -647,6 +236,7 @@ class StatArray {
|
|
|
647
236
|
|
|
648
237
|
const isOlderPart = (act, req)=>req > act || act.length > req.length && act.startsWith(req);
|
|
649
238
|
const getGroupKey = (lvl)=>String(lvl);
|
|
239
|
+
const hasGroupValue = (value)=>value !== undefined && value !== null && value !== '';
|
|
650
240
|
function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
|
|
651
241
|
const objIndex = lvl - 1;
|
|
652
242
|
if (keys[0] in obj && lvl > 0) {
|
|
@@ -708,27 +298,59 @@ function flatten(input) {
|
|
|
708
298
|
return res.reverse();
|
|
709
299
|
}
|
|
710
300
|
function getPath(groups, value, defaultValue) {
|
|
711
|
-
if (!groups.length) {
|
|
712
|
-
return undefined;
|
|
713
|
-
}
|
|
714
301
|
const path = [];
|
|
715
302
|
for (const grp of groups){
|
|
716
303
|
const item = value[grp];
|
|
717
|
-
if (item
|
|
718
|
-
|
|
719
|
-
break;
|
|
304
|
+
if (!hasGroupValue(item)) {
|
|
305
|
+
continue;
|
|
720
306
|
}
|
|
721
307
|
path.push(item);
|
|
722
308
|
}
|
|
723
309
|
return path.length ? path.join('.') : defaultValue;
|
|
724
310
|
}
|
|
725
|
-
function
|
|
311
|
+
function resolveGroup(value, treeLeafKey, allGroups, groupIndex) {
|
|
312
|
+
for(let idx = groupIndex; idx < allGroups.length; idx++){
|
|
313
|
+
const groupKey = getGroupKey(allGroups[idx]);
|
|
314
|
+
if (hasGroupValue(value[groupKey])) {
|
|
315
|
+
return {
|
|
316
|
+
group: groupKey,
|
|
317
|
+
groupIndex: idx,
|
|
318
|
+
value: value[groupKey]
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (hasGroupValue(value[treeLeafKey])) {
|
|
323
|
+
return {
|
|
324
|
+
group: treeLeafKey,
|
|
325
|
+
groupIndex,
|
|
326
|
+
value: value[treeLeafKey]
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
function ensureGroup(tmp, data, groupValue, addKeys) {
|
|
332
|
+
if (groupValue in tmp) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
tmp[groupValue] = {
|
|
336
|
+
value: 0
|
|
337
|
+
};
|
|
338
|
+
const tmpRef = tmp[groupValue];
|
|
339
|
+
addKeys.forEach((k)=>{
|
|
340
|
+
tmpRef[k] = 0;
|
|
341
|
+
});
|
|
342
|
+
data[groupValue] = [];
|
|
343
|
+
}
|
|
344
|
+
function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
|
|
726
345
|
const key = keys[0];
|
|
727
346
|
const addKeys = keys.slice(1);
|
|
728
347
|
const tmp = Object.create(null);
|
|
729
348
|
const data = Object.create(null);
|
|
730
349
|
const ret = [];
|
|
731
|
-
|
|
350
|
+
const resolvedAllGroups = allGroups.length ? allGroups : [
|
|
351
|
+
grp
|
|
352
|
+
];
|
|
353
|
+
const resolvedGroupIndex = allGroups.length ? groupIndex : 0;
|
|
732
354
|
let i;
|
|
733
355
|
let n;
|
|
734
356
|
for(i = 0, n = values.length; i < n; ++i){
|
|
@@ -736,27 +358,21 @@ function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [])
|
|
|
736
358
|
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
737
359
|
continue;
|
|
738
360
|
}
|
|
739
|
-
|
|
740
|
-
if (!
|
|
361
|
+
const itemGroup = resolveGroup(v, treeLeafKey, resolvedAllGroups, resolvedGroupIndex);
|
|
362
|
+
if (!itemGroup) {
|
|
741
363
|
return [];
|
|
742
364
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
value: 0
|
|
746
|
-
};
|
|
747
|
-
const tmpRef = tmp[g];
|
|
748
|
-
addKeys.forEach((k)=>{
|
|
749
|
-
tmpRef[k] = 0;
|
|
750
|
-
});
|
|
751
|
-
data[g] = [];
|
|
752
|
-
}
|
|
365
|
+
const g = itemGroup.value;
|
|
366
|
+
ensureGroup(tmp, data, g, addKeys);
|
|
753
367
|
tmp[g].value += +v[key];
|
|
754
|
-
tmp[g].label =
|
|
368
|
+
tmp[g].label = g;
|
|
369
|
+
tmp[g].group = itemGroup.group;
|
|
370
|
+
tmp[g].groupIndex = itemGroup.groupIndex;
|
|
755
371
|
const tmpRef = tmp[g];
|
|
756
372
|
addKeys.forEach((k)=>{
|
|
757
373
|
tmpRef[k] += v[k];
|
|
758
374
|
});
|
|
759
|
-
tmp[g].path = getPath(
|
|
375
|
+
tmp[g].path = getPath(resolvedAllGroups.slice(0, itemGroup.groupIndex + 1), v, g);
|
|
760
376
|
data[g].push(v);
|
|
761
377
|
}
|
|
762
378
|
Object.keys(tmp).forEach((k)=>{
|
|
@@ -768,6 +384,8 @@ function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [])
|
|
|
768
384
|
v[ak] = +tmp[k][ak];
|
|
769
385
|
});
|
|
770
386
|
v[grp] = tmp[k].label;
|
|
387
|
+
v.group = tmp[k].group;
|
|
388
|
+
v.groupIndex = tmp[k].groupIndex;
|
|
771
389
|
v.label = k;
|
|
772
390
|
v.path = tmp[k].path;
|
|
773
391
|
if (mainGrp) {
|
|
@@ -856,42 +474,301 @@ function squarify(values, rectangle, keys = [], grp, lvl, gsum) {
|
|
|
856
474
|
if (!n) {
|
|
857
475
|
return rows;
|
|
858
476
|
}
|
|
859
|
-
const tmp = values.slice();
|
|
860
|
-
const key = index(tmp, keys[0]);
|
|
861
|
-
if (!rectangle?.unsorted) {
|
|
862
|
-
sort(tmp, key);
|
|
477
|
+
const tmp = values.slice();
|
|
478
|
+
const key = index(tmp, keys[0]);
|
|
479
|
+
if (!rectangle?.unsorted) {
|
|
480
|
+
sort(tmp, key);
|
|
481
|
+
}
|
|
482
|
+
const val = (idx)=>key ? +tmp[idx][key] : +tmp[idx];
|
|
483
|
+
const gval = (idx)=>grp && tmp[idx][grp];
|
|
484
|
+
for(i = 0; i < n; ++i){
|
|
485
|
+
o = {
|
|
486
|
+
_data: values[tmp[i]._idx],
|
|
487
|
+
group: undefined,
|
|
488
|
+
groupSum: gsum,
|
|
489
|
+
level: undefined,
|
|
490
|
+
value: val(i)
|
|
491
|
+
};
|
|
492
|
+
if (grp) {
|
|
493
|
+
o.level = tmp[i].groupIndex ?? lvl;
|
|
494
|
+
o.group = gval(i);
|
|
495
|
+
const tmpRef = tmp[i];
|
|
496
|
+
o.values = keys.reduce((obj, k)=>{
|
|
497
|
+
obj[k] = +tmpRef[k];
|
|
498
|
+
return obj;
|
|
499
|
+
}, {});
|
|
500
|
+
}
|
|
501
|
+
o = row.pushIf(o, compareAspectRatio, length);
|
|
502
|
+
if (o) {
|
|
503
|
+
rows.push(rect.map(row));
|
|
504
|
+
length = rect.side;
|
|
505
|
+
row.reset();
|
|
506
|
+
row.push(o);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (row.length) {
|
|
510
|
+
rows.push(rect.map(row));
|
|
511
|
+
}
|
|
512
|
+
return flatten(rows);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const widthCache = new Map();
|
|
516
|
+
function shouldDrawCaption(displayMode, rect, options) {
|
|
517
|
+
if (!options || options.display === false) {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
if (displayMode === 'headerBoxes') {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
const { w, h } = rect;
|
|
524
|
+
const font = helpers.toFont(options.font || {});
|
|
525
|
+
const min = font.lineHeight;
|
|
526
|
+
const padding = Math.max(Math.min(helpers.valueOrDefault(options.padding, 3) * 2, Math.min(w, h)), 0);
|
|
527
|
+
return w - padding > min && h - padding > min;
|
|
528
|
+
}
|
|
529
|
+
function getCaptionHeight(displayMode, rect, font, padding) {
|
|
530
|
+
if (displayMode !== 'headerBoxes') {
|
|
531
|
+
return font.lineHeight + padding * 2;
|
|
532
|
+
}
|
|
533
|
+
const captionHeight = font.lineHeight + padding * 2;
|
|
534
|
+
return rect.h < 2 * captionHeight ? rect.h / 3 : captionHeight;
|
|
535
|
+
}
|
|
536
|
+
function drawText(ctx, rect, options, item, element) {
|
|
537
|
+
const { captions, labels, displayMode } = options;
|
|
538
|
+
ctx.save();
|
|
539
|
+
ctx.beginPath();
|
|
540
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
541
|
+
ctx.clip();
|
|
542
|
+
const isLeaf = item && (!helpers.defined(item.l) || item.isLeaf);
|
|
543
|
+
if (isLeaf && labels.display) {
|
|
544
|
+
drawLabel(ctx, rect, options, item, element);
|
|
545
|
+
} else if (!isLeaf && shouldDrawCaption(displayMode, rect, captions)) {
|
|
546
|
+
drawCaption(ctx, rect, options, item, element);
|
|
547
|
+
}
|
|
548
|
+
ctx.restore();
|
|
549
|
+
}
|
|
550
|
+
function callbackContext(element, item) {
|
|
551
|
+
const context = element.$context || {
|
|
552
|
+
active: !!element.active,
|
|
553
|
+
raw: item,
|
|
554
|
+
type: 'data'
|
|
555
|
+
};
|
|
556
|
+
context.active = !!element.active;
|
|
557
|
+
context.raw = item;
|
|
558
|
+
return context;
|
|
559
|
+
}
|
|
560
|
+
function resolveOption(option, context) {
|
|
561
|
+
return typeof option === 'function' ? option(context) : option;
|
|
562
|
+
}
|
|
563
|
+
function resolveCaptionText(captions, element, item) {
|
|
564
|
+
return resolveOption(captions.formatter, callbackContext(element, item)) || item.g || '';
|
|
565
|
+
}
|
|
566
|
+
function drawCaption(ctx, rect, options, item, element) {
|
|
567
|
+
const { captions, spacing, rtl, displayMode } = options;
|
|
568
|
+
const { color, hoverColor, font, hoverFont, padding, align } = captions;
|
|
569
|
+
const oColor = (rect.active ? hoverColor : color) || color;
|
|
570
|
+
const oAlign = align || (rtl ? 'right' : 'left');
|
|
571
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
572
|
+
const oFont = helpers.toFont(optFont);
|
|
573
|
+
const fonts = [
|
|
574
|
+
oFont
|
|
575
|
+
];
|
|
576
|
+
if (oFont.lineHeight > rect.h) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
let text = resolveCaptionText(captions, element, item);
|
|
580
|
+
if (!text) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const captionSize = measureLabelSize(ctx, [
|
|
584
|
+
text
|
|
585
|
+
], fonts);
|
|
586
|
+
if (captionSize.width + 2 * padding > rect.w) {
|
|
587
|
+
text = sliceTextToFitWidth(ctx, text, rect.w - 2 * padding, fonts);
|
|
588
|
+
}
|
|
589
|
+
const lh = oFont.lineHeight / 2;
|
|
590
|
+
const x = calculateX(rect, oAlign, padding);
|
|
591
|
+
ctx.fillStyle = oColor;
|
|
592
|
+
ctx.font = oFont.string;
|
|
593
|
+
ctx.textAlign = oAlign;
|
|
594
|
+
ctx.textBaseline = 'middle';
|
|
595
|
+
const y = displayMode === 'headerBoxes' ? rect.y + rect.h / 2 : rect.y + padding + spacing + lh;
|
|
596
|
+
ctx.fillText(text, x, y);
|
|
597
|
+
}
|
|
598
|
+
function sliceTextToFitWidth(ctx, text, width, fonts) {
|
|
599
|
+
const ellipsis = '...';
|
|
600
|
+
const ellipsisWidth = measureLabelSize(ctx, [
|
|
601
|
+
ellipsis
|
|
602
|
+
], fonts).width;
|
|
603
|
+
if (ellipsisWidth >= width) {
|
|
604
|
+
return '';
|
|
605
|
+
}
|
|
606
|
+
let lowerBoundLen = 1;
|
|
607
|
+
let upperBoundLen = text.length;
|
|
608
|
+
let currentWidth;
|
|
609
|
+
while(lowerBoundLen <= upperBoundLen){
|
|
610
|
+
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
611
|
+
const currentText = text.slice(0, currentLen);
|
|
612
|
+
currentWidth = measureLabelSize(ctx, [
|
|
613
|
+
currentText
|
|
614
|
+
], fonts).width;
|
|
615
|
+
if (currentWidth + ellipsisWidth > width) {
|
|
616
|
+
upperBoundLen = currentLen - 1;
|
|
617
|
+
} else {
|
|
618
|
+
lowerBoundLen = currentLen + 1;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
622
|
+
return slicedText ? slicedText + ellipsis : '';
|
|
623
|
+
}
|
|
624
|
+
function measureLabelSize(ctx, lines, fonts) {
|
|
625
|
+
const fontsKey = fonts.reduce((prev, item)=>{
|
|
626
|
+
prev += item.string;
|
|
627
|
+
return prev;
|
|
628
|
+
}, '');
|
|
629
|
+
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
630
|
+
let size = widthCache.get(mapKey);
|
|
631
|
+
if (!size) {
|
|
632
|
+
ctx.save();
|
|
633
|
+
const count = lines.length;
|
|
634
|
+
let width = 0;
|
|
635
|
+
let height = 0;
|
|
636
|
+
for(let i = 0; i < count; i++){
|
|
637
|
+
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
638
|
+
ctx.font = font.string;
|
|
639
|
+
const text = lines[i];
|
|
640
|
+
width = Math.max(width, ctx.measureText(text).width);
|
|
641
|
+
height += font.lineHeight;
|
|
642
|
+
}
|
|
643
|
+
ctx.restore();
|
|
644
|
+
size = {
|
|
645
|
+
height,
|
|
646
|
+
width
|
|
647
|
+
};
|
|
648
|
+
widthCache.set(mapKey, size);
|
|
649
|
+
}
|
|
650
|
+
return size;
|
|
651
|
+
}
|
|
652
|
+
function toFonts(fonts, fitRatio) {
|
|
653
|
+
return fonts.map((f)=>{
|
|
654
|
+
const { lineHeight: _lineHeight, ...font } = f;
|
|
655
|
+
return helpers.toFont({
|
|
656
|
+
...font,
|
|
657
|
+
size: Math.floor(f.size * fitRatio)
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
function labelToDraw(_ctx, rect, options, labelSize) {
|
|
662
|
+
const { overflow, padding } = options;
|
|
663
|
+
const { width, height } = labelSize;
|
|
664
|
+
if (overflow === 'hidden') {
|
|
665
|
+
return !(width + padding * 2 > rect.w || height + padding * 2 > rect.h);
|
|
666
|
+
} else if (overflow === 'fit') {
|
|
667
|
+
const ratio = Math.min(rect.w / (width + padding * 2), rect.h / (height + padding * 2));
|
|
668
|
+
if (ratio < 1) {
|
|
669
|
+
return ratio;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
674
|
+
function getFontFromOptions(rect, labels) {
|
|
675
|
+
const { font, hoverFont } = labels;
|
|
676
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
677
|
+
return Array.isArray(optFont) ? optFont.map((f)=>helpers.toFont(f)) : [
|
|
678
|
+
helpers.toFont(optFont)
|
|
679
|
+
];
|
|
680
|
+
}
|
|
681
|
+
function drawLabel(ctx, rect, options, item, element) {
|
|
682
|
+
const labels = options.labels;
|
|
683
|
+
const content = resolveOption(labels.formatter, callbackContext(element, item));
|
|
684
|
+
if (!content) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const contents = helpers.isArray(content) ? content : [
|
|
688
|
+
content
|
|
689
|
+
];
|
|
690
|
+
let fonts = getFontFromOptions(rect, labels);
|
|
691
|
+
let labelSize = measureLabelSize(ctx, contents, fonts);
|
|
692
|
+
const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
|
|
693
|
+
if (!lblToDraw) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (helpers.isNumber(lblToDraw)) {
|
|
697
|
+
labelSize = {
|
|
698
|
+
height: labelSize.height * lblToDraw,
|
|
699
|
+
width: labelSize.width * lblToDraw
|
|
700
|
+
};
|
|
701
|
+
fonts = toFonts(fonts, lblToDraw);
|
|
702
|
+
}
|
|
703
|
+
const { color, hoverColor, align } = labels;
|
|
704
|
+
const optColor = (rect.active ? hoverColor : color) || color;
|
|
705
|
+
const colors = helpers.isArray(optColor) ? optColor : [
|
|
706
|
+
optColor
|
|
707
|
+
];
|
|
708
|
+
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
709
|
+
ctx.textAlign = align;
|
|
710
|
+
ctx.textBaseline = 'middle';
|
|
711
|
+
let lhs = 0;
|
|
712
|
+
contents.forEach((l, i)=>{
|
|
713
|
+
const c = colors[Math.min(i, colors.length - 1)];
|
|
714
|
+
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
715
|
+
const lh = f.lineHeight;
|
|
716
|
+
ctx.font = f.string;
|
|
717
|
+
ctx.fillStyle = c;
|
|
718
|
+
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
719
|
+
lhs += lh;
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
function drawDivider(ctx, rect, options, item) {
|
|
723
|
+
const dividers = options.dividers;
|
|
724
|
+
const children = item._data?.children;
|
|
725
|
+
if (!dividers.display || !Array.isArray(children) || !children.length) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const { x, y, w, h } = rect;
|
|
729
|
+
const { lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth } = dividers;
|
|
730
|
+
ctx.save();
|
|
731
|
+
ctx.strokeStyle = lineColor;
|
|
732
|
+
ctx.lineCap = lineCapStyle;
|
|
733
|
+
ctx.setLineDash(lineDash);
|
|
734
|
+
ctx.lineDashOffset = lineDashOffset;
|
|
735
|
+
ctx.lineWidth = lineWidth;
|
|
736
|
+
ctx.beginPath();
|
|
737
|
+
if (w > h) {
|
|
738
|
+
const w2 = w / 2;
|
|
739
|
+
ctx.moveTo(x + w2, y);
|
|
740
|
+
ctx.lineTo(x + w2, y + h);
|
|
741
|
+
} else {
|
|
742
|
+
const h2 = h / 2;
|
|
743
|
+
ctx.moveTo(x, y + h2);
|
|
744
|
+
ctx.lineTo(x + w, y + h2);
|
|
863
745
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
const tmpRef = tmp[i];
|
|
878
|
-
o.values = keys.reduce((obj, k)=>{
|
|
879
|
-
obj[k] = +tmpRef[k];
|
|
880
|
-
return obj;
|
|
881
|
-
}, {});
|
|
882
|
-
}
|
|
883
|
-
o = row.pushIf(o, compareAspectRatio, length);
|
|
884
|
-
if (o) {
|
|
885
|
-
rows.push(rect.map(row));
|
|
886
|
-
length = rect.side;
|
|
887
|
-
row.reset();
|
|
888
|
-
row.push(o);
|
|
889
|
-
}
|
|
746
|
+
ctx.stroke();
|
|
747
|
+
ctx.restore();
|
|
748
|
+
}
|
|
749
|
+
function calculateXYLabel(rect, options, labelSize) {
|
|
750
|
+
const { align, position, padding } = options;
|
|
751
|
+
const x = calculateX(rect, align, padding);
|
|
752
|
+
let y;
|
|
753
|
+
if (position === 'top') {
|
|
754
|
+
y = rect.y + padding;
|
|
755
|
+
} else if (position === 'bottom') {
|
|
756
|
+
y = rect.y + rect.h - padding - labelSize.height;
|
|
757
|
+
} else {
|
|
758
|
+
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
890
759
|
}
|
|
891
|
-
|
|
892
|
-
|
|
760
|
+
return {
|
|
761
|
+
x,
|
|
762
|
+
y
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function calculateX(rect, align, padding) {
|
|
766
|
+
if (align === 'left') {
|
|
767
|
+
return rect.x + padding;
|
|
768
|
+
} else if (align === 'right') {
|
|
769
|
+
return rect.x + rect.w - padding;
|
|
893
770
|
}
|
|
894
|
-
return
|
|
771
|
+
return rect.x + rect.w / 2;
|
|
895
772
|
}
|
|
896
773
|
|
|
897
774
|
function buildData(tree, dataset, keys, mainRect) {
|
|
@@ -905,36 +782,40 @@ function buildData(tree, dataset, keys, mainRect) {
|
|
|
905
782
|
const captions = dataset.captions || {};
|
|
906
783
|
const font = helpers.toFont(captions.font);
|
|
907
784
|
const padding = helpers.valueOrDefault(captions.padding, 3);
|
|
785
|
+
function getSubRect(sq, rect) {
|
|
786
|
+
const bw = dataset.displayMode === 'headerBoxes' ? {
|
|
787
|
+
b: 0,
|
|
788
|
+
l: 0,
|
|
789
|
+
r: 0,
|
|
790
|
+
t: 0
|
|
791
|
+
} : parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
792
|
+
const subRect = {
|
|
793
|
+
...rect,
|
|
794
|
+
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
795
|
+
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
796
|
+
x: sq.x + sp + bw.l,
|
|
797
|
+
y: sq.y + sp + bw.t
|
|
798
|
+
};
|
|
799
|
+
if (shouldDrawCaption(dataset.displayMode, subRect, captions)) {
|
|
800
|
+
const captionHeight = getCaptionHeight(dataset.displayMode, subRect, font, padding);
|
|
801
|
+
subRect.y += captionHeight;
|
|
802
|
+
subRect.h -= captionHeight;
|
|
803
|
+
}
|
|
804
|
+
return subRect;
|
|
805
|
+
}
|
|
908
806
|
function recur(treeElements, gidx, rect, parent, gs) {
|
|
909
807
|
const g = getGroupKey(groups[gidx]);
|
|
910
|
-
const
|
|
911
|
-
const gdata = group(treeElements, g, keys, treeLeafKey, pg, parent, groups.filter((_item, index)=>index <= gidx));
|
|
808
|
+
const gdata = group(treeElements, g, keys, treeLeafKey, undefined, parent, groups.filter((_item, index)=>index <= gidx), groups, gidx);
|
|
912
809
|
const gsq = squarify(gdata, rect, keys, g, gidx, gs);
|
|
913
810
|
const ret = gsq.slice();
|
|
914
811
|
if (gidx < glen - 1) {
|
|
915
812
|
gsq.forEach((sq)=>{
|
|
916
|
-
const
|
|
917
|
-
b: 0,
|
|
918
|
-
l: 0,
|
|
919
|
-
r: 0,
|
|
920
|
-
t: 0
|
|
921
|
-
} : parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
922
|
-
const subRect = {
|
|
923
|
-
...rect,
|
|
924
|
-
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
925
|
-
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
926
|
-
x: sq.x + sp + bw.l,
|
|
927
|
-
y: sq.y + sp + bw.t
|
|
928
|
-
};
|
|
929
|
-
if (shouldDrawCaption(dataset.displayMode, subRect, captions)) {
|
|
930
|
-
const captionHeight = getCaptionHeight(dataset.displayMode, subRect, font, padding);
|
|
931
|
-
subRect.y += captionHeight;
|
|
932
|
-
subRect.h -= captionHeight;
|
|
933
|
-
}
|
|
813
|
+
const subRect = getSubRect(sq, rect);
|
|
934
814
|
const children = [];
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
815
|
+
const nextGroupIndex = sq._data.groupIndex + 1;
|
|
816
|
+
if (nextGroupIndex < glen) {
|
|
817
|
+
children.push(...recur(sq._data.children, nextGroupIndex, subRect, sq.g, sq.s));
|
|
818
|
+
}
|
|
938
819
|
ret.push(...children);
|
|
939
820
|
sq.isLeaf = !children.length;
|
|
940
821
|
});
|
|
@@ -1083,7 +964,15 @@ TreemapController.defaults = {
|
|
|
1083
964
|
};
|
|
1084
965
|
TreemapController.descriptors = {
|
|
1085
966
|
_indexable: false,
|
|
1086
|
-
_scriptable: true
|
|
967
|
+
_scriptable: true,
|
|
968
|
+
captions: {
|
|
969
|
+
_fallback: true,
|
|
970
|
+
_scriptable: (name)=>name !== 'formatter'
|
|
971
|
+
},
|
|
972
|
+
labels: {
|
|
973
|
+
_fallback: true,
|
|
974
|
+
_scriptable: (name)=>name !== 'formatter'
|
|
975
|
+
}
|
|
1087
976
|
};
|
|
1088
977
|
TreemapController.overrides = {
|
|
1089
978
|
hover: {},
|
|
@@ -1154,6 +1043,190 @@ TreemapController.afterUnregister = ()=>{
|
|
|
1154
1043
|
}
|
|
1155
1044
|
};
|
|
1156
1045
|
|
|
1046
|
+
function getBounds(rect, useFinalPosition) {
|
|
1047
|
+
const { x, y, width, height } = rect.getProps([
|
|
1048
|
+
'x',
|
|
1049
|
+
'y',
|
|
1050
|
+
'width',
|
|
1051
|
+
'height'
|
|
1052
|
+
], useFinalPosition);
|
|
1053
|
+
return {
|
|
1054
|
+
bottom: y + height,
|
|
1055
|
+
left: x,
|
|
1056
|
+
right: x + width,
|
|
1057
|
+
top: y
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function boundingRects(rect) {
|
|
1061
|
+
const bounds = getBounds(rect);
|
|
1062
|
+
const width = bounds.right - bounds.left;
|
|
1063
|
+
const height = bounds.bottom - bounds.top;
|
|
1064
|
+
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
1065
|
+
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
1066
|
+
const outer = {
|
|
1067
|
+
active: rect.active,
|
|
1068
|
+
h: height,
|
|
1069
|
+
radius,
|
|
1070
|
+
w: width,
|
|
1071
|
+
x: bounds.left,
|
|
1072
|
+
y: bounds.top
|
|
1073
|
+
};
|
|
1074
|
+
return {
|
|
1075
|
+
inner: {
|
|
1076
|
+
active: rect.active,
|
|
1077
|
+
h: outer.h - border.t - border.b,
|
|
1078
|
+
radius: {
|
|
1079
|
+
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
1080
|
+
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
1081
|
+
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
1082
|
+
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r))
|
|
1083
|
+
},
|
|
1084
|
+
w: outer.w - border.l - border.r,
|
|
1085
|
+
x: outer.x + border.l,
|
|
1086
|
+
y: outer.y + border.t
|
|
1087
|
+
},
|
|
1088
|
+
outer
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function inRange(rect, x, y, useFinalPosition) {
|
|
1092
|
+
const skipX = x === null;
|
|
1093
|
+
const skipY = y === null;
|
|
1094
|
+
const bounds = !rect || skipX && skipY ? false : getBounds(rect, useFinalPosition);
|
|
1095
|
+
return bounds && (skipX || x >= bounds.left && x <= bounds.right) && (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
1096
|
+
}
|
|
1097
|
+
function hasRadius(radius) {
|
|
1098
|
+
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
1099
|
+
}
|
|
1100
|
+
function addNormalRectPath(ctx, rect) {
|
|
1101
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
class TreemapElement extends chart_js.Element {
|
|
1105
|
+
draw(ctx, data) {
|
|
1106
|
+
if (!data) {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const options = this.options;
|
|
1110
|
+
const { inner, outer } = boundingRects(this);
|
|
1111
|
+
const addRectPath = hasRadius(outer.radius) ? helpers.addRoundedRectPath : addNormalRectPath;
|
|
1112
|
+
ctx.save();
|
|
1113
|
+
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
1114
|
+
ctx.beginPath();
|
|
1115
|
+
addRectPath(ctx, outer);
|
|
1116
|
+
ctx.clip();
|
|
1117
|
+
addRectPath(ctx, inner);
|
|
1118
|
+
ctx.fillStyle = options.borderColor;
|
|
1119
|
+
ctx.fill('evenodd');
|
|
1120
|
+
}
|
|
1121
|
+
ctx.beginPath();
|
|
1122
|
+
addRectPath(ctx, inner);
|
|
1123
|
+
ctx.fillStyle = options.backgroundColor;
|
|
1124
|
+
ctx.fill();
|
|
1125
|
+
drawDivider(ctx, inner, options, data);
|
|
1126
|
+
drawText(ctx, inner, options, data, this);
|
|
1127
|
+
ctx.restore();
|
|
1128
|
+
}
|
|
1129
|
+
inRange(mouseX, mouseY, useFinalPosition) {
|
|
1130
|
+
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
1131
|
+
}
|
|
1132
|
+
inXRange(mouseX, useFinalPosition) {
|
|
1133
|
+
return inRange(this, mouseX, null, useFinalPosition);
|
|
1134
|
+
}
|
|
1135
|
+
inYRange(mouseY, useFinalPosition) {
|
|
1136
|
+
return inRange(this, null, mouseY, useFinalPosition);
|
|
1137
|
+
}
|
|
1138
|
+
getCenterPoint(useFinalPosition) {
|
|
1139
|
+
const { x, y, width, height } = this.getProps([
|
|
1140
|
+
'x',
|
|
1141
|
+
'y',
|
|
1142
|
+
'width',
|
|
1143
|
+
'height'
|
|
1144
|
+
], useFinalPosition);
|
|
1145
|
+
return {
|
|
1146
|
+
x: x + width / 2,
|
|
1147
|
+
y: y + height / 2
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
tooltipPosition() {
|
|
1151
|
+
return this.getCenterPoint();
|
|
1152
|
+
}
|
|
1153
|
+
constructor(cfg){
|
|
1154
|
+
super();
|
|
1155
|
+
this.width = undefined;
|
|
1156
|
+
this.height = undefined;
|
|
1157
|
+
if (cfg) {
|
|
1158
|
+
Object.assign(this, cfg);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
TreemapElement.id = 'treemap';
|
|
1163
|
+
TreemapElement.defaults = {
|
|
1164
|
+
borderRadius: 0,
|
|
1165
|
+
borderWidth: 0,
|
|
1166
|
+
captions: {
|
|
1167
|
+
align: undefined,
|
|
1168
|
+
color: 'black',
|
|
1169
|
+
display: true,
|
|
1170
|
+
font: {},
|
|
1171
|
+
formatter: (ctx)=>{
|
|
1172
|
+
const label = ctx.raw._data?.label;
|
|
1173
|
+
return ctx.raw.g || (typeof label === 'string' ? label : '');
|
|
1174
|
+
},
|
|
1175
|
+
padding: 3
|
|
1176
|
+
},
|
|
1177
|
+
displayMode: 'containerBoxes',
|
|
1178
|
+
dividers: {
|
|
1179
|
+
display: false,
|
|
1180
|
+
lineCapStyle: 'butt',
|
|
1181
|
+
lineColor: 'black',
|
|
1182
|
+
lineDash: [],
|
|
1183
|
+
lineDashOffset: 0,
|
|
1184
|
+
lineWidth: 1
|
|
1185
|
+
},
|
|
1186
|
+
label: undefined,
|
|
1187
|
+
labels: {
|
|
1188
|
+
align: 'center',
|
|
1189
|
+
color: 'black',
|
|
1190
|
+
display: false,
|
|
1191
|
+
font: {},
|
|
1192
|
+
formatter (ctx) {
|
|
1193
|
+
const label = ctx.raw._data?.label;
|
|
1194
|
+
if (ctx.raw.g) {
|
|
1195
|
+
return [
|
|
1196
|
+
ctx.raw.g,
|
|
1197
|
+
`${ctx.raw.v}`
|
|
1198
|
+
];
|
|
1199
|
+
}
|
|
1200
|
+
return typeof label === 'string' ? [
|
|
1201
|
+
label,
|
|
1202
|
+
`${ctx.raw.v}`
|
|
1203
|
+
] : `${ctx.raw.v}`;
|
|
1204
|
+
},
|
|
1205
|
+
overflow: 'cut',
|
|
1206
|
+
padding: 3,
|
|
1207
|
+
position: 'middle'
|
|
1208
|
+
},
|
|
1209
|
+
rtl: false,
|
|
1210
|
+
spacing: 0.5,
|
|
1211
|
+
unsorted: false
|
|
1212
|
+
};
|
|
1213
|
+
TreemapElement.descriptors = {
|
|
1214
|
+
_indexable: false,
|
|
1215
|
+
_scriptable: true,
|
|
1216
|
+
captions: {
|
|
1217
|
+
_fallback: true,
|
|
1218
|
+
_scriptable: (name)=>name !== 'formatter'
|
|
1219
|
+
},
|
|
1220
|
+
labels: {
|
|
1221
|
+
_fallback: true,
|
|
1222
|
+
_scriptable: (name)=>name !== 'formatter'
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
TreemapElement.defaultRoutes = {
|
|
1226
|
+
backgroundColor: 'backgroundColor',
|
|
1227
|
+
borderColor: 'borderColor'
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1157
1230
|
chart_js.Chart.register(TreemapController, TreemapElement);
|
|
1158
1231
|
|
|
1159
1232
|
exports.TreemapController = TreemapController;
|