chartjs-chart-treemap 3.1.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.
@@ -1,1181 +1,1221 @@
1
1
  /*!
2
- * chartjs-chart-treemap v3.1.0
2
+ * chartjs-chart-treemap v0.0.0-development
3
3
  * https://chartjs-chart-treemap.pages.dev/
4
- * (c) 2024 Jukka Kurkela
4
+ * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
6
6
  */
7
- import { Element, DatasetController, Chart, registry } from 'chart.js';
8
- import { isObject, addRoundedRectPath, defined, toFont, isArray, isNumber, toTRBL, toTRBLCorners, valueOrDefault, clipArea, unclipArea } from 'chart.js/helpers';
7
+ import { DatasetController, Chart, registry, Element } from 'chart.js';
8
+ import { toTRBL, toTRBLCorners, isObject, defined, toFont, valueOrDefault, isArray, isNumber, clipArea, unclipArea, addRoundedRectPath } from 'chart.js/helpers';
9
9
 
10
- const isOlderPart = (act, req) => req > act || (act.length > req.length && act.slice(0, req.length) === req);
10
+ var version = "0.0.0-development";
11
11
 
12
- const getGroupKey = (lvl) => '' + lvl;
13
-
14
- function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
15
- const objIndex = lvl - 1;
16
- if (keys[0] in obj && lvl > 0) {
17
- const record = tree.reduce(function(reduced, item, i) {
18
- if (i !== objIndex) {
19
- reduced[getGroupKey(i)] = item;
20
- }
21
- return reduced;
22
- }, {});
23
- record[treeLeafKey] = tree[objIndex];
24
- keys.forEach(function(k) {
25
- record[k] = obj[k];
26
- });
27
- result.push(record);
28
- } else {
29
- for (const childKey of Object.keys(obj)) {
30
- const child = obj[childKey];
31
- if (isObject(child)) {
32
- tree.push(childKey);
33
- scanTreeObject(keys, treeLeafKey, child, tree, lvl + 1, result);
34
- }
35
- }
36
- }
37
- tree.splice(objIndex, 1);
38
- return result;
12
+ function scaleRect(sq, xScale, yScale, sp) {
13
+ const sp2 = sp * 2;
14
+ const x = xScale.getPixelForValue(sq.x);
15
+ const y = yScale.getPixelForValue(sq.y);
16
+ const w = xScale.getPixelForValue(sq.x + sq.w) - x;
17
+ const h = yScale.getPixelForValue(sq.y + sq.h) - y;
18
+ return {
19
+ height: h - sp2,
20
+ hidden: sp2 > w || sp2 > h,
21
+ width: w - sp2,
22
+ x: x + sp,
23
+ y: y + sp
24
+ };
39
25
  }
