squarified 0.6.0 → 0.6.2

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.
@@ -0,0 +1,1743 @@
1
+ 'use strict';
2
+
3
+ const DEG_TO_RAD = Math.PI / 180;
4
+ const PI_2 = Math.PI * 2;
5
+ const DEFAULT_MATRIX_LOC = {
6
+ a: 1,
7
+ b: 0,
8
+ c: 0,
9
+ d: 1,
10
+ e: 0,
11
+ f: 0
12
+ };
13
+ class Matrix2D {
14
+ a;
15
+ b;
16
+ c;
17
+ d;
18
+ e;
19
+ f;
20
+ constructor(loc = {}){
21
+ this.a = loc.a || 1;
22
+ this.b = loc.b || 0;
23
+ this.c = loc.c || 0;
24
+ this.d = loc.d || 1;
25
+ this.e = loc.e || 0;
26
+ this.f = loc.f || 0;
27
+ }
28
+ create(loc) {
29
+ Object.assign(this, loc);
30
+ return this;
31
+ }
32
+ transform(x, y, scaleX, scaleY, rotation, skewX, skewY) {
33
+ this.scale(scaleX, scaleY).translation(x, y);
34
+ if (skewX || skewY) {
35
+ this.skew(skewX, skewY);
36
+ } else {
37
+ this.roate(rotation);
38
+ }
39
+ return this;
40
+ }
41
+ translation(x, y) {
42
+ this.e += x;
43
+ this.f += y;
44
+ return this;
45
+ }
46
+ scale(a, d) {
47
+ this.a *= a;
48
+ this.d *= d;
49
+ return this;
50
+ }
51
+ skew(x, y) {
52
+ const tanX = Math.tan(x * DEG_TO_RAD);
53
+ const tanY = Math.tan(y * DEG_TO_RAD);
54
+ const a = this.a + this.b * tanX;
55
+ const b = this.b + this.a * tanY;
56
+ const c = this.c + this.d * tanX;
57
+ const d = this.d + this.c * tanY;
58
+ this.a = a;
59
+ this.b = b;
60
+ this.c = c;
61
+ this.d = d;
62
+ return this;
63
+ }
64
+ roate(rotation) {
65
+ if (rotation > 0) {
66
+ const rad = rotation * DEG_TO_RAD;
67
+ const cosTheta = Math.cos(rad);
68
+ const sinTheta = Math.sin(rad);
69
+ const a = this.a * cosTheta - this.b * sinTheta;
70
+ const b = this.a * sinTheta + this.b * cosTheta;
71
+ const c = this.c * cosTheta - this.d * sinTheta;
72
+ const d = this.c * sinTheta + this.d * cosTheta;
73
+ this.a = a;
74
+ this.b = b;
75
+ this.c = c;
76
+ this.d = d;
77
+ }
78
+ return this;
79
+ }
80
+ }
81
+
82
+ const SELF_ID = {
83
+ id: 0,
84
+ get () {
85
+ return this.id++;
86
+ }
87
+ };
88
+ var DisplayType = /*#__PURE__*/ function(DisplayType) {
89
+ DisplayType["Graph"] = "Graph";
90
+ DisplayType["Box"] = "Box";
91
+ DisplayType["Text"] = "Text";
92
+ DisplayType["RoundRect"] = "RoundRect";
93
+ return DisplayType;
94
+ }({});
95
+ class Display {
96
+ parent;
97
+ id;
98
+ matrix;
99
+ constructor(){
100
+ this.parent = null;
101
+ this.id = SELF_ID.get();
102
+ this.matrix = new Matrix2D();
103
+ }
104
+ destory() {
105
+ //
106
+ }
107
+ }
108
+ const ASSIGN_MAPPINGS = {
109
+ fillStyle: 0o1,
110
+ strokeStyle: 0o2,
111
+ font: 0o4,
112
+ lineWidth: 0o10,
113
+ textAlign: 0o20,
114
+ textBaseline: 0o40
115
+ };
116
+ const ASSIGN_MAPPINGS_MODE = ASSIGN_MAPPINGS.fillStyle | ASSIGN_MAPPINGS.strokeStyle | ASSIGN_MAPPINGS.font | ASSIGN_MAPPINGS.lineWidth | ASSIGN_MAPPINGS.textAlign | ASSIGN_MAPPINGS.textBaseline;
117
+ const CALL_MAPPINGS_MODE = 0o0;
118
+ function createInstruction() {
119
+ return {
120
+ mods: [],
121
+ fillStyle (...args) {
122
+ this.mods.push({
123
+ mod: [
124
+ 'fillStyle',
125
+ args
126
+ ],
127
+ type: ASSIGN_MAPPINGS.fillStyle
128
+ });
129
+ },
130
+ fillRect (...args) {
131
+ this.mods.push({
132
+ mod: [
133
+ 'fillRect',
134
+ args
135
+ ],
136
+ type: CALL_MAPPINGS_MODE
137
+ });
138
+ },
139
+ strokeStyle (...args) {
140
+ this.mods.push({
141
+ mod: [
142
+ 'strokeStyle',
143
+ args
144
+ ],
145
+ type: ASSIGN_MAPPINGS.strokeStyle
146
+ });
147
+ },
148
+ lineWidth (...args) {
149
+ this.mods.push({
150
+ mod: [
151
+ 'lineWidth',
152
+ args
153
+ ],
154
+ type: ASSIGN_MAPPINGS.lineWidth
155
+ });
156
+ },
157
+ strokeRect (...args) {
158
+ this.mods.push({
159
+ mod: [
160
+ 'strokeRect',
161
+ args
162
+ ],
163
+ type: CALL_MAPPINGS_MODE
164
+ });
165
+ },
166
+ fillText (...args) {
167
+ this.mods.push({
168
+ mod: [
169
+ 'fillText',
170
+ args
171
+ ],
172
+ type: CALL_MAPPINGS_MODE
173
+ });
174
+ },
175
+ font (...args) {
176
+ this.mods.push({
177
+ mod: [
178
+ 'font',
179
+ args
180
+ ],
181
+ type: ASSIGN_MAPPINGS.font
182
+ });
183
+ },
184
+ textBaseline (...args) {
185
+ this.mods.push({
186
+ mod: [
187
+ 'textBaseline',
188
+ args
189
+ ],
190
+ type: ASSIGN_MAPPINGS.textBaseline
191
+ });
192
+ },
193
+ textAlign (...args) {
194
+ this.mods.push({
195
+ mod: [
196
+ 'textAlign',
197
+ args
198
+ ],
199
+ type: ASSIGN_MAPPINGS.textAlign
200
+ });
201
+ },
202
+ beginPath () {
203
+ this.mods.push({
204
+ mod: [
205
+ 'beginPath',
206
+ []
207
+ ],
208
+ type: CALL_MAPPINGS_MODE
209
+ });
210
+ },
211
+ moveTo (...args) {
212
+ this.mods.push({
213
+ mod: [
214
+ 'moveTo',
215
+ args
216
+ ],
217
+ type: CALL_MAPPINGS_MODE
218
+ });
219
+ },
220
+ arcTo (...args) {
221
+ this.mods.push({
222
+ mod: [
223
+ 'arcTo',
224
+ args
225
+ ],
226
+ type: CALL_MAPPINGS_MODE
227
+ });
228
+ },
229
+ closePath () {
230
+ this.mods.push({
231
+ mod: [
232
+ 'closePath',
233
+ []
234
+ ],
235
+ type: CALL_MAPPINGS_MODE
236
+ });
237
+ },
238
+ fill () {
239
+ this.mods.push({
240
+ mod: [
241
+ 'fill',
242
+ []
243
+ ],
244
+ type: CALL_MAPPINGS_MODE
245
+ });
246
+ },
247
+ stroke () {
248
+ this.mods.push({
249
+ mod: [
250
+ 'stroke',
251
+ []
252
+ ],
253
+ type: CALL_MAPPINGS_MODE
254
+ });
255
+ },
256
+ drawImage (...args) {
257
+ // @ts-expect-error safe
258
+ this.mods.push({
259
+ mod: [
260
+ 'drawImage',
261
+ args
262
+ ],
263
+ type: CALL_MAPPINGS_MODE
264
+ });
265
+ }
266
+ };
267
+ }
268
+ class S extends Display {
269
+ width;
270
+ height;
271
+ x;
272
+ y;
273
+ scaleX;
274
+ scaleY;
275
+ rotation;
276
+ skewX;
277
+ skewY;
278
+ constructor(options = {}){
279
+ super();
280
+ this.width = options.width || 0;
281
+ this.height = options.height || 0;
282
+ this.x = options.x || 0;
283
+ this.y = options.y || 0;
284
+ this.scaleX = options.scaleX || 1;
285
+ this.scaleY = options.scaleY || 1;
286
+ this.rotation = options.rotation || 0;
287
+ this.skewX = options.skewX || 0;
288
+ this.skewY = options.skewY || 0;
289
+ }
290
+ }
291
+ // For performance. we need impl AABB Check for render.
292
+ class Graph extends S {
293
+ instruction;
294
+ __options__;
295
+ constructor(options = {}){
296
+ super(options);
297
+ this.instruction = createInstruction();
298
+ this.__options__ = options;
299
+ }
300
+ render(ctx) {
301
+ this.create();
302
+ const cap = this.instruction.mods.length;
303
+ for(let i = 0; i < cap; i++){
304
+ const { mod, type } = this.instruction.mods[i];
305
+ const [direct, ...args] = mod;
306
+ if (type & ASSIGN_MAPPINGS_MODE) {
307
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
308
+ // @ts-expect-error
309
+ ctx[direct] = args[0];
310
+ continue;
311
+ }
312
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
313
+ // @ts-expect-error
314
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
315
+ ctx[direct].apply(ctx, ...args);
316
+ }
317
+ }
318
+ get __instanceOf__() {
319
+ return "Graph";
320
+ }
321
+ }
322
+
323
+ function isGraph(display) {
324
+ return display.__instanceOf__ === DisplayType.Graph;
325
+ }
326
+ function isBox(display) {
327
+ return display.__instanceOf__ === DisplayType.Box;
328
+ }
329
+ function isRoundRect(display) {
330
+ return isGraph(display) && display.__shape__ === DisplayType.RoundRect;
331
+ }
332
+ function isText(display) {
333
+ return isGraph(display) && display.__shape__ === DisplayType.Text;
334
+ }
335
+ const asserts = {
336
+ isGraph,
337
+ isBox,
338
+ isText,
339
+ isRoundRect
340
+ };
341
+
342
+ class C extends Display {
343
+ elements;
344
+ constructor(){
345
+ super();
346
+ this.elements = [];
347
+ }
348
+ add(...elements) {
349
+ const cap = elements.length;
350
+ for(let i = 0; i < cap; i++){
351
+ const element = elements[i];
352
+ if (element.parent) ;
353
+ this.elements.push(element);
354
+ element.parent = this;
355
+ }
356
+ }
357
+ remove(...elements) {
358
+ const cap = elements.length;
359
+ for(let i = 0; i < cap; i++){
360
+ for(let j = this.elements.length - 1; j >= 0; j--){
361
+ const element = this.elements[j];
362
+ if (element.id === elements[i].id) {
363
+ this.elements.splice(j, 1);
364
+ element.parent = null;
365
+ }
366
+ }
367
+ }
368
+ }
369
+ destory() {
370
+ this.elements.forEach((element)=>element.parent = null);
371
+ this.elements.length = 0;
372
+ }
373
+ }
374
+ class Box extends C {
375
+ elements;
376
+ constructor(){
377
+ super();
378
+ this.elements = [];
379
+ }
380
+ add(...elements) {
381
+ const cap = elements.length;
382
+ for(let i = 0; i < cap; i++){
383
+ const element = elements[i];
384
+ if (element.parent) ;
385
+ this.elements.push(element);
386
+ element.parent = this;
387
+ }
388
+ }
389
+ remove(...elements) {
390
+ const cap = elements.length;
391
+ for(let i = 0; i < cap; i++){
392
+ for(let j = this.elements.length - 1; j >= 0; j--){
393
+ const element = this.elements[j];
394
+ if (element.id === elements[i].id) {
395
+ this.elements.splice(j, 1);
396
+ element.parent = null;
397
+ }
398
+ }
399
+ }
400
+ }
401
+ destory() {
402
+ this.elements.forEach((element)=>element.parent = null);
403
+ this.elements.length = 0;
404
+ }
405
+ get __instanceOf__() {
406
+ return DisplayType.Box;
407
+ }
408
+ clone() {
409
+ const box = new Box();
410
+ if (this.elements.length) {
411
+ const stack = [
412
+ {
413
+ elements: this.elements,
414
+ parent: box
415
+ }
416
+ ];
417
+ while(stack.length > 0){
418
+ const { elements, parent } = stack.pop();
419
+ const cap = elements.length;
420
+ for(let i = 0; i < cap; i++){
421
+ const element = elements[i];
422
+ if (asserts.isBox(element)) {
423
+ const newBox = new Box();
424
+ newBox.parent = parent;
425
+ parent.add(newBox);
426
+ stack.push({
427
+ elements: element.elements,
428
+ parent: newBox
429
+ });
430
+ } else if (asserts.isGraph(element)) {
431
+ const el = element.clone();
432
+ el.parent = parent;
433
+ parent.add(el);
434
+ }
435
+ }
436
+ }
437
+ }
438
+ return box;
439
+ }
440
+ }
441
+
442
+ // Runtime is designed for graph element
443
+ function decodeHLS(meta) {
444
+ const { h, l, s, a } = meta;
445
+ if ('a' in meta) {
446
+ return `hsla(${h}deg, ${s}%, ${l}%, ${a})`;
447
+ }
448
+ return `hsl(${h}deg, ${s}%, ${l}%)`;
449
+ }
450
+ function decodeRGB(meta) {
451
+ const { r, g, b, a } = meta;
452
+ if ('a' in meta) {
453
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
454
+ }
455
+ return `rgb(${r}, ${g}, ${b})`;
456
+ }
457
+ function decodeColor(meta) {
458
+ return meta.mode === 'rgb' ? decodeRGB(meta.desc) : decodeHLS(meta.desc);
459
+ }
460
+ function evaluateFillStyle(primitive, opacity = 1) {
461
+ const descibe = {
462
+ mode: primitive.mode,
463
+ desc: {
464
+ ...primitive.desc,
465
+ a: opacity
466
+ }
467
+ };
468
+ return decodeColor(descibe);
469
+ }
470
+ const runtime = {
471
+ evaluateFillStyle
472
+ };
473
+
474
+ class RoundRect extends Graph {
475
+ style;
476
+ constructor(options = {}){
477
+ super(options);
478
+ this.style = options.style || Object.create(null);
479
+ }
480
+ get __shape__() {
481
+ return DisplayType.RoundRect;
482
+ }
483
+ create() {
484
+ const padding = this.style.padding;
485
+ const x = 0;
486
+ const y = 0;
487
+ const width = this.width - padding * 2;
488
+ const height = this.height - padding * 2;
489
+ const radius = this.style.radius || 0;
490
+ this.instruction.beginPath();
491
+ this.instruction.moveTo(x + radius, y);
492
+ this.instruction.arcTo(x + width, y, x + width, y + height, radius);
493
+ this.instruction.arcTo(x + width, y + height, x, y + height, radius);
494
+ this.instruction.arcTo(x, y + height, x, y, radius);
495
+ this.instruction.arcTo(x, y, x + width, y, radius);
496
+ this.instruction.closePath();
497
+ if (this.style.fill) {
498
+ this.instruction.closePath();
499
+ this.instruction.fillStyle(runtime.evaluateFillStyle(this.style.fill, this.style.opacity));
500
+ this.instruction.fill();
501
+ }
502
+ if (this.style.stroke) {
503
+ if (typeof this.style.lineWidth === 'number') {
504
+ this.instruction.lineWidth(this.style.lineWidth);
505
+ }
506
+ this.instruction.strokeStyle(this.style.stroke);
507
+ this.instruction.stroke();
508
+ }
509
+ }
510
+ clone() {
511
+ return new RoundRect({
512
+ ...this.style,
513
+ ...this.__options__
514
+ });
515
+ }
516
+ }
517
+
518
+ class Text extends Graph {
519
+ text;
520
+ style;
521
+ constructor(options = {}){
522
+ super(options);
523
+ this.text = options.text || '';
524
+ this.style = options.style || Object.create(null);
525
+ }
526
+ create() {
527
+ if (this.style.fill) {
528
+ this.instruction.font(this.style.font);
529
+ this.instruction.lineWidth(this.style.lineWidth);
530
+ this.instruction.textBaseline(this.style.baseline);
531
+ this.instruction.textAlign(this.style.textAlign);
532
+ this.instruction.fillStyle(this.style.fill);
533
+ this.instruction.fillText(this.text, 0, 0);
534
+ }
535
+ }
536
+ clone() {
537
+ return new Text({
538
+ ...this.style,
539
+ ...this.__options__
540
+ });
541
+ }
542
+ get __shape__() {
543
+ return DisplayType.Text;
544
+ }
545
+ }
546
+
547
+ function traverse(graphs, handler) {
548
+ const len = graphs.length;
549
+ for(let i = 0; i < len; i++){
550
+ const graph = graphs[i];
551
+ if (asserts.isGraph(graph)) {
552
+ handler(graph);
553
+ } else if (asserts.isBox(graph)) {
554
+ traverse(graph.elements, handler);
555
+ }
556
+ }
557
+ }
558
+
559
+ class Event {
560
+ eventCollections;
561
+ constructor(){
562
+ this.eventCollections = Object.create(null);
563
+ }
564
+ on(evt, handler, c) {
565
+ if (!(evt in this.eventCollections)) {
566
+ this.eventCollections[evt] = [];
567
+ }
568
+ const data = {
569
+ name: evt,
570
+ handler,
571
+ ctx: c || this,
572
+ silent: false
573
+ };
574
+ this.eventCollections[evt].push(data);
575
+ }
576
+ off(evt, handler) {
577
+ if (evt in this.eventCollections) {
578
+ if (!handler) {
579
+ this.eventCollections[evt] = [];
580
+ return;
581
+ }
582
+ this.eventCollections[evt] = this.eventCollections[evt].filter((d)=>d.handler !== handler);
583
+ }
584
+ }
585
+ silent(evt, handler) {
586
+ if (!(evt in this.eventCollections)) {
587
+ return;
588
+ }
589
+ this.eventCollections[evt].forEach((d)=>{
590
+ if (!handler || d.handler === handler) {
591
+ d.silent = true;
592
+ }
593
+ });
594
+ }
595
+ active(evt, handler) {
596
+ if (!(evt in this.eventCollections)) {
597
+ return;
598
+ }
599
+ this.eventCollections[evt].forEach((d)=>{
600
+ if (!handler || d.handler === handler) {
601
+ d.silent = false;
602
+ }
603
+ });
604
+ }
605
+ emit(evt, ...args) {
606
+ if (!this.eventCollections[evt]) {
607
+ return;
608
+ }
609
+ const handlers = this.eventCollections[evt];
610
+ if (handlers.length) {
611
+ handlers.forEach((d)=>{
612
+ if (d.silent) {
613
+ return;
614
+ }
615
+ d.handler.call(d.ctx, ...args);
616
+ });
617
+ }
618
+ }
619
+ bindWithContext(c) {
620
+ return (evt, handler)=>this.on(evt, handler, c);
621
+ }
622
+ }
623
+
624
+ function getOffset(el) {
625
+ let e = 0;
626
+ let f = 0;
627
+ if (document.documentElement.getBoundingClientRect && el.getBoundingClientRect) {
628
+ const { top, left } = el.getBoundingClientRect();
629
+ e = top;
630
+ f = left;
631
+ } else {
632
+ for(let elt = el; elt; elt = el.offsetParent){
633
+ e += el.offsetLeft;
634
+ f += el.offsetTop;
635
+ }
636
+ }
637
+ return [
638
+ e + Math.max(document.documentElement.scrollLeft, document.body.scrollLeft),
639
+ f + Math.max(document.documentElement.scrollTop, document.body.scrollTop)
640
+ ];
641
+ }
642
+ function captureBoxXY(c, evt, a, d, translateX, translateY) {
643
+ const boundingClientRect = c.getBoundingClientRect();
644
+ if (evt instanceof MouseEvent) {
645
+ const [e, f] = getOffset(c);
646
+ return {
647
+ x: (evt.clientX - boundingClientRect.left - e - translateX) / a,
648
+ y: (evt.clientY - boundingClientRect.top - f - translateY) / d
649
+ };
650
+ }
651
+ return {
652
+ x: 0,
653
+ y: 0
654
+ };
655
+ }
656
+ function createEffectRun(c) {
657
+ return (fn)=>{
658
+ const effect = ()=>{
659
+ const done = fn();
660
+ if (!done) {
661
+ c.animationFrameID = raf(effect);
662
+ }
663
+ };
664
+ if (!c.animationFrameID) {
665
+ c.animationFrameID = raf(effect);
666
+ }
667
+ };
668
+ }
669
+ function createEffectStop(c) {
670
+ return ()=>{
671
+ if (c.animationFrameID) {
672
+ window.cancelAnimationFrame(c.animationFrameID);
673
+ c.animationFrameID = null;
674
+ }
675
+ };
676
+ }
677
+ function createSmoothFrame() {
678
+ const c = {
679
+ animationFrameID: null
680
+ };
681
+ const run = createEffectRun(c);
682
+ const stop = createEffectStop(c);
683
+ return {
684
+ run,
685
+ stop
686
+ };
687
+ }
688
+
689
+ function hashCode(str) {
690
+ let hash = 0;
691
+ for(let i = 0; i < str.length; i++){
692
+ const code = str.charCodeAt(i);
693
+ hash = (hash << 5) - hash + code;
694
+ hash = hash & hash;
695
+ }
696
+ return hash;
697
+ }
698
+ // For strings we only check the first character to determine if it's a number (I think it's enough)
699
+ function perferNumeric(s) {
700
+ if (typeof s === 'number') {
701
+ return true;
702
+ }
703
+ return s.charCodeAt(0) >= 48 && s.charCodeAt(0) <= 57;
704
+ }
705
+ function noop() {}
706
+ function createRoundBlock(x, y, width, height, style) {
707
+ return new RoundRect({
708
+ width,
709
+ height,
710
+ x,
711
+ y,
712
+ style: {
713
+ ...style
714
+ }
715
+ });
716
+ }
717
+ function createTitleText(text, x, y, font, color) {
718
+ return new Text({
719
+ text,
720
+ x,
721
+ y,
722
+ style: {
723
+ fill: color,
724
+ textAlign: 'center',
725
+ baseline: 'middle',
726
+ font,
727
+ lineWidth: 1
728
+ }
729
+ });
730
+ }
731
+ const raf = window.requestAnimationFrame;
732
+ function createCanvasElement() {
733
+ return document.createElement('canvas');
734
+ }
735
+ function applyCanvasTransform(ctx, matrix, dpr) {
736
+ ctx.setTransform(matrix.a * dpr, matrix.b * dpr, matrix.c * dpr, matrix.d * dpr, matrix.e * dpr, matrix.f * dpr);
737
+ }
738
+ function mixin(app, methods) {
739
+ methods.forEach(({ name, fn })=>{
740
+ Object.defineProperty(app, name, {
741
+ value: fn(app),
742
+ writable: false,
743
+ enumerable: true
744
+ });
745
+ });
746
+ return app;
747
+ }
748
+ function typedForIn(obj, callback) {
749
+ for(const key in obj){
750
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
751
+ callback(key, obj[key]);
752
+ }
753
+ }
754
+ }
755
+ function stackMatrixTransform(graph, e, f, scale) {
756
+ graph.x = graph.x * scale + e;
757
+ graph.y = graph.y * scale + f;
758
+ graph.scaleX = scale;
759
+ graph.scaleY = scale;
760
+ }
761
+ function stackMatrixTransformWithGraphAndLayer(graphs, e, f, scale) {
762
+ traverse(graphs, (graph)=>stackMatrixTransform(graph, e, f, scale));
763
+ }
764
+ function smoothFrame(callback, opts) {
765
+ const frame = createSmoothFrame();
766
+ const startTime = Date.now();
767
+ const condtion = (process)=>{
768
+ if (Array.isArray(opts.deps)) {
769
+ return opts.deps.some((dep)=>dep());
770
+ }
771
+ return process >= 1;
772
+ };
773
+ frame.run(()=>{
774
+ const elapsed = Date.now() - startTime;
775
+ const progress = Math.min(elapsed / opts.duration, 1);
776
+ if (condtion(progress)) {
777
+ frame.stop();
778
+ if (opts.onStop) {
779
+ opts.onStop();
780
+ }
781
+ return true;
782
+ }
783
+ return callback(progress, frame.stop);
784
+ });
785
+ }
786
+ function isScrollWheelOrRightButtonOnMouseupAndDown(e) {
787
+ return e.which === 2 || e.which === 3;
788
+ }
789
+ class DefaultMap extends Map {
790
+ defaultFactory;
791
+ constructor(defaultFactory, entries){
792
+ super(entries);
793
+ this.defaultFactory = defaultFactory;
794
+ }
795
+ get(key) {
796
+ if (!super.has(key)) {
797
+ return this.defaultFactory();
798
+ }
799
+ return super.get(key);
800
+ }
801
+ getOrInsert(key, value) {
802
+ if (!super.has(key)) {
803
+ const defaultValue = value || this.defaultFactory();
804
+ super.set(key, defaultValue);
805
+ return defaultValue;
806
+ }
807
+ return super.get(key);
808
+ }
809
+ }
810
+
811
+ const createLogger = (namespace)=>{
812
+ return {
813
+ error: (message)=>{
814
+ return console.error(`[${namespace}] ${message}`);
815
+ },
816
+ panic: (message)=>{
817
+ throw new Error(`[${namespace}] ${message}`);
818
+ }
819
+ };
820
+ };
821
+ function assertExists(value, logger, message) {
822
+ if (value === null || value === undefined) {
823
+ logger.panic(message);
824
+ }
825
+ }
826
+
827
+ const NAME_SPACE = 'etoile';
828
+ const log = createLogger(NAME_SPACE);
829
+
830
+ function writeBoundingRectForCanvas(c, w, h, dpr) {
831
+ c.width = w * dpr;
832
+ c.height = h * dpr;
833
+ c.style.cssText = `width: ${w}px; height: ${h}px`;
834
+ }
835
+ class Canvas {
836
+ canvas;
837
+ ctx;
838
+ constructor(options){
839
+ this.canvas = createCanvasElement();
840
+ this.setOptions(options);
841
+ this.ctx = this.canvas.getContext('2d');
842
+ }
843
+ setOptions(options) {
844
+ writeBoundingRectForCanvas(this.canvas, options.width, options.height, options.devicePixelRatio);
845
+ }
846
+ }
847
+ class Render {
848
+ options;
849
+ container;
850
+ constructor(to, options){
851
+ this.container = new Canvas(options);
852
+ this.options = options;
853
+ this.initOptions(options);
854
+ if (!options.shaow) {
855
+ to.appendChild(this.container.canvas);
856
+ }
857
+ }
858
+ clear(width, height) {
859
+ this.ctx.clearRect(0, 0, width, height);
860
+ }
861
+ get canvas() {
862
+ return this.container.canvas;
863
+ }
864
+ get ctx() {
865
+ return this.container.ctx;
866
+ }
867
+ initOptions(userOptions = {}) {
868
+ Object.assign(this.options, userOptions);
869
+ this.container.setOptions(this.options);
870
+ }
871
+ destory() {}
872
+ }
873
+
874
+ // First cleanup canvas
875
+ function drawGraphIntoCanvas(graph, opts) {
876
+ const { ctx, dpr } = opts;
877
+ ctx.save();
878
+ if (asserts.isBox(graph)) {
879
+ const elements = graph.elements;
880
+ const cap = elements.length;
881
+ for(let i = 0; i < cap; i++){
882
+ const element = elements[i];
883
+ drawGraphIntoCanvas(element, opts);
884
+ }
885
+ }
886
+ if (asserts.isGraph(graph)) {
887
+ const matrix = graph.matrix.create({
888
+ a: 1,
889
+ b: 0,
890
+ c: 0,
891
+ d: 1,
892
+ e: 0,
893
+ f: 0
894
+ });
895
+ matrix.transform(graph.x, graph.y, graph.scaleX, graph.scaleY, graph.rotation, graph.skewX, graph.skewY);
896
+ applyCanvasTransform(ctx, matrix, dpr);
897
+ graph.render(ctx);
898
+ }
899
+ ctx.restore();
900
+ }
901
+ class Schedule extends Box {
902
+ render;
903
+ to;
904
+ event;
905
+ constructor(to, renderOptions = {}){
906
+ super();
907
+ this.to = typeof to === 'string' ? document.querySelector(to) : to;
908
+ if (!this.to) {
909
+ log.panic('The element to bind is not found.');
910
+ }
911
+ const { width, height } = this.to.getBoundingClientRect();
912
+ Object.assign(renderOptions, {
913
+ width,
914
+ height
915
+ }, {
916
+ devicePixelRatio: window.devicePixelRatio || 1
917
+ });
918
+ this.event = new Event();
919
+ this.render = new Render(this.to, renderOptions);
920
+ }
921
+ update() {
922
+ this.render.clear(this.render.options.width, this.render.options.height);
923
+ this.execute(this.render, this);
924
+ const matrix = this.matrix.create({
925
+ a: 1,
926
+ b: 0,
927
+ c: 0,
928
+ d: 1,
929
+ e: 0,
930
+ f: 0
931
+ });
932
+ applyCanvasTransform(this.render.ctx, matrix, this.render.options.devicePixelRatio);
933
+ }
934
+ // execute all graph elements
935
+ execute(render, graph = this) {
936
+ drawGraphIntoCanvas(graph, {
937
+ c: render.canvas,
938
+ ctx: render.ctx,
939
+ dpr: render.options.devicePixelRatio
940
+ });
941
+ }
942
+ }
943
+
944
+ function sortChildrenByKey(data, ...keys) {
945
+ return data.sort((a, b)=>{
946
+ for (const key of keys){
947
+ const v = a[key];
948
+ const v2 = b[key];
949
+ if (perferNumeric(v) && perferNumeric(v2)) {
950
+ if (v2 > v) {
951
+ return 1;
952
+ }
953
+ if (v2 < v) {
954
+ return -1;
955
+ }
956
+ continue;
957
+ }
958
+ // Not numeric, compare as string
959
+ const comparison = ('' + v).localeCompare('' + v2);
960
+ if (comparison !== 0) {
961
+ return comparison;
962
+ }
963
+ }
964
+ return 0;
965
+ });
966
+ }
967
+ function c2m(data, key, modifier) {
968
+ if (Array.isArray(data.groups)) {
969
+ data.groups = sortChildrenByKey(data.groups.map((d)=>c2m(d, key, modifier)), 'weight');
970
+ }
971
+ const obj = {
972
+ ...data,
973
+ weight: data[key]
974
+ };
975
+ if (modifier) {
976
+ Object.assign(obj, modifier(obj));
977
+ }
978
+ return obj;
979
+ }
980
+ function flatten(data) {
981
+ const result = [];
982
+ for(let i = 0; i < data.length; i++){
983
+ const { groups, ...rest } = data[i];
984
+ result.push(rest);
985
+ if (groups) {
986
+ result.push(...flatten(groups));
987
+ }
988
+ }
989
+ return result;
990
+ }
991
+ function bindParentForModule(modules, parent) {
992
+ return modules.map((module)=>{
993
+ const next = {
994
+ ...module
995
+ };
996
+ next.parent = parent;
997
+ if (next.groups && Array.isArray(next.groups)) {
998
+ next.groups = bindParentForModule(next.groups, next);
999
+ }
1000
+ return next;
1001
+ });
1002
+ }
1003
+ function getNodeDepth(node) {
1004
+ let depth = 0;
1005
+ while(node.parent){
1006
+ node = node.parent;
1007
+ depth++;
1008
+ }
1009
+ return depth;
1010
+ }
1011
+ function visit(data, fn) {
1012
+ if (!data) {
1013
+ return null;
1014
+ }
1015
+ for (const d of data){
1016
+ if (d.children) {
1017
+ const result = visit(d.children, fn);
1018
+ if (result) {
1019
+ return result;
1020
+ }
1021
+ }
1022
+ const stop = fn(d);
1023
+ if (stop) {
1024
+ return d;
1025
+ }
1026
+ }
1027
+ return null;
1028
+ }
1029
+ function findRelativeNode(p, layoutNodes) {
1030
+ return visit(layoutNodes, (node)=>{
1031
+ const [x, y, w, h] = node.layout;
1032
+ if (p.x >= x && p.y >= y && p.x < x + w && p.y < y + h) {
1033
+ return true;
1034
+ }
1035
+ });
1036
+ }
1037
+ function findRelativeNodeById(id, layoutNodes) {
1038
+ return visit(layoutNodes, (node)=>{
1039
+ if (node.node.id === id) {
1040
+ return true;
1041
+ }
1042
+ });
1043
+ }
1044
+
1045
+ function generateStableCombinedNodeId(weight, nodes) {
1046
+ const name = nodes.map((node)=>node.id).sort().join('-');
1047
+ return Math.abs(hashCode(name)) + '-' + weight;
1048
+ }
1049
+ function processSquarifyData(data, totalArea, minNodeSize, minNodeArea) {
1050
+ if (!data || !data.length) {
1051
+ return [];
1052
+ }
1053
+ const totalWeight = data.reduce((sum, node)=>sum + node.weight, 0);
1054
+ if (totalWeight <= 0) {
1055
+ return [];
1056
+ }
1057
+ const processedNodes = [];
1058
+ const tooSmallNodes = [];
1059
+ data.forEach((node)=>{
1060
+ const nodeArea = node.weight / totalWeight * totalArea;
1061
+ const estimatedSize = Math.sqrt(nodeArea);
1062
+ if (estimatedSize < minNodeSize || nodeArea < minNodeArea) {
1063
+ tooSmallNodes.push({
1064
+ ...node
1065
+ });
1066
+ } else {
1067
+ processedNodes.push({
1068
+ ...node
1069
+ });
1070
+ }
1071
+ });
1072
+ if (tooSmallNodes.length > 0) {
1073
+ const combinedWeight = tooSmallNodes.reduce((sum, node)=>sum + node.weight, 0);
1074
+ if (combinedWeight > 0 && combinedWeight / totalWeight * totalArea >= minNodeArea) {
1075
+ const combinedNode = {
1076
+ id: `combined-node-${generateStableCombinedNodeId(combinedWeight, tooSmallNodes)}`,
1077
+ weight: combinedWeight,
1078
+ isCombinedNode: true,
1079
+ originalNodeCount: tooSmallNodes.length,
1080
+ // @ts-expect-error fixme
1081
+ parent: null,
1082
+ groups: [],
1083
+ originalNodes: tooSmallNodes
1084
+ };
1085
+ processedNodes.push(combinedNode);
1086
+ }
1087
+ }
1088
+ return processedNodes;
1089
+ }
1090
+ function squarify(data, rect, config, scale = 1) {
1091
+ const result = [];
1092
+ if (!data.length) {
1093
+ return result;
1094
+ }
1095
+ const totalArea = rect.w * rect.h;
1096
+ const containerSize = Math.min(rect.w, rect.h);
1097
+ const scaleFactor = Math.max(0.5, Math.min(1, containerSize / 800));
1098
+ const baseMinSize = 20;
1099
+ const minRenderableSize = Math.max(8, baseMinSize / Math.sqrt(scale));
1100
+ const minRenderableArea = minRenderableSize * minRenderableSize;
1101
+ const scaledGap = config.rectGap * scaleFactor;
1102
+ const scaledRadius = config.rectRadius * scaleFactor;
1103
+ const processedData = processSquarifyData(data, totalArea, minRenderableSize, minRenderableArea);
1104
+ if (!processedData.length) {
1105
+ return result;
1106
+ }
1107
+ let workingRect = rect;
1108
+ if (scaledGap > 0) {
1109
+ workingRect = {
1110
+ x: rect.x + scaledGap / 2,
1111
+ y: rect.y + scaledGap / 2,
1112
+ w: Math.max(0, rect.w - scaledGap),
1113
+ h: Math.max(0, rect.h - scaledGap)
1114
+ };
1115
+ }
1116
+ const worst = (start, end, shortestSide, totalWeight, aspectRatio)=>{
1117
+ const max = processedData[start].weight * aspectRatio;
1118
+ const min = processedData[end].weight * aspectRatio;
1119
+ return Math.max(shortestSide * shortestSide * max / (totalWeight * totalWeight), totalWeight * totalWeight / (shortestSide * shortestSide * min));
1120
+ };
1121
+ const recursion = (start, rect, depth = 0)=>{
1122
+ const depthFactor = Math.max(0.4, 1 - depth * 0.15);
1123
+ const currentGap = scaledGap * depthFactor;
1124
+ const currentRadius = scaledRadius * depthFactor;
1125
+ while(start < processedData.length){
1126
+ let totalWeight = 0;
1127
+ for(let i = start; i < processedData.length; i++){
1128
+ totalWeight += processedData[i].weight;
1129
+ }
1130
+ const shortestSide = Math.min(rect.w, rect.h);
1131
+ const aspectRatio = rect.w * rect.h / totalWeight;
1132
+ let end = start;
1133
+ let areaInRun = 0;
1134
+ let oldWorst = 0;
1135
+ while(end < processedData.length){
1136
+ const area = processedData[end].weight * aspectRatio || 0;
1137
+ const newWorst = worst(start, end, shortestSide, areaInRun + area, aspectRatio);
1138
+ if (end > start && oldWorst < newWorst) {
1139
+ break;
1140
+ }
1141
+ areaInRun += area;
1142
+ oldWorst = newWorst;
1143
+ end++;
1144
+ }
1145
+ const splited = Math.round(areaInRun / shortestSide);
1146
+ let areaInLayout = 0;
1147
+ const isHorizontalLayout = rect.w >= rect.h;
1148
+ for(let i = start; i < end; i++){
1149
+ const isFirst = i === start;
1150
+ const isLast = i === end - 1;
1151
+ const children = processedData[i];
1152
+ const area = children.weight * aspectRatio;
1153
+ const lower = Math.round(shortestSide * areaInLayout / areaInRun);
1154
+ const upper = Math.round(shortestSide * (areaInLayout + area) / areaInRun);
1155
+ let x, y, w, h;
1156
+ if (isHorizontalLayout) {
1157
+ x = rect.x;
1158
+ y = rect.y + lower;
1159
+ w = splited;
1160
+ h = upper - lower;
1161
+ } else {
1162
+ x = rect.x + lower;
1163
+ y = rect.y;
1164
+ w = upper - lower;
1165
+ h = splited;
1166
+ }
1167
+ if (currentGap > 0) {
1168
+ const edgeGap = currentGap / 2;
1169
+ if (!isFirst) {
1170
+ if (isHorizontalLayout) {
1171
+ y += edgeGap;
1172
+ h = Math.max(0, h - edgeGap);
1173
+ } else {
1174
+ x += edgeGap;
1175
+ w = Math.max(0, w - edgeGap);
1176
+ }
1177
+ }
1178
+ if (!isLast) {
1179
+ if (isHorizontalLayout) {
1180
+ h = Math.max(0, h - edgeGap);
1181
+ } else {
1182
+ w = Math.max(0, w - edgeGap);
1183
+ }
1184
+ }
1185
+ }
1186
+ const nodeDepth = getNodeDepth(children) || 1;
1187
+ const { titleAreaHeight } = config;
1188
+ const diff = titleAreaHeight.max / nodeDepth;
1189
+ const titleHeight = diff < titleAreaHeight.min ? titleAreaHeight.min : diff;
1190
+ w = Math.max(1, w);
1191
+ h = Math.max(1, h);
1192
+ let childrenLayout = [];
1193
+ const hasValidChildren = children.groups && children.groups.length > 0;
1194
+ if (hasValidChildren) {
1195
+ const childGapOffset = currentGap > 0 ? currentGap : 0;
1196
+ const childRect = {
1197
+ x: x + childGapOffset,
1198
+ y: y + titleHeight,
1199
+ w: Math.max(0, w - childGapOffset * 2),
1200
+ h: Math.max(0, h - titleHeight - childGapOffset)
1201
+ };
1202
+ const minChildSize = currentRadius > 0 ? currentRadius * 2 : 1;
1203
+ if (childRect.w >= minChildSize && childRect.h >= minChildSize) {
1204
+ childrenLayout = squarify(children.groups || [], childRect, {
1205
+ ...config,
1206
+ rectGap: currentGap,
1207
+ rectRadius: currentRadius
1208
+ }, scale);
1209
+ }
1210
+ }
1211
+ result.push({
1212
+ layout: [
1213
+ x,
1214
+ y,
1215
+ w,
1216
+ h
1217
+ ],
1218
+ node: children,
1219
+ children: childrenLayout,
1220
+ config: {
1221
+ titleAreaHeight: titleHeight,
1222
+ rectGap: currentGap,
1223
+ rectRadius: currentRadius
1224
+ }
1225
+ });
1226
+ areaInLayout += area;
1227
+ }
1228
+ start = end;
1229
+ const rectGapOffset = currentGap > 0 ? currentGap : 0;
1230
+ if (isHorizontalLayout) {
1231
+ rect.x += splited + rectGapOffset;
1232
+ rect.w = Math.max(0, rect.w - splited - rectGapOffset);
1233
+ } else {
1234
+ rect.y += splited + rectGapOffset;
1235
+ rect.h = Math.max(0, rect.h - splited - rectGapOffset);
1236
+ }
1237
+ }
1238
+ };
1239
+ recursion(0, workingRect);
1240
+ return result;
1241
+ }
1242
+
1243
+ function definePlugin(plugin) {
1244
+ return plugin;
1245
+ }
1246
+ class PluginDriver {
1247
+ plugins = new Map();
1248
+ pluginContext;
1249
+ constructor(component){
1250
+ this.pluginContext = {
1251
+ resolveModuleById (id) {
1252
+ return findRelativeNodeById(id, component.layoutNodes);
1253
+ },
1254
+ getPluginMetadata: (pluginName)=>{
1255
+ return this.getPluginMetadata(pluginName);
1256
+ },
1257
+ get instance () {
1258
+ return component;
1259
+ }
1260
+ };
1261
+ }
1262
+ use(plugin) {
1263
+ if (!plugin.name) {
1264
+ logger.error('Plugin name is required');
1265
+ return;
1266
+ }
1267
+ if (this.plugins.has(plugin.name)) {
1268
+ logger.panic(`Plugin ${plugin.name} is already registered`);
1269
+ }
1270
+ this.plugins.set(plugin.name, plugin);
1271
+ }
1272
+ runHook(hookName, ...args) {
1273
+ this.plugins.forEach((plugin)=>{
1274
+ const hook = plugin[hookName];
1275
+ if (hook) {
1276
+ // @ts-expect-error fixme
1277
+ hook.apply(this.pluginContext, args);
1278
+ }
1279
+ });
1280
+ }
1281
+ cascadeHook(hookName, ...args) {
1282
+ const finalResult = {};
1283
+ this.plugins.forEach((plugin)=>{
1284
+ const hook = plugin[hookName];
1285
+ if (hook) {
1286
+ // @ts-expect-error fixme
1287
+ const hookResult = hook.call(this.pluginContext, ...args);
1288
+ if (hookResult) {
1289
+ Object.assign(finalResult, hookResult);
1290
+ }
1291
+ }
1292
+ });
1293
+ return finalResult;
1294
+ }
1295
+ getPluginMetadata(pluginName) {
1296
+ const plugin = this.plugins.get(pluginName);
1297
+ return plugin?.meta || null;
1298
+ }
1299
+ }
1300
+
1301
+ const logger = createLogger('Treemap');
1302
+ const DEFAULT_RECT_FILL_DESC = {
1303
+ mode: 'rgb',
1304
+ desc: {
1305
+ r: 0,
1306
+ g: 0,
1307
+ b: 0
1308
+ }
1309
+ };
1310
+ const DEFAULT_TITLE_AREA_HEIGHT = {
1311
+ min: 30,
1312
+ max: 60
1313
+ };
1314
+ const DEFAULT_RECT_GAP = 4;
1315
+ const DEFAULT_RECT_BORDER_RADIUS = 4;
1316
+ const DEFAULT_FONT_SIZE = {
1317
+ max: 70,
1318
+ min: 12
1319
+ };
1320
+ const DEFAULT_FONT_FAMILY = 'sans-serif';
1321
+ const DEFAULT_FONT_COLOR = '#000';
1322
+ class Component extends Schedule {
1323
+ pluginDriver;
1324
+ data;
1325
+ colorMappings;
1326
+ rectLayer;
1327
+ textLayer;
1328
+ layoutNodes;
1329
+ config;
1330
+ caches;
1331
+ constructor(config, ...args){
1332
+ super(...args);
1333
+ this.data = [];
1334
+ this.config = config;
1335
+ this.colorMappings = {};
1336
+ this.pluginDriver = new PluginDriver(this);
1337
+ this.rectLayer = new Box();
1338
+ this.textLayer = new Box();
1339
+ this.caches = new DefaultMap(()=>14);
1340
+ this.layoutNodes = [];
1341
+ }
1342
+ clearFontCacheInAABB(aabb) {
1343
+ const affectedModules = this.getModulesInAABB(this.layoutNodes, aabb);
1344
+ for (const module of affectedModules){
1345
+ this.caches.delete(module.node.id);
1346
+ }
1347
+ }
1348
+ getModulesInAABB(modules, aabb) {
1349
+ const result = [];
1350
+ for (const module of modules){
1351
+ const [x, y, w, h] = module.layout;
1352
+ const moduleAABB = {
1353
+ x,
1354
+ y,
1355
+ width: w,
1356
+ height: h
1357
+ };
1358
+ if (this.isAABBIntersecting(moduleAABB, aabb)) {
1359
+ result.push(module);
1360
+ if (module.children && module.children.length > 0) {
1361
+ result.push(...this.getModulesInAABB(module.children, aabb));
1362
+ }
1363
+ }
1364
+ }
1365
+ return result;
1366
+ }
1367
+ getViewportAABB(matrixE, matrixF) {
1368
+ const { width, height } = this.render.options;
1369
+ const viewportX = -matrixE;
1370
+ const viewportY = -matrixF;
1371
+ const viewportWidth = width;
1372
+ const viewportHeight = height;
1373
+ return {
1374
+ x: viewportX,
1375
+ y: viewportY,
1376
+ width: viewportWidth,
1377
+ height: viewportHeight
1378
+ };
1379
+ }
1380
+ getAABBUnion(a, b) {
1381
+ const minX = Math.min(a.x, b.x);
1382
+ const minY = Math.min(a.y, b.y);
1383
+ const maxX = Math.max(a.x + a.width, b.x + b.width);
1384
+ const maxY = Math.max(a.y + a.height, b.y + b.height);
1385
+ return {
1386
+ x: minX,
1387
+ y: minY,
1388
+ width: maxX - minX,
1389
+ height: maxY - minY
1390
+ };
1391
+ }
1392
+ handleTransformCacheInvalidation(oldMatrix, newMatrix) {
1393
+ const oldViewportAABB = this.getViewportAABB(oldMatrix.e, oldMatrix.f);
1394
+ const newViewportAABB = this.getViewportAABB(newMatrix.e, newMatrix.f);
1395
+ const affectedAABB = this.getAABBUnion(oldViewportAABB, newViewportAABB);
1396
+ this.clearFontCacheInAABB(affectedAABB);
1397
+ }
1398
+ isAABBIntersecting(a, b) {
1399
+ return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);
1400
+ }
1401
+ drawBroundRect(node) {
1402
+ const [x, y, w, h] = node.layout;
1403
+ const { rectRadius } = node.config;
1404
+ const effectiveRadius = Math.min(rectRadius, w / 4, h / 4);
1405
+ const fill = this.colorMappings[node.node.id] || DEFAULT_RECT_FILL_DESC;
1406
+ const rect = createRoundBlock(x, y, w, h, {
1407
+ fill,
1408
+ padding: 0,
1409
+ radius: effectiveRadius
1410
+ });
1411
+ this.rectLayer.add(rect);
1412
+ for (const child of node.children){
1413
+ this.drawBroundRect(child);
1414
+ }
1415
+ }
1416
+ drawText(node) {
1417
+ if (!node.node.label && !node.node.isCombinedNode) {
1418
+ return;
1419
+ }
1420
+ const [x, y, w, h] = node.layout;
1421
+ const { titleAreaHeight } = node.config;
1422
+ const content = node.node.isCombinedNode ? `+ ${node.node.originalNodeCount} Modules` : node.node.label;
1423
+ const availableHeight = node.children && node.children.length > 0 ? titleAreaHeight - DEFAULT_RECT_GAP * 2 : h - DEFAULT_RECT_GAP * 2;
1424
+ const availableWidth = w - DEFAULT_RECT_GAP * 2;
1425
+ if (availableWidth <= 0 || availableHeight <= 0) {
1426
+ return;
1427
+ }
1428
+ const config = {
1429
+ fontSize: this.config.font?.fontSize || DEFAULT_FONT_SIZE,
1430
+ family: this.config.font?.family || DEFAULT_FONT_FAMILY,
1431
+ color: this.config.font?.color || DEFAULT_FONT_COLOR
1432
+ };
1433
+ const optimalFontSize = this.caches.getOrInsert(node.node.id, evaluateOptimalFontSize(this.render.ctx, content, config, availableWidth, availableHeight));
1434
+ const font = `${optimalFontSize}px ${config.family}`;
1435
+ this.render.ctx.font = font;
1436
+ const result = getTextLayout(this.render.ctx, content, availableWidth, availableHeight);
1437
+ if (!result.valid) {
1438
+ return;
1439
+ }
1440
+ const { text } = result;
1441
+ const textX = x + Math.round(w / 2);
1442
+ const textY = y + (node.children && node.children.length > 0 ? Math.round(titleAreaHeight / 2) : Math.round(h / 2));
1443
+ const textComponent = createTitleText(text, textX, textY, font, config.color);
1444
+ this.textLayer.add(textComponent);
1445
+ for (const child of node.children){
1446
+ this.drawText(child);
1447
+ }
1448
+ }
1449
+ draw(flush = true, update = true) {
1450
+ // prepare data
1451
+ const { width, height } = this.render.options;
1452
+ if (update) {
1453
+ this.layoutNodes = this.calculateLayoutNodes(this.data, {
1454
+ w: width,
1455
+ h: height,
1456
+ x: 0,
1457
+ y: 0
1458
+ });
1459
+ }
1460
+ if (flush) {
1461
+ const result = this.pluginDriver.cascadeHook('onModuleInit', this.layoutNodes);
1462
+ if (result) {
1463
+ this.colorMappings = result.colorMappings || {};
1464
+ }
1465
+ }
1466
+ for (const node of this.layoutNodes){
1467
+ this.drawBroundRect(node);
1468
+ }
1469
+ for (const node of this.layoutNodes){
1470
+ this.drawText(node);
1471
+ }
1472
+ this.add(this.rectLayer, this.textLayer);
1473
+ if (update) {
1474
+ this.update();
1475
+ }
1476
+ }
1477
+ cleanup() {
1478
+ this.remove(this.rectLayer, this.textLayer);
1479
+ this.rectLayer.destory();
1480
+ this.textLayer.destory();
1481
+ }
1482
+ calculateLayoutNodes(data, rect, scale = 1) {
1483
+ const config = {
1484
+ titleAreaHeight: this.config.layout?.titleAreaHeight ?? DEFAULT_TITLE_AREA_HEIGHT,
1485
+ rectRadius: this.config.layout?.rectRadius ?? DEFAULT_RECT_BORDER_RADIUS,
1486
+ rectGap: this.config.layout?.rectGap ?? DEFAULT_RECT_GAP
1487
+ };
1488
+ const layoutNodes = squarify(data, rect, config, scale);
1489
+ const result = this.pluginDriver.cascadeHook('onLayoutCalculated', layoutNodes, rect, config);
1490
+ if (result && result.layoutNodes?.length) {
1491
+ return result.layoutNodes;
1492
+ }
1493
+ return layoutNodes;
1494
+ }
1495
+ }
1496
+ function evaluateOptimalFontSize(c, text, config, desiredW, desiredH) {
1497
+ desiredW = Math.floor(desiredW);
1498
+ desiredH = Math.floor(desiredH);
1499
+ const { fontSize, family } = config;
1500
+ let min = fontSize.min;
1501
+ let max = fontSize.max;
1502
+ while(max - min >= 1){
1503
+ const current = min + (max - min) / 2;
1504
+ c.font = `${current}px ${family}`;
1505
+ const textWidth = c.measureText(text).width;
1506
+ const metrics = c.measureText(text);
1507
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
1508
+ if (textWidth <= desiredW && textHeight <= desiredH) {
1509
+ min = current;
1510
+ } else {
1511
+ max = current;
1512
+ }
1513
+ }
1514
+ return Math.floor(min);
1515
+ }
1516
+ function getTextLayout(c, text, width, height) {
1517
+ const textWidth = c.measureText(text).width;
1518
+ const metrics = c.measureText(text);
1519
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
1520
+ if (textHeight > height) {
1521
+ return {
1522
+ valid: false,
1523
+ text: '',
1524
+ direction: 'horizontal',
1525
+ width: 0
1526
+ };
1527
+ }
1528
+ if (textWidth <= width) {
1529
+ return {
1530
+ valid: true,
1531
+ text,
1532
+ direction: 'horizontal',
1533
+ width: textWidth
1534
+ };
1535
+ }
1536
+ const ellipsisWidth = c.measureText('...').width;
1537
+ if (width <= ellipsisWidth) {
1538
+ return {
1539
+ valid: false,
1540
+ text: '',
1541
+ direction: 'horizontal',
1542
+ width: 0
1543
+ };
1544
+ }
1545
+ let left = 0;
1546
+ let right = text.length;
1547
+ let bestFit = '';
1548
+ while(left <= right){
1549
+ const mid = Math.floor((left + right) / 2);
1550
+ const substring = text.substring(0, mid);
1551
+ const subWidth = c.measureText(substring).width;
1552
+ if (subWidth + ellipsisWidth <= width) {
1553
+ bestFit = substring;
1554
+ left = mid + 1;
1555
+ } else {
1556
+ right = mid - 1;
1557
+ }
1558
+ }
1559
+ return bestFit.length > 0 ? {
1560
+ valid: true,
1561
+ text: bestFit + '...',
1562
+ direction: 'horizontal',
1563
+ width
1564
+ } : {
1565
+ valid: true,
1566
+ text: '...',
1567
+ direction: 'horizontal',
1568
+ width: ellipsisWidth
1569
+ };
1570
+ }
1571
+
1572
+ // I think those event is enough for user.
1573
+ const DOM_EVENTS = [
1574
+ 'click',
1575
+ 'mousedown',
1576
+ 'mousemove',
1577
+ 'mouseup',
1578
+ 'mouseover',
1579
+ 'mouseout',
1580
+ 'wheel',
1581
+ 'contextmenu'
1582
+ ];
1583
+ const STATE_TRANSITION = {
1584
+ IDLE: 'IDLE'};
1585
+ class StateManager {
1586
+ current;
1587
+ constructor(){
1588
+ this.current = STATE_TRANSITION.IDLE;
1589
+ }
1590
+ canTransition(to) {
1591
+ switch(this.current){
1592
+ case 'IDLE':
1593
+ return to === 'PRESSED' || to === 'MOVE' || to === 'SCALING' || to === 'ZOOMING' || to === 'PANNING';
1594
+ case 'PRESSED':
1595
+ return to === 'DRAGGING' || to === 'IDLE';
1596
+ case 'DRAGGING':
1597
+ return to === 'IDLE';
1598
+ case 'MOVE':
1599
+ return to === 'PRESSED' || to === 'IDLE';
1600
+ case 'SCALING':
1601
+ return to === 'IDLE';
1602
+ case 'ZOOMING':
1603
+ return to === 'IDLE';
1604
+ case 'PANNING':
1605
+ return to === 'IDLE';
1606
+ default:
1607
+ return false;
1608
+ }
1609
+ }
1610
+ transition(to) {
1611
+ const valid = this.canTransition(to);
1612
+ if (valid) {
1613
+ this.current = to;
1614
+ }
1615
+ return valid;
1616
+ }
1617
+ reset() {
1618
+ this.current = STATE_TRANSITION.IDLE;
1619
+ }
1620
+ isInState(state) {
1621
+ return this.current === state;
1622
+ }
1623
+ }
1624
+ function isWheelEvent(metadata) {
1625
+ return metadata.kind === 'wheel';
1626
+ }
1627
+ function isMouseEvent(metadata) {
1628
+ return [
1629
+ 'mousedown',
1630
+ 'mouseup',
1631
+ 'mousemove'
1632
+ ].includes(metadata.kind);
1633
+ }
1634
+ function isClickEvent(metadata) {
1635
+ return metadata.kind === 'click';
1636
+ }
1637
+ function isContextMenuEvent(metadata) {
1638
+ return metadata.kind === 'contextmenu';
1639
+ }
1640
+ function bindDOMEvent(el, evt, dom) {
1641
+ const handler = (e)=>{
1642
+ const data = {
1643
+ native: e
1644
+ };
1645
+ Object.defineProperty(data, 'kind', {
1646
+ value: evt,
1647
+ enumerable: true,
1648
+ configurable: false,
1649
+ writable: false
1650
+ });
1651
+ // @ts-expect-error safe operation
1652
+ dom.emit(evt, data);
1653
+ };
1654
+ el.addEventListener(evt, handler);
1655
+ return {
1656
+ evt,
1657
+ handler
1658
+ };
1659
+ }
1660
+ // We don't consider db click for us library
1661
+ // So the trigger step follows:
1662
+ // mousedown => mouseup => click
1663
+ // For menu click (downstream demand)
1664
+ class DOMEvent extends Event {
1665
+ domEvents;
1666
+ el;
1667
+ currentModule;
1668
+ component;
1669
+ matrix;
1670
+ stateManager;
1671
+ constructor(component){
1672
+ super();
1673
+ this.component = component;
1674
+ this.el = component.render.canvas;
1675
+ this.matrix = new Matrix2D();
1676
+ this.currentModule = null;
1677
+ this.stateManager = new StateManager();
1678
+ this.domEvents = DOM_EVENTS.map((evt)=>bindDOMEvent(this.el, evt, this));
1679
+ DOM_EVENTS.forEach((evt)=>{
1680
+ this.on(evt, (e)=>{
1681
+ this.dispatch(evt, e);
1682
+ });
1683
+ });
1684
+ }
1685
+ destory() {
1686
+ if (this.el) {
1687
+ this.domEvents.forEach(({ evt, handler })=>this.el?.removeEventListener(evt, handler));
1688
+ this.domEvents = [];
1689
+ for(const evt in this.eventCollections){
1690
+ this.off(evt);
1691
+ }
1692
+ this.matrix.create(DEFAULT_MATRIX_LOC);
1693
+ }
1694
+ }
1695
+ dispatch(kind, e) {
1696
+ const node = this.findRelativeNode(e);
1697
+ this.component.pluginDriver.runHook('onDOMEventTriggered', kind, e, node, this);
1698
+ this.emit('__exposed__', kind, {
1699
+ native: e.native,
1700
+ module: node
1701
+ });
1702
+ }
1703
+ findRelativeNode(e) {
1704
+ return findRelativeNode(captureBoxXY(this.el, e.native, 1, 1, this.matrix.e, this.matrix.f), this.component.layoutNodes);
1705
+ }
1706
+ }
1707
+
1708
+ exports.Component = Component;
1709
+ exports.DEFAULT_MATRIX_LOC = DEFAULT_MATRIX_LOC;
1710
+ exports.DOMEvent = DOMEvent;
1711
+ exports.DefaultMap = DefaultMap;
1712
+ exports.Event = Event;
1713
+ exports.PI_2 = PI_2;
1714
+ exports.Schedule = Schedule;
1715
+ exports.applyCanvasTransform = applyCanvasTransform;
1716
+ exports.assertExists = assertExists;
1717
+ exports.bindParentForModule = bindParentForModule;
1718
+ exports.c2m = c2m;
1719
+ exports.createCanvasElement = createCanvasElement;
1720
+ exports.createRoundBlock = createRoundBlock;
1721
+ exports.createTitleText = createTitleText;
1722
+ exports.definePlugin = definePlugin;
1723
+ exports.findRelativeNode = findRelativeNode;
1724
+ exports.findRelativeNodeById = findRelativeNodeById;
1725
+ exports.flatten = flatten;
1726
+ exports.getNodeDepth = getNodeDepth;
1727
+ exports.hashCode = hashCode;
1728
+ exports.isClickEvent = isClickEvent;
1729
+ exports.isContextMenuEvent = isContextMenuEvent;
1730
+ exports.isMouseEvent = isMouseEvent;
1731
+ exports.isScrollWheelOrRightButtonOnMouseupAndDown = isScrollWheelOrRightButtonOnMouseupAndDown;
1732
+ exports.isWheelEvent = isWheelEvent;
1733
+ exports.logger = logger;
1734
+ exports.mixin = mixin;
1735
+ exports.noop = noop;
1736
+ exports.perferNumeric = perferNumeric;
1737
+ exports.raf = raf;
1738
+ exports.smoothFrame = smoothFrame;
1739
+ exports.sortChildrenByKey = sortChildrenByKey;
1740
+ exports.stackMatrixTransform = stackMatrixTransform;
1741
+ exports.stackMatrixTransformWithGraphAndLayer = stackMatrixTransformWithGraphAndLayer;
1742
+ exports.typedForIn = typedForIn;
1743
+ exports.visit = visit;