chartjs-chart-treemap 2.0.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,70 @@
1
1
  /*!
2
- * chartjs-chart-treemap v2.0.2
2
+ * chartjs-chart-treemap v2.1.1
3
3
  * https://chartjs-chart-treemap.pages.dev/
4
- * (c) 2021 Jukka Kurkela
4
+ * (c) 2022 Jukka Kurkela
5
5
  * Released under the MIT license
6
6
  */
7
- import { Chart, registry, DatasetController, Element } from 'chart.js';
8
- import { toFont, valueOrDefault, isArray } from 'chart.js/helpers';
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, v;
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,35 +192,427 @@ function requireVersion(min, ver) {
115
192
  }
116
193
  }
117
194
 
118
- function round(v, n) {
119
- // @ts-ignore
120
- return (+(Math.round(v + 'e+' + n) + 'e-' + n)) || 0;
195
+ const rasterize = (v, dpr) => Math.round(v * dpr) / dpr;
196
+
197
+ function rasterizeRect(rect, dpr) {
198
+ return {
199
+ ...rect,
200
+ x: rasterize(rect.x, dpr),
201
+ y: rasterize(rect.y, dpr),
202
+ w: rasterize(rect.w, dpr),
203
+ h: rasterize(rect.h, dpr),
204
+ };
205
+ }
206
+
207
+ const widthCache = new Map();
208
+
209
+ /**
210
+ * Helper function to get the bounds of the rect
211
+ * @param {TreemapElement} rect the rect
212
+ * @param {boolean} [useFinalPosition]
213
+ * @return {object} bounds of the rect
214
+ * @private
215
+ */
216
+ function getBounds(rect, useFinalPosition) {
217
+ const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
218
+ return {left: x, top: y, right: x + width, bottom: y + height};
219
+ }
220
+
221
+ function limit(value, min, max) {
222
+ return Math.max(Math.min(value, max), min);
223
+ }
224
+
225
+ function parseBorderWidth(value, maxW, maxH) {
226
+ const o = toTRBL(value);
227
+
228
+ return {
229
+ t: limit(o.top, 0, maxH),
230
+ r: limit(o.right, 0, maxW),
231
+ b: limit(o.bottom, 0, maxH),
232
+ l: limit(o.left, 0, maxW)
233
+ };
121
234
  }
122
235
 
123
- function getDims(itm, w2, s2, key) {
236
+ function parseBorderRadius(value, maxW, maxH) {
237
+ const o = toTRBLCorners(value);
238
+ const maxR = Math.min(maxW, maxH);
239
+
240
+ return {
241
+ topLeft: limit(o.topLeft, 0, maxR),
242
+ topRight: limit(o.topRight, 0, maxR),
243
+ bottomLeft: limit(o.bottomLeft, 0, maxR),
244
+ bottomRight: limit(o.bottomRight, 0, maxR)
245
+ };
246
+ }
247
+
248
+ function boundingRects(rect, dpr) {
249
+ const bounds = getBounds(rect);
250
+ const width = bounds.right - bounds.left;
251
+ const height = bounds.bottom - bounds.top;
252
+ const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
253
+ const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
254
+ const outer = rasterizeRect({
255
+ x: bounds.left,
256
+ y: bounds.top,
257
+ w: width,
258
+ h: height,
259
+ radius
260
+ }, dpr);
261
+
262
+ return {
263
+ outer,
264
+ inner: {
265
+ x: outer.x + border.l,
266
+ y: outer.y + border.t,
267
+ w: outer.w - border.l - border.r,
268
+ h: outer.h - border.t - border.b,
269
+ radius: {
270
+ topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
271
+ topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
272
+ bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
273
+ bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
274
+ }
275
+ }
276
+ };
277
+ }
278
+
279
+ function inRange(rect, x, y, useFinalPosition) {
280
+ const skipX = x === null;
281
+ const skipY = y === null;
282
+ const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
283
+
284
+ return bounds
285
+ && (skipX || x >= bounds.left && x <= bounds.right)
286
+ && (skipY || y >= bounds.top && y <= bounds.bottom);
287
+ }
288
+
289
+ function hasRadius(radius) {
290
+ return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
291
+ }
292
+
293
+ /**
294
+ * Add a path of a rectangle to the current sub-path
295
+ * @param {CanvasRenderingContext2D} ctx Context
296
+ * @param {*} rect Bounding rect
297
+ */
298
+ function addNormalRectPath(ctx, rect) {
299
+ ctx.rect(rect.x, rect.y, rect.w, rect.h);
300
+ }
301
+
302
+ function shouldDrawCaption(rect, options) {
303
+ if (!options || options.display === false) {
304
+ return false;
305
+ }
306
+ const {w, h} = rect;
307
+ const font = toFont(options.font);
308
+ const min = font.lineHeight;
309
+ const padding = limit(valueOrDefault(options.padding, 3) * 2, 0, Math.min(w, h));
310
+ return (w - padding) > min && (h - padding) > min;
311
+ }
312
+
313
+ function drawText(ctx, rect, options, item, levels) {
314
+ const {captions, labels} = options;
315
+ ctx.save();
316
+ ctx.beginPath();
317
+ ctx.rect(rect.x, rect.y, rect.w, rect.h);
318
+ ctx.clip();
319
+ const isLeaf = (!('l' in item) || item.l === levels);
320
+ if (isLeaf && labels.display) {
321
+ drawLabel(ctx, rect, options);
322
+ } else if (!isLeaf && shouldDrawCaption(rect, captions)) {
323
+ drawCaption(ctx, rect, options, item);
324
+ }
325
+ ctx.restore();
326
+ }
327
+
328
+ function drawCaption(ctx, rect, options, item) {
329
+ const {captions, spacing, rtl} = options;
330
+ const {color, hoverColor, font, hoverFont, padding, align, formatter} = captions;
331
+ const oColor = (rect.active ? hoverColor : color) || color;
332
+ const oAlign = align || (rtl ? 'right' : 'left');
333
+ const optFont = (rect.active ? hoverFont : font) || font;
334
+ const oFont = toFont(optFont);
335
+ const lh = oFont.lineHeight / 2;
336
+ const x = calculateX(rect, oAlign, padding);
337
+ ctx.fillStyle = oColor;
338
+ ctx.font = oFont.string;
339
+ ctx.textAlign = oAlign;
340
+ ctx.textBaseline = 'middle';
341
+ ctx.fillText(formatter || item.g, x, rect.y + padding + spacing + lh);
342
+ }
343
+
344
+ function measureLabelSize(ctx, lines, fonts) {
345
+ const fontsKey = fonts.reduce(function(prev, item) {
346
+ prev += item.string;
347
+ return prev;
348
+ }, '');
349
+ const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
350
+ if (!widthCache.has(mapKey)) {
351
+ ctx.save();
352
+ const count = lines.length;
353
+ let width = 0;
354
+ let height = 0;
355
+ for (let i = 0; i < count; i++) {
356
+ const font = fonts[Math.min(i, fonts.length - 1)];
357
+ ctx.font = font.string;
358
+ const text = lines[i];
359
+ width = Math.max(width, ctx.measureText(text).width);
360
+ height += font.lineHeight;
361
+ }
362
+ ctx.restore();
363
+ widthCache.set(mapKey, {width, height});
364
+ }
365
+ return widthCache.get(mapKey);
366
+ }
367
+
368
+ function labelToDraw(ctx, rect, options, labelSize) {
369
+ const {overflow, padding} = options;
370
+ const {width, height} = labelSize;
371
+ if (overflow === 'hidden') {
372
+ return !((width + padding * 2) > rect.w || (height + padding * 2) > rect.h);
373
+ }
374
+ return true;
375
+ }
376
+
377
+ function drawLabel(ctx, rect, options) {
378
+ const labels = options.labels;
379
+ const content = labels.formatter;
380
+ if (!content) {
381
+ return;
382
+ }
383
+ const contents = isArray(content) ? content : [content];
384
+ const {font, hoverFont} = labels;
385
+ const optFont = (rect.active ? hoverFont : font) || font;
386
+ const fonts = isArray(optFont) ? optFont.map(f => toFont(f)) : [toFont(optFont)];
387
+ const labelSize = measureLabelSize(ctx, contents, fonts);
388
+ if (!labelToDraw(ctx, rect, labels, labelSize)) {
389
+ return;
390
+ }
391
+ const {color, hoverColor, align} = labels;
392
+ const optColor = (rect.active ? hoverColor : color) || color;
393
+ const colors = isArray(optColor) ? optColor : [optColor];
394
+ const xyPoint = calculateXYLabel(rect, labels, labelSize);
395
+ ctx.textAlign = align;
396
+ ctx.textBaseline = 'middle';
397
+ let lhs = 0;
398
+ contents.forEach(function(l, i) {
399
+ const c = colors[Math.min(i, colors.length - 1)];
400
+ const f = fonts[Math.min(i, fonts.length - 1)];
401
+ const lh = f.lineHeight;
402
+ ctx.font = f.string;
403
+ ctx.fillStyle = c;
404
+ ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
405
+ lhs += lh;
406
+ });
407
+ }
408
+
409
+ function drawDivider(ctx, rect, options, item) {
410
+ const dividers = options.dividers;
411
+ if (!dividers.display || !item._data.children.length) {
412
+ return;
413
+ }
414
+ const {x, y, w, h} = rect;
415
+ const {lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth} = dividers;
416
+ ctx.save();
417
+ ctx.strokeStyle = lineColor;
418
+ ctx.lineCap = lineCapStyle;
419
+ ctx.setLineDash(lineDash);
420
+ ctx.lineDashOffset = lineDashOffset;
421
+ ctx.lineWidth = lineWidth;
422
+ ctx.beginPath();
423
+ if (w > h) {
424
+ const w2 = w / 2;
425
+ ctx.moveTo(x + w2, y);
426
+ ctx.lineTo(x + w2, y + h);
427
+ } else {
428
+ const h2 = h / 2;
429
+ ctx.moveTo(x, y + h2);
430
+ ctx.lineTo(x + w, y + h2);
431
+ }
432
+ ctx.stroke();
433
+ ctx.restore();
434
+ }
435
+
436
+ function calculateXYLabel(rect, options, labelSize) {
437
+ const {align, position, padding} = options;
438
+ let x, y;
439
+ x = calculateX(rect, align, padding);
440
+ if (position === 'top') {
441
+ y = rect.y + padding;
442
+ } else if (position === 'bottom') {
443
+ y = rect.y + rect.h - padding - labelSize.height;
444
+ } else {
445
+ y = rect.y + (rect.h - labelSize.height) / 2 + padding;
446
+ }
447
+ return {x, y};
448
+ }
449
+
450
+ function calculateX(rect, align, padding) {
451
+ if (align === 'left') {
452
+ return rect.x + padding;
453
+ } else if (align === 'right') {
454
+ return rect.x + rect.w - padding;
455
+ }
456
+ return rect.x + rect.w / 2;
457
+ }
458
+
459
+ class TreemapElement extends Element {
460
+
461
+ constructor(cfg) {
462
+ super();
463
+
464
+ this.options = undefined;
465
+ this.width = undefined;
466
+ this.height = undefined;
467
+
468
+ if (cfg) {
469
+ Object.assign(this, cfg);
470
+ }
471
+ }
472
+
473
+ draw(ctx, data, levels = 0, dpr) {
474
+ if (!data) {
475
+ return;
476
+ }
477
+ const options = this.options;
478
+ const {inner, outer} = boundingRects(this, dpr);
479
+
480
+ const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
481
+
482
+ ctx.save();
483
+
484
+ if (outer.w !== inner.w || outer.h !== inner.h) {
485
+ ctx.beginPath();
486
+ addRectPath(ctx, outer);
487
+ ctx.clip();
488
+ addRectPath(ctx, inner);
489
+ ctx.fillStyle = options.borderColor;
490
+ ctx.fill('evenodd');
491
+ }
492
+
493
+ ctx.beginPath();
494
+ addRectPath(ctx, inner);
495
+ ctx.fillStyle = options.backgroundColor;
496
+ ctx.fill();
497
+
498
+ drawDivider(ctx, inner, options, data);
499
+ drawText(ctx, inner, options, data, levels);
500
+ ctx.restore();
501
+ }
502
+
503
+ inRange(mouseX, mouseY, useFinalPosition) {
504
+ return inRange(this, mouseX, mouseY, useFinalPosition);
505
+ }
506
+
507
+ inXRange(mouseX, useFinalPosition) {
508
+ return inRange(this, mouseX, null, useFinalPosition);
509
+ }
510
+
511
+ inYRange(mouseY, useFinalPosition) {
512
+ return inRange(this, null, mouseY, useFinalPosition);
513
+ }
514
+
515
+ getCenterPoint(useFinalPosition) {
516
+ const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
517
+ return {
518
+ x: x + width / 2,
519
+ y: y + height / 2
520
+ };
521
+ }
522
+
523
+ tooltipPosition() {
524
+ return this.getCenterPoint();
525
+ }
526
+
527
+ /**
528
+ * @todo: remove this unused function in v3
529
+ */
530
+ getRange(axis) {
531
+ return axis === 'x' ? this.width / 2 : this.height / 2;
532
+ }
533
+ }
534
+
535
+ TreemapElement.id = 'treemap';
536
+
537
+ TreemapElement.defaults = {
538
+ label: undefined,
539
+ borderRadius: 0,
540
+ borderWidth: 0,
541
+ captions: {
542
+ align: undefined,
543
+ color: 'black',
544
+ display: true,
545
+ font: {},
546
+ formatter: (ctx) => ctx.raw.g || ctx.raw._data.label || '',
547
+ padding: 3
548
+ },
549
+ dividers: {
550
+ display: false,
551
+ lineCapStyle: 'butt',
552
+ lineColor: 'black',
553
+ lineDash: [],
554
+ lineDashOffset: 0,
555
+ lineWidth: 1,
556
+ },
557
+ labels: {
558
+ align: 'center',
559
+ color: 'black',
560
+ display: false,
561
+ font: {},
562
+ formatter(ctx) {
563
+ if (ctx.raw.g) {
564
+ return [ctx.raw.g, ctx.raw.v + ''];
565
+ }
566
+ return ctx.raw._data.label ? [ctx.raw._data.label, ctx.raw.v + ''] : ctx.raw.v + '';
567
+ },
568
+ overflow: 'cut',
569
+ position: 'middle',
570
+ padding: 3
571
+ },
572
+ rtl: false,
573
+ spacing: 0.5
574
+ };
575
+
576
+ TreemapElement.descriptors = {
577
+ labels: {
578
+ _fallback: true
579
+ },
580
+ captions: {
581
+ _fallback: true
582
+ },
583
+ _scriptable: true,
584
+ _indexable: false
585
+ };
586
+
587
+ TreemapElement.defaultRoutes = {
588
+ backgroundColor: 'backgroundColor',
589
+ borderColor: 'borderColor'
590
+ };
591
+
592
+ function getDims(itm, w2, s2, key, dpr, maxd2) {
124
593
  const a = itm._normalized;
125
594
  const ar = w2 * a / s2;
126
- const d1 = Math.sqrt(a * ar);
127
- const d2 = a / d1;
595
+ const d1 = rasterize(Math.sqrt(a * ar), dpr);
596
+ const d2 = Math.min(rasterize(a / d1, dpr), maxd2);
128
597
  const w = key === '_ix' ? d1 : d2;
129
598
  const h = key === '_ix' ? d2 : d1;
130
599
 
131
600
  return {d1, d2, w, h};
132
601
  }
133
602
 
134
- const getX = (rect, w) => round(rect.rtl ? rect.x + rect.w - rect._ix - w : rect.x + rect._ix, 4);
603
+ const getX = (rect, w) => rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
135
604
 
136
- function buildRow(rect, itm, dims, sum) {
137
- const r = {
605
+ function buildRow(rect, itm, dims, sum, dpr) {
606
+ const r = rasterizeRect({
138
607
  x: getX(rect, dims.w),
139
- y: round(rect.y + rect._iy, 4),
140
- w: round(dims.w, 4),
141
- h: round(dims.h, 4),
142
- a: round(itm._normalized, 4),
608
+ y: rect.y + rect._iy,
609
+ w: dims.w,
610
+ h: dims.h,
611
+ a: itm._normalized,
143
612
  v: itm.value,
144
613
  s: sum,
145
614
  _data: itm._data
146
- };
615
+ }, dpr);
147
616
  if (itm.group) {
148
617
  r.g = itm.group;
149
618
  r.l = itm.level;
@@ -153,16 +622,16 @@ function buildRow(rect, itm, dims, sum) {
153
622
  }
154
623
 
155
624
  class Rect {
156
- constructor(r) {
157
- const me = this;
625
+ constructor(r, dpr) {
626
+ this.dpr = dpr;
158
627
  r = r || {w: 1, h: 1};
159
- me.rtl = !!r.rtl;
160
- me.x = r.x || r.left || 0;
161
- me.y = r.y || r.top || 0;
162
- me._ix = 0;
163
- me._iy = 0;
164
- me.w = r.w || r.width || (r.right - r.left);
165
- me.h = r.h || r.height || (r.bottom - r.top);
628
+ this.rtl = !!r.rtl;
629
+ this.x = r.x || r.left || 0;
630
+ this.y = r.y || r.top || 0;
631
+ this._ix = 0;
632
+ this._iy = 0;
633
+ this.w = r.w || r.width || (r.right - r.left);
634
+ this.h = r.h || r.height || (r.bottom - r.top);
166
635
  }
167
636
 
168
637
  get area() {
@@ -183,34 +652,61 @@ class Rect {
183
652
  }
184
653
 
185
654
  get side() {
186
- return this.dir === 'x' ? this.iw : this.ih;
655
+ return rasterize(this.dir === 'x' ? this.iw : this.ih, this.dpr);
187
656
  }
188
657
 
189
658
  map(arr) {
190
- const me = this;
191
- const ret = [];
659
+ const {dir, side, dpr} = this;
660
+ const {key, d2prop, id2prop, d2key} = getPropNames(dir);
192
661
  const sum = arr.nsum;
193
662
  const row = arr.get();
194
- const dir = me.dir;
195
- const side = me.side;
196
663
  const w2 = side * side;
197
- const key = dir === 'x' ? '_ix' : '_iy';
664
+ const availd2 = rasterize(this[id2prop], dpr);
198
665
  const s2 = sum * sum;
666
+ const ret = [];
199
667
  let maxd2 = 0;
200
668
  let totd1 = 0;
201
669
  for (const itm of row) {
202
- const dims = getDims(itm, w2, s2, key);
670
+ const dims = getDims(itm, w2, s2, key, dpr, availd2);
203
671
  totd1 += dims.d1;
204
- maxd2 = Math.max(maxd2, dims.d2);
205
- ret.push(buildRow(me, itm, dims, arr.sum));
206
- me[key] += dims.d1;
672
+ maxd2 = Math.min(Math.max(maxd2, dims.d2), availd2);
673
+ ret.push(buildRow(this, itm, dims, arr.sum, dpr));
674
+ this[key] += dims.d1;
207
675
  }
208
- me[dir === 'y' ? '_ix' : '_iy'] += maxd2;
209
- me[key] -= totd1;
676
+ // make sure all rects are same size in d2 direction
677
+ for (const r of ret) {
678
+ const d2 = r[d2prop];
679
+ if (d2 !== maxd2) {
680
+ if (this.rtl && dir === 'y') {
681
+ // for RTL, x-coordinate needs to be adjusted too
682
+ r.x -= maxd2 - d2;
683
+ }
684
+ r[d2prop] = maxd2;
685
+ }
686
+ }
687
+ this[d2key] += maxd2;
688
+ this[key] -= totd1;
210
689
  return ret;
211
690
  }
212
691
  }
213
692
 
693
+ function getPropNames(dir) {
694
+ if (dir === 'x') {
695
+ return {
696
+ key: '_ix',
697
+ d2prop: 'h',
698
+ id2prop: 'ih',
699
+ d2key: '_iy'
700
+ };
701
+ }
702
+ return {
703
+ key: '_iy',
704
+ d2prop: 'w',
705
+ id2prop: 'iw',
706
+ d2key: '_ix'
707
+ };
708
+ }
709
+
214
710
  const min = Math.min;
215
711
  const max = Math.max;
216
712
 
@@ -308,15 +804,16 @@ function compareAspectRatio(oldStat, newStat, args) {
308
804
  *
309
805
  * @param {number[]|object[]} values
310
806
  * @param {object} rectangle
311
- * @param {string} key
312
- * @param {*} grp
313
- * @param {*} lvl
314
- * @param {*} gsum
807
+ * @param {string} [key]
808
+ * @param {number} [dpr=1]
809
+ * @param {*} [grp]
810
+ * @param {*} [lvl]
811
+ * @param {*} [gsum]
315
812
  */
316
- function squarify(values, rectangle, key, grp, lvl, gsum) {
813
+ function squarify(values, rectangle, key, dpr = 1, grp, lvl, gsum) {
317
814
  values = values || [];
318
815
  const rows = [];
319
- const rect = new Rect(rectangle);
816
+ const rect = new Rect(rectangle, dpr);
320
817
  const row = new StatArray('value', rect.area / sum(values, key));
321
818
  let length = rect.side;
322
819
  const n = values.length;
@@ -353,7 +850,7 @@ function squarify(values, rectangle, key, grp, lvl, gsum) {
353
850
  return flatten(rows);
354
851
  }
355
852
 
356
- var version = "2.0.2";
853
+ var version = "2.1.1";
357
854
 
358
855
  function rectNotEqual(r1, r2) {
359
856
  return !r1 || !r2
@@ -378,98 +875,41 @@ function arrayNotEqual(a1, a2) {
378
875
  return false;
379
876
  }
380
877
 
381
- function shouldDrawCaption(rect, font) {
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) {
878
+ function buildData(dataset, mainRect, dpr) {
449
879
  const key = dataset.key || '';
880
+ const treeLeafKey = dataset.treeLeafKey || '_leaf';
450
881
  let tree = dataset.tree || [];
882
+ if (isObject(tree)) {
883
+ tree = normalizeTreeToArray(key, treeLeafKey, tree);
884
+ }
451
885
  const groups = dataset.groups || [];
452
886
  const glen = groups.length;
453
- const sp = valueOrDefault(dataset.spacing, 0) + valueOrDefault(dataset.borderWidth, 0);
454
- const captionsFont = captions.font || {};
455
- const font = toFont(captionsFont);
887
+ const sp = valueOrDefault(dataset.spacing, 0);
888
+ const captions = dataset.captions || {};
889
+ const font = toFont(captions.font);
456
890
  const padding = valueOrDefault(captions.padding, 3);
457
891
 
458
892
  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);
462
- const gsq = squarify(gdata, rect, key, g, gidx, gs);
893
+ const g = getGroupKey(groups[gidx]);
894
+ const pg = (gidx > 0) && getGroupKey(groups[gidx - 1]);
895
+ const gdata = group(tree, g, key, treeLeafKey, pg, parent, groups.filter((item, index) => index <= gidx));
896
+ const gsq = squarify(gdata, rect, key, dpr, g, gidx, gs);
463
897
  const ret = gsq.slice();
464
- let subRect;
465
898
  if (gidx < glen - 1) {
466
899
  gsq.forEach((sq) => {
467
- subRect = {x: sq.x + sp, y: sq.y + sp, w: sq.w - 2 * sp, h: sq.h - 2 * sp};
468
- if (valueOrDefault(captions.display, true) && shouldDrawCaption(sq, font)) {
900
+ const bw = parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
901
+ const subRect = {
902
+ ...rect,
903
+ x: sq.x + sp + bw.l,
904
+ y: sq.y + sp + bw.t,
905
+ w: sq.w - 2 * sp - bw.l - bw.r,
906
+ h: sq.h - 2 * sp - bw.t - bw.b,
907
+ };
908
+ if (shouldDrawCaption(subRect, captions)) {
469
909
  subRect.y += font.lineHeight + padding * 2;
470
910
  subRect.h -= font.lineHeight + padding * 2;
471
911
  }
472
- ret.push(...recur(gidx + 1, subRect, sq.g, sq.s));
912
+ ret.push(...recur(gidx + 1, rasterizeRect(subRect, dpr), sq.g, sq.s));
473
913
  });
474
914
  }
475
915
  return ret;
@@ -481,54 +921,22 @@ function buildData(dataset, mainRect, captions) {
481
921
 
482
922
  return glen
483
923
  ? recur(0, mainRect)
484
- : squarify(tree, mainRect, key);
924
+ : squarify(tree, mainRect, key, dpr);
485
925
  }
486
926
 
487
- function drawLabels(ctx, item, rect) {
488
- const opts = rect.options;
489
- const labelsOpts = opts.labels;
490
- if (!labelsOpts || !labelsOpts.display) {
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};
927
+ function getMinMax(data, useTree) {
928
+ const vMax = useTree ? 1 : maxValue(data);
929
+ const vMin = useTree ? 0 : minValue(data, vMax);
930
+ return {vMin, vMax};
523
931
  }
524
932
 
525
- function calculateX(rect, align, padding, borderWidth) {
526
- if (align === 'left') {
527
- return rect.x + padding + borderWidth;
528
- } else if (align === 'right') {
529
- return rect.x + rect.width - padding - borderWidth;
530
- }
531
- return rect.x + rect.width / 2;
933
+ function getArea({xScale, yScale}, data, rtl, useTree) {
934
+ const {vMin, vMax} = getMinMax(data, useTree);
935
+ const xMin = xScale.getPixelForValue(0);
936
+ const xMax = xScale.getPixelForValue(1);
937
+ const yMin = yScale.getPixelForValue(vMin);
938
+ const yMax = yScale.getPixelForValue(vMax);
939
+ return {x: xMin, y: yMax, w: xMax - xMin, h: yMin - yMax, rtl};
532
940
  }
533
941
 
534
942
  class TreemapController extends DatasetController {
@@ -538,6 +946,7 @@ class TreemapController extends DatasetController {
538
946
  this._rect = undefined;
539
947
  this._key = undefined;
540
948
  this._groups = undefined;
949
+ this._useTree = undefined;
541
950
  }
542
951
 
543
952
  initialize() {
@@ -545,38 +954,52 @@ class TreemapController extends DatasetController {
545
954
  super.initialize();
546
955
  }
547
956
 
957
+ /**
958
+ * @todo: Remove with https://github.com/kurkle/chartjs-chart-treemap/issues/137
959
+ */
960
+ updateRangeFromParsed(range, scale) {
961
+ if (range.updated) {
962
+ return;
963
+ }
964
+ range.updated = true;
965
+ if (scale.axis === 'x') {
966
+ range.min = 0;
967
+ range.max = 1;
968
+ return;
969
+ }
970
+ const dataset = this.getDataset();
971
+ const {vMin, vMax} = getMinMax(dataset.data, this._useTree);
972
+ range.min = vMin;
973
+ range.max = vMax;
974
+ }
975
+
548
976
  update(mode) {
549
- const me = this;
550
- const meta = me.getMeta();
551
- const dataset = me.getDataset();
977
+ const meta = this.getMeta();
978
+ const dataset = this.getDataset();
979
+ const dpr = this.chart.currentDevicePixelRatio;
980
+
981
+ if (!defined(this._useTree)) {
982
+ this._useTree = !!dataset.tree;
983
+ }
552
984
  const groups = dataset.groups || (dataset.groups = []);
553
- const captions = dataset.captions ? dataset.captions : {};
554
- const area = me.chart.chartArea;
555
985
  const key = dataset.key || '';
556
986
  const rtl = !!dataset.rtl;
557
987
 
558
- const mainRect = {x: area.left, y: area.top, w: area.right - area.left, h: area.bottom - area.top, rtl};
988
+ const mainRect = rasterizeRect(getArea(meta, dataset.data, rtl, this._useTree), dpr);
559
989
 
560
- if (mode === 'reset' || rectNotEqual(me._rect, mainRect) || me._key !== key || arrayNotEqual(me._groups, groups)) {
561
- me._rect = mainRect;
562
- me._groups = groups.slice();
563
- me._key = key;
990
+ if (mode === 'reset' || rectNotEqual(this._rect, mainRect) || this._key !== key || arrayNotEqual(this._groups, groups)) {
991
+ this._rect = mainRect;
992
+ this._groups = groups.slice();
993
+ this._key = key;
564
994
 
565
- dataset.data = buildData(dataset, mainRect, captions);
995
+ dataset.data = buildData(dataset, mainRect, dpr);
566
996
  // @ts-ignore using private stuff
567
- me._dataCheck();
997
+ this._dataCheck();
568
998
  // @ts-ignore using private stuff
569
- me._resyncElements();
999
+ this._resyncElements();
570
1000
  }
571
1001
 
572
- me.updateElements(meta.data, 0, meta.data.length, mode);
573
- }
574
-
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;
1002
+ this.updateElements(meta.data, 0, meta.data.length, mode);
580
1003
  }
581
1004
 
582
1005
  updateElements(rects, start, count, mode) {
@@ -609,41 +1032,21 @@ class TreemapController extends DatasetController {
609
1032
  me.updateSharedOptions(sharedOptions, mode, firstOpts);
610
1033
  }
611
1034
 
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
- }
1035
+ draw() {
1036
+ const {ctx, chartArea, currentDevicePixelRatio: dpr} = this.chart;
1037
+ const metadata = this.getMeta().data || [];
1038
+ const dataset = this.getDataset();
1039
+ const levels = (dataset.groups || []).length - 1;
1040
+ const data = dataset.data;
622
1041
 
623
- _drawRects(ctx, data, metadata, levels) {
1042
+ clipArea(ctx, chartArea);
624
1043
  for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
625
1044
  const rect = metadata[i];
626
- const item = data[i];
627
1045
  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
- }
1046
+ rect.draw(ctx, data[i], levels, dpr);
633
1047
  }
634
1048
  }
635
- }
636
-
637
- draw() {
638
- const me = this;
639
- const ctx = me.chart.ctx;
640
- const metadata = me.getMeta().data || [];
641
- const dataset = me.getDataset();
642
- const levels = (dataset.groups || []).length - 1;
643
- const data = dataset.data || [];
644
-
645
- me._drawRects(ctx, data, metadata, levels);
646
- me._drawDividers(ctx, data, metadata);
1049
+ unclipArea(ctx);
647
1050
  }
648
1051
  }
649
1052
 
@@ -661,13 +1064,6 @@ TreemapController.defaults = {
661
1064
  },
662
1065
  },
663
1066
 
664
- borderWidth: 0,
665
- spacing: 0.5,
666
- dividers: {
667
- display: false,
668
- lineWidth: 1,
669
- }
670
-
671
1067
  };
672
1068
 
673
1069
  TreemapController.descriptors = {
@@ -678,6 +1074,7 @@ TreemapController.descriptors = {
678
1074
  TreemapController.overrides = {
679
1075
  interaction: {
680
1076
  mode: 'point',
1077
+ includeInvisible: true,
681
1078
  intersect: true
682
1079
  },
683
1080
 
@@ -698,7 +1095,7 @@ TreemapController.overrides = {
698
1095
  label(item) {
699
1096
  const dataset = item.dataset;
700
1097
  const dataItem = dataset.data[item.dataIndex];
701
- const label = dataItem.g || dataset.label;
1098
+ const label = dataItem.g || dataItem._data.label || dataset.label;
702
1099
  return (label ? label + ': ' : '') + dataItem.v;
703
1100
  }
704
1101
  }
@@ -717,7 +1114,7 @@ TreemapController.overrides = {
717
1114
  };
718
1115
 
719
1116
  TreemapController.beforeRegister = function() {
720
- requireVersion('3.6', Chart.version);
1117
+ requireVersion('3.8', Chart.version);
721
1118
  };
722
1119
 
723
1120
  TreemapController.afterRegister = function() {
@@ -733,6 +1130,8 @@ TreemapController.afterRegister = function() {
733
1130
 
734
1131
  return el.tooltipPosition();
735
1132
  };
1133
+ } else {
1134
+ console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
736
1135
  }
737
1136
  };
738
1137
 
@@ -743,181 +1142,4 @@ TreemapController.afterUnregister = function() {
743
1142
  }
744
1143
  };
745
1144
 
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
1145
  export { TreemapController, TreemapElement };