squarified 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -10
- package/dist/{dom-event-Bkw3ecGf.mjs → dom-event-CKb3Ko4-.mjs} +151 -92
- package/dist/{dom-event-Dz0I7Z12.js → dom-event-CxIxc4YC.js} +150 -94
- package/dist/{index-Dskgz6nc.d.ts → index-Bb8ZzMCD.d.ts} +37 -38
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -8
- package/dist/index.mjs +4 -7
- package/dist/plugin.d.mts +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +47 -27
- package/dist/plugin.mjs +47 -27
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,25 +1,250 @@
|
|
|
1
1
|
# Squarified
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A minimal and powerful treemap visualization library for creating interactive hierarchical data visualizations.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Lightweight**: Minimal bundle size with maximum performance
|
|
8
|
+
- 🎨 **Customizable**: Rich theming and styling options
|
|
9
|
+
- 🔌 **Plugin System**: Extensible architecture with built-in plugins
|
|
10
|
+
- 📱 **Responsive**: Automatic resizing
|
|
11
|
+
- ⚡ **Interactive**: Built-in zoom, drag, highlight, and menu interactions
|
|
12
|
+
- 🎯 **TypeScript**: Full type safety and excellent DX
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
6
15
|
|
|
7
16
|
```shell
|
|
8
|
-
|
|
17
|
+
npm install squarified
|
|
18
|
+
# or
|
|
19
|
+
yarn add squarified
|
|
20
|
+
# or
|
|
21
|
+
pnpm add squarified
|
|
9
22
|
```
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
## Quick Start
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
```typescript
|
|
27
|
+
import { c2m, createTreemap, sortChildrenByKey } from 'squarified'
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
// Create a treemap instance
|
|
30
|
+
const treemap = createTreemap({
|
|
31
|
+
plugins: [
|
|
32
|
+
// Add built-in plugins for interactions
|
|
33
|
+
]
|
|
34
|
+
})
|
|
16
35
|
|
|
17
|
-
|
|
36
|
+
// Initialize with a DOM element
|
|
37
|
+
const container = document.querySelector('#treemap-container')
|
|
38
|
+
treemap.init(container)
|
|
39
|
+
|
|
40
|
+
// Set your data
|
|
41
|
+
treemap.setOptions({
|
|
42
|
+
data: [
|
|
43
|
+
{
|
|
44
|
+
id: 'root',
|
|
45
|
+
label: 'Root',
|
|
46
|
+
weight: 100,
|
|
47
|
+
groups: [
|
|
48
|
+
{ id: 'child1', label: 'Child 1', weight: 60 },
|
|
49
|
+
{ id: 'child2', label: 'Child 2', weight: 40 }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Complete Example
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import {
|
|
60
|
+
c2m,
|
|
61
|
+
createTreemap,
|
|
62
|
+
presetColorPlugin,
|
|
63
|
+
presetDragElementPlugin,
|
|
64
|
+
presetHighlightPlugin,
|
|
65
|
+
presetMenuPlugin,
|
|
66
|
+
presetScalePlugin,
|
|
67
|
+
presetZoomablePlugin,
|
|
68
|
+
sortChildrenByKey
|
|
69
|
+
} from 'squarified'
|
|
70
|
+
|
|
71
|
+
// Create treemap with plugins
|
|
72
|
+
const treemap = createTreemap({
|
|
73
|
+
plugins: [
|
|
74
|
+
presetColorPlugin,
|
|
75
|
+
presetZoomablePlugin,
|
|
76
|
+
presetHighlightPlugin,
|
|
77
|
+
presetDragElementPlugin,
|
|
78
|
+
presetScalePlugin(),
|
|
79
|
+
presetMenuPlugin({
|
|
80
|
+
style: {
|
|
81
|
+
borderRadius: '5px',
|
|
82
|
+
padding: '6px 12px',
|
|
83
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
84
|
+
color: 'white',
|
|
85
|
+
cursor: 'pointer',
|
|
86
|
+
userSelect: 'none'
|
|
87
|
+
},
|
|
88
|
+
render: () => [
|
|
89
|
+
{ html: '<span>🔍 Zoom In</span>', action: 'zoom' },
|
|
90
|
+
{ html: '<span>🔄 Reset</span>', action: 'reset' }
|
|
91
|
+
],
|
|
92
|
+
onClick(action, module) {
|
|
93
|
+
switch (action) {
|
|
94
|
+
case 'zoom':
|
|
95
|
+
if (module?.node.id) {
|
|
96
|
+
treemap.zoom(module.node.id)
|
|
97
|
+
}
|
|
98
|
+
break
|
|
99
|
+
case 'reset':
|
|
100
|
+
treemap.resize()
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Convert and prepare data
|
|
109
|
+
async function loadData() {
|
|
110
|
+
const response = await fetch('/api/data')
|
|
111
|
+
const rawData = await response.json()
|
|
112
|
+
|
|
113
|
+
// Transform data structure
|
|
114
|
+
const convertedData = rawData.map((item) => ({
|
|
115
|
+
...item,
|
|
116
|
+
groups: item.children?.map((child) => convertChildrenToGroups(child))
|
|
117
|
+
}))
|
|
118
|
+
|
|
119
|
+
// Convert to treemap format and sort
|
|
120
|
+
const treemapData = sortChildrenByKey(
|
|
121
|
+
convertedData.map((item) =>
|
|
122
|
+
c2m(item, 'value', (d) => ({
|
|
123
|
+
...d,
|
|
124
|
+
id: d.path,
|
|
125
|
+
label: d.name
|
|
126
|
+
}))
|
|
127
|
+
),
|
|
128
|
+
'weight'
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return treemapData
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Initialize
|
|
135
|
+
const container = document.querySelector('#app')
|
|
136
|
+
treemap.init(container)
|
|
137
|
+
|
|
138
|
+
// Load and set data
|
|
139
|
+
loadData().then((data) => {
|
|
140
|
+
treemap.setOptions({ data })
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Handle events
|
|
144
|
+
treemap.on('click', (event, module) => {
|
|
145
|
+
console.log('Clicked:', module?.node)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// Auto-resize on container changes
|
|
149
|
+
new ResizeObserver(() => {
|
|
150
|
+
treemap.resize()
|
|
151
|
+
}).observe(container)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## API Reference
|
|
155
|
+
|
|
156
|
+
### Creating a Treemap
|
|
157
|
+
|
|
158
|
+
#### `createTreemap(options?)`
|
|
159
|
+
|
|
160
|
+
Creates a new treemap instance.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
interface CreateTreemapOptions {
|
|
164
|
+
plugins?: Plugin[]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Instance Methods
|
|
169
|
+
|
|
170
|
+
#### `init(element: HTMLElement)`
|
|
171
|
+
|
|
172
|
+
Initialize the treemap with a DOM container.
|
|
173
|
+
|
|
174
|
+
#### `setOptions(options: TreemapOptions)`
|
|
18
175
|
|
|
19
|
-
|
|
176
|
+
Update treemap configuration and data.
|
|
20
177
|
|
|
21
|
-
|
|
178
|
+
#### `resize()`
|
|
22
179
|
|
|
23
|
-
|
|
180
|
+
Manually trigger a resize recalculation.
|
|
181
|
+
|
|
182
|
+
#### `on(event: string, handler: Function)`
|
|
183
|
+
|
|
184
|
+
Subscribe to treemap events.
|
|
185
|
+
|
|
186
|
+
#### `dispose()`
|
|
187
|
+
|
|
188
|
+
Clean up the treemap instance.
|
|
189
|
+
|
|
190
|
+
### Data Format
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
interface TreemapNode {
|
|
194
|
+
id: string
|
|
195
|
+
label: string
|
|
196
|
+
weight: number
|
|
197
|
+
groups?: TreemapNode[]
|
|
198
|
+
// Custom properties...
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Utility Functions
|
|
203
|
+
|
|
204
|
+
#### `c2m(data, weightKey, transform?)`
|
|
205
|
+
|
|
206
|
+
Convert hierarchical data to treemap format.
|
|
207
|
+
|
|
208
|
+
#### `sortChildrenByKey(data, key)`
|
|
209
|
+
|
|
210
|
+
Sort treemap nodes by a specific key.
|
|
211
|
+
|
|
212
|
+
## Built-in Plugins
|
|
213
|
+
|
|
214
|
+
- **presetColorPlugin**: Automatic color assignment
|
|
215
|
+
- **presetZoomablePlugin**: Zoom interactions
|
|
216
|
+
- **presetHighlightPlugin**: Hover highlighting
|
|
217
|
+
- **presetDragElementPlugin**: Drag interactions
|
|
218
|
+
- **presetScalePlugin**: Scaling controls
|
|
219
|
+
- **presetMenuPlugin**: Context menus
|
|
220
|
+
|
|
221
|
+
## Browser Support
|
|
222
|
+
|
|
223
|
+
- Chrome/Edge 80+
|
|
224
|
+
- Firefox 75+
|
|
225
|
+
- Safari 13+
|
|
226
|
+
|
|
227
|
+
## Performance
|
|
228
|
+
|
|
229
|
+
Squarified is optimized for performance with:
|
|
230
|
+
|
|
231
|
+
- Canvas-based rendering
|
|
232
|
+
- Efficient layout algorithms
|
|
233
|
+
- Smart redraw optimizations
|
|
234
|
+
- Memory-conscious design
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
Contributions are welcome! Please read our [contributing guide](CONTRIBUTING.md) for details.
|
|
239
|
+
|
|
240
|
+
## License
|
|
24
241
|
|
|
25
242
|
[MIT](./LICENSE)
|
|
243
|
+
|
|
244
|
+
## Auth
|
|
245
|
+
|
|
246
|
+
Kanno
|
|
247
|
+
|
|
248
|
+
## Credits
|
|
249
|
+
|
|
250
|
+
Algorithm ported from [esbuild Bundle Size Analyzer](https://esbuild.github.io/analyze/) by [Evan Wallace](https://github.com/evanw), refactored and optimized for general-purpose treemap visualization.
|
|
@@ -88,7 +88,6 @@ var DisplayType = /*#__PURE__*/ function(DisplayType) {
|
|
|
88
88
|
DisplayType["Box"] = "Box";
|
|
89
89
|
DisplayType["Text"] = "Text";
|
|
90
90
|
DisplayType["RoundRect"] = "RoundRect";
|
|
91
|
-
DisplayType["Bitmap"] = "Bitmap";
|
|
92
91
|
return DisplayType;
|
|
93
92
|
}({});
|
|
94
93
|
class Display {
|
|
@@ -291,12 +290,10 @@ class S extends Display {
|
|
|
291
290
|
class Graph extends S {
|
|
292
291
|
instruction;
|
|
293
292
|
__options__;
|
|
294
|
-
__widget__;
|
|
295
293
|
constructor(options = {}){
|
|
296
294
|
super(options);
|
|
297
295
|
this.instruction = createInstruction();
|
|
298
296
|
this.__options__ = options;
|
|
299
|
-
this.__widget__ = null;
|
|
300
297
|
}
|
|
301
298
|
render(ctx) {
|
|
302
299
|
this.create();
|
|
@@ -333,15 +330,11 @@ function isRoundRect(display) {
|
|
|
333
330
|
function isText(display) {
|
|
334
331
|
return isGraph(display) && display.__shape__ === DisplayType.Text;
|
|
335
332
|
}
|
|
336
|
-
function isBitmap(display) {
|
|
337
|
-
return isGraph(display) && display.__shape__ === DisplayType.Bitmap;
|
|
338
|
-
}
|
|
339
333
|
const asserts = {
|
|
340
334
|
isGraph,
|
|
341
335
|
isBox,
|
|
342
336
|
isText,
|
|
343
|
-
isRoundRect
|
|
344
|
-
isBitmap
|
|
337
|
+
isRoundRect
|
|
345
338
|
};
|
|
346
339
|
|
|
347
340
|
class C extends Display {
|
|
@@ -741,16 +734,6 @@ function applyCanvasTransform(ctx, matrix, dpr) {
|
|
|
741
734
|
ctx.setTransform(matrix.a * dpr, matrix.b * dpr, matrix.c * dpr, matrix.d * dpr, matrix.e * dpr, matrix.f * dpr);
|
|
742
735
|
}
|
|
743
736
|
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
737
|
methods.forEach(({ name, fn })=>{
|
|
755
738
|
Object.defineProperty(app, name, {
|
|
756
739
|
value: fn(app),
|
|
@@ -760,12 +743,6 @@ function mixinWithParams(app, methods) {
|
|
|
760
743
|
});
|
|
761
744
|
return app;
|
|
762
745
|
}
|
|
763
|
-
function prettyStrJoin(...s) {
|
|
764
|
-
return s.join('');
|
|
765
|
-
}
|
|
766
|
-
function isMacOS() {
|
|
767
|
-
return /Mac OS X/.test(navigator.userAgent);
|
|
768
|
-
}
|
|
769
746
|
function typedForIn(obj, callback) {
|
|
770
747
|
for(const key in obj){
|
|
771
748
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
@@ -1125,12 +1102,15 @@ function squarify(data, rect, config, scale = 1) {
|
|
|
1125
1102
|
if (!processedData.length) {
|
|
1126
1103
|
return result;
|
|
1127
1104
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1105
|
+
let workingRect = rect;
|
|
1106
|
+
if (scaledGap > 0) {
|
|
1107
|
+
workingRect = {
|
|
1108
|
+
x: rect.x + scaledGap / 2,
|
|
1109
|
+
y: rect.y + scaledGap / 2,
|
|
1110
|
+
w: Math.max(0, rect.w - scaledGap),
|
|
1111
|
+
h: Math.max(0, rect.h - scaledGap)
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1134
1114
|
const worst = (start, end, shortestSide, totalWeight, aspectRatio)=>{
|
|
1135
1115
|
const max = processedData[start].weight * aspectRatio;
|
|
1136
1116
|
const min = processedData[end].weight * aspectRatio;
|
|
@@ -1182,39 +1162,43 @@ function squarify(data, rect, config, scale = 1) {
|
|
|
1182
1162
|
w = upper - lower;
|
|
1183
1163
|
h = splited;
|
|
1184
1164
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
if (
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1165
|
+
if (currentGap > 0) {
|
|
1166
|
+
const edgeGap = currentGap / 2;
|
|
1167
|
+
if (!isFirst) {
|
|
1168
|
+
if (isHorizontalLayout) {
|
|
1169
|
+
y += edgeGap;
|
|
1170
|
+
h = Math.max(0, h - edgeGap);
|
|
1171
|
+
} else {
|
|
1172
|
+
x += edgeGap;
|
|
1173
|
+
w = Math.max(0, w - edgeGap);
|
|
1174
|
+
}
|
|
1193
1175
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1176
|
+
if (!isLast) {
|
|
1177
|
+
if (isHorizontalLayout) {
|
|
1178
|
+
h = Math.max(0, h - edgeGap);
|
|
1179
|
+
} else {
|
|
1180
|
+
w = Math.max(0, w - edgeGap);
|
|
1181
|
+
}
|
|
1200
1182
|
}
|
|
1201
1183
|
}
|
|
1202
1184
|
const nodeDepth = getNodeDepth(children) || 1;
|
|
1203
1185
|
const { titleAreaHeight } = config;
|
|
1204
1186
|
const diff = titleAreaHeight.max / nodeDepth;
|
|
1205
1187
|
const titleHeight = diff < titleAreaHeight.min ? titleAreaHeight.min : diff;
|
|
1206
|
-
w = Math.max(
|
|
1207
|
-
h = Math.max(
|
|
1188
|
+
w = Math.max(1, w);
|
|
1189
|
+
h = Math.max(1, h);
|
|
1208
1190
|
let childrenLayout = [];
|
|
1209
1191
|
const hasValidChildren = children.groups && children.groups.length > 0;
|
|
1210
1192
|
if (hasValidChildren) {
|
|
1193
|
+
const childGapOffset = currentGap > 0 ? currentGap : 0;
|
|
1211
1194
|
const childRect = {
|
|
1212
|
-
x: x +
|
|
1195
|
+
x: x + childGapOffset,
|
|
1213
1196
|
y: y + titleHeight,
|
|
1214
|
-
w: Math.max(0, w -
|
|
1215
|
-
h: Math.max(0, h - titleHeight -
|
|
1197
|
+
w: Math.max(0, w - childGapOffset * 2),
|
|
1198
|
+
h: Math.max(0, h - titleHeight - childGapOffset)
|
|
1216
1199
|
};
|
|
1217
|
-
|
|
1200
|
+
const minChildSize = currentRadius > 0 ? currentRadius * 2 : 1;
|
|
1201
|
+
if (childRect.w >= minChildSize && childRect.h >= minChildSize) {
|
|
1218
1202
|
childrenLayout = squarify(children.groups || [], childRect, {
|
|
1219
1203
|
...config,
|
|
1220
1204
|
rectGap: currentGap,
|
|
@@ -1240,16 +1224,17 @@ function squarify(data, rect, config, scale = 1) {
|
|
|
1240
1224
|
areaInLayout += area;
|
|
1241
1225
|
}
|
|
1242
1226
|
start = end;
|
|
1227
|
+
const rectGapOffset = currentGap > 0 ? currentGap : 0;
|
|
1243
1228
|
if (isHorizontalLayout) {
|
|
1244
|
-
rect.x += splited +
|
|
1245
|
-
rect.w
|
|
1229
|
+
rect.x += splited + rectGapOffset;
|
|
1230
|
+
rect.w = Math.max(0, rect.w - splited - rectGapOffset);
|
|
1246
1231
|
} else {
|
|
1247
|
-
rect.y += splited +
|
|
1248
|
-
rect.h
|
|
1232
|
+
rect.y += splited + rectGapOffset;
|
|
1233
|
+
rect.h = Math.max(0, rect.h - splited - rectGapOffset);
|
|
1249
1234
|
}
|
|
1250
1235
|
}
|
|
1251
1236
|
};
|
|
1252
|
-
recursion(0,
|
|
1237
|
+
recursion(0, workingRect);
|
|
1253
1238
|
return result;
|
|
1254
1239
|
}
|
|
1255
1240
|
|
|
@@ -1296,6 +1281,7 @@ class PluginDriver {
|
|
|
1296
1281
|
this.plugins.forEach((plugin)=>{
|
|
1297
1282
|
const hook = plugin[hookName];
|
|
1298
1283
|
if (hook) {
|
|
1284
|
+
// @ts-expect-error fixme
|
|
1299
1285
|
const hookResult = hook.call(this.pluginContext, ...args);
|
|
1300
1286
|
if (hookResult) {
|
|
1301
1287
|
Object.assign(finalResult, hookResult);
|
|
@@ -1351,6 +1337,65 @@ class Component extends Schedule {
|
|
|
1351
1337
|
this.caches = new DefaultMap(()=>14);
|
|
1352
1338
|
this.layoutNodes = [];
|
|
1353
1339
|
}
|
|
1340
|
+
clearFontCacheInAABB(aabb) {
|
|
1341
|
+
const affectedModules = this.getModulesInAABB(this.layoutNodes, aabb);
|
|
1342
|
+
for (const module of affectedModules){
|
|
1343
|
+
this.caches.delete(module.node.id);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
getModulesInAABB(modules, aabb) {
|
|
1347
|
+
const result = [];
|
|
1348
|
+
for (const module of modules){
|
|
1349
|
+
const [x, y, w, h] = module.layout;
|
|
1350
|
+
const moduleAABB = {
|
|
1351
|
+
x,
|
|
1352
|
+
y,
|
|
1353
|
+
width: w,
|
|
1354
|
+
height: h
|
|
1355
|
+
};
|
|
1356
|
+
if (this.isAABBIntersecting(moduleAABB, aabb)) {
|
|
1357
|
+
result.push(module);
|
|
1358
|
+
if (module.children && module.children.length > 0) {
|
|
1359
|
+
result.push(...this.getModulesInAABB(module.children, aabb));
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return result;
|
|
1364
|
+
}
|
|
1365
|
+
getViewportAABB(matrixE, matrixF, scale) {
|
|
1366
|
+
const { width, height } = this.render.options;
|
|
1367
|
+
const worldX = -matrixE / scale;
|
|
1368
|
+
const worldY = -matrixF / scale;
|
|
1369
|
+
const worldWidth = width / scale;
|
|
1370
|
+
const worldHeight = height / scale;
|
|
1371
|
+
return {
|
|
1372
|
+
x: worldX,
|
|
1373
|
+
y: worldY,
|
|
1374
|
+
width: worldWidth,
|
|
1375
|
+
height: worldHeight
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
getAABBUnion(a, b) {
|
|
1379
|
+
const minX = Math.min(a.x, b.x);
|
|
1380
|
+
const minY = Math.min(a.y, b.y);
|
|
1381
|
+
const maxX = Math.max(a.x + a.width, b.x + b.width);
|
|
1382
|
+
const maxY = Math.max(a.y + a.height, b.y + b.height);
|
|
1383
|
+
return {
|
|
1384
|
+
x: minX,
|
|
1385
|
+
y: minY,
|
|
1386
|
+
width: maxX - minX,
|
|
1387
|
+
height: maxY - minY
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
handleTransformCacheInvalidation(oldMatrix, newMatrix) {
|
|
1391
|
+
const oldViewportAABB = this.getViewportAABB(oldMatrix.e, oldMatrix.f, oldMatrix.a);
|
|
1392
|
+
const newViewportAABB = this.getViewportAABB(newMatrix.e, newMatrix.f, newMatrix.a);
|
|
1393
|
+
const affectedAABB = this.getAABBUnion(oldViewportAABB, newViewportAABB);
|
|
1394
|
+
this.clearFontCacheInAABB(affectedAABB);
|
|
1395
|
+
}
|
|
1396
|
+
isAABBIntersecting(a, b) {
|
|
1397
|
+
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);
|
|
1398
|
+
}
|
|
1354
1399
|
drawBroundRect(node) {
|
|
1355
1400
|
const [x, y, w, h] = node.layout;
|
|
1356
1401
|
const { rectRadius } = node.config;
|
|
@@ -1361,7 +1406,6 @@ class Component extends Schedule {
|
|
|
1361
1406
|
padding: 0,
|
|
1362
1407
|
radius: effectiveRadius
|
|
1363
1408
|
});
|
|
1364
|
-
rect.__widget__ = node;
|
|
1365
1409
|
this.rectLayer.add(rect);
|
|
1366
1410
|
for (const child of node.children){
|
|
1367
1411
|
this.drawBroundRect(child);
|
|
@@ -1435,11 +1479,16 @@ class Component extends Schedule {
|
|
|
1435
1479
|
}
|
|
1436
1480
|
calculateLayoutNodes(data, rect, scale = 1) {
|
|
1437
1481
|
const config = {
|
|
1438
|
-
titleAreaHeight: this.config.layout?.titleAreaHeight
|
|
1439
|
-
rectRadius: this.config.layout?.rectRadius
|
|
1440
|
-
rectGap: this.config.layout?.rectGap
|
|
1482
|
+
titleAreaHeight: this.config.layout?.titleAreaHeight ?? DEFAULT_TITLE_AREA_HEIGHT,
|
|
1483
|
+
rectRadius: this.config.layout?.rectRadius ?? DEFAULT_RECT_BORDER_RADIUS,
|
|
1484
|
+
rectGap: this.config.layout?.rectGap ?? DEFAULT_RECT_GAP
|
|
1441
1485
|
};
|
|
1442
|
-
|
|
1486
|
+
const layoutNodes = squarify(data, rect, config, scale);
|
|
1487
|
+
const result = this.pluginDriver.cascadeHook('onLayoutCalculated', layoutNodes, rect, config);
|
|
1488
|
+
if (result && result.layoutNodes?.length) {
|
|
1489
|
+
return result.layoutNodes;
|
|
1490
|
+
}
|
|
1491
|
+
return layoutNodes;
|
|
1443
1492
|
}
|
|
1444
1493
|
}
|
|
1445
1494
|
function evaluateOptimalFontSize(c, text, config, desiredW, desiredH) {
|
|
@@ -1448,39 +1497,25 @@ function evaluateOptimalFontSize(c, text, config, desiredW, desiredH) {
|
|
|
1448
1497
|
const { fontSize, family } = config;
|
|
1449
1498
|
let min = fontSize.min;
|
|
1450
1499
|
let max = fontSize.max;
|
|
1451
|
-
const cache = new Map();
|
|
1452
1500
|
while(max - min >= 1){
|
|
1453
1501
|
const current = min + (max - min) / 2;
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
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 {
|
|
1502
|
+
c.font = `${current}px ${family}`;
|
|
1503
|
+
const textWidth = c.measureText(text).width;
|
|
1504
|
+
const metrics = c.measureText(text);
|
|
1505
|
+
const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
1506
|
+
if (textWidth <= desiredW && textHeight <= desiredH) {
|
|
1468
1507
|
min = current;
|
|
1508
|
+
} else {
|
|
1509
|
+
max = current;
|
|
1469
1510
|
}
|
|
1470
1511
|
}
|
|
1471
1512
|
return Math.floor(min);
|
|
1472
1513
|
}
|
|
1473
1514
|
function getTextLayout(c, text, width, height) {
|
|
1474
|
-
const
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
valid: true,
|
|
1479
|
-
text: '...',
|
|
1480
|
-
direction: 'vertical',
|
|
1481
|
-
width: ellipsisWidth / 3
|
|
1482
|
-
};
|
|
1483
|
-
}
|
|
1515
|
+
const textWidth = c.measureText(text).width;
|
|
1516
|
+
const metrics = c.measureText(text);
|
|
1517
|
+
const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
1518
|
+
if (textHeight > height) {
|
|
1484
1519
|
return {
|
|
1485
1520
|
valid: false,
|
|
1486
1521
|
text: '',
|
|
@@ -1488,8 +1523,7 @@ function getTextLayout(c, text, width, height) {
|
|
|
1488
1523
|
width: 0
|
|
1489
1524
|
};
|
|
1490
1525
|
}
|
|
1491
|
-
|
|
1492
|
-
if (textWidth < width) {
|
|
1526
|
+
if (textWidth <= width) {
|
|
1493
1527
|
return {
|
|
1494
1528
|
valid: true,
|
|
1495
1529
|
text,
|
|
@@ -1497,16 +1531,41 @@ function getTextLayout(c, text, width, height) {
|
|
|
1497
1531
|
width: textWidth
|
|
1498
1532
|
};
|
|
1499
1533
|
}
|
|
1500
|
-
|
|
1534
|
+
const ellipsisWidth = c.measureText('...').width;
|
|
1535
|
+
if (width <= ellipsisWidth) {
|
|
1536
|
+
return {
|
|
1537
|
+
valid: false,
|
|
1538
|
+
text: '',
|
|
1539
|
+
direction: 'horizontal',
|
|
1540
|
+
width: 0
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
let left = 0;
|
|
1544
|
+
let right = text.length;
|
|
1545
|
+
let bestFit = '';
|
|
1546
|
+
while(left <= right){
|
|
1547
|
+
const mid = Math.floor((left + right) / 2);
|
|
1548
|
+
const substring = text.substring(0, mid);
|
|
1549
|
+
const subWidth = c.measureText(substring).width;
|
|
1550
|
+
if (subWidth + ellipsisWidth <= width) {
|
|
1551
|
+
bestFit = substring;
|
|
1552
|
+
left = mid + 1;
|
|
1553
|
+
} else {
|
|
1554
|
+
right = mid - 1;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return bestFit.length > 0 ? {
|
|
1558
|
+
valid: true,
|
|
1559
|
+
text: bestFit + '...',
|
|
1560
|
+
direction: 'horizontal',
|
|
1561
|
+
width
|
|
1562
|
+
} : {
|
|
1501
1563
|
valid: true,
|
|
1502
1564
|
text: '...',
|
|
1503
1565
|
direction: 'horizontal',
|
|
1504
1566
|
width: ellipsisWidth
|
|
1505
1567
|
};
|
|
1506
1568
|
}
|
|
1507
|
-
function measureTextWidth(c, text) {
|
|
1508
|
-
return c.measureText(text).width;
|
|
1509
|
-
}
|
|
1510
1569
|
|
|
1511
1570
|
// I think those event is enough for user.
|
|
1512
1571
|
const DOM_EVENTS = [
|
|
@@ -1645,4 +1704,4 @@ class DOMEvent extends Event {
|
|
|
1645
1704
|
}
|
|
1646
1705
|
}
|
|
1647
1706
|
|
|
1648
|
-
export {
|
|
1707
|
+
export { stackMatrixTransformWithGraphAndLayer as A, smoothFrame as B, Component as C, DOMEvent as D, Event as E, isScrollWheelOrRightButtonOnMouseupAndDown as F, DefaultMap as G, DEFAULT_MATRIX_LOC as H, 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, isWheelEvent as n, hashCode as o, perferNumeric as p, noop as q, createRoundBlock as r, sortChildrenByKey as s, createTitleText as t, raf as u, visit as v, createCanvasElement as w, applyCanvasTransform as x, typedForIn as y, stackMatrixTransform as z };
|