40
-
41
- function normalizeTreeToArray(keys, treeLeafKey, obj) {
42
- const data = scanTreeObject(keys, treeLeafKey, obj);
43
- if (!data.length) {
44
- return data;
45
- }
46
- const max = data.reduce(function(maxVal, element) {
47
- // minus 2 because _leaf and value properties are added
48
- // on top to groups ones
49
- const ikeys = Object.keys(element).length - 2;
50
- return maxVal > ikeys ? maxVal : ikeys;
51
- });
52
- data.forEach(function(element) {
53
- for (let i = 0; i < max; i++) {
54
- const groupKey = getGroupKey(i);
55
- if (!element[groupKey]) {
56
- element[groupKey] = '';
57
- }
58
- }
59
- });
60
- return data;
26
+ function rectNotEqual(r1, r2) {
27
+ return !r1 || !r2 || r1.x !== r2.x || r1.y !== r2.y || r1.w !== r2.w || r1.h !== r2.h || r1.rtl !== r2.rtl || r1.unsorted !== r2.unsorted;
61
28
  }
62
-
63
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
64
- function flatten(input) {
65
- const stack = [...input];
66
- const res = [];
67
- while (stack.length) {
68
- // pop value from stack
69
- const next = stack.pop();
70
- if (Array.isArray(next)) {
71
- // push back array items, won't modify the original input
72
- stack.push(...next);
73
- } else {
74
- res.push(next);
29
+ function arrayNotEqual(a, b) {
30
+ let i;
31
+ let n;
32
+ if (!a || !b) {
33
+ return true;
75
34
  }
76
- }
77
- // reverse to restore input order
78
- return res.reverse();
79
- }
80
-
81
- function getPath(groups, value, defaultValue) {
82
- if (!groups.length) {
83
- return;
84
- }
85
- const path = [];
86
- for (const grp of groups) {
87
- const item = value[grp];
88
- if (item === '') {
89
- path.push(defaultValue);
90
- break;
91
- }
92
- path.push(item);
93
- }
94
- return path.length ? path.join('.') : defaultValue;
95
- }
96
-
97
- /**
98
- * @param {[]} values
99
- * @param {string} grp
100
- * @param {[string]} keys
101
- * @param {string} treeeLeafKey
102
- * @param {string} [mainGrp]
103
- * @param {*} [mainValue]
104
- * @param {[]} groups
105
- */
106
- function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = []) {
107
- const key = keys[0];
108
- const addKeys = keys.slice(1);
109
- const tmp = Object.create(null);
110
- const data = Object.create(null);
111
- const ret = [];
112
- let g, i, n;
113
- for (i = 0, n = values.length; i < n; ++i) {
114
- const v = values[i];
115
- if (mainGrp && v[mainGrp] !== mainValue) {
116
- continue;
117
- }
118
- g = v[grp] || v[treeLeafKey] || '';
119
- if (!(g in tmp)) {
120
- const tmpRef = tmp[g] = {value: 0};
121
- addKeys.forEach(function(k) {
122
- tmpRef[k] = 0;
123
- });
124
- data[g] = [];
125
- }
126
- tmp[g].value += +v[key];
127
- tmp[g].label = v[grp] || '';
128
- const tmpRef = tmp[g];
129
- addKeys.forEach(function(k) {
130
- tmpRef[k] += v[k];
131
- });
132
- tmp[g].path = getPath(groups, v, g);
133
- data[g].push(v);
134
- }
135
-
136
- Object.keys(tmp).forEach((k) => {
137
- const v = {children: data[k]};
138
- v[key] = +tmp[k].value;
139
- addKeys.forEach(function(ak) {
140
- v[ak] = +tmp[k][ak];
141
- });
142
- v[grp] = tmp[k].label;
143
- v.label = k;
144
- v.path = tmp[k].path;
145
-
146
- if (mainGrp) {
147
- v[mainGrp] = mainValue;
35
+ if (a === b) {
36
+ return false;
148
37
  }
149
- ret.push(v);
150
- });
151
-
152
- return ret;
153
- }
154
-
155
- function index(values, key) {
156
- let n = values.length;
157
- let i;
158
-
159
- if (!n) {
160
- return key;
161
- }
162
-
163
- const obj = isObject(values[0]);
164
- key = obj ? key : 'v';
165
-
166
- for (i = 0, n = values.length; i < n; ++i) {
167
- if (obj) {
168
- values[i]._idx = i;
169
- } else {
170
- values[i] = {v: values[i], _idx: i};
38
+ if (a.length !== b.length) {
39
+ return true;
40
+ }
41
+ for(i = 0, n = a.length; i < n; ++i){
42
+ if (a[i] !== b[i]) {
43
+ return true;
44
+ }
171
45
  }
172
- }
173
- return key;
46
+ return false;
174
47
  }
175
48
 
176
- function sort(values, key) {
177
- if (key) {
178
- values.sort((a, b) => +b[key] - +a[key]);
179
- } else {
180
- values.sort((a, b) => +b - +a);
181
- }
49
+ function limit(value, min, max) {
50
+ return Math.max(Math.min(value, max), min);
182
51
  }
183
-
184
- function sum(values, key) {
185
- let s, i, n;
186
-
187
- for (s = 0, i = 0, n = values.length; i < n; ++i) {
188
- s += key ? +values[i][key] : +values[i];
189
- }
190
-
191
- return s;
52
+ function parseBorderWidth(value, maxW, maxH) {
53
+ const o = toTRBL(value);
54
+ return {
55
+ b: limit(o.bottom, 0, maxH),
56
+ l: limit(o.left, 0, maxW),
57
+ r: limit(o.right, 0, maxW),
58
+ t: limit(o.top, 0, maxH)
59
+ };
60
+ }
61
+ function parseBorderRadius(value, maxW, maxH) {
62
+ const o = toTRBLCorners(value);
63
+ const maxR = Math.min(maxW, maxH);
64
+ return {
65
+ bottomLeft: limit(o.bottomLeft, 0, maxR),
66
+ bottomRight: limit(o.bottomRight, 0, maxR),
67
+ topLeft: limit(o.topLeft, 0, maxR),
68
+ topRight: limit(o.topRight, 0, maxR)
69
+ };
192
70
  }
193
71
 
194
- /**
195
- * @param {string} pkg
196
- * @param {string} min
197
- * @param {string} ver
198
- * @param {boolean} [strict=true]
199
- * @returns {boolean}
200
- */
201
- function requireVersion(pkg, min, ver, strict = true) {
202
- const parts = ver.split('.');
203
- let i = 0;
204
- for (const req of min.split('.')) {
205
- const act = parts[i++];
206
- if (parseInt(req, 10) < parseInt(act, 10)) {
207
- break;
208
- }
209
- if (isOlderPart(act, req)) {
210
- if (strict) {
211
- throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`);
212
- } else {
213
- return false;
214
- }
72
+ function getDims(itm, w2, s2, key) {
73
+ const a = itm._normalized;
74
+ const ar = w2 * a / s2;
75
+ const d1 = Math.sqrt(a * ar);
76
+ const d2 = a / d1;
77
+ const w = key === '_ix' ? d1 : d2;
78
+ const h = key === '_ix' ? d2 : d1;
79
+ return {
80
+ d1,
81
+ d2,
82
+ h,
83
+ w
84
+ };
85
+ }
86
+ const getX = (rect, w)=>rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
87
+ function buildRow(rect, itm, dims, sum) {
88
+ const r = {
89
+ _data: itm._data,
90
+ a: itm._normalized,
91
+ h: dims.h,
92
+ s: sum,
93
+ v: itm.value,
94
+ vs: itm.values,
95
+ w: dims.w,
96
+ x: getX(rect, dims.w),
97
+ y: rect.y + rect._iy
98
+ };
99
+ if (itm.group) {
100
+ r.g = itm.group;
101
+ r.l = itm.level;
102
+ r.gs = itm.groupSum;
215
103
  }
216
- }
217
- return true;
104
+ return r;
218
105
  }
219
-
220
- const widthCache = new Map();
221
-
222
- /**
223
- * Helper function to get the bounds of the rect
224
- * @param {TreemapElement} rect the rect
225
- * @param {boolean} [useFinalPosition]
226
- * @return {object} bounds of the rect
227
- * @private
228
- */
229
- function getBounds(rect, useFinalPosition) {
230
- const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
231
- return {left: x, top: y, right: x + width, bottom: y + height};
106
+ class Rect {
107
+ get area() {
108
+ return this.w * this.h;
109
+ }
110
+ get iw() {
111
+ return this.w - this._ix;
112
+ }
113
+ get ih() {
114
+ return this.h - this._iy;
115
+ }
116
+ get dir() {
117
+ const ih = this.ih;
118
+ return ih <= this.iw && ih > 0 ? 'y' : 'x';
119
+ }
120
+ get side() {
121
+ return this.dir === 'x' ? this.iw : this.ih;
122
+ }
123
+ map(arr) {
124
+ const { dir, side } = this;
125
+ const key = dir === 'x' ? '_ix' : '_iy';
126
+ const sum = arr.nsum;
127
+ const row = arr.get();
128
+ const w2 = side * side;
129
+ const s2 = sum * sum;
130
+ const ret = [];
131
+ let maxd2 = 0;
132
+ let totd1 = 0;
133
+ for (const itm of row){
134
+ const dims = getDims(itm, w2, s2, key);
135
+ totd1 += dims.d1;
136
+ maxd2 = Math.max(maxd2, dims.d2);
137
+ ret.push(buildRow(this, itm, dims, arr.sum));
138
+ this[key] += dims.d1;
139
+ }
140
+ this[dir === 'x' ? '_iy' : '_ix'] += maxd2;
141
+ this[key] -= totd1;
142
+ return ret;
143
+ }
144
+ constructor(r){
145
+ r = r || {
146
+ h: 1,
147
+ w: 1
148
+ };
149
+ this.rtl = !!r.rtl;
150
+ this.unsorted = !!r.unsorted;
151
+ this.x = r.x || r.left || 0;
152
+ this.y = r.y || r.top || 0;
153
+ this._ix = 0;
154
+ this._iy = 0;
155
+ this.w = r.w || r.width || r.right - r.left;
156
+ this.h = r.h || r.height || r.bottom - r.top;
157
+ }
232
158
  }
233
159
 
234
- function limit(value, min, max) {
235
- return Math.max(Math.min(value, max), min);
160
+ const min = Math.min;
161
+ const max = Math.max;
162
+ function getStat(sa) {
163
+ return {
164
+ max: sa.max,
165
+ min: sa.min,
166
+ nmax: sa.nmax,
167
+ nmin: sa.nmin,
168
+ nsum: sa.nsum,
169
+ sum: sa.sum
170
+ };
236
171
  }
237
-
238
- function parseBorderWidth(value, maxW, maxH) {
239
- const o = toTRBL(value);
240
-
241
- return {
242
- t: limit(o.top, 0, maxH),
243
- r: limit(o.right, 0, maxW),
244
- b: limit(o.bottom, 0, maxH),
245
- l: limit(o.left, 0, maxW)
246
- };
172
+ function getNewStat(sa, o) {
173
+ const v = +o[sa.key];
174
+ const n = v * sa.ratio;
175
+ o._normalized = n;
176
+ return {
177
+ max: max(sa.max, v),
178
+ min: min(sa.min, v),
179
+ nmax: max(sa.nmax, n),
180
+ nmin: min(sa.nmin, n),
181
+ nsum: sa.nsum + n,
182
+ sum: sa.sum + v
183
+ };
247
184
  }
248
-
249
- function parseBorderRadius(value, maxW, maxH) {
250
- const o = toTRBLCorners(value);
251
- const maxR = Math.min(maxW, maxH);
252
-
253
- return {
254
- topLeft: limit(o.topLeft, 0, maxR),
255
- topRight: limit(o.topRight, 0, maxR),
256
- bottomLeft: limit(o.bottomLeft, 0, maxR),
257
- bottomRight: limit(o.bottomRight, 0, maxR)
258
- };
185
+ function setStat(sa, stat) {
186
+ Object.assign(sa, stat);
259
187
  }
260
-
261
- function boundingRects(rect) {
262
- const bounds = getBounds(rect);
263
- const width = bounds.right - bounds.left;
264
- const height = bounds.bottom - bounds.top;
265
- const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
266
- const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
267
- const outer = {
268
- x: bounds.left,
269
- y: bounds.top,
270
- w: width,
271
- h: height,
272
- active: rect.active,
273
- radius
274
- };
275
-
276
- return {
277
- outer,
278
- inner: {
279
- x: outer.x + border.l,
280
- y: outer.y + border.t,
281
- w: outer.w - border.l - border.r,
282
- h: outer.h - border.t - border.b,
283
- active: rect.active,
284
- radius: {
285
- topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
286
- topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
287
- bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
288
- bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
289
- }
290
- }
291
- };
188
+ function push(sa, o, stat) {
189
+ sa._arr.push(o);
190
+ setStat(sa, stat);
292
191
  }
293
-
294
- function inRange(rect, x, y, useFinalPosition) {
295
- const skipX = x === null;
296
- const skipY = y === null;
297
- const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
298
-
299
- return bounds
300
- && (skipX || x >= bounds.left && x <= bounds.right)
301
- && (skipY || y >= bounds.top && y <= bounds.bottom);
192
+ class StatArray {
193
+ get length() {
194
+ return this._arr.length;
195
+ }
196
+ reset() {
197
+ this._arr = [];
198
+ this._hist = [];
199
+ this.sum = 0;
200
+ this.nsum = 0;
201
+ this.min = Infinity;
202
+ this.max = -Infinity;
203
+ this.nmin = Infinity;
204
+ this.nmax = -Infinity;
205
+ }
206
+ push(o) {
207
+ push(this, o, getNewStat(this, o));
208
+ }
209
+ pushIf(o, fn, ...args) {
210
+ const nstat = getNewStat(this, o);
211
+ if (!fn(getStat(this), nstat, args)) {
212
+ return o;
213
+ }
214
+ push(this, o, nstat);
215
+ }
216
+ get() {
217
+ return this._arr;
218
+ }
219
+ constructor(key, ratio){
220
+ this._arr = [];
221
+ this._hist = [];
222
+ this.sum = 0;
223
+ this.nsum = 0;
224
+ this.min = Infinity;
225
+ this.max = -Infinity;
226
+ this.nmin = Infinity;
227
+ this.nmax = -Infinity;
228
+ this.key = key;
229
+ this.ratio = ratio;
230
+ this.reset();
231
+ }
302
232
  }
303
233
 
304
- function hasRadius(radius) {
305
- return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
234
+ const isOlderPart = (act, req)=>req > act || act.length > req.length && act.startsWith(req);
235
+ const getGroupKey = (lvl)=>String(lvl);
236
+ const hasGroupValue = (value)=>value !== undefined && value !== null && value !== '';
237
+ function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
238
+ const objIndex = lvl - 1;
239
+ if (keys[0] in obj && lvl > 0) {
240
+ const record = tree.reduce((reduced, item, i)=>{
241
+ if (i !== objIndex) {
242
+ reduced[getGroupKey(i)] = item;
243
+ }
244
+ return reduced;
245
+ }, {});
246
+ record[treeLeafKey] = tree[objIndex];
247
+ keys.forEach((k)=>{
248
+ record[k] = obj[k];
249
+ });
250
+ result.push(record);
251
+ } else {
252
+ for (const childKey of Object.keys(obj)){
253
+ const child = obj[childKey];
254
+ if (isObject(child)) {
255
+ tree.push(childKey);
256
+ scanTreeObject(keys, treeLeafKey, child, tree, lvl + 1, result);
257
+ }
258
+ }
259
+ }
260
+ tree.splice(objIndex, 1);
261
+ return result;
306
262
  }
307
-
308
- /**
309
- * Add a path of a rectangle to the current sub-path
310
- * @param {CanvasRenderingContext2D} ctx Context
311
- * @param {*} rect Bounding rect
312
- */
313
- function addNormalRectPath(ctx, rect) {
314
- ctx.rect(rect.x, rect.y, rect.w, rect.h);
263
+ function normalizeTreeToArray(keys, treeLeafKey, obj) {
264
+ const data = scanTreeObject(keys, treeLeafKey, obj);
265
+ if (!data.length) {
266
+ return data;
267
+ }
268
+ const max = data.reduce((maxVal, element)=>{
269
+ const ikeys = Object.keys(element).length - 2;
270
+ return Math.max(maxVal, ikeys);
271
+ }, 0);
272
+ data.forEach((element)=>{
273
+ for(let i = 0; i < max; i++){
274
+ const groupKey = getGroupKey(i);
275
+ if (!element[groupKey]) {
276
+ element[groupKey] = '';
277
+ }
278
+ }
279
+ });
280
+ return data;
315
281
  }
316
-
317
- function shouldDrawCaption(rect, options) {
318
- if (!options || options.display === false) {
319
- return false;
320
- }
321
- const {w, h} = rect;
322
- const font = toFont(options.font);
323
- const min = font.lineHeight;
324
- const padding = limit(valueOrDefault(options.padding, 3) * 2, 0, Math.min(w, h));
325
- return (w - padding) > min && (h - padding) > min;
282
+ function flatten(input) {
283
+ const stack = [
284
+ ...input
285
+ ];
286
+ const res = [];
287
+ while(stack.length){
288
+ const next = stack.pop();
289
+ if (Array.isArray(next)) {
290
+ stack.push(...next);
291
+ } else {
292
+ res.push(next);
293
+ }
294
+ }
295
+ return res.reverse();
326
296
  }
327
-
328
- function drawText(ctx, rect, options, item, levels) {
329
- const {captions, labels} = options;
330
- ctx.save();
331
- ctx.beginPath();
332
- ctx.rect(rect.x, rect.y, rect.w, rect.h);
333
- ctx.clip();
334
- const isLeaf = item && (!defined(item.l) || item.l === levels);
335
- if (isLeaf && labels.display) {
336
- drawLabel(ctx, rect, options);
337
- } else if (!isLeaf && shouldDrawCaption(rect, captions)) {
338
- drawCaption(ctx, rect, options, item);
339
- }
340
- ctx.restore();
297
+ function getPath(groups, value, defaultValue) {
298
+ const path = [];
299
+ for (const grp of groups){
300
+ const item = value[grp];
301
+ if (!hasGroupValue(item)) {
302
+ continue;
303
+ }
304
+ path.push(item);
305
+ }
306
+ return path.length ? path.join('.') : defaultValue;
341
307
  }
342
-
343
- function drawCaption(ctx, rect, options, item) {
344
- const {captions, spacing, rtl} = options;
345
- const {color, hoverColor, font, hoverFont, padding, align, formatter} = captions;
346
- const oColor = (rect.active ? hoverColor : color) || color;
347
- const oAlign = align || (rtl ? 'right' : 'left');
348
- const optFont = (rect.active ? hoverFont : font) || font;
349
- const oFont = toFont(optFont);
350
- const lh = oFont.lineHeight / 2;
351
- const x = calculateX(rect, oAlign, padding);
352
- ctx.fillStyle = oColor;
353
- ctx.font = oFont.string;
354
- ctx.textAlign = oAlign;
355
- ctx.textBaseline = 'middle';
356
- ctx.fillText(formatter || item.g, x, rect.y + padding + spacing + lh);
308
+ function resolveGroup(value, treeLeafKey, allGroups, groupIndex) {
309
+ for(let idx = groupIndex; idx < allGroups.length; idx++){
310
+ const groupKey = getGroupKey(allGroups[idx]);
311
+ if (hasGroupValue(value[groupKey])) {
312
+ return {
313
+ group: groupKey,
314
+ groupIndex: idx,
315
+ value: value[groupKey]
316
+ };
317
+ }
318
+ }
319
+ if (hasGroupValue(value[treeLeafKey])) {
320
+ return {
321
+ group: treeLeafKey,
322
+ groupIndex,
323
+ value: value[treeLeafKey]
324
+ };
325
+ }
326
+ return undefined;
357
327
  }
358
-
359
- function measureLabelSize(ctx, lines, fonts) {
360
- const fontsKey = fonts.reduce(function(prev, item) {
361
- prev += item.string;
362
- return prev;
363
- }, '');
364
- const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
365
- if (!widthCache.has(mapKey)) {
366
- ctx.save();
367
- const count = lines.length;
368
- let width = 0;
369
- let height = 0;
370
- for (let i = 0; i < count; i++) {
371
- const font = fonts[Math.min(i, fonts.length - 1)];
372
- ctx.font = font.string;
373
- const text = lines[i];
374
- width = Math.max(width, ctx.measureText(text).width);
375
- height += font.lineHeight;
328
+ function ensureGroup(tmp, data, groupValue, addKeys) {
329
+ if (groupValue in tmp) {
330
+ return;
376
331
  }
377
- ctx.restore();
378
- widthCache.set(mapKey, {width, height});
379
- }
380
- return widthCache.get(mapKey);
332
+ tmp[groupValue] = {
333
+ value: 0
334
+ };
335
+ const tmpRef = tmp[groupValue];
336
+ addKeys.forEach((k)=>{
337
+ tmpRef[k] = 0;
338
+ });
339
+ data[groupValue] = [];
381
340
  }
382
-
383
- function toFonts(fonts, fitRatio) {
384
- return fonts.map(function(f) {
385
- f.size = Math.floor(f.size * fitRatio);
386
- f.lineHeight = undefined;
387
- return toFont(f);
388
- });
341
+ function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
342
+ const key = keys[0];
343
+ const addKeys = keys.slice(1);
344
+ const tmp = Object.create(null);
345
+ const data = Object.create(null);
346
+ const ret = [];
347
+ const resolvedAllGroups = allGroups.length ? allGroups : [
348
+ grp
349
+ ];
350
+ const resolvedGroupIndex = allGroups.length ? groupIndex : 0;
351
+ let i;
352
+ let n;
353
+ for(i = 0, n = values.length; i < n; ++i){
354
+ const v = values[i];
355
+ const itemGroup = resolveGroup(v, treeLeafKey, resolvedAllGroups, resolvedGroupIndex);
356
+ if (!itemGroup) {
357
+ return [];
358
+ }
359
+ const g = itemGroup.value;
360
+ ensureGroup(tmp, data, g, addKeys);
361
+ tmp[g].value += +v[key];
362
+ tmp[g].label = g;
363
+ tmp[g].group = itemGroup.group;
364
+ tmp[g].groupIndex = itemGroup.groupIndex;
365
+ const tmpRef = tmp[g];
366
+ addKeys.forEach((k)=>{
367
+ tmpRef[k] += v[k];
368
+ });
369
+ tmp[g].path = getPath(resolvedAllGroups.slice(0, itemGroup.groupIndex + 1), v, g);
370
+ data[g].push(v);
371
+ }
372
+ Object.keys(tmp).forEach((k)=>{
373
+ const v = {
374
+ children: data[k]
375
+ };
376
+ v[key] = +tmp[k].value;
377
+ addKeys.forEach((ak)=>{
378
+ v[ak] = +tmp[k][ak];
379
+ });
380
+ v[grp] = tmp[k].label;
381
+ v.group = tmp[k].group;
382
+ v.groupIndex = tmp[k].groupIndex;
383
+ v.label = k;
384
+ v.path = tmp[k].path;
385
+ ret.push(v);
386
+ });
387
+ return ret;
389
388
  }
390
-
391
- function labelToDraw(ctx, rect, options, labelSize) {
392
- const {overflow, padding} = options;
393
- const {width, height} = labelSize;
394
- if (overflow === 'hidden') {
395
- return !((width + padding * 2) > rect.w || (height + padding * 2) > rect.h);
396
- } else if (overflow === 'fit') {
397
- const ratio = Math.min(rect.w / (width + padding * 2), rect.h / (height + padding * 2));
398
- if (ratio < 1) {
399
- return ratio;
400
- }
401
- }
402
- return true;
389
+ function index(values, key) {
390
+ let n = values.length;
391
+ let i;
392
+ if (!n) {
393
+ return key;
394
+ }
395
+ const obj = isObject(values[0]);
396
+ key = obj ? key : 'v';
397
+ for(i = 0, n = values.length; i < n; ++i){
398
+ if (obj) {
399
+ values[i]._idx = i;
400
+ } else {
401
+ values[i] = {
402
+ _idx: i,
403
+ v: values[i]
404
+ };
405
+ }
406
+ }
407
+ return key;
403
408
  }
404
-
405
- function getFontFromOptions(rect, labels) {
406
- const {font, hoverFont} = labels;
407
- const optFont = (rect.active ? hoverFont : font) || font;
408
- return isArray(optFont) ? optFont.map(f => toFont(f)) : [toFont(optFont)];
409
+ function sort(values, key) {
410
+ if (key) {
411
+ values.sort((a, b)=>+b[key] - +a[key]);
412
+ } else {
413
+ values.sort((a, b)=>+b - +a);
414
+ }
409
415
  }
410
-
411
- function drawLabel(ctx, rect, options) {
412
- const labels = options.labels;
413
- const content = labels.formatter;
414
- if (!content) {
415
- return;
416
- }
417
- const contents = isArray(content) ? content : [content];
418
- let fonts = getFontFromOptions(rect, labels);
419
- let labelSize = measureLabelSize(ctx, contents, fonts);
420
- const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
421
- if (!lblToDraw) {
422
- return;
423
- }
424
- if (isNumber(lblToDraw)) {
425
- labelSize = {width: labelSize.width * lblToDraw, height: labelSize.height * lblToDraw};
426
- fonts = toFonts(fonts, lblToDraw);
427
- }
428
- const {color, hoverColor, align} = labels;
429
- const optColor = (rect.active ? hoverColor : color) || color;
430
- const colors = isArray(optColor) ? optColor : [optColor];
431
- const xyPoint = calculateXYLabel(rect, labels, labelSize);
432
- ctx.textAlign = align;
433
- ctx.textBaseline = 'middle';
434
- let lhs = 0;
435
- contents.forEach(function(l, i) {
436
- const c = colors[Math.min(i, colors.length - 1)];
437
- const f = fonts[Math.min(i, fonts.length - 1)];
438
- const lh = f.lineHeight;
439
- ctx.font = f.string;
440
- ctx.fillStyle = c;
441
- ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
442
- lhs += lh;
443
- });
416
+ function sum(values, key) {
417
+ let s;
418
+ let i;
419
+ let n;
420
+ for(s = 0, i = 0, n = values.length; i < n; ++i){
421
+ s += key ? +values[i][key] : +values[i];
422
+ }
423
+ return s;
444
424
  }
445
-
446
- function drawDivider(ctx, rect, options, item) {
447
- const dividers = options.dividers;
448
- if (!dividers.display || !item._data.children.length) {
449
- return;
450
- }
451
- const {x, y, w, h} = rect;
452
- const {lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth} = dividers;
453
- ctx.save();
454
- ctx.strokeStyle = lineColor;
455
- ctx.lineCap = lineCapStyle;
456
- ctx.setLineDash(lineDash);
457
- ctx.lineDashOffset = lineDashOffset;
458
- ctx.lineWidth = lineWidth;
459
- ctx.beginPath();
460
- if (w > h) {
461
- const w2 = w / 2;
462
- ctx.moveTo(x + w2, y);
463
- ctx.lineTo(x + w2, y + h);
464
- } else {
465
- const h2 = h / 2;
466
- ctx.moveTo(x, y + h2);
467
- ctx.lineTo(x + w, y + h2);
468
- }
469
- ctx.stroke();
470
- ctx.restore();
425
+ function requireVersion(pkg, min, ver, strict = true) {
426
+ const parts = ver.split('.');
427
+ let i = 0;
428
+ for (const req of min.split('.')){
429
+ const act = parts[i++];
430
+ if (Number.parseInt(req, 10) < Number.parseInt(act, 10)) {
431
+ break;
432
+ }
433
+ if (isOlderPart(act, req)) {
434
+ if (strict) {
435
+ throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`);
436
+ } else {
437
+ return false;
438
+ }
439
+ }
440
+ }
441
+ return true;
471
442
  }
472
443
 
473
- function calculateXYLabel(rect, options, labelSize) {
474
- const {align, position, padding} = options;
475
- let x, y;
476
- x = calculateX(rect, align, padding);
477
- if (position === 'top') {
478
- y = rect.y + padding;
479
- } else if (position === 'bottom') {
480
- y = rect.y + rect.h - padding - labelSize.height;
481
- } else {
482
- y = rect.y + (rect.h - labelSize.height) / 2 + padding;
483
- }
484
- return {x, y};
444
+ function compareAspectRatio(oldStat, newStat, args) {
445
+ if (oldStat.sum === 0) {
446
+ return true;
447
+ }
448
+ const [length] = args;
449
+ const os2 = oldStat.nsum * oldStat.nsum;
450
+ const ns2 = newStat.nsum * newStat.nsum;
451
+ const l2 = length * length;
452
+ const or = Math.max(l2 * oldStat.nmax / os2, os2 / (l2 * oldStat.nmin));
453
+ const nr = Math.max(l2 * newStat.nmax / ns2, ns2 / (l2 * newStat.nmin));
454
+ return nr <= or;
485
455
  }
486
-
487
- function calculateX(rect, align, padding) {
488
- if (align === 'left') {
489
- return rect.x + padding;
490
- } else if (align === 'right') {
491
- return rect.x + rect.w - padding;
492
- }
493
- return rect.x + rect.w / 2;
456
+ function squarify(values, rectangle, keys = [], grp, lvl, gsum) {
457
+ values = values || [];
458
+ const rows = [];
459
+ const rect = new Rect(rectangle);
460
+ const row = new StatArray('value', rect.area / sum(values, keys[0]));
461
+ let length = rect.side;
462
+ const n = values.length;
463
+ let i;
464
+ let o;
465
+ if (!n) {
466
+ return rows;
467
+ }
468
+ const tmp = values.slice();
469
+ const key = index(tmp, keys[0]);
470
+ if (!rectangle?.unsorted) {
471
+ sort(tmp, key);
472
+ }
473
+ const val = (idx)=>key ? +tmp[idx][key] : +tmp[idx];
474
+ const gval = (idx)=>grp && tmp[idx][grp];
475
+ for(i = 0; i < n; ++i){
476
+ o = {
477
+ _data: values[tmp[i]._idx],
478
+ group: undefined,
479
+ groupSum: gsum,
480
+ level: undefined,
481
+ value: val(i)
482
+ };
483
+ if (grp) {
484
+ o.level = tmp[i].groupIndex ?? lvl;
485
+ o.group = gval(i);
486
+ const tmpRef = tmp[i];
487
+ o.values = keys.reduce((obj, k)=>{
488
+ obj[k] = +tmpRef[k];
489
+ return obj;
490
+ }, {});
491
+ }
492
+ o = row.pushIf(o, compareAspectRatio, length);
493
+ if (o) {
494
+ rows.push(rect.map(row));
495
+ length = rect.side;
496
+ row.reset();
497
+ row.push(o);
498
+ }
499
+ }
500
+ if (row.length) {
501
+ rows.push(rect.map(row));
502
+ }
503
+ return flatten(rows);
494
504
  }
495
505
 
496
- class TreemapElement extends Element {
497
-
498
- constructor(cfg) {
499
- super();
500
-
501
- this.options = undefined;
502
- this.width = undefined;
503
- this.height = undefined;
504
-
505
- if (cfg) {
506
- Object.assign(this, cfg);
506
+ const widthCache = new Map();
507
+ function shouldDrawCaption(displayMode, rect, options) {
508
+ if (!options || options.display === false) {
509
+ return false;
507
510
  }
508
- }
509
-
510
- draw(ctx, data, levels = 0) {
511
- if (!data) {
512
- return;
511
+ if (displayMode === 'headerBoxes') {
512
+ return true;
513
513
  }
514
- const options = this.options;
515
- const {inner, outer} = boundingRects(this);
516
-
517
- const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
518
-
519
- ctx.save();
520
-
521
- if (outer.w !== inner.w || outer.h !== inner.h) {
522
- ctx.beginPath();
523
- addRectPath(ctx, outer);
524
- ctx.clip();
525
- addRectPath(ctx, inner);
526
- ctx.fillStyle = options.borderColor;
527
- ctx.fill('evenodd');
514
+ const { w, h } = rect;
515
+ const font = toFont(options.font || {});
516
+ const min = font.lineHeight;
517
+ const padding = Math.max(Math.min(valueOrDefault(options.padding, 3) * 2, Math.min(w, h)), 0);
518
+ return w - padding > min && h - padding > min;
519
+ }
520
+ function getCaptionHeight(displayMode, rect, font, padding) {
521
+ if (displayMode !== 'headerBoxes') {
522
+ return font.lineHeight + padding * 2;
528
523
  }
529
-
524
+ const captionHeight = font.lineHeight + padding * 2;
525
+ return rect.h < 2 * captionHeight ? rect.h / 3 : captionHeight;
526
+ }
527
+ function drawText(ctx, rect, options, item, element) {
528
+ const { captions, labels, displayMode } = options;
529
+ ctx.save();
530
530
  ctx.beginPath();
531
- addRectPath(ctx, inner);
532
- ctx.fillStyle = options.backgroundColor;
533
- ctx.fill();
534
-
535
- drawDivider(ctx, inner, options, data);
536
- drawText(ctx, inner, options, data, levels);
531
+ ctx.rect(rect.x, rect.y, rect.w, rect.h);
532
+ ctx.clip();
533
+ const isLeaf = item && (!defined(item.l) || item.isLeaf);
534
+ if (isLeaf && labels.display) {
535
+ drawLabel(ctx, rect, options, item, element);
536
+ } else if (!isLeaf && shouldDrawCaption(displayMode, rect, captions)) {
537
+ drawCaption(ctx, rect, options, item, element);
538
+ }
537
539
  ctx.restore();
538
- }
539
-
540
- inRange(mouseX, mouseY, useFinalPosition) {
541
- return inRange(this, mouseX, mouseY, useFinalPosition);
542
- }
543
-
544
- inXRange(mouseX, useFinalPosition) {
545
- return inRange(this, mouseX, null, useFinalPosition);
546
- }
547
-
548
- inYRange(mouseY, useFinalPosition) {
549
- return inRange(this, null, mouseY, useFinalPosition);
550
- }
551
-
552
- getCenterPoint(useFinalPosition) {
553
- const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
554
- return {
555
- x: x + width / 2,
556
- y: y + height / 2
540
+ }
541
+ function callbackContext(element, item) {
542
+ const context = element.$context || {
543
+ active: !!element.active,
544
+ raw: item,
545
+ type: 'data'
557
546
  };
558
- }
559
-
560
- tooltipPosition() {
561
- return this.getCenterPoint();
562
- }
563
-
564
- /**
565
- * @todo: remove this unused function in v3
566
- */
567
- getRange(axis) {
568
- return axis === 'x' ? this.width / 2 : this.height / 2;
569
- }
547
+ context.active = !!element.active;
548
+ context.raw = item;
549
+ return context;
570
550
  }
571
-
572
- TreemapElement.id = 'treemap';
573
-
574
- TreemapElement.defaults = {
575
- borderRadius: 0,
576
- borderWidth: 0,
577
- captions: {
578
- align: undefined,
579
- color: 'black',
580
- display: true,
581
- font: {},
582
- formatter: (ctx) => ctx.raw.g || ctx.raw._data.label || '',
583
- padding: 3
584
- },
585
- dividers: {
586
- display: false,
587
- lineCapStyle: 'butt',
588
- lineColor: 'black',
589
- lineDash: [],
590
- lineDashOffset: 0,
591
- lineWidth: 1,
592
- },
593
- label: undefined,
594
- labels: {
595
- align: 'center',
596
- color: 'black',
597
- display: false,
598
- font: {},
599
- formatter(ctx) {
600
- if (ctx.raw.g) {
601
- return [ctx.raw.g, ctx.raw.v + ''];
602
- }
603
- return ctx.raw._data.label ? [ctx.raw._data.label, ctx.raw.v + ''] : ctx.raw.v + '';
604
- },
605
- overflow: 'cut',
606
- position: 'middle',
607
- padding: 3
608
- },
609
- rtl: false,
610
- spacing: 0.5,
611
- unsorted: false,
612
- };
613
-
614
- TreemapElement.descriptors = {
615
- captions: {
616
- _fallback: true
617
- },
618
- labels: {
619
- _fallback: true
620
- },
621
- _scriptable: true,
622
- _indexable: false
623
- };
624
-
625
- TreemapElement.defaultRoutes = {
626
- backgroundColor: 'backgroundColor',
627
- borderColor: 'borderColor'
628
- };
629
-
630
- function getDims(itm, w2, s2, key) {
631
- const a = itm._normalized;
632
- const ar = w2 * a / s2;
633
- const d1 = Math.sqrt(a * ar);
634
- const d2 = a / d1;
635
- const w = key === '_ix' ? d1 : d2;
636
- const h = key === '_ix' ? d2 : d1;
637
-
638
- return {d1, d2, w, h};
551
+ function resolveOption(option, context) {
552
+ return typeof option === 'function' ? option(context) : option;
639
553
  }
640
-
641
- const getX = (rect, w) => rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
642
-
643
- function buildRow(rect, itm, dims, sum) {
644
- const r = {
645
- x: getX(rect, dims.w),
646
- y: rect.y + rect._iy,
647
- w: dims.w,
648
- h: dims.h,
649
- a: itm._normalized,
650
- v: itm.value,
651
- vs: itm.values,
652
- s: sum,
653
- _data: itm._data
654
- };
655
- if (itm.group) {
656
- r.g = itm.group;
657
- r.l = itm.level;
658
- r.gs = itm.groupSum;
659
- }
660
- return r;
554
+ function resolveCaptionText(captions, element, item) {
555
+ return resolveOption(captions.formatter, callbackContext(element, item)) || item.g || '';
661
556
  }
662
-
663
- class Rect {
664
- constructor(r) {
665
- r = r || {w: 1, h: 1};
666
- this.rtl = !!r.rtl;
667
- this.unsorted = !!r.unsorted;
668
- this.x = r.x || r.left || 0;
669
- this.y = r.y || r.top || 0;
670
- this._ix = 0;
671
- this._iy = 0;
672
- this.w = r.w || r.width || (r.right - r.left);
673
- this.h = r.h || r.height || (r.bottom - r.top);
674
- }
675
-
676
- get area() {
677
- return this.w * this.h;
678
- }
679
-
680
- get iw() {
681
- return this.w - this._ix;
682
- }
683
-
684
- get ih() {
685
- return this.h - this._iy;
686
- }
687
-
688
- get dir() {
689
- const ih = this.ih;
690
- return ih <= this.iw && ih > 0 ? 'y' : 'x';
691
- }
692
-
693
- get side() {
694
- return this.dir === 'x' ? this.iw : this.ih;
695
- }
696
-
697
- map(arr) {
698
- const {dir, side} = this;
699
- const key = dir === 'x' ? '_ix' : '_iy';
700
- const sum = arr.nsum;
701
- const row = arr.get();
702
- const w2 = side * side;
703
- const s2 = sum * sum;
704
- const ret = [];
705
- let maxd2 = 0;
706
- let totd1 = 0;
707
- for (const itm of row) {
708
- const dims = getDims(itm, w2, s2, key);
709
- totd1 += dims.d1;
710
- maxd2 = Math.max(maxd2, dims.d2);
711
- ret.push(buildRow(this, itm, dims, arr.sum));
712
- this[key] += dims.d1;
557
+ function drawCaption(ctx, rect, options, item, element) {
558
+ const { captions, spacing, rtl, displayMode } = options;
559
+ const { color, hoverColor, font, hoverFont, padding, align } = captions;
560
+ const oColor = (rect.active ? hoverColor : color) || color;
561
+ const oAlign = align || (rtl ? 'right' : 'left');
562
+ const optFont = (rect.active ? hoverFont : font) || font;
563
+ const oFont = toFont(optFont);
564
+ const fonts = [
565
+ oFont
566
+ ];
567
+ if (oFont.lineHeight > rect.h) {
568
+ return;
713
569
  }
714
-
715
- this[dir === 'x' ? '_iy' : '_ix'] += maxd2;
716
- this[key] -= totd1;
717
- return ret;
718
- }
719
- }
720
-
721
- const min = Math.min;
722
- const max = Math.max;
723
-
724
- function getStat(sa) {
725
- return {
726
- min: sa.min,
727
- max: sa.max,
728
- sum: sa.sum,
729
- nmin: sa.nmin,
730
- nmax: sa.nmax,
731
- nsum: sa.nsum
732
- };
570
+ let text = resolveCaptionText(captions, element, item);
571
+ if (!text) {
572
+ return;
573
+ }
574
+ const captionSize = measureLabelSize(ctx, [
575
+ text
576
+ ], fonts);
577
+ if (captionSize.width + 2 * padding > rect.w) {
578
+ text = sliceTextToFitWidth(ctx, text, rect.w - 2 * padding, fonts);
579
+ }
580
+ const lh = oFont.lineHeight / 2;
581
+ const x = calculateX(rect, oAlign, padding);
582
+ ctx.fillStyle = oColor;
583
+ ctx.font = oFont.string;
584
+ ctx.textAlign = oAlign;
585
+ ctx.textBaseline = 'middle';
586
+ const y = displayMode === 'headerBoxes' ? rect.y + rect.h / 2 : rect.y + padding + spacing + lh;
587
+ ctx.fillText(text, x, y);
733
588
  }
734
-
735
- function getNewStat(sa, o) {
736
- const v = +o[sa.key];
737
- const n = v * sa.ratio;
738
- o._normalized = n;
739
-
740
- return {
741
- min: min(sa.min, v),
742
- max: max(sa.max, v),
743
- sum: sa.sum + v,
744
- nmin: min(sa.nmin, n),
745
- nmax: max(sa.nmax, n),
746
- nsum: sa.nsum + n
747
- };
589
+ function sliceTextToFitWidth(ctx, text, width, fonts) {
590
+ const ellipsis = '...';
591
+ const ellipsisWidth = measureLabelSize(ctx, [
592
+ ellipsis
593
+ ], fonts).width;
594
+ if (ellipsisWidth >= width) {
595
+ return '';
596
+ }
597
+ let lowerBoundLen = 1;
598
+ let upperBoundLen = text.length;
599
+ let currentWidth;
600
+ while(lowerBoundLen <= upperBoundLen){
601
+ const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
602
+ const currentText = text.slice(0, currentLen);
603
+ currentWidth = measureLabelSize(ctx, [
604
+ currentText
605
+ ], fonts).width;
606
+ if (currentWidth + ellipsisWidth > width) {
607
+ upperBoundLen = currentLen - 1;
608
+ } else {
609
+ lowerBoundLen = currentLen + 1;
610
+ }
611
+ }
612
+ const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
613
+ return slicedText ? slicedText + ellipsis : '';
748
614
  }
749
-
750
- function setStat(sa, stat) {
751
- Object.assign(sa, stat);
615
+ function measureLabelSize(ctx, lines, fonts) {
616
+ const fontsKey = fonts.reduce((prev, item)=>{
617
+ prev += item.string;
618
+ return prev;
619
+ }, '');
620
+ const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
621
+ let size = widthCache.get(mapKey);
622
+ if (!size) {
623
+ ctx.save();
624
+ const count = lines.length;
625
+ let width = 0;
626
+ let height = 0;
627
+ for(let i = 0; i < count; i++){
628
+ const font = fonts[Math.min(i, fonts.length - 1)];
629
+ ctx.font = font.string;
630
+ const text = lines[i];
631
+ width = Math.max(width, ctx.measureText(text).width);
632
+ height += font.lineHeight;
633
+ }
634
+ ctx.restore();
635
+ size = {
636
+ height,
637
+ width
638
+ };
639
+ widthCache.set(mapKey, size);
640
+ }
641
+ return size;
752
642
  }
753
-
754
- function push(sa, o, stat) {
755
- sa._arr.push(o);
756
- setStat(sa, stat);
643
+ function toFonts(fonts, fitRatio) {
644
+ return fonts.map((f)=>{
645
+ const { lineHeight: _lineHeight, ...font } = f;
646
+ return toFont({
647
+ ...font,
648
+ size: Math.floor(f.size * fitRatio)
649
+ });
650
+ });
757
651
  }
758
-
759
- class StatArray {
760
- constructor(key, ratio) {
761
- const me = this;
762
- me.key = key;
763
- me.ratio = ratio;
764
- me.reset();
765
- }
766
-
767
- get length() {
768
- return this._arr.length;
769
- }
770
-
771
- reset() {
772
- const me = this;
773
- me._arr = [];
774
- me._hist = [];
775
- me.sum = 0;
776
- me.nsum = 0;
777
- me.min = Infinity;
778
- me.max = -Infinity;
779
- me.nmin = Infinity;
780
- me.nmax = -Infinity;
781
- }
782
-
783
- push(o) {
784
- push(this, o, getNewStat(this, o));
785
- }
786
-
787
- pushIf(o, fn, ...args) {
788
- const nstat = getNewStat(this, o);
789
- if (!fn(getStat(this), nstat, args)) {
790
- return o;
652
+ function labelToDraw(_ctx, rect, options, labelSize) {
653
+ const { overflow, padding } = options;
654
+ const { width, height } = labelSize;
655
+ if (overflow === 'hidden') {
656
+ return !(width + padding * 2 > rect.w || height + padding * 2 > rect.h);
657
+ } else if (overflow === 'fit') {
658
+ const ratio = Math.min(rect.w / (width + padding * 2), rect.h / (height + padding * 2));
659
+ if (ratio < 1) {
660
+ return ratio;
661
+ }
791
662
  }
792
- push(this, o, nstat);
793
- }
794
-
795
- get() {
796
- return this._arr;
797
- }
798
- }
799
-
800
- function compareAspectRatio(oldStat, newStat, args) {
801
- if (oldStat.sum === 0) {
802
663
  return true;
803
- }
804
-
805
- const [length] = args;
806
- const os2 = oldStat.nsum * oldStat.nsum;
807
- const ns2 = newStat.nsum * newStat.nsum;
808
- const l2 = length * length;
809
- const or = Math.max(l2 * oldStat.nmax / os2, os2 / (l2 * oldStat.nmin));
810
- const nr = Math.max(l2 * newStat.nmax / ns2, ns2 / (l2 * newStat.nmin));
811
- return nr <= or;
812
664
  }
813
-
814
- /**
815
- *
816
- * @param {number[]|object[]} values
817
- * @param {object} rectangle
818
- * @param {string} [key]
819
- * @param {string} [grp]
820
- * @param {number} [lvl]
821
- * @param {number} [gsum]
822
- */
823
- function squarify(values, rectangle, keys = [], grp, lvl, gsum) {
824
- values = values || [];
825
- const rows = [];
826
- const rect = new Rect(rectangle);
827
- const row = new StatArray('value', rect.area / sum(values, keys[0]));
828
- let length = rect.side;
829
- const n = values.length;
830
- let i, o;
831
-
832
- if (!n) {
833
- return rows;
834
- }
835
-
836
- const tmp = values.slice();
837
- let key = index(tmp, keys[0]);
838
-
839
- if (!rectangle?.unsorted) {
840
- sort(tmp, key);
841
- }
842
-
843
- const val = (idx) => key ? +tmp[idx][key] : +tmp[idx];
844
- const gval = (idx) => grp && tmp[idx][grp];
845
-
846
- for (i = 0; i < n; ++i) {
847
- o = {value: val(i), groupSum: gsum, _data: values[tmp[i]._idx], level: undefined, group: undefined};
848
- if (grp) {
849
- o.level = lvl;
850
- o.group = gval(i);
851
- const tmpRef = tmp[i];
852
- o.values = keys.reduce(function(obj, k) {
853
- obj[k] = +tmpRef[k];
854
- return obj;
855
- }, {});
856
- }
857
- o = row.pushIf(o, compareAspectRatio, length);
858
- if (o) {
859
- rows.push(rect.map(row));
860
- length = rect.side;
861
- row.reset();
862
- row.push(o);
863
- }
864
- }
865
- if (row.length) {
866
- rows.push(rect.map(row));
867
- }
868
- return flatten(rows);
665
+ function getFontFromOptions(rect, labels) {
666
+ const { font, hoverFont } = labels;
667
+ const optFont = (rect.active ? hoverFont : font) || font;
668
+ return Array.isArray(optFont) ? optFont.map((f)=>toFont(f)) : [
669
+ toFont(optFont)
670
+ ];
869
671
  }
870
-
871
- var version = "3.1.0";
872
-
873
- function scaleRect(sq, xScale, yScale, sp) {
874
- const sp2 = sp * 2;
875
- const x = xScale.getPixelForValue(sq.x);
876
- const y = yScale.getPixelForValue(sq.y);
877
- const w = xScale.getPixelForValue(sq.x + sq.w) - x;
878
- const h = yScale.getPixelForValue(sq.y + sq.h) - y;
879
- return {
880
- x: x + sp,
881
- y: y + sp,
882
- width: w - sp2,
883
- height: h - sp2,
884
- hidden: sp2 > w || sp2 > h,
885
- };
672
+ function drawLabel(ctx, rect, options, item, element) {
673
+ const labels = options.labels;
674
+ const content = resolveOption(labels.formatter, callbackContext(element, item));
675
+ if (!content) {
676
+ return;
677
+ }
678
+ const contents = isArray(content) ? content : [
679
+ content
680
+ ];
681
+ let fonts = getFontFromOptions(rect, labels);
682
+ let labelSize = measureLabelSize(ctx, contents, fonts);
683
+ const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
684
+ if (!lblToDraw) {
685
+ return;
686
+ }
687
+ if (isNumber(lblToDraw)) {
688
+ labelSize = {
689
+ height: labelSize.height * lblToDraw,
690
+ width: labelSize.width * lblToDraw
691
+ };
692
+ fonts = toFonts(fonts, lblToDraw);
693
+ }
694
+ const { color, hoverColor, align } = labels;
695
+ const optColor = (rect.active ? hoverColor : color) || color;
696
+ const colors = isArray(optColor) ? optColor : [
697
+ optColor
698
+ ];
699
+ const xyPoint = calculateXYLabel(rect, labels, labelSize);
700
+ ctx.textAlign = align;
701
+ ctx.textBaseline = 'middle';
702
+ let lhs = 0;
703
+ contents.forEach((l, i)=>{
704
+ const c = colors[Math.min(i, colors.length - 1)];
705
+ const f = fonts[Math.min(i, fonts.length - 1)];
706
+ const lh = f.lineHeight;
707
+ ctx.font = f.string;
708
+ ctx.fillStyle = c;
709
+ ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
710
+ lhs += lh;
711
+ });
886
712
  }
887
-
888
- function rectNotEqual(r1, r2) {
889
- return !r1 || !r2
890
- || r1.x !== r2.x
891
- || r1.y !== r2.y
892
- || r1.w !== r2.w
893
- || r1.h !== r2.h
894
- || r1.rtl !== r2.rtl
895
- || r1.unsorted !== r2.unsorted;
713
+ function drawDivider(ctx, rect, options, item) {
714
+ const dividers = options.dividers;
715
+ const children = item._data?.children;
716
+ if (!dividers.display || !Array.isArray(children) || !children.length) {
717
+ return;
718
+ }
719
+ const { x, y, w, h } = rect;
720
+ const { lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth } = dividers;
721
+ ctx.save();
722
+ ctx.strokeStyle = lineColor;
723
+ ctx.lineCap = lineCapStyle;
724
+ ctx.setLineDash(lineDash);
725
+ ctx.lineDashOffset = lineDashOffset;
726
+ ctx.lineWidth = lineWidth;
727
+ ctx.beginPath();
728
+ if (w > h) {
729
+ const w2 = w / 2;
730
+ ctx.moveTo(x + w2, y);
731
+ ctx.lineTo(x + w2, y + h);
732
+ } else {
733
+ const h2 = h / 2;
734
+ ctx.moveTo(x, y + h2);
735
+ ctx.lineTo(x + w, y + h2);
736
+ }
737
+ ctx.stroke();
738
+ ctx.restore();
896
739
  }
897
-
898
- function arrayNotEqual(a, b) {
899
- let i, n;
900
-
901
- if (!a || !b) {
902
- return true;
903
- }
904
-
905
- if (a === b) {
906
- return false;
907
- }
908
-
909
- if (a.length !== b.length) {
910
- return true;
911
- }
912
-
913
- for (i = 0, n = a.length; i < n; ++i) {
914
- if (a[i] !== b[i]) {
915
- return true;
740
+ function calculateXYLabel(rect, options, labelSize) {
741
+ const { align, position, padding } = options;
742
+ const x = calculateX(rect, align, padding);
743
+ let y;
744
+ if (position === 'top') {
745
+ y = rect.y + padding;
746
+ } else if (position === 'bottom') {
747
+ y = rect.y + rect.h - padding - labelSize.height;
748
+ } else {
749
+ y = rect.y + (rect.h - labelSize.height) / 2 + padding;
750
+ }
751
+ return {
752
+ x,
753
+ y
754
+ };
755
+ }
756
+ function calculateX(rect, align, padding) {
757
+ if (align === 'left') {
758
+ return rect.x + padding;
759
+ } else if (align === 'right') {
760
+ return rect.x + rect.w - padding;
916
761
  }
917
- }
918
- return false;
762
+ return rect.x + rect.w / 2;
919
763
  }
920
764
 
921
765
  function buildData(tree, dataset, keys, mainRect) {
922
- const treeLeafKey = dataset.treeLeafKey || '_leaf';
923
- if (isObject(tree)) {
924
- tree = normalizeTreeToArray(keys, treeLeafKey, tree);
925
- }
926
- const groups = dataset.groups || [];
927
- const glen = groups.length;
928
- const sp = valueOrDefault(dataset.spacing, 0);
929
- const captions = dataset.captions || {};
930
- const font = toFont(captions.font);
931
- const padding = valueOrDefault(captions.padding, 3);
932
-
933
- function recur(treeElements, gidx, rect, parent, gs) {
934
- const g = getGroupKey(groups[gidx]);
935
- const pg = (gidx > 0) && getGroupKey(groups[gidx - 1]);
936
- const gdata = group(treeElements, g, keys, treeLeafKey, pg, parent, groups.filter((item, index) => index <= gidx));
937
- const gsq = squarify(gdata, rect, keys, g, gidx, gs);
938
- const ret = gsq.slice();
939
- if (gidx < glen - 1) {
940
- gsq.forEach((sq) => {
941
- const bw = parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
766
+ const treeLeafKey = dataset.treeLeafKey || '_leaf';
767
+ if (isObject(tree)) {
768
+ tree = normalizeTreeToArray(keys, treeLeafKey, tree);
769
+ }
770
+ const groups = dataset.groups || [];
771
+ const glen = groups.length;
772
+ const sp = dataset.displayMode === 'headerBoxes' ? 0 : valueOrDefault(dataset.spacing, 0);
773
+ const captions = dataset.captions || {};
774
+ const font = toFont(captions.font);
775
+ const padding = valueOrDefault(captions.padding, 3);
776
+ function getSubRect(sq, rect) {
777
+ const bw = dataset.displayMode === 'headerBoxes' ? {
778
+ b: 0,
779
+ l: 0,
780
+ r: 0,
781
+ t: 0
782
+ } : parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
942
783
  const subRect = {
943
- ...rect,
944
- x: sq.x + sp + bw.l,
945
- y: sq.y + sp + bw.t,
946
- w: sq.w - 2 * sp - bw.l - bw.r,
947
- h: sq.h - 2 * sp - bw.t - bw.b,
784
+ ...rect,
785
+ h: sq.h - 2 * sp - bw.t - bw.b,
786
+ w: sq.w - 2 * sp - bw.l - bw.r,
787
+ x: sq.x + sp + bw.l,
788
+ y: sq.y + sp + bw.t
948
789
  };
949
- if (shouldDrawCaption(subRect, captions)) {
950
- subRect.y += font.lineHeight + padding * 2;
951
- subRect.h -= font.lineHeight + padding * 2;
790
+ if (shouldDrawCaption(dataset.displayMode, subRect, captions)) {
791
+ const captionHeight = getCaptionHeight(dataset.displayMode, subRect, font, padding);
792
+ subRect.y += captionHeight;
793
+ subRect.h -= captionHeight;
952
794
  }
953
- gdata.forEach((gEl) => {
954
- ret.push(...recur(gEl.children, gidx + 1, subRect, sq.g, sq.s));
955
- });
956
- });
795
+ return subRect;
957
796
  }
958
- return ret;
959
- }
960
-
961
- return glen
962
- ? recur(tree, 0, mainRect)
963
- : squarify(tree, mainRect, keys);
797
+ function recur(treeElements, gidx, rect, parent, gs) {
798
+ const g = getGroupKey(groups[gidx]);
799
+ const gdata = group(treeElements, g, keys, treeLeafKey, undefined, parent, groups.filter((_item, index)=>index <= gidx), groups, gidx);
800
+ const gsq = squarify(gdata, rect, keys, g, gidx, gs);
801
+ const ret = gsq.slice();
802
+ if (gidx < glen - 1) {
803
+ gsq.forEach((sq)=>{
804
+ const subRect = getSubRect(sq, rect);
805
+ const children = [];
806
+ const nextGroupIndex = sq._data.groupIndex + 1;
807
+ if (nextGroupIndex < glen) {
808
+ children.push(...recur(sq._data.children, nextGroupIndex, subRect, sq.g, sq.s));
809
+ }
810
+ ret.push(...children);
811
+ sq.isLeaf = !children.length;
812
+ });
813
+ } else {
814
+ gsq.forEach((sq)=>{
815
+ sq.isLeaf = true;
816
+ });
817
+ }
818
+ return ret;
819
+ }
820
+ const result = glen ? recur(tree, 0, mainRect) : squarify(tree, mainRect, keys);
821
+ return result.map((d)=>{
822
+ if (dataset.displayMode !== 'headerBoxes' || d.isLeaf) {
823
+ return d;
824
+ }
825
+ if (!shouldDrawCaption(dataset.displayMode, d, captions)) {
826
+ return undefined;
827
+ }
828
+ const captionHeight = getCaptionHeight(dataset.displayMode, d, font, padding);
829
+ return {
830
+ ...d,
831
+ h: captionHeight
832
+ };
833
+ }).filter(Boolean);
964
834
  }
965
-
966
835
  class TreemapController extends DatasetController {
967
- constructor(chart, datasetIndex) {
968
- super(chart, datasetIndex);
969
-
970
- this._groups = undefined;
971
- this._keys = undefined;
972
- this._rect = undefined;
973
- this._rectChanged = true;
974
- }
975
-
976
- initialize() {
977
- this.enableOptionSharing = true;
978
- super.initialize();
979
- }
980
-
981
- getMinMax(scale) {
982
- return {
983
- min: 0,
984
- max: scale.axis === 'x' ? scale.right - scale.left : scale.bottom - scale.top
985
- };
986
- }
987
-
988
- configure() {
989
- super.configure();
990
- const {xScale, yScale} = this.getMeta();
991
- if (!xScale || !yScale) {
992
- // configure is called once before `linkScales`, and at that call we don't have any scales linked yet
993
- return;
836
+ initialize() {
837
+ this.enableOptionSharing = true;
838
+ super.initialize();
994
839
  }
995
-
996
- const w = xScale.right - xScale.left;
997
- const h = yScale.bottom - yScale.top;
998
- const rect = {x: 0, y: 0, w, h, rtl: !!this.options.rtl, unsorted: !!this.options.unsorted};
999
-
1000
- if (rectNotEqual(this._rect, rect)) {
1001
- this._rect = rect;
1002
- this._rectChanged = true;
840
+ getMinMax(scale) {
841
+ return {
842
+ max: scale.axis === 'x' ? scale.right - scale.left : scale.bottom - scale.top,
843
+ min: 0
844
+ };
1003
845
  }
1004
-
1005
- if (this._rectChanged) {
1006
- xScale.max = w;
1007
- xScale.configure();
1008
- yScale.max = h;
1009
- yScale.configure();
846
+ configure() {
847
+ super.configure();
848
+ const { xScale, yScale } = this.getMeta();
849
+ if (!xScale || !yScale) {
850
+ return;
851
+ }
852
+ const w = xScale.right - xScale.left;
853
+ const h = yScale.bottom - yScale.top;
854
+ const rect = {
855
+ h,
856
+ rtl: !!this.options.rtl,
857
+ unsorted: !!this.options.unsorted,
858
+ w,
859
+ x: 0,
860
+ y: 0
861
+ };
862
+ if (rectNotEqual(this._rect, rect)) {
863
+ this._rect = rect;
864
+ this._rectChanged = true;
865
+ }
866
+ if (this._rectChanged) {
867
+ xScale.max = w;
868
+ xScale.configure();
869
+ yScale.max = h;
870
+ yScale.configure();
871
+ }
1010
872
  }
1011
- }
1012
-
1013
- update(mode) {
1014
- const dataset = this.getDataset();
1015
- const {data} = this.getMeta();
1016
- const groups = dataset.groups || [];
1017
- const keys = [dataset.key || ''].concat(dataset.sumKeys || []);
1018
- const tree = dataset.tree = dataset.tree || dataset.data || [];
1019
-
1020
- if (mode === 'reset') {
1021
- // reset is called before 2nd configure and is only called if animations are enabled. So wen need an extra configure call here.
1022
- this.configure();
873
+ update(mode) {
874
+ const dataset = this.getDataset();
875
+ const { data } = this.getMeta();
876
+ const groups = dataset.groups || [];
877
+ const keys = [
878
+ dataset.key || ''
879
+ ].concat(dataset.sumKeys || []);
880
+ dataset.tree = dataset.tree || dataset.data || [];
881
+ const tree = dataset.tree;
882
+ if (mode === 'reset') {
883
+ this.configure();
884
+ }
885
+ if (this._rectChanged || arrayNotEqual(this._keys || [], keys) || arrayNotEqual(this._groups || [], groups) || this._prevTree !== tree) {
886
+ this._groups = groups.slice();
887
+ this._keys = keys.slice();
888
+ this._prevTree = tree;
889
+ this._rectChanged = false;
890
+ dataset.data = buildData(tree, dataset, this._keys, this._rect);
891
+ this._dataCheck();
892
+ this._resyncElements();
893
+ }
894
+ this.updateElements(data, 0, data.length, mode);
1023
895
  }
1024
-
1025
- if (this._rectChanged || arrayNotEqual(this._keys, keys) || arrayNotEqual(this._groups, groups) || this._prevTree !== tree) {
1026
- this._groups = groups.slice();
1027
- this._keys = keys.slice();
1028
- this._prevTree = tree;
1029
- this._rectChanged = false;
1030
-
1031
- dataset.data = buildData(tree, dataset, this._keys, this._rect);
1032
- // @ts-ignore using private stuff
1033
- this._dataCheck();
1034
- // @ts-ignore using private stuff
1035
- this._resyncElements();
896
+ updateElements(rects, start, count, mode) {
897
+ const reset = mode === 'reset';
898
+ const dataset = this.getDataset();
899
+ const firstOpts = this.resolveDataElementOptions(start, mode);
900
+ this._rect.options = firstOpts;
901
+ const sharedOptions = this.getSharedOptions(firstOpts);
902
+ const includeOptions = this.includeOptions(mode, sharedOptions || {});
903
+ const { xScale, yScale } = this.getMeta();
904
+ for(let i = start; i < start + count; i++){
905
+ const options = sharedOptions || this.resolveDataElementOptions(i, mode);
906
+ const properties = scaleRect(dataset.data[i], xScale, yScale, options.spacing);
907
+ if (reset) {
908
+ properties.width = 0;
909
+ properties.height = 0;
910
+ }
911
+ if (includeOptions) {
912
+ properties.options = options;
913
+ }
914
+ this.updateElement(rects[i], i, properties, mode);
915
+ }
916
+ this.updateSharedOptions(sharedOptions || {}, mode, firstOpts);
1036
917
  }
1037
-
1038
- this.updateElements(data, 0, data.length, mode);
1039
- }
1040
-
1041
- updateElements(rects, start, count, mode) {
1042
- const reset = mode === 'reset';
1043
- const dataset = this.getDataset();
1044
- const firstOpts = this._rect.options = this.resolveDataElementOptions(start, mode);
1045
- const sharedOptions = this.getSharedOptions(firstOpts);
1046
- const includeOptions = this.includeOptions(mode, sharedOptions);
1047
- const {xScale, yScale} = this.getMeta(this.index);
1048
-
1049
- for (let i = start; i < start + count; i++) {
1050
- const options = sharedOptions || this.resolveDataElementOptions(i, mode);
1051
- const properties = scaleRect(dataset.data[i], xScale, yScale, options.spacing);
1052
- if (reset) {
1053
- properties.width = 0;
1054
- properties.height = 0;
1055
- }
1056
-
1057
- if (includeOptions) {
1058
- properties.options = options;
1059
- }
1060
- this.updateElement(rects[i], i, properties, mode);
918
+ draw() {
919
+ const { ctx, chartArea } = this.chart;
920
+ const metadata = this.getMeta().data || [];
921
+ const dataset = this.getDataset();
922
+ const data = dataset.data;
923
+ clipArea(ctx, chartArea);
924
+ for(let i = 0, ilen = metadata.length; i < ilen; ++i){
925
+ const rect = metadata[i];
926
+ if (!rect.hidden) {
927
+ rect.draw(ctx, data[i]);
928
+ }
929
+ }
930
+ unclipArea(ctx);
931
+ }
932
+ constructor(chart, datasetIndex){
933
+ super(chart, datasetIndex);
934
+ this._groups = undefined;
935
+ this._keys = undefined;
936
+ this._rect = undefined;
937
+ this._rectChanged = true;
1061
938
  }
1062
-
1063
- this.updateSharedOptions(sharedOptions, mode, firstOpts);
1064
- }
1065
-
1066
- draw() {
1067
- const {ctx, chartArea} = this.chart;
1068
- const metadata = this.getMeta().data || [];
1069
- const dataset = this.getDataset();
1070
- const levels = (dataset.groups || []).length - 1;
1071
- const data = dataset.data;
1072
-
1073
- clipArea(ctx, chartArea);
1074
- for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
1075
- const rect = metadata[i];
1076
- if (!rect.hidden) {
1077
- rect.draw(ctx, data[i], levels);
1078
- }
1079
- }
1080
- unclipArea(ctx);
1081
- }
1082
939
  }
1083
-
1084
940
  TreemapController.id = 'treemap';
1085
-
1086
941
  TreemapController.version = version;
1087
-
1088
942
  TreemapController.defaults = {
1089
- dataElementType: 'treemap',
1090
-
1091
- animations: {
1092
- numbers: {
1093
- type: 'number',
1094
- properties: ['x', 'y', 'width', 'height']
943
+ animations: {
944
+ numbers: {
945
+ properties: [
946
+ 'x',
947
+ 'y',
948
+ 'width',
949
+ 'height'
950
+ ],
951
+ type: 'number'
952
+ }
1095
953
  },
1096
- },
1097
-
954
+ dataElementType: 'treemap'
1098
955
  };
1099
-
1100
956
  TreemapController.descriptors = {
1101
- _scriptable: true,
1102
- _indexable: false
957
+ _indexable: false,
958
+ _scriptable: true,
959
+ captions: {
960
+ _fallback: true,
961
+ _scriptable: (name)=>name !== 'formatter'
962
+ },
963
+ labels: {
964
+ _fallback: true,
965
+ _scriptable: (name)=>name !== 'formatter'
966
+ }
1103
967
  };
1104
-
1105
968
  TreemapController.overrides = {
1106
- interaction: {
1107
- mode: 'point',
1108
- includeInvisible: true,
1109
- intersect: true
1110
- },
1111
-
1112
- hover: {},
1113
-
1114
- plugins: {
1115
- tooltip: {
1116
- position: 'treemap',
1117
- intersect: true,
1118
- callbacks: {
1119
- title(items) {
1120
- if (items.length) {
1121
- const item = items[0];
1122
- return item.dataset.key || '';
1123
- }
1124
- return '';
1125
- },
1126
- label(item) {
1127
- const dataset = item.dataset;
1128
- const dataItem = dataset.data[item.dataIndex];
1129
- const label = dataItem.g || dataItem._data.label || dataset.label;
1130
- return (label ? label + ': ' : '') + dataItem.v;
1131
- }
1132
- }
969
+ hover: {},
970
+ interaction: {
971
+ includeInvisible: true,
972
+ intersect: true,
973
+ mode: 'point'
1133
974
  },
1134
- },
1135
- scales: {
1136
- x: {
1137
- type: 'linear',
1138
- alignToPixels: true,
1139
- bounds: 'data',
1140
- display: false
975
+ plugins: {
976
+ tooltip: {
977
+ callbacks: {
978
+ label (item) {
979
+ const dataset = item.dataset;
980
+ const dataItem = dataset.data[item.dataIndex];
981
+ const label = dataItem.g || dataItem._data.label || dataset.label;
982
+ return (label ? `${label}: ` : '') + dataItem.v;
983
+ },
984
+ title (items) {
985
+ if (items.length) {
986
+ const item = items[0];
987
+ return item.dataset.key || '';
988
+ }
989
+ return '';
990
+ }
991
+ },
992
+ intersect: true,
993
+ position: 'treemap'
994
+ }
1141
995
  },
1142
- y: {
1143
- type: 'linear',
1144
- alignToPixels: true,
1145
- bounds: 'data',
1146
- display: false,
1147
- reverse: true
1148
- }
1149
- },
996
+ scales: {
997
+ x: {
998
+ alignToPixels: true,
999
+ bounds: 'data',
1000
+ display: false,
1001
+ type: 'linear'
1002
+ },
1003
+ y: {
1004
+ alignToPixels: true,
1005
+ bounds: 'data',
1006
+ display: false,
1007
+ reverse: true,
1008
+ type: 'linear'
1009
+ }
1010
+ }
1150
1011
  };
1151
-
1152
- TreemapController.beforeRegister = function() {
1153
- requireVersion('chart.js', '3.8', Chart.version);
1012
+ TreemapController.beforeRegister = ()=>{
1013
+ requireVersion('chart.js', '3.8', Chart.version);
1014
+ };
1015
+ TreemapController.afterRegister = ()=>{
1016
+ const tooltipPlugin = registry.plugins.get('tooltip');
1017
+ if (tooltipPlugin) {
1018
+ tooltipPlugin.positioners.treemap = (active)=>{
1019
+ if (!active.length) {
1020
+ return false;
1021
+ }
1022
+ const item = active.at(-1);
1023
+ const el = item.element;
1024
+ return el.tooltipPosition();
1025
+ };
1026
+ } else {
1027
+ console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
1028
+ }
1029
+ };
1030
+ TreemapController.afterUnregister = ()=>{
1031
+ const tooltipPlugin = registry.plugins.get('tooltip');
1032
+ if (tooltipPlugin) {
1033
+ delete tooltipPlugin.positioners.treemap;
1034
+ }
1154
1035
  };
1155
1036
 
1156
- TreemapController.afterRegister = function() {
1157
- const tooltipPlugin = registry.plugins.get('tooltip');
1158
- if (tooltipPlugin) {
1159
- tooltipPlugin.positioners.treemap = function(active) {
1160
- if (!active.length) {
1161
- return false;
1162
- }
1163
-
1164
- const item = active[active.length - 1];
1165
- const el = item.element;
1166
-
1167
- return el.tooltipPosition();
1037
+ function getBounds(rect, useFinalPosition) {
1038
+ const { x, y, width, height } = rect.getProps([
1039
+ 'x',
1040
+ 'y',
1041
+ 'width',
1042
+ 'height'
1043
+ ], useFinalPosition);
1044
+ return {
1045
+ bottom: y + height,
1046
+ left: x,
1047
+ right: x + width,
1048
+ top: y
1168
1049
  };
1169
- } else {
1170
- console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
1171
- }
1172
- };
1050
+ }
1051
+ function boundingRects(rect) {
1052
+ const bounds = getBounds(rect);
1053
+ const width = bounds.right - bounds.left;
1054
+ const height = bounds.bottom - bounds.top;
1055
+ const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
1056
+ const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
1057
+ const outer = {
1058
+ active: rect.active,
1059
+ h: height,
1060
+ radius,
1061
+ w: width,
1062
+ x: bounds.left,
1063
+ y: bounds.top
1064
+ };
1065
+ return {
1066
+ inner: {
1067
+ active: rect.active,
1068
+ h: outer.h - border.t - border.b,
1069
+ radius: {
1070
+ bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
1071
+ bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
1072
+ topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
1073
+ topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r))
1074
+ },
1075
+ w: outer.w - border.l - border.r,
1076
+ x: outer.x + border.l,
1077
+ y: outer.y + border.t
1078
+ },
1079
+ outer
1080
+ };
1081
+ }
1082
+ function inRange(rect, x, y, useFinalPosition) {
1083
+ const skipX = x === null;
1084
+ const skipY = y === null;
1085
+ const bounds = !rect || skipX && skipY ? false : getBounds(rect, useFinalPosition);
1086
+ return bounds && (skipX || x >= bounds.left && x <= bounds.right) && (skipY || y >= bounds.top && y <= bounds.bottom);
1087
+ }
1088
+ function hasRadius(radius) {
1089
+ return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
1090
+ }
1091
+ function addNormalRectPath(ctx, rect) {
1092
+ ctx.rect(rect.x, rect.y, rect.w, rect.h);
1093
+ }
1173
1094
 
1174
- TreemapController.afterUnregister = function() {
1175
- const tooltipPlugin = registry.plugins.get('tooltip');
1176
- if (tooltipPlugin) {
1177
- delete tooltipPlugin.positioners.treemap;
1178
- }
1095
+ class TreemapElement extends Element {
1096
+ draw(ctx, data) {
1097
+ if (!data) {
1098
+ return;
1099
+ }
1100
+ const options = this.options;
1101
+ const { inner, outer } = boundingRects(this);
1102
+ const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
1103
+ ctx.save();
1104
+ if (outer.w !== inner.w || outer.h !== inner.h) {
1105
+ ctx.beginPath();
1106
+ addRectPath(ctx, outer);
1107
+ ctx.clip();
1108
+ addRectPath(ctx, inner);
1109
+ ctx.fillStyle = options.borderColor;
1110
+ ctx.fill('evenodd');
1111
+ }
1112
+ ctx.beginPath();
1113
+ addRectPath(ctx, inner);
1114
+ ctx.fillStyle = options.backgroundColor;
1115
+ ctx.fill();
1116
+ drawDivider(ctx, inner, options, data);
1117
+ drawText(ctx, inner, options, data, this);
1118
+ ctx.restore();
1119
+ }
1120
+ inRange(mouseX, mouseY, useFinalPosition) {
1121
+ return inRange(this, mouseX, mouseY, useFinalPosition);
1122
+ }
1123
+ inXRange(mouseX, useFinalPosition) {
1124
+ return inRange(this, mouseX, null, useFinalPosition);
1125
+ }
1126
+ inYRange(mouseY, useFinalPosition) {
1127
+ return inRange(this, null, mouseY, useFinalPosition);
1128
+ }
1129
+ getCenterPoint(useFinalPosition) {
1130
+ const { x, y, width, height } = this.getProps([
1131
+ 'x',
1132
+ 'y',
1133
+ 'width',
1134
+ 'height'
1135
+ ], useFinalPosition);
1136
+ return {
1137
+ x: x + width / 2,
1138
+ y: y + height / 2
1139
+ };
1140
+ }
1141
+ tooltipPosition() {
1142
+ return this.getCenterPoint();
1143
+ }
1144
+ constructor(cfg){
1145
+ super();
1146
+ this.width = undefined;
1147
+ this.height = undefined;
1148
+ if (cfg) {
1149
+ Object.assign(this, cfg);
1150
+ }
1151
+ }
1152
+ }
1153
+ TreemapElement.id = 'treemap';
1154
+ TreemapElement.defaults = {
1155
+ borderRadius: 0,
1156
+ borderWidth: 0,
1157
+ captions: {
1158
+ align: undefined,
1159
+ color: 'black',
1160
+ display: true,
1161
+ font: {},
1162
+ formatter: (ctx)=>{
1163
+ const label = ctx.raw._data?.label;
1164
+ return ctx.raw.g || (typeof label === 'string' ? label : '');
1165
+ },
1166
+ padding: 3
1167
+ },
1168
+ displayMode: 'containerBoxes',
1169
+ dividers: {
1170
+ display: false,
1171
+ lineCapStyle: 'butt',
1172
+ lineColor: 'black',
1173
+ lineDash: [],
1174
+ lineDashOffset: 0,
1175
+ lineWidth: 1
1176
+ },
1177
+ label: undefined,
1178
+ labels: {
1179
+ align: 'center',
1180
+ color: 'black',
1181
+ display: false,
1182
+ font: {},
1183
+ formatter (ctx) {
1184
+ const label = ctx.raw._data?.label;
1185
+ if (ctx.raw.g) {
1186
+ return [
1187
+ ctx.raw.g,
1188
+ `${ctx.raw.v}`
1189
+ ];
1190
+ }
1191
+ return typeof label === 'string' ? [
1192
+ label,
1193
+ `${ctx.raw.v}`
1194
+ ] : `${ctx.raw.v}`;
1195
+ },
1196
+ overflow: 'cut',
1197
+ padding: 3,
1198
+ position: 'middle'
1199
+ },
1200
+ rtl: false,
1201
+ spacing: 0.5,
1202
+ unsorted: false
1203
+ };
1204
+ TreemapElement.descriptors = {
1205
+ _indexable: false,
1206
+ _scriptable: true,
1207
+ captions: {
1208
+ _fallback: true,
1209
+ _scriptable: (name)=>name !== 'formatter'
1210
+ },
1211
+ labels: {
1212
+ _fallback: true,
1213
+ _scriptable: (name)=>name !== 'formatter'
1214
+ }
1215
+ };
1216
+ TreemapElement.defaultRoutes = {
1217
+ backgroundColor: 'backgroundColor',
1218
+ borderColor: 'borderColor'
1179
1219
  };
1180
1220
 
1181
1221
  export { TreemapController, TreemapElement };