mintwaterfall 0.8.6
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/CHANGELOG.md +223 -0
- package/CONTRIBUTING.md +199 -0
- package/README.md +363 -0
- package/dist/index.d.ts +149 -0
- package/dist/mintwaterfall.cjs.js +7978 -0
- package/dist/mintwaterfall.esm.js +7907 -0
- package/dist/mintwaterfall.min.js +7 -0
- package/dist/mintwaterfall.umd.js +7978 -0
- package/index.d.ts +149 -0
- package/package.json +126 -0
- package/src/enterprise/enterprise-core.js +0 -0
- package/src/enterprise/enterprise-feature-template.js +0 -0
- package/src/enterprise/feature-registry.js +0 -0
- package/src/enterprise/features/breakdown.js +0 -0
- package/src/features/breakdown.js +0 -0
- package/src/features/conditional-formatting.js +0 -0
- package/src/index.js +111 -0
- package/src/mintwaterfall-accessibility.ts +680 -0
- package/src/mintwaterfall-advanced-data.ts +1034 -0
- package/src/mintwaterfall-advanced-interactions.ts +649 -0
- package/src/mintwaterfall-advanced-performance.ts +582 -0
- package/src/mintwaterfall-animations.ts +595 -0
- package/src/mintwaterfall-brush.ts +471 -0
- package/src/mintwaterfall-chart-core.ts +296 -0
- package/src/mintwaterfall-chart.ts +1915 -0
- package/src/mintwaterfall-data.ts +1100 -0
- package/src/mintwaterfall-export.ts +475 -0
- package/src/mintwaterfall-hierarchical-layouts.ts +724 -0
- package/src/mintwaterfall-layouts.ts +647 -0
- package/src/mintwaterfall-performance.ts +573 -0
- package/src/mintwaterfall-scales.ts +437 -0
- package/src/mintwaterfall-shapes.ts +385 -0
- package/src/mintwaterfall-statistics.ts +821 -0
- package/src/mintwaterfall-themes.ts +391 -0
- package/src/mintwaterfall-tooltip.ts +450 -0
- package/src/mintwaterfall-zoom.ts +399 -0
- package/src/types/js-modules.d.ts +25 -0
- package/src/utils/compatibility-layer.js +0 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
// MintWaterfall - D3.js compatible hierarchical layout system - TypeScript Version
|
|
2
|
+
// Implements d3.hierarchy, d3.treemap, d3.partition, and other layout algorithms with full type safety
|
|
3
|
+
|
|
4
|
+
import * as d3 from 'd3';
|
|
5
|
+
|
|
6
|
+
// Type definitions for hierarchical layout system
|
|
7
|
+
export interface HierarchicalData {
|
|
8
|
+
id?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
value?: number;
|
|
11
|
+
data?: any;
|
|
12
|
+
children?: HierarchicalData[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LayoutOptions {
|
|
16
|
+
size?: [number, number];
|
|
17
|
+
padding?: number;
|
|
18
|
+
round?: boolean;
|
|
19
|
+
type?: LayoutType;
|
|
20
|
+
paddingInner?: number;
|
|
21
|
+
paddingOuter?: number;
|
|
22
|
+
paddingTop?: number;
|
|
23
|
+
paddingRight?: number;
|
|
24
|
+
paddingBottom?: number;
|
|
25
|
+
paddingLeft?: number;
|
|
26
|
+
ratio?: number;
|
|
27
|
+
orientation?: "horizontal" | "vertical";
|
|
28
|
+
nodeSize?: [number, number] | null;
|
|
29
|
+
separation?: ((a: d3.HierarchyNode<any>, b: d3.HierarchyNode<any>) => number) | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface HierarchyOptions {
|
|
33
|
+
idAccessor?: (d: any) => string;
|
|
34
|
+
parentAccessor?: (d: any) => string | null;
|
|
35
|
+
valueAccessor?: (d: any) => number;
|
|
36
|
+
sort?: (a: d3.HierarchyNode<any>, b: d3.HierarchyNode<any>) => number;
|
|
37
|
+
includeRoot?: boolean;
|
|
38
|
+
includeInternal?: boolean;
|
|
39
|
+
maxDepth?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ExtractionOptions {
|
|
43
|
+
includeRoot?: boolean;
|
|
44
|
+
includeInternal?: boolean;
|
|
45
|
+
maxDepth?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ConversionOptions extends ExtractionOptions {
|
|
49
|
+
colorScale?: any;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ExtractedNode {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
value: number;
|
|
56
|
+
depth: number;
|
|
57
|
+
height: number;
|
|
58
|
+
parent: string | null;
|
|
59
|
+
x0: number;
|
|
60
|
+
y0: number;
|
|
61
|
+
x1: number;
|
|
62
|
+
y1: number;
|
|
63
|
+
r?: number;
|
|
64
|
+
originalData: any;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface WaterfallStackItem {
|
|
68
|
+
value: number;
|
|
69
|
+
color: string;
|
|
70
|
+
label: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface WaterfallFormatNode {
|
|
74
|
+
label: string;
|
|
75
|
+
stacks: WaterfallStackItem[];
|
|
76
|
+
hierarchyData: {
|
|
77
|
+
id: string;
|
|
78
|
+
depth: number;
|
|
79
|
+
height: number;
|
|
80
|
+
parent: string | null;
|
|
81
|
+
x0: number;
|
|
82
|
+
y0: number;
|
|
83
|
+
x1: number;
|
|
84
|
+
y1: number;
|
|
85
|
+
r?: number;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type LayoutType = "treemap" | "partition" | "pack" | "cluster" | "tree";
|
|
90
|
+
|
|
91
|
+
export interface PartitionOptions {
|
|
92
|
+
orientation: "horizontal" | "vertical";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface TreeOptions {
|
|
96
|
+
nodeSize: [number, number] | null;
|
|
97
|
+
separation: ((a: d3.HierarchyNode<any>, b: d3.HierarchyNode<any>) => number) | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Extended interfaces for D3 hierarchy nodes with layout properties
|
|
101
|
+
export interface LayoutNode extends d3.HierarchyNode<any> {
|
|
102
|
+
x0?: number;
|
|
103
|
+
y0?: number;
|
|
104
|
+
x1?: number;
|
|
105
|
+
y1?: number;
|
|
106
|
+
r?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface HierarchicalLayout {
|
|
110
|
+
(data: d3.HierarchyNode<any>): LayoutNode;
|
|
111
|
+
size(): [number, number];
|
|
112
|
+
size(size: [number, number]): HierarchicalLayout;
|
|
113
|
+
padding(): number;
|
|
114
|
+
padding(padding: number): HierarchicalLayout;
|
|
115
|
+
paddingInner(): number;
|
|
116
|
+
paddingInner(padding: number): HierarchicalLayout;
|
|
117
|
+
paddingOuter(): number;
|
|
118
|
+
paddingOuter(padding: number): HierarchicalLayout;
|
|
119
|
+
paddingTop(): number;
|
|
120
|
+
paddingTop(padding: number): HierarchicalLayout;
|
|
121
|
+
paddingRight(): number;
|
|
122
|
+
paddingRight(padding: number): HierarchicalLayout;
|
|
123
|
+
paddingBottom(): number;
|
|
124
|
+
paddingBottom(padding: number): HierarchicalLayout;
|
|
125
|
+
paddingLeft(): number;
|
|
126
|
+
paddingLeft(padding: number): HierarchicalLayout;
|
|
127
|
+
round(): boolean;
|
|
128
|
+
round(round: boolean): HierarchicalLayout;
|
|
129
|
+
ratio(): number;
|
|
130
|
+
ratio(ratio: number): HierarchicalLayout;
|
|
131
|
+
type(): LayoutType;
|
|
132
|
+
type(type: LayoutType): HierarchicalLayout;
|
|
133
|
+
partitionOrientation(): "horizontal" | "vertical";
|
|
134
|
+
partitionOrientation(orientation: "horizontal" | "vertical"): HierarchicalLayout;
|
|
135
|
+
nodeSize(): [number, number] | null;
|
|
136
|
+
nodeSize(size: [number, number] | null): HierarchicalLayout;
|
|
137
|
+
separation(): ((a: d3.HierarchyNode<any>, b: d3.HierarchyNode<any>) => number) | null;
|
|
138
|
+
separation(separation: ((a: d3.HierarchyNode<any>, b: d3.HierarchyNode<any>) => number) | null): HierarchicalLayout;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface HierarchyLayouts {
|
|
142
|
+
treemap(data: HierarchicalData | any[], options?: LayoutOptions & HierarchyOptions): LayoutNode;
|
|
143
|
+
partition(data: HierarchicalData | any[], options?: LayoutOptions & HierarchyOptions): LayoutNode;
|
|
144
|
+
pack(data: HierarchicalData | any[], options?: LayoutOptions & HierarchyOptions): LayoutNode;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Creates a hierarchical layout system for advanced data visualization
|
|
149
|
+
* @returns {HierarchicalLayout} Layout system API
|
|
150
|
+
*/
|
|
151
|
+
export function createHierarchicalLayout(): HierarchicalLayout {
|
|
152
|
+
let size: [number, number] = [800, 400];
|
|
153
|
+
let padding = 0;
|
|
154
|
+
let round = false;
|
|
155
|
+
let layoutType: LayoutType = "treemap";
|
|
156
|
+
let paddingInner = 0;
|
|
157
|
+
let paddingOuter = 0;
|
|
158
|
+
let paddingTop = 0;
|
|
159
|
+
let paddingRight = 0;
|
|
160
|
+
let paddingBottom = 0;
|
|
161
|
+
let paddingLeft = 0;
|
|
162
|
+
let ratio = 1.618033988749895; // Golden ratio by default
|
|
163
|
+
|
|
164
|
+
// Additional layout-specific options
|
|
165
|
+
let partitionOptions: PartitionOptions = {
|
|
166
|
+
orientation: "horizontal"
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
let treeOptions: TreeOptions = {
|
|
170
|
+
nodeSize: null,
|
|
171
|
+
separation: null
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Main layout function that processes hierarchical data
|
|
176
|
+
* @param {d3.HierarchyNode<any>} data - d3.hierarchy compatible data
|
|
177
|
+
* @returns {LayoutNode} Processed layout data
|
|
178
|
+
*/
|
|
179
|
+
function layout(data: d3.HierarchyNode<any>): LayoutNode {
|
|
180
|
+
if (!data) {
|
|
181
|
+
console.error("MintWaterfall: No hierarchical data provided to layout");
|
|
182
|
+
throw new Error("No hierarchical data provided");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Ensure we have a d3.hierarchy object with proper sum calculation
|
|
186
|
+
const root = data;
|
|
187
|
+
|
|
188
|
+
// Apply the selected layout
|
|
189
|
+
switch (layoutType) {
|
|
190
|
+
case "treemap":
|
|
191
|
+
return applyTreemapLayout(root);
|
|
192
|
+
case "partition":
|
|
193
|
+
return applyPartitionLayout(root);
|
|
194
|
+
case "pack":
|
|
195
|
+
return applyPackLayout(root);
|
|
196
|
+
case "cluster":
|
|
197
|
+
return applyClusterLayout(root);
|
|
198
|
+
case "tree":
|
|
199
|
+
return applyTreeLayout(root);
|
|
200
|
+
default:
|
|
201
|
+
console.warn(`MintWaterfall: Unknown layout type '${layoutType}', falling back to treemap`);
|
|
202
|
+
return applyTreemapLayout(root);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Applies treemap layout to hierarchical data
|
|
208
|
+
* @param {d3.HierarchyNode<any>} root - d3.hierarchy data
|
|
209
|
+
* @returns {d3.HierarchyNode<any>} Processed layout data
|
|
210
|
+
*/
|
|
211
|
+
function applyTreemapLayout(root: d3.HierarchyNode<any>): LayoutNode {
|
|
212
|
+
const treemap = d3.treemap()
|
|
213
|
+
.size(size)
|
|
214
|
+
.round(round)
|
|
215
|
+
.padding(padding);
|
|
216
|
+
|
|
217
|
+
// Apply additional padding options if they exist
|
|
218
|
+
if (paddingInner !== undefined) treemap.paddingInner(paddingInner);
|
|
219
|
+
if (paddingOuter !== undefined) treemap.paddingOuter(paddingOuter);
|
|
220
|
+
if (paddingTop !== undefined) treemap.paddingTop(paddingTop);
|
|
221
|
+
if (paddingRight !== undefined) treemap.paddingRight(paddingRight);
|
|
222
|
+
if (paddingBottom !== undefined) treemap.paddingBottom(paddingBottom);
|
|
223
|
+
if (paddingLeft !== undefined) treemap.paddingLeft(paddingLeft);
|
|
224
|
+
|
|
225
|
+
return treemap(root) as LayoutNode;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Applies partition layout to hierarchical data
|
|
230
|
+
* @param {d3.HierarchyNode<any>} root - d3.hierarchy data
|
|
231
|
+
* @returns {LayoutNode} Processed layout data
|
|
232
|
+
*/
|
|
233
|
+
function applyPartitionLayout(root: d3.HierarchyNode<any>): LayoutNode {
|
|
234
|
+
const partitionLayout = d3.partition()
|
|
235
|
+
.size(size)
|
|
236
|
+
.round(round)
|
|
237
|
+
.padding(padding);
|
|
238
|
+
|
|
239
|
+
// Apply partition layout
|
|
240
|
+
const result = partitionLayout(root) as LayoutNode;
|
|
241
|
+
|
|
242
|
+
// Handle orientation for partition layout
|
|
243
|
+
if (partitionOptions.orientation === "vertical") {
|
|
244
|
+
// Swap x/y coordinates and dimensions for vertical orientation
|
|
245
|
+
result.each((node: LayoutNode) => {
|
|
246
|
+
if (node.x0 !== undefined && node.y0 !== undefined &&
|
|
247
|
+
node.x1 !== undefined && node.y1 !== undefined) {
|
|
248
|
+
const temp = node.x0;
|
|
249
|
+
node.x0 = node.y0;
|
|
250
|
+
node.y0 = temp;
|
|
251
|
+
|
|
252
|
+
const tempX1 = node.x1;
|
|
253
|
+
node.x1 = node.y1;
|
|
254
|
+
node.y1 = tempX1;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Applies pack layout to hierarchical data
|
|
264
|
+
* @param {d3.HierarchyNode<any>} root - d3.hierarchy data
|
|
265
|
+
* @returns {LayoutNode} Processed layout data
|
|
266
|
+
*/
|
|
267
|
+
function applyPackLayout(root: d3.HierarchyNode<any>): LayoutNode {
|
|
268
|
+
return d3.pack()
|
|
269
|
+
.size(size)
|
|
270
|
+
.padding(padding)
|
|
271
|
+
(root) as LayoutNode;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Applies cluster layout to hierarchical data
|
|
276
|
+
* @param {d3.HierarchyNode<any>} root - d3.hierarchy data
|
|
277
|
+
* @returns {LayoutNode} Processed layout data
|
|
278
|
+
*/
|
|
279
|
+
function applyClusterLayout(root: d3.HierarchyNode<any>): LayoutNode {
|
|
280
|
+
const clusterLayout = d3.cluster()
|
|
281
|
+
.size(size);
|
|
282
|
+
|
|
283
|
+
if (treeOptions.nodeSize) {
|
|
284
|
+
clusterLayout.nodeSize(treeOptions.nodeSize);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (treeOptions.separation) {
|
|
288
|
+
clusterLayout.separation(treeOptions.separation);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return clusterLayout(root) as LayoutNode;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Applies tree layout to hierarchical data
|
|
296
|
+
* @param {d3.HierarchyNode<any>} root - d3.hierarchy data
|
|
297
|
+
* @returns {LayoutNode} Processed layout data
|
|
298
|
+
*/
|
|
299
|
+
function applyTreeLayout(root: d3.HierarchyNode<any>): LayoutNode {
|
|
300
|
+
const treeLayout = d3.tree()
|
|
301
|
+
.size(size);
|
|
302
|
+
|
|
303
|
+
if (treeOptions.nodeSize) {
|
|
304
|
+
treeLayout.nodeSize(treeOptions.nodeSize);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (treeOptions.separation) {
|
|
308
|
+
treeLayout.separation(treeOptions.separation);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return treeLayout(root) as LayoutNode;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Create layout object with simple object assignment
|
|
315
|
+
const hierarchicalLayout = layout as any;
|
|
316
|
+
|
|
317
|
+
// Add chainable methods
|
|
318
|
+
hierarchicalLayout.size = function(_?: [number, number]) {
|
|
319
|
+
return arguments.length ? (size = _!, hierarchicalLayout) : size;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
hierarchicalLayout.padding = function(_?: number) {
|
|
323
|
+
return arguments.length ? (padding = _!, hierarchicalLayout) : padding;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
hierarchicalLayout.paddingInner = function(_?: number) {
|
|
327
|
+
return arguments.length ? (paddingInner = _!, hierarchicalLayout) : paddingInner;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
hierarchicalLayout.paddingOuter = function(_?: number) {
|
|
331
|
+
return arguments.length ? (paddingOuter = _!, hierarchicalLayout) : paddingOuter;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
hierarchicalLayout.paddingTop = function(_?: number) {
|
|
335
|
+
return arguments.length ? (paddingTop = _!, hierarchicalLayout) : paddingTop;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
hierarchicalLayout.paddingRight = function(_?: number) {
|
|
339
|
+
return arguments.length ? (paddingRight = _!, hierarchicalLayout) : paddingRight;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
hierarchicalLayout.paddingBottom = function(_?: number) {
|
|
343
|
+
return arguments.length ? (paddingBottom = _!, hierarchicalLayout) : paddingBottom;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
hierarchicalLayout.paddingLeft = function(_?: number) {
|
|
347
|
+
return arguments.length ? (paddingLeft = _!, hierarchicalLayout) : paddingLeft;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
hierarchicalLayout.round = function(_?: boolean) {
|
|
351
|
+
return arguments.length ? (round = _!, hierarchicalLayout) : round;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
hierarchicalLayout.ratio = function(_?: number) {
|
|
355
|
+
return arguments.length ? (ratio = _!, hierarchicalLayout) : ratio;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
hierarchicalLayout.type = function(_?: LayoutType) {
|
|
359
|
+
return arguments.length ? (layoutType = _!, hierarchicalLayout) : layoutType;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
hierarchicalLayout.partitionOrientation = function(_?: "horizontal" | "vertical") {
|
|
363
|
+
return arguments.length ? (partitionOptions.orientation = _!, hierarchicalLayout) : partitionOptions.orientation;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
hierarchicalLayout.nodeSize = function(_?: [number, number] | null) {
|
|
367
|
+
return arguments.length ? (treeOptions.nodeSize = _!, hierarchicalLayout) : treeOptions.nodeSize;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
hierarchicalLayout.separation = function(_?: ((a: d3.HierarchyNode<any>, b: d3.HierarchyNode<any>) => number) | null) {
|
|
371
|
+
return arguments.length ? (treeOptions.separation = _!, hierarchicalLayout) : treeOptions.separation;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
return hierarchicalLayout as HierarchicalLayout;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Helper function to create hierarchical data structure from flat data
|
|
379
|
+
* @param {any[]} data - Flat data array
|
|
380
|
+
* @param {(d: any) => string} idAccessor - Function to get node ID
|
|
381
|
+
* @param {(d: any) => string | null} parentAccessor - Function to get parent ID
|
|
382
|
+
* @param {(d: any) => number} valueAccessor - Function to get node value
|
|
383
|
+
* @returns {HierarchicalData | null} Hierarchical data structure
|
|
384
|
+
*/
|
|
385
|
+
export function createHierarchyFromFlatData(
|
|
386
|
+
data: any[],
|
|
387
|
+
idAccessor: (d: any) => string,
|
|
388
|
+
parentAccessor: (d: any) => string | null,
|
|
389
|
+
valueAccessor?: (d: any) => number
|
|
390
|
+
): HierarchicalData | null {
|
|
391
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
392
|
+
console.error("MintWaterfall: Invalid data provided to createHierarchyFromFlatData");
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Create map for fast lookup
|
|
397
|
+
const dataMap = new Map<string, HierarchicalData>();
|
|
398
|
+
|
|
399
|
+
// First pass: create nodes
|
|
400
|
+
const root: HierarchicalData = {
|
|
401
|
+
id: "root",
|
|
402
|
+
name: "Root",
|
|
403
|
+
children: []
|
|
404
|
+
};
|
|
405
|
+
dataMap.set("root", root);
|
|
406
|
+
|
|
407
|
+
// Create nodes for each data item
|
|
408
|
+
data.forEach(item => {
|
|
409
|
+
const id = idAccessor(item);
|
|
410
|
+
if (!id) {
|
|
411
|
+
console.warn("MintWaterfall: Item missing ID in createHierarchyFromFlatData");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const node: HierarchicalData = {
|
|
416
|
+
id,
|
|
417
|
+
name: id,
|
|
418
|
+
data: item,
|
|
419
|
+
value: valueAccessor ? valueAccessor(item) : undefined,
|
|
420
|
+
children: []
|
|
421
|
+
};
|
|
422
|
+
dataMap.set(id, node);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Second pass: establish parent-child relationships
|
|
426
|
+
data.forEach(item => {
|
|
427
|
+
const id = idAccessor(item);
|
|
428
|
+
const parentId = parentAccessor(item) || "root";
|
|
429
|
+
|
|
430
|
+
const node = dataMap.get(id);
|
|
431
|
+
const parent = dataMap.get(parentId);
|
|
432
|
+
|
|
433
|
+
if (parent && node && parent.children) {
|
|
434
|
+
parent.children.push(node);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Return the root node
|
|
439
|
+
return root;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Creates a d3.hierarchy object from data
|
|
444
|
+
* @param {HierarchicalData | any[]} data - Hierarchical data or flat data array
|
|
445
|
+
* @param {HierarchyOptions} options - Configuration options
|
|
446
|
+
* @returns {d3.HierarchyNode<any> | null} d3.hierarchy object
|
|
447
|
+
*/
|
|
448
|
+
export function createHierarchy(
|
|
449
|
+
data: HierarchicalData | any[],
|
|
450
|
+
options: HierarchyOptions = {}
|
|
451
|
+
): d3.HierarchyNode<any> | null {
|
|
452
|
+
if (!data) {
|
|
453
|
+
console.error("MintWaterfall: No data provided to createHierarchy");
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
let hierarchyData: HierarchicalData | null;
|
|
458
|
+
|
|
459
|
+
// If data is flat array and we have accessor functions, convert to hierarchical
|
|
460
|
+
if (Array.isArray(data) && options.idAccessor && options.parentAccessor) {
|
|
461
|
+
hierarchyData = createHierarchyFromFlatData(
|
|
462
|
+
data,
|
|
463
|
+
options.idAccessor,
|
|
464
|
+
options.parentAccessor,
|
|
465
|
+
options.valueAccessor
|
|
466
|
+
);
|
|
467
|
+
} else {
|
|
468
|
+
// Assume data is already in hierarchical format
|
|
469
|
+
hierarchyData = data as HierarchicalData;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (!hierarchyData) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Create d3.hierarchy object
|
|
477
|
+
const hierarchy = d3.hierarchy(hierarchyData);
|
|
478
|
+
|
|
479
|
+
// Apply value accessor if provided
|
|
480
|
+
if (options.valueAccessor) {
|
|
481
|
+
hierarchy.sum(d => {
|
|
482
|
+
// For leaf nodes, use the value accessor
|
|
483
|
+
if (!d.children || d.children.length === 0) {
|
|
484
|
+
return d.value !== undefined ? d.value : (options.valueAccessor!(d) || 0);
|
|
485
|
+
}
|
|
486
|
+
// For parent nodes, return 0 (sum will be calculated from children)
|
|
487
|
+
return 0;
|
|
488
|
+
});
|
|
489
|
+
} else {
|
|
490
|
+
hierarchy.sum(d => d.value || 0);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Apply sorting if provided
|
|
494
|
+
if (options.sort) {
|
|
495
|
+
hierarchy.sort(options.sort);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return hierarchy;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Helper function to extract data from a hierarchical layout
|
|
503
|
+
* @param {d3.HierarchyNode<any>} layoutData - Processed layout data
|
|
504
|
+
* @param {ExtractionOptions} options - Extraction options
|
|
505
|
+
* @returns {ExtractedNode[]} Extracted data
|
|
506
|
+
*/
|
|
507
|
+
export function extractLayoutData(
|
|
508
|
+
layoutData: LayoutNode,
|
|
509
|
+
options: ExtractionOptions = {}
|
|
510
|
+
): ExtractedNode[] {
|
|
511
|
+
if (!layoutData) {
|
|
512
|
+
console.error("MintWaterfall: No layout data provided to extractLayoutData");
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const result: ExtractedNode[] = [];
|
|
517
|
+
const includeRoot = options.includeRoot || false;
|
|
518
|
+
const includeInternal = options.includeInternal || false;
|
|
519
|
+
const maxDepth = options.maxDepth || Infinity;
|
|
520
|
+
|
|
521
|
+
// Traverse the hierarchy
|
|
522
|
+
layoutData.each((node: LayoutNode) => {
|
|
523
|
+
// Skip root if not included
|
|
524
|
+
if (!includeRoot && !node.parent) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Skip internal nodes if not included
|
|
529
|
+
if (!includeInternal && node.children && node.children.length > 0) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Skip nodes beyond max depth
|
|
534
|
+
if (node.depth > maxDepth) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Extract node data with proper type casting
|
|
539
|
+
result.push({
|
|
540
|
+
id: node.data.id || `node-${node.depth}-${node.height}`,
|
|
541
|
+
name: node.data.name || "",
|
|
542
|
+
value: node.value!,
|
|
543
|
+
depth: node.depth,
|
|
544
|
+
height: node.height,
|
|
545
|
+
parent: node.parent ? (node.parent.data.id || `node-${node.parent.depth}-${node.parent.height}`) : null,
|
|
546
|
+
x0: node.x0 || 0,
|
|
547
|
+
y0: node.y0 || 0,
|
|
548
|
+
x1: node.x1 || 0,
|
|
549
|
+
y1: node.y1 || 0,
|
|
550
|
+
r: node.r,
|
|
551
|
+
originalData: node.data
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Helper function to convert hierarchical layout to waterfall-compatible format
|
|
560
|
+
* @param {d3.HierarchyNode<any>} layoutData - Processed layout data
|
|
561
|
+
* @param {ConversionOptions} options - Conversion options
|
|
562
|
+
* @returns {WaterfallFormatNode[]} Waterfall-compatible data
|
|
563
|
+
*/
|
|
564
|
+
export function convertToWaterfallFormat(
|
|
565
|
+
layoutData: LayoutNode,
|
|
566
|
+
options: ConversionOptions = {}
|
|
567
|
+
): WaterfallFormatNode[] {
|
|
568
|
+
if (!layoutData) {
|
|
569
|
+
console.error("MintWaterfall: No layout data provided to convertToWaterfallFormat");
|
|
570
|
+
return [];
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const extractedData = extractLayoutData(layoutData, {
|
|
574
|
+
includeRoot: options.includeRoot || false,
|
|
575
|
+
includeInternal: options.includeInternal || true,
|
|
576
|
+
maxDepth: options.maxDepth || 2
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Generate color scale if not provided
|
|
580
|
+
const colorScale = options.colorScale || d3.scaleOrdinal(d3.schemeCategory10);
|
|
581
|
+
|
|
582
|
+
// Convert to waterfall format
|
|
583
|
+
return extractedData.map(node => {
|
|
584
|
+
return {
|
|
585
|
+
label: node.name,
|
|
586
|
+
stacks: [{
|
|
587
|
+
value: node.value,
|
|
588
|
+
color: typeof colorScale === "function" ? colorScale(node.depth.toString()) : colorScale,
|
|
589
|
+
label: node.value.toString()
|
|
590
|
+
}],
|
|
591
|
+
hierarchyData: {
|
|
592
|
+
id: node.id,
|
|
593
|
+
depth: node.depth,
|
|
594
|
+
height: node.height,
|
|
595
|
+
parent: node.parent,
|
|
596
|
+
x0: node.x0,
|
|
597
|
+
y0: node.y0,
|
|
598
|
+
x1: node.x1,
|
|
599
|
+
y1: node.y1,
|
|
600
|
+
r: node.r
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Export layout helpers for direct use
|
|
607
|
+
export const hierarchyLayouts: HierarchyLayouts = {
|
|
608
|
+
treemap: function(data: HierarchicalData | any[], options: LayoutOptions & HierarchyOptions = {}) {
|
|
609
|
+
const layout = createHierarchicalLayout()
|
|
610
|
+
.type("treemap")
|
|
611
|
+
.size(options.size || [800, 400])
|
|
612
|
+
.padding(options.padding || 1);
|
|
613
|
+
|
|
614
|
+
const hierarchy = createHierarchy(data, options);
|
|
615
|
+
if (!hierarchy) {
|
|
616
|
+
throw new Error("Failed to create hierarchy");
|
|
617
|
+
}
|
|
618
|
+
return layout(hierarchy);
|
|
619
|
+
},
|
|
620
|
+
|
|
621
|
+
partition: function(data: HierarchicalData | any[], options: LayoutOptions & HierarchyOptions = {}) {
|
|
622
|
+
const layout = createHierarchicalLayout()
|
|
623
|
+
.type("partition")
|
|
624
|
+
.size(options.size || [800, 400])
|
|
625
|
+
.padding(options.padding || 1)
|
|
626
|
+
.partitionOrientation(options.orientation || "horizontal");
|
|
627
|
+
|
|
628
|
+
const hierarchy = createHierarchy(data, options);
|
|
629
|
+
if (!hierarchy) {
|
|
630
|
+
throw new Error("Failed to create hierarchy");
|
|
631
|
+
}
|
|
632
|
+
return layout(hierarchy);
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
pack: function(data: HierarchicalData | any[], options: LayoutOptions & HierarchyOptions = {}) {
|
|
636
|
+
const layout = createHierarchicalLayout()
|
|
637
|
+
.type("pack")
|
|
638
|
+
.size(options.size || [800, 400])
|
|
639
|
+
.padding(options.padding || 1);
|
|
640
|
+
|
|
641
|
+
const hierarchy = createHierarchy(data, options);
|
|
642
|
+
if (!hierarchy) {
|
|
643
|
+
throw new Error("Failed to create hierarchy");
|
|
644
|
+
}
|
|
645
|
+
return layout(hierarchy);
|
|
646
|
+
}
|
|
647
|
+
};
|