@acorex/charts 19.13.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.
Files changed (46) hide show
  1. package/README.md +72 -0
  2. package/bar-chart/README.md +3 -0
  3. package/bar-chart/index.d.ts +3 -0
  4. package/bar-chart/lib/bar-chart.component.d.ts +123 -0
  5. package/bar-chart/lib/bar-chart.config.d.ts +6 -0
  6. package/bar-chart/lib/bar-chart.type.d.ts +44 -0
  7. package/chart-tooltip/README.md +3 -0
  8. package/chart-tooltip/index.d.ts +2 -0
  9. package/chart-tooltip/lib/chart-tooltip.component.d.ts +43 -0
  10. package/chart-tooltip/lib/chart-tooltip.type.d.ts +7 -0
  11. package/donut-chart/README.md +3 -0
  12. package/donut-chart/index.d.ts +3 -0
  13. package/donut-chart/lib/donut-chart.component.d.ts +143 -0
  14. package/donut-chart/lib/donut-chart.config.d.ts +6 -0
  15. package/donut-chart/lib/donut-chart.type.d.ts +25 -0
  16. package/fesm2022/acorex-charts-bar-chart.mjs +563 -0
  17. package/fesm2022/acorex-charts-bar-chart.mjs.map +1 -0
  18. package/fesm2022/acorex-charts-chart-tooltip.mjs +75 -0
  19. package/fesm2022/acorex-charts-chart-tooltip.mjs.map +1 -0
  20. package/fesm2022/acorex-charts-donut-chart.mjs +616 -0
  21. package/fesm2022/acorex-charts-donut-chart.mjs.map +1 -0
  22. package/fesm2022/acorex-charts-gauge-chart.mjs +548 -0
  23. package/fesm2022/acorex-charts-gauge-chart.mjs.map +1 -0
  24. package/fesm2022/acorex-charts-hierarchy-chart.mjs +652 -0
  25. package/fesm2022/acorex-charts-hierarchy-chart.mjs.map +1 -0
  26. package/fesm2022/acorex-charts-line-chart.mjs +738 -0
  27. package/fesm2022/acorex-charts-line-chart.mjs.map +1 -0
  28. package/fesm2022/acorex-charts.mjs +8 -0
  29. package/fesm2022/acorex-charts.mjs.map +1 -0
  30. package/gauge-chart/README.md +3 -0
  31. package/gauge-chart/index.d.ts +3 -0
  32. package/gauge-chart/lib/gauge-chart.component.d.ts +110 -0
  33. package/gauge-chart/lib/gauge-chart.config.d.ts +6 -0
  34. package/gauge-chart/lib/gauge-chart.type.d.ts +37 -0
  35. package/hierarchy-chart/README.md +61 -0
  36. package/hierarchy-chart/index.d.ts +3 -0
  37. package/hierarchy-chart/lib/hierarchy-chart.component.d.ts +99 -0
  38. package/hierarchy-chart/lib/hierarchy-chart.config.d.ts +6 -0
  39. package/hierarchy-chart/lib/hierarchy-chart.type.d.ts +227 -0
  40. package/index.d.ts +1 -0
  41. package/line-chart/README.md +3 -0
  42. package/line-chart/index.d.ts +3 -0
  43. package/line-chart/lib/line-chart.component.d.ts +96 -0
  44. package/line-chart/lib/line-chart.config.d.ts +6 -0
  45. package/line-chart/lib/line-chart.type.d.ts +61 -0
  46. package/package.json +48 -0
