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