@@ -0,0 +1,652 @@
1
+ import { AXPanViewDirective } from '@acorex/cdk/pan-view';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { InjectionToken, inject, viewChild, contentChild, NgZone, ViewContainerRef, signal, input, output, computed, afterNextRender, effect, Component } from '@angular/core';
5
+ import { AX_GLOBAL_CONFIG } from '@acorex/core/config';
6
+ import { set } from 'lodash-es';
7
+
8
+ const AXHierarchyChartDefaultConfig = {
9
+ direction: 'vertical',
10
+ margin: { top: 40, right: 120, bottom: 40, left: 120 },
11
+ nodeRadius: 30,
12
+ nodeColor: 'rgba(var(--ax-sys-color-primary-500))',
13
+ nodeStrokeColor: 'rgba(var(--ax-sys-color-primary-400))',
14
+ nodeStrokeWidth: 1.5,
15
+ linkColor: 'rgba(var(--ax-sys-color-primary-400))',
16
+ linkWidth: 1.5,
17
+ linkStyle: 'curved',
18
+ nodeWidth: 120,
19
+ nodeHeight: 60,
20
+ nodeSpacingX: 80,
21
+ nodeSpacingY: 120,
22
+ showTooltip: true,
23
+ collapsible: true,
24
+ expandAll: false,
25
+ animationDuration: 100,
26
+ animationEasing: 'cubic-out',
27
+ };
28
+ const AX_HIERARCHY_CHART_CONFIG = new InjectionToken('AX_HIERARCHY_CHART_CONFIG', {
29
+ providedIn: 'root',
30
+ factory: () => {
31
+ const global = inject(AX_GLOBAL_CONFIG);
32
+ set(global, 'chart.hierarchyChart', AXHierarchyChartDefaultConfig);
33
+ return AXHierarchyChartDefaultConfig;
34
+ },
35
+ });
36
+ function hierarchyChartConfig(config = {}) {
37
+ const result = {
38
+ ...AXHierarchyChartDefaultConfig,
39
+ ...config,
40
+ };
41
+ return result;
42
+ }
43
+
44
+ /**
45
+ * A highly customizable hierarchical visualization component that can be used for:
46
+ * - Organization charts
47
+ * - Tree diagrams
48
+ * - Dependency visualizations
49
+ * - Process flows
50
+ *
51
+ * Supports both default node styling and custom node templates.
52
+ */
53
+ class AXHierarchyChartComponent {
54
+ // Chart container reference
55
+ chartContainer = viewChild.required('chartContainer');
56
+ // Custom node template provided by the user
57
+ customNodeTemplate = contentChild('nodeTemplate');
58
+ // Services
59
+ ngZone = inject(NgZone);
60
+ viewContainerRef = inject(ViewContainerRef);
61
+ configToken = inject(AX_HIERARCHY_CHART_CONFIG);
62
+ // D3 instance
63
+ d3;
64
+ // Internal state signals
65
+ _expandedNodes = signal(new Map());
66
+ _initialized = signal(false);
67
+ _rendered = signal(false);
68
+ _dimensions = signal({ width: 0, height: 0 });
69
+ _nodeElements = signal(new Map()); // Store references to node elements
70
+ _chartData = signal(null); // Store the current chart data and layout
71
+ // Inputs
72
+ data = input([]);
73
+ options = input({});
74
+ nodeTemplate = input(null);
75
+ // Outputs
76
+ itemClick = output();
77
+ nodeToggle = output();
78
+ // Computed values
79
+ processedData = computed(() => {
80
+ const data = this.data();
81
+ // Handle both single node and array formats
82
+ return Array.isArray(data) ? { id: 'root', children: data } : data;
83
+ });
84
+ // Check if custom template is available
85
+ hasCustomTemplate = computed(() => {
86
+ return Boolean(this.customNodeTemplate() || this.nodeTemplate());
87
+ });
88
+ effectiveOptions = computed(() => {
89
+ return {
90
+ ...this.configToken,
91
+ ...this.options(),
92
+ };
93
+ });
94
+ constructor() {
95
+ // Dynamically load D3 and initialize the chart when the component is ready
96
+ afterNextRender(() => {
97
+ this._initialized.set(true);
98
+ this.loadD3();
99
+ this.initializeExpandedState();
100
+ });
101
+ // Watch for changes to redraw the chart
102
+ effect(() => {
103
+ // Access these to track them
104
+ this.processedData();
105
+ this.nodeTemplate();
106
+ this.customNodeTemplate();
107
+ this.effectiveOptions();
108
+ this._expandedNodes();
109
+ if (this._initialized() && this.d3 && this._rendered()) {
110
+ this.createChart();
111
+ }
112
+ });
113
+ }
114
+ /**
115
+ * Initialize the expanded state for all nodes
116
+ */
117
+ initializeExpandedState() {
118
+ const newExpandedNodes = new Map();
119
+ const data = this.processedData();
120
+ const expandAll = this.effectiveOptions().expandAll;
121
+ // Helper function to recursively process nodes
122
+ const processNode = (node) => {
123
+ if (node.children && node.children.length > 0) {
124
+ // Set initial expansion state based on node property or global option
125
+ newExpandedNodes.set(node.id, node.isExpanded !== undefined ? node.isExpanded : expandAll);
126
+ // Process children
127
+ node.children.forEach((child) => processNode(child));
128
+ }
129
+ };
130
+ // Start processing
131
+ if (data.children) {
132
+ data.children.forEach((node) => processNode(node));
133
+ }
134
+ else if (data.id) {
135
+ processNode(data);
136
+ }
137
+ this._expandedNodes.set(newExpandedNodes);
138
+ }
139
+ /**
140
+ * Dynamically load D3.js to reduce initial bundle size
141
+ */
142
+ async loadD3() {
143
+ try {
144
+ this.d3 = await import('d3');
145
+ if (this._initialized() && this.chartContainer()) {
146
+ this.updateDimensions();
147
+ this.createChart();
148
+ this._rendered.set(true);
149
+ }
150
+ }
151
+ catch (error) {
152
+ console.error('Failed to load D3.js:', error);
153
+ }
154
+ }
155
+ /**
156
+ * Update dimensions based on container size
157
+ */
158
+ updateDimensions() {
159
+ const container = this.chartContainer().nativeElement;
160
+ const options = this.effectiveOptions();
161
+ const containerRect = container.getBoundingClientRect();
162
+ const width = options.width || Math.max(containerRect.width, 300);
163
+ const height = options.height || Math.max(containerRect.height, 400);
164
+ this._dimensions.set({ width, height });
165
+ }
166
+ /**
167
+ * Create and render the hierarchy chart
168
+ */
169
+ createChart() {
170
+ if (!this.d3 || !this.processedData()) {
171
+ return;
172
+ }
173
+ // Clear any existing chart
174
+ this.clearChart();
175
+ const container = this.chartContainer().nativeElement;
176
+ const options = this.effectiveOptions();
177
+ const dimensions = this._dimensions();
178
+ const expandedNodes = this._expandedNodes();
179
+ this.ngZone.runOutsideAngular(() => {
180
+ // Get dimensions and options
181
+ const { width, height } = dimensions;
182
+ const margin = options.margin || { top: 40, right: 120, bottom: 40, left: 120 };
183
+ const nodeWidth = options.nodeWidth || 120;
184
+ const nodeHeight = options.nodeHeight || 60;
185
+ const nodeSpacingX = options.nodeSpacingX || 80;
186
+ const nodeSpacingY = options.nodeSpacingY || 120;
187
+ const isHorizontal = options.direction === 'horizontal';
188
+ // Create SVG container with viewBox for responsiveness
189
+ const svg = this.d3
190
+ .select(container)
191
+ .append('svg')
192
+ .attr('width', '100%')
193
+ .attr('height', '100%')
194
+ .attr('viewBox', `0 0 ${width} ${height}`)
195
+ .attr('preserveAspectRatio', 'xMidYMid meet')
196
+ .style('overflow', 'visible')
197
+ .classed(options.className || '', !!options.className);
198
+ // Create hierarchy layout from data
199
+ const rootData = this.processedData();
200
+ const root = this.d3.hierarchy(rootData);
201
+ // Apply expansion state to nodes
202
+ root.descendants().forEach((node) => {
203
+ if (node.data.id !== 'root' && node.children) {
204
+ if (expandedNodes.has(node.data.id) && !expandedNodes.get(node.data.id)) {
205
+ node._children = node.children; // Store children
206
+ node.children = null; // Collapse node
207
+ }
208
+ }
209
+ });
210
+ // Set up the tree layout based on direction option
211
+ const treeLayout = this.d3
212
+ .tree()
213
+ .nodeSize([
214
+ isHorizontal ? nodeHeight + nodeSpacingY : nodeWidth + nodeSpacingX,
215
+ isHorizontal ? nodeWidth + nodeSpacingX : nodeHeight + nodeSpacingY,
216
+ ])
217
+ .separation((a, b) => {
218
+ return a.parent === b.parent ? 1.2 : 1.5;
219
+ });
220
+ treeLayout(root);
221
+ // Store the current tree layout data for future updates
222
+ this._chartData.set({
223
+ root,
224
+ treeLayout,
225
+ isHorizontal,
226
+ nodeWidth,
227
+ nodeHeight,
228
+ });
229
+ // Adjust root position based on direction
230
+ const rootX = isHorizontal ? margin.top : width / 2;
231
+ const rootY = isHorizontal ? margin.left : margin.top;
232
+ // Calculate bounds of the tree after layout
233
+ let minX = Infinity;
234
+ let maxX = -Infinity;
235
+ let minY = Infinity;
236
+ let maxY = -Infinity;
237
+ root.descendants().forEach((d) => {
238
+ if (d.data.id === 'root')
239
+ return;
240
+ const x = isHorizontal ? d.x : d.x;
241
+ const y = isHorizontal ? d.y : d.y;
242
+ minX = Math.min(minX, x);
243
+ maxX = Math.max(maxX, x);
244
+ minY = Math.min(minY, y);
245
+ maxY = Math.max(maxY, y);
246
+ });
247
+ // Calculate the center of the tree
248
+ const centerX = (minX + maxX) / 2;
249
+ const centerY = (minY + maxY) / 2;
250
+ // Create a group for the chart content with proper centering
251
+ const g = svg
252
+ .append('g')
253
+ .attr('transform', isHorizontal
254
+ ? `translate(${margin.left}, ${height / 2 - centerX})`
255
+ : `translate(${width / 2}, ${margin.top})`);
256
+ // Draw links between nodes
257
+ const links = g
258
+ .selectAll('.link')
259
+ .data(root.links().filter((d) => d.source.data.id !== 'root'))
260
+ .enter()
261
+ .append('path')
262
+ .attr('class', 'ax-hierarchy-chart-link')
263
+ .attr('d', (d) => {
264
+ // Get the link style from options
265
+ const linkStyle = options.linkStyle || 'curved';
266
+ // Source and target coordinates based on direction
267
+ const sourceX = isHorizontal ? d.source.y : d.source.x;
268
+ const sourceY = isHorizontal ? d.source.x : d.source.y;
269
+ const targetX = isHorizontal ? d.target.y : d.target.x;
270
+ const targetY = isHorizontal ? d.target.x : d.target.y;
271
+ // Variables for rounded corners
272
+ let xDistance, yDistance, cornerRadius, midX, midY;
273
+ switch (linkStyle) {
274
+ case 'straight':
275
+ // Direct straight line
276
+ return `M${sourceX},${sourceY}L${targetX},${targetY}`;
277
+ case 'rounded':
278
+ // Curved line with rounded corners
279
+ xDistance = Math.abs(targetX - sourceX);
280
+ yDistance = Math.abs(targetY - sourceY);
281
+ cornerRadius = Math.min(xDistance, yDistance) * 0.2; // Reduced radius for better appearance
282
+ if (isHorizontal) {
283
+ // For horizontal layout
284
+ const halfDistance = (targetX - sourceX) / 2;
285
+ const xMid = sourceX + halfDistance;
286
+ return `
287
+ M${sourceX},${sourceY}
288
+ H${xMid - cornerRadius}
289
+ Q${xMid},${sourceY} ${xMid},${sourceY + Math.sign(targetY - sourceY) * cornerRadius}
290
+ V${targetY - Math.sign(targetY - sourceY) * cornerRadius}
291
+ Q${xMid},${targetY} ${xMid + cornerRadius},${targetY}
292
+ H${targetX}
293
+ `;
294
+ }
295
+ else {
296
+ // For vertical layout
297
+ const halfDistance = (targetY - sourceY) / 2;
298
+ const yMid = sourceY + halfDistance;
299
+ return `
300
+ M${sourceX},${sourceY}
301
+ V${yMid - cornerRadius}
302
+ Q${sourceX},${yMid} ${sourceX + Math.sign(targetX - sourceX) * cornerRadius},${yMid}
303
+ H${targetX - Math.sign(targetX - sourceX) * cornerRadius}
304
+ Q${targetX},${yMid} ${targetX},${yMid + cornerRadius}
305
+ V${targetY}
306
+ `;
307
+ }
308
+ case 'step':
309
+ // L-shaped stepped line
310
+ if (isHorizontal) {
311
+ return `M${sourceX},${sourceY}H${(sourceX + targetX) / 2}V${targetY}H${targetX}`;
312
+ }
313
+ else {
314
+ return `M${sourceX},${sourceY}V${(sourceY + targetY) / 2}H${targetX}V${targetY}`;
315
+ }
316
+ case 'curved':
317
+ default:
318
+ // Default curved line using D3's built-in link generator
319
+ if (isHorizontal) {
320
+ return this.d3
321
+ .linkHorizontal()
322
+ .x((d) => d.y)
323
+ .y((d) => d.x)(d);
324
+ }
325
+ else {
326
+ return this.d3
327
+ .linkVertical()
328
+ .x((d) => d.x)
329
+ .y((d) => d.y)(d);
330
+ }
331
+ }
332
+ })
333
+ .attr('stroke', options.linkColor)
334
+ .attr('stroke-width', options.linkWidth)
335
+ .attr('fill', 'none')
336
+ .attr('opacity', 1);
337
+ // Determine which template to use for rendering nodes
338
+ const useCustomTemplate = this.hasCustomTemplate();
339
+ const templateToUse = this.nodeTemplate() || this.customNodeTemplate();
340
+ // Create a map to store node elements for future updates
341
+ const nodeElementsMap = new Map();
342
+ // Create node groups
343
+ const nodeGroups = g
344
+ .selectAll('.node-group')
345
+ .data(root.descendants().filter((d) => d.data.id !== 'root'))
346
+ .enter()
347
+ .append('g')
348
+ .attr('class', 'ax-hierarchy-chart-node-group')
349
+ .attr('data-node-id', (d) => d.data.id) // Add a data attribute for easier selection
350
+ .attr('transform', (d) => {
351
+ const x = isHorizontal ? d.y : d.x;
352
+ const y = isHorizontal ? d.x : d.y;
353
+ return `translate(${x},${y})`;
354
+ })
355
+ .attr('opacity', 1); // Display nodes immediately with full opacity
356
+ // Store node elements in the map
357
+ nodeGroups.each(function (d) {
358
+ nodeElementsMap.set(d.data.id, this);
359
+ });
360
+ // Update the node elements signal
361
+ this._nodeElements.set(nodeElementsMap);
362
+ if (useCustomTemplate && templateToUse) {
363
+ // Render custom node templates
364
+ nodeGroups.each((d, i, nodes) => {
365
+ const node = d.data;
366
+ const element = nodes[i];
367
+ const hasChildren = !!(node.children && node.children.length > 0);
368
+ const isExpanded = hasChildren && this._expandedNodes().has(node.id) && this._expandedNodes().get(node.id);
369
+ // Enhance the node with expanded state and toggle function
370
+ node.expanded = isExpanded || false;
371
+ node.toggleExpanded = () => this.toggleNode(node.id);
372
+ // Create a foreignObject for the template
373
+ const foreignObject = this.d3
374
+ .select(element)
375
+ .append('foreignObject')
376
+ .attr('x', -nodeWidth / 2)
377
+ .attr('y', -nodeHeight / 2)
378
+ .attr('width', nodeWidth)
379
+ .attr('height', nodeHeight)
380
+ .attr('class', 'ax-hierarchy-chart-node-container')
381
+ .on('click', (event) => {
382
+ // Don't propagate the event if it's coming from an interactive element
383
+ // like a button inside the template
384
+ const target = event.target;
385
+ const isInteractive = target.tagName === 'BUTTON' || target.tagName === 'A' || target.closest('button, a, [role="button"]');
386
+ if (!isInteractive) {
387
+ this.handleNodeClick(event, d);
388
+ }
389
+ });
390
+ // Create a div to hold the template
391
+ const div = foreignObject
392
+ .append('xhtml:div')
393
+ .attr('class', 'ax-hierarchy-chart-node-content')
394
+ .style('width', '100%')
395
+ .style('height', '100%');
396
+ // Render the template into the div
397
+ this.ngZone.run(() => {
398
+ // Create context with enhanced node as the implicit value
399
+ const context = {
400
+ $implicit: node,
401
+ };
402
+ const viewRef = this.viewContainerRef.createEmbeddedView(templateToUse, context);
403
+ viewRef.detectChanges();
404
+ // Append template contents to the div
405
+ const nodes = viewRef.rootNodes;
406
+ for (const node of nodes) {
407
+ div.node().appendChild(node);
408
+ }
409
+ });
410
+ });
411
+ }
412
+ else {
413
+ // Render enhanced default nodes (rectangles with text and icons)
414
+ nodeGroups.each((d, i, nodes) => {
415
+ const group = this.d3.select(nodes[i]);
416
+ const node = d.data;
417
+ const hasChildren = !!(node.children && node.children.length > 0);
418
+ // Node background rect with rounded corners
419
+ group
420
+ .append('rect')
421
+ .attr('x', -nodeWidth / 2)
422
+ .attr('y', -nodeHeight / 2)
423
+ .attr('width', nodeWidth)
424
+ .attr('height', nodeHeight)
425
+ .attr('rx', 6)
426
+ .attr('ry', 6)
427
+ .attr('fill', node.color || options.nodeColor)
428
+ .attr('stroke', options.nodeStrokeColor)
429
+ .attr('stroke-width', options.nodeStrokeWidth)
430
+ .attr('class', 'ax-hierarchy-chart-node');
431
+ // Main title/name
432
+ group
433
+ .append('text')
434
+ .attr('y', -10)
435
+ .attr('text-anchor', 'middle')
436
+ .attr('class', 'ax-hierarchy-chart-node-title')
437
+ .attr('fill', 'white')
438
+ .attr('font-weight', 'bold')
439
+ .text(node.name || node.label || '');
440
+ // Subtitle if provided
441
+ if (node.subtitle) {
442
+ group
443
+ .append('text')
444
+ .attr('y', 10)
445
+ .attr('text-anchor', 'middle')
446
+ .attr('class', 'ax-hierarchy-chart-node-subtitle')
447
+ .attr('fill', 'rgba(255, 255, 255, 0.8)')
448
+ .attr('font-size', '0.8em')
449
+ .text(node.subtitle);
450
+ }
451
+ // Type label if provided
452
+ if (node.type) {
453
+ group
454
+ .append('text')
455
+ .attr('y', 25)
456
+ .attr('text-anchor', 'middle')
457
+ .attr('class', 'ax-hierarchy-chart-node-type')
458
+ .attr('fill', 'rgba(255, 255, 255, 0.7)')
459
+ .attr('font-size', '0.7em')
460
+ .text(node.type);
461
+ }
462
+ // Add expand/collapse indicator if node has children
463
+ if (hasChildren && options.collapsible) {
464
+ const isExpanded = this._expandedNodes().has(node.id) && this._expandedNodes().get(node.id);
465
+ // Toggle button with better styling
466
+ group
467
+ .append('circle')
468
+ .attr('r', 12)
469
+ .attr('cx', nodeWidth / 2 - 10)
470
+ .attr('cy', -nodeHeight / 2 + 12)
471
+ .attr('fill', 'white')
472
+ .attr('stroke', options.nodeStrokeColor)
473
+ .attr('stroke-width', 1)
474
+ .attr('class', 'ax-hierarchy-chart-toggle-indicator')
475
+ .style('cursor', 'pointer')
476
+ .on('click', (event) => {
477
+ event.stopPropagation();
478
+ this.toggleNode(node.id);
479
+ });
480
+ // Toggle icon
481
+ group
482
+ .append('text')
483
+ .attr('x', nodeWidth / 2 - 10)
484
+ .attr('y', -nodeHeight / 2 + 12)
485
+ .attr('dy', '0.32em')
486
+ .attr('text-anchor', 'middle')
487
+ .attr('class', 'ax-hierarchy-chart-toggle-icon')
488
+ .attr('fill', node.color || options.nodeColor)
489
+ .attr('font-weight', 'bold')
490
+ .style('cursor', 'pointer')
491
+ .text(isExpanded ? '−' : '+')
492
+ .on('click', (event) => {
493
+ event.stopPropagation();
494
+ this.toggleNode(node.id);
495
+ });
496
+ }
497
+ // Icon if provided
498
+ if (node.icon) {
499
+ group
500
+ .append('text')
501
+ .attr('x', -nodeWidth / 2 + 15)
502
+ .attr('y', 0)
503
+ .attr('dy', '0.32em')
504
+ .attr('class', 'ax-hierarchy-chart-node-icon')
505
+ .attr('fill', 'white')
506
+ .attr('class', node.icon);
507
+ }
508
+ // Add click handler to the whole node
509
+ group.on('click', (event) => {
510
+ this.handleNodeClick(event, d);
511
+ });
512
+ // Add tooltip if enabled
513
+ if (options.showTooltip && (node.tooltip || node.description)) {
514
+ group.append('title').text(node.tooltip || node.description || '');
515
+ }
516
+ });
517
+ }
518
+ });
519
+ }
520
+ /**
521
+ * Toggle node expansion state
522
+ */
523
+ toggleNode(nodeId) {
524
+ const expandedNodes = new Map(this._expandedNodes());
525
+ const currentState = expandedNodes.get(nodeId);
526
+ const newState = currentState === undefined ? false : !currentState;
527
+ expandedNodes.set(nodeId, newState);
528
+ this._expandedNodes.set(expandedNodes);
529
+ // Emit toggle event
530
+ this.findNodeById(this.processedData(), nodeId).then((node) => {
531
+ if (node) {
532
+ this.nodeToggle.emit({
533
+ node: node,
534
+ expanded: newState,
535
+ });
536
+ }
537
+ });
538
+ // Update toggle indicator immediately for better UX
539
+ this.updateToggleIndicator(nodeId, newState);
540
+ }
541
+ /**
542
+ * Update just the toggle indicator without redrawing the chart
543
+ */
544
+ updateToggleIndicator(nodeId, expanded) {
545
+ if (!this.d3 || !this.chartContainer())
546
+ return;
547
+ this.ngZone.runOutsideAngular(() => {
548
+ const container = this.chartContainer().nativeElement;
549
+ const svg = container.querySelector('svg');
550
+ if (!svg)
551
+ return;
552
+ // Find and update the toggle indicator for the specific node
553
+ const nodeGroup = this.d3.select(svg).select(`[data-node-id="${nodeId}"]`);
554
+ if (nodeGroup.empty())
555
+ return;
556
+ // Update the toggle icon (if it exists)
557
+ const toggleIcon = nodeGroup.select('.ax-hierarchy-chart-toggle-icon');
558
+ if (!toggleIcon.empty()) {
559
+ toggleIcon.text(expanded ? '−' : '+');
560
+ }
561
+ });
562
+ // Force effect to run that will update the chart
563
+ // This leverages Angular's reactivity rather than a direct method call
564
+ // which is more consistent with Angular's reactive approach
565
+ const expandedNodes = new Map(this._expandedNodes());
566
+ this._expandedNodes.set(expandedNodes);
567
+ }
568
+ /**
569
+ * Find a node by ID
570
+ */
571
+ async findNodeById(data, id) {
572
+ if (data.id === id) {
573
+ return data;
574
+ }
575
+ if (data.children) {
576
+ for (const child of data.children) {
577
+ const found = await this.findNodeById(child, id);
578
+ if (found) {
579
+ return found;
580
+ }
581
+ }
582
+ }
583
+ return null;
584
+ }
585
+ /**
586
+ * Handle node click events
587
+ */
588
+ handleNodeClick(event, d) {
589
+ const node = d.data;
590
+ // Emit click event
591
+ this.itemClick.emit({
592
+ event: event,
593
+ item: node,
594
+ element: event.currentTarget,
595
+ });
596
+ }
597
+ /**
598
+ * Clear existing chart
599
+ */
600
+ clearChart() {
601
+ const container = this.chartContainer()?.nativeElement;
602
+ if (!container)
603
+ return;
604
+ // Remove existing SVG
605
+ const svg = container.querySelector('svg');
606
+ if (svg) {
607
+ svg.remove();
608
+ }
609
+ }
610
+ /**
611
+ * Get D3 easing function from string name
612
+ */
613
+ getEasingFunction(easing) {
614
+ if (!easing)
615
+ return this.d3.easeCubicInOut;
616
+ switch (easing) {
617
+ case 'linear':
618
+ return this.d3.easeLinear;
619
+ case 'ease':
620
+ return this.d3.easePolyInOut;
621
+ case 'ease-in':
622
+ return this.d3.easePolyIn;
623
+ case 'ease-out':
624
+ return this.d3.easePolyOut;
625
+ case 'ease-in-out':
626
+ return this.d3.easePolyInOut;
627
+ case 'cubic':
628
+ return this.d3.easeCubic;
629
+ case 'cubic-in':
630
+ return this.d3.easeCubicIn;
631
+ case 'cubic-out':
632
+ return this.d3.easeCubicOut;
633
+ case 'cubic-in-out':
634
+ return this.d3.easeCubicInOut;
635
+ default:
636
+ return this.d3.easeCubicInOut;
637
+ }
638
+ }
639
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXHierarchyChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
640
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.9", type: AXHierarchyChartComponent, isStandalone: true, selector: "ax-hierarchy-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, nodeTemplate: { classPropertyName: "nodeTemplate", publicName: "nodeTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick", nodeToggle: "nodeToggle" }, queries: [{ propertyName: "customNodeTemplate", first: true, predicate: ["nodeTemplate"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "chartContainer", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div axPanView class=\"ax-hierarchy-chart\" #chartContainer></div>\n", styles: [".ax-hierarchy-chart{width:100%;height:100%;min-height:300px;position:relative;overflow:visible}.ax-hierarchy-chart svg{display:block;width:100%;height:100%;overflow:visible}.ax-hierarchy-chart .ax-hierarchy-chart-link{fill:none;stroke-width:1.5px;stroke-linecap:round}.ax-hierarchy-chart .ax-hierarchy-chart-link:hover{stroke-opacity:.8}.ax-hierarchy-chart .ax-hierarchy-chart-node-group{cursor:pointer}.ax-hierarchy-chart .ax-hierarchy-chart-node-group:hover .ax-hierarchy-chart-node{filter:brightness(1.05)}.ax-hierarchy-chart .ax-hierarchy-chart-node{fill:rgba(var(--ax-sys-color-primary-500));stroke:rgba(var(--ax-sys-color-primary-400));stroke-width:1.5px;transition:all .2s ease}.ax-hierarchy-chart .ax-hierarchy-chart-node-text{font-size:12px;font-weight:600;fill:rgba(var(--ax-sys-color-on-primary-text));-webkit-user-select:none;user-select:none;pointer-events:none}.ax-hierarchy-chart .ax-hierarchy-chart-toggle-indicator{cursor:pointer;transition:all .2s ease}.ax-hierarchy-chart .ax-hierarchy-chart-toggle-indicator:hover{fill:rgba(var(--ax-sys-color-surface-300))}.ax-hierarchy-chart .ax-hierarchy-chart-toggle-icon{font-size:14px;font-weight:700;fill:rgba(var(--ax-sys-color-on-surface-text));-webkit-user-select:none;user-select:none;pointer-events:none}.ax-hierarchy-chart .ax-hierarchy-chart-node-container{overflow:visible}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: AXPanViewDirective, selector: "[axPanView]", inputs: ["zoomStep", "minZoom", "maxZoom", "freeMode", "fitContent", "disablePan", "disableZoom", "wrapperClasses", "panX", "panY", "zoom"], outputs: ["panXChange", "panYChange", "zoomChange", "positionChange"], exportAs: ["axPanView"] }] });
641
+ }
642
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXHierarchyChartComponent, decorators: [{
643
+ type: Component,
644
+ args: [{ selector: 'ax-hierarchy-chart', standalone: true, imports: [CommonModule, AXPanViewDirective], template: "<div axPanView class=\"ax-hierarchy-chart\" #chartContainer></div>\n", styles: [".ax-hierarchy-chart{width:100%;height:100%;min-height:300px;position:relative;overflow:visible}.ax-hierarchy-chart svg{display:block;width:100%;height:100%;overflow:visible}.ax-hierarchy-chart .ax-hierarchy-chart-link{fill:none;stroke-width:1.5px;stroke-linecap:round}.ax-hierarchy-chart .ax-hierarchy-chart-link:hover{stroke-opacity:.8}.ax-hierarchy-chart .ax-hierarchy-chart-node-group{cursor:pointer}.ax-hierarchy-chart .ax-hierarchy-chart-node-group:hover .ax-hierarchy-chart-node{filter:brightness(1.05)}.ax-hierarchy-chart .ax-hierarchy-chart-node{fill:rgba(var(--ax-sys-color-primary-500));stroke:rgba(var(--ax-sys-color-primary-400));stroke-width:1.5px;transition:all .2s ease}.ax-hierarchy-chart .ax-hierarchy-chart-node-text{font-size:12px;font-weight:600;fill:rgba(var(--ax-sys-color-on-primary-text));-webkit-user-select:none;user-select:none;pointer-events:none}.ax-hierarchy-chart .ax-hierarchy-chart-toggle-indicator{cursor:pointer;transition:all .2s ease}.ax-hierarchy-chart .ax-hierarchy-chart-toggle-indicator:hover{fill:rgba(var(--ax-sys-color-surface-300))}.ax-hierarchy-chart .ax-hierarchy-chart-toggle-icon{font-size:14px;font-weight:700;fill:rgba(var(--ax-sys-color-on-surface-text));-webkit-user-select:none;user-select:none;pointer-events:none}.ax-hierarchy-chart .ax-hierarchy-chart-node-container{overflow:visible}\n"] }]
645
+ }], ctorParameters: () => [] });
646
+
647
+ /**
648
+ * Generated bundle index. Do not edit.
649
+ */
650
+
651
+ export { AXHierarchyChartComponent, AXHierarchyChartDefaultConfig, AX_HIERARCHY_CHART_CONFIG, hierarchyChartConfig };
652
+ //# sourceMappingURL=acorex-charts-hierarchy-chart.mjs.map