@acorex/charts 21.0.3-next.26 → 21.0.3-next.28

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.
@@ -1,4 +1,4 @@
1
- import { AXChartComponent, getEasingFunction, computeTooltipPosition, formatLargeNumber } from '@acorex/charts';
1
+ import { AX_CHART_DEFAULT_MESSAGES, AXChartComponent, mergeChartOptions, mergeChartMessages, createChartInstanceId, renderChartEmptyMessage, getEasingFunction, computeTooltipPosition, formatLargeNumber } from '@acorex/charts';
2
2
  import { AXChartTooltipComponent } from '@acorex/charts/chart-tooltip';
3
3
  import * as i0 from '@angular/core';
4
4
  import { InjectionToken, input, viewChild, signal, inject, computed, afterNextRender, effect, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
@@ -13,8 +13,7 @@ const AXGaugeChartDefaultConfig = {
13
13
  animationDuration: 750,
14
14
  animationEasing: 'cubic-out',
15
15
  messages: {
16
- noData: 'No data available',
17
- noDataHelp: 'Please provide a value to display the gauge',
16
+ ...AX_CHART_DEFAULT_MESSAGES,
18
17
  noDataIcon: 'fa-light fa-gauge',
19
18
  },
20
19
  };
@@ -23,11 +22,14 @@ const AX_GAUGE_CHART_CONFIG = new InjectionToken('AX_GAUGE_CHART_CONFIG', {
23
22
  factory: () => AXGaugeChartDefaultConfig,
24
23
  });
25
24
  function gaugeChartConfig(config = {}) {
26
- const result = {
25
+ return {
27
26
  ...AXGaugeChartDefaultConfig,
28
27
  ...config,
28
+ messages: {
29
+ ...AXGaugeChartDefaultConfig.messages,
30
+ ...config.messages,
31
+ },
29
32
  };
30
- return result;
31
33
  }
32
34
 
33
35
  /**
@@ -70,12 +72,8 @@ class AXGaugeChartComponent extends AXChartComponent {
70
72
  // Inject configuration
71
73
  configToken = inject(AX_GAUGE_CHART_CONFIG);
72
74
  // Computed configuration options
73
- effectiveOptions = computed(() => {
74
- return {
75
- ...this.configToken,
76
- ...this.options(),
77
- };
78
- }, ...(ngDevMode ? [{ debugName: "effectiveOptions" }] : []));
75
+ effectiveOptions = computed(() => mergeChartOptions(this.configToken, this.options()), ...(ngDevMode ? [{ debugName: "effectiveOptions" }] : []));
76
+ effectiveMessages = computed(() => mergeChartMessages(this.configToken.messages, this.options()?.messages), ...(ngDevMode ? [{ debugName: "effectiveMessages" }] : []));
79
77
  // Constants
80
78
  TOOLTIP_GAP = 10;
81
79
  HALF_CIRCLE_RADIANS = Math.PI;
@@ -85,6 +83,13 @@ class AXGaugeChartComponent extends AXChartComponent {
85
83
  TICK_LABEL_CHAR_WIDTH_RATIO = 0.62;
86
84
  TICK_LABEL_SIDE_PADDING = 6;
87
85
  LABEL_TICK_CLEARANCE = 4;
86
+ instanceId = createChartInstanceId('ax-gauge');
87
+ get bgGradientId() {
88
+ return `${this.instanceId}-bg-gradient`;
89
+ }
90
+ thresholdGradientId(index) {
91
+ return `${this.instanceId}-threshold-gradient-${index}`;
92
+ }
88
93
  constructor() {
89
94
  super();
90
95
  // Dynamically load D3 and initialize the chart when the component is ready
@@ -94,20 +99,19 @@ class AXGaugeChartComponent extends AXChartComponent {
94
99
  });
95
100
  // Watch for changes to redraw the chart
96
101
  effect(() => {
97
- // Access inputs to track them
98
102
  const currentValue = this.value();
99
103
  const options = this.effectiveOptions();
100
- // Update previous value
101
104
  this._prevValue.set(currentValue);
102
- // Only update if already rendered
103
105
  if (this._rendered()) {
104
- // If only the value changed, just update the value display and dial
105
106
  const optionsString = JSON.stringify(options);
106
- if (this._prevOptionsString === optionsString) {
107
+ if (currentValue == null || !this.svgElement) {
108
+ this._prevOptionsString = optionsString;
109
+ this.updateChart();
110
+ }
111
+ else if (this._prevOptionsString === optionsString) {
107
112
  this.updateValueAndDial(currentValue);
108
113
  }
109
114
  else {
110
- // If options changed, recreate the whole chart
111
115
  this._prevOptionsString = optionsString;
112
116
  this.updateChart();
113
117
  }
@@ -139,13 +143,28 @@ class AXGaugeChartComponent extends AXChartComponent {
139
143
  * Creates the gauge chart with all elements
140
144
  */
141
145
  createChart() {
142
- // Clear container and create SVG
146
+ const containerElement = this.chartContainerEl().nativeElement;
143
147
  if (this.svgElement) {
144
148
  this.svgElement.remove();
149
+ this.svgElement = null;
150
+ }
151
+ containerElement.querySelectorAll('.ax-chart-empty').forEach((el) => el.remove());
152
+ if (this.value() == null) {
153
+ if (this.d3) {
154
+ const messages = this.effectiveMessages();
155
+ renderChartEmptyMessage(this.d3, containerElement, {
156
+ icon: messages.noDataIcon,
157
+ title: messages.noData,
158
+ help: messages.noDataHelp,
159
+ });
160
+ }
161
+ this._rendered.set(true);
162
+ return;
145
163
  }
164
+ const currentValue = this.value();
146
165
  // Create SVG element
147
166
  this.svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
148
- this.chartContainerEl().nativeElement.appendChild(this.svgElement);
167
+ containerElement.appendChild(this.svgElement);
149
168
  // Get current options
150
169
  const options = this.effectiveOptions();
151
170
  // Store options string for comparison
@@ -153,7 +172,6 @@ class AXGaugeChartComponent extends AXChartComponent {
153
172
  const { minValue, maxValue, showValue, label = '', gaugeWidth, cornerRadius, animationDuration } = options;
154
173
  const thresholds = options.thresholds ?? [];
155
174
  // Calculate responsive dimensions
156
- const containerElement = this.chartContainerEl().nativeElement;
157
175
  const containerWidth = containerElement.clientWidth;
158
176
  const containerHeight = containerElement.clientHeight;
159
177
  // Cache container dimensions
@@ -162,8 +180,8 @@ class AXGaugeChartComponent extends AXChartComponent {
162
180
  const width = options.width ?? containerWidth;
163
181
  const height = options.height ?? containerHeight;
164
182
  // Ensure minimum dimensions
165
- const effectiveWidth = Math.max(width, 100);
166
- const effectiveHeight = Math.max(height, 50);
183
+ const effectiveWidth = Math.max(width || 1, 1);
184
+ const effectiveHeight = Math.max(height || 1, 1);
167
185
  // For a semi-circular gauge, width should be approximately twice the height
168
186
  const size = Math.min(effectiveWidth, effectiveHeight * 2);
169
187
  // Calculate margin as percentage of size (5-8% with reasonable bounds)
@@ -212,10 +230,10 @@ class AXGaugeChartComponent extends AXChartComponent {
212
230
  // Draw tick marks
213
231
  this.drawTicks(chartGroup, outerRadius, minValue, maxValue, size, tickCount, tickFontPx);
214
232
  // Draw the dial/needle with animation
215
- this.drawDial(chartGroup, radius, this.value(), minValue, maxValue, animationDuration);
233
+ this.drawDial(chartGroup, radius, currentValue, minValue, maxValue, animationDuration);
216
234
  // Draw the value display (after the dial so it's on top)
217
235
  if (showValue) {
218
- this.drawValueDisplay(chartGroup, this.value(), label, radius, size);
236
+ this.drawValueDisplay(chartGroup, currentValue, label, radius, size);
219
237
  }
220
238
  // Hide tooltip initially
221
239
  this._tooltipVisible.set(false);
@@ -269,7 +287,7 @@ class AXGaugeChartComponent extends AXChartComponent {
269
287
  .append('path')
270
288
  .attr('d', backgroundArc)
271
289
  .attr('class', 'gauge-arc gauge-base')
272
- .attr('fill', 'url(#gauge-bg-gradient)')
290
+ .attr('fill', `url(#${this.bgGradientId})`)
273
291
  .attr('filter', 'drop-shadow(0px 2px 3px rgba(0,0,0,0.1))');
274
292
  // Add tooltip for single arc if no thresholds
275
293
  if (!this.effectiveOptions().thresholds || this.effectiveOptions().thresholds.length === 0) {
@@ -319,7 +337,7 @@ class AXGaugeChartComponent extends AXChartComponent {
319
337
  startAngle: previousEndAngle,
320
338
  endAngle,
321
339
  }))
322
- .attr('fill', threshold.color || `url(#threshold-gradient-${i})`)
340
+ .attr('fill', threshold.color || `url(#${this.thresholdGradientId(i)})`)
323
341
  .attr('class', 'gauge-arc threshold-arc')
324
342
  .attr('data-value', threshold.value)
325
343
  .style('cursor', 'pointer');
@@ -354,7 +372,7 @@ class AXGaugeChartComponent extends AXChartComponent {
354
372
  startAngle: previousEndAngle,
355
373
  endAngle: this.QUARTER_CIRCLE_RADIANS,
356
374
  }))
357
- .attr('fill', 'url(#gauge-bg-gradient)')
375
+ .attr('fill', `url(#${this.bgGradientId})`)
358
376
  .attr('class', 'gauge-arc threshold-arc')
359
377
  .style('cursor', 'pointer');
360
378
  // Add tooltip for the last segment
@@ -444,6 +462,7 @@ class AXGaugeChartComponent extends AXChartComponent {
444
462
  this.svgElement.remove();
445
463
  this.svgElement = null;
446
464
  }
465
+ this.chartContainerEl()?.nativeElement.querySelectorAll('.ax-chart-empty').forEach((el) => el.remove());
447
466
  this._tooltipVisible.set(false);
448
467
  this._lastContainerRect = null;
449
468
  }
@@ -455,7 +474,7 @@ class AXGaugeChartComponent extends AXChartComponent {
455
474
  // Create a radial gradient for the background
456
475
  const bgGradient = defs
457
476
  .append('radialGradient')
458
- .attr('id', 'gauge-bg-gradient')
477
+ .attr('id', this.bgGradientId)
459
478
  .attr('cx', '50%')
460
479
  .attr('cy', '50%')
461
480
  .attr('r', '50%')
@@ -479,7 +498,7 @@ class AXGaugeChartComponent extends AXChartComponent {
479
498
  thresholds.forEach((threshold, i) => {
480
499
  const gradient = defs
481
500
  .append('linearGradient')
482
- .attr('id', `threshold-gradient-${i}`)
501
+ .attr('id', this.thresholdGradientId(i))
483
502
  .attr('gradientUnits', 'userSpaceOnUse')
484
503
  .attr('x1', '-1')
485
504
  .attr('y1', '0')
@@ -710,21 +729,21 @@ class AXGaugeChartComponent extends AXChartComponent {
710
729
  * Scales a value to an angle (in radians)
711
730
  */
712
731
  scaleValueToAngle(value, min, max) {
713
- const valueRange = max - min;
732
+ const valueRange = max - min || 1;
714
733
  return ((value - min) / valueRange) * this.HALF_CIRCLE_RADIANS - this.QUARTER_CIRCLE_RADIANS;
715
734
  }
716
735
  /**
717
736
  * Scales a value to an angle for color arcs (in radians)
718
737
  */
719
738
  scaleValueToColorAngle(value, min, max) {
720
- const valueRange = max - min;
739
+ const valueRange = max - min || 1;
721
740
  return ((value - min) / valueRange) * this.HALF_CIRCLE_RADIANS - this.QUARTER_CIRCLE_RADIANS;
722
741
  }
723
742
  /**
724
743
  * Converts an angle back to a value
725
744
  */
726
745
  angleToValue(angle, min, max) {
727
- const valueRange = max - min;
746
+ const valueRange = max - min || 1;
728
747
  return min + ((angle + this.QUARTER_CIRCLE_RADIANS) / this.HALF_CIRCLE_RADIANS) * valueRange;
729
748
  }
730
749
  /**
@@ -734,11 +753,11 @@ class AXGaugeChartComponent extends AXChartComponent {
734
753
  return radians * this.DEGREES_PER_RADIAN;
735
754
  }
736
755
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXGaugeChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
737
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.3", type: AXGaugeChartComponent, isStandalone: true, selector: "ax-gauge-chart", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-gauge-chart\" role=\"img\" #chartContainer>\n <ax-chart-tooltip [data]=\"tooltipData()\" [position]=\"tooltipPosition()\" [visible]=\"tooltipVisible()\"></ax-chart-tooltip>\n</div>\n", styles: ["ax-gauge-chart{display:block;width:100%;height:100%;min-height:0;box-sizing:border-box;--ax-comp-gauge-chart-bg-color: 0, 0, 0, 0;--ax-comp-gauge-chart-text-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-gauge-chart-track-color: var(--ax-sys-color-dark-surface);--ax-comp-gauge-chart-needle-color: var(--ax-sys-color-primary-500)}ax-gauge-chart .ax-gauge-chart{position:relative;width:100%;height:100%;box-sizing:border-box;padding:clamp(.5rem,1.2vw,.875rem);overflow:hidden;color:rgb(var(--ax-comp-gauge-chart-text-color));background-color:rgba(var(--ax-comp-gauge-chart-bg-color));border-radius:.5rem}ax-gauge-chart .ax-gauge-chart svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden}ax-gauge-chart .ax-gauge-chart svg g:has(text){font-family:inherit}ax-gauge-chart .ax-gauge-chart-no-data-message{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:1rem;width:100%;height:100%;color:rgb(var(--ax-comp-gauge-chart-text-color));background-color:rgb(var(--ax-comp-gauge-chart-bg-color))}ax-gauge-chart .ax-gauge-chart-no-data-icon{margin-bottom:.75rem;color:rgba(var(--ax-comp-gauge-chart-text-color),.6)}ax-gauge-chart .ax-gauge-chart-no-data-text{font-weight:600;color:rgb(var(--ax-comp-gauge-chart-text-color))}\n"], dependencies: [{ kind: "component", type: AXChartTooltipComponent, selector: "ax-chart-tooltip", inputs: ["data", "position", "visible", "showPercentage", "style"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
756
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.3", type: AXGaugeChartComponent, isStandalone: true, selector: "ax-gauge-chart", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-gauge-chart\" role=\"img\" #chartContainer>\n <ax-chart-tooltip [data]=\"tooltipData()\" [position]=\"tooltipPosition()\" [visible]=\"tooltipVisible()\"></ax-chart-tooltip>\n</div>\n", styles: [".ax-chart-empty{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:flex;align-items:center;justify-content:center;box-sizing:border-box;padding:1rem;pointer-events:none}.ax-chart-empty__card{text-align:center;padding:1.5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-surface));width:80%;max-width:300px;box-sizing:border-box}.ax-chart-empty__icon{opacity:.6;margin-bottom:.75rem}.ax-chart-empty__title{font-size:1rem;font-weight:600;margin-bottom:.5rem}.ax-chart-empty__help{font-size:.8rem;opacity:.6}ax-gauge-chart{display:block;width:100%;height:100%;min-height:clamp(220px,38vw,360px);box-sizing:border-box;--ax-comp-gauge-chart-bg-color: 0, 0, 0, 0;--ax-comp-gauge-chart-text-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-gauge-chart-track-color: var(--ax-sys-color-dark-surface);--ax-comp-gauge-chart-needle-color: var(--ax-sys-color-primary-500)}ax-gauge-chart .ax-gauge-chart{position:relative;width:100%;height:100%;min-height:0;box-sizing:border-box;padding:clamp(.5rem,1.2vw,.875rem);overflow:hidden;color:rgb(var(--ax-comp-gauge-chart-text-color));background-color:rgba(var(--ax-comp-gauge-chart-bg-color));border-radius:.5rem}ax-gauge-chart .ax-gauge-chart svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden}ax-gauge-chart .ax-gauge-chart svg g:has(text){font-family:inherit}\n"], dependencies: [{ kind: "component", type: AXChartTooltipComponent, selector: "ax-chart-tooltip", inputs: ["data", "position", "visible", "showPercentage", "style"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
738
757
  }
739
758
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXGaugeChartComponent, decorators: [{
740
759
  type: Component,
741
- args: [{ selector: 'ax-gauge-chart', encapsulation: ViewEncapsulation.None, imports: [AXChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-gauge-chart\" role=\"img\" #chartContainer>\n <ax-chart-tooltip [data]=\"tooltipData()\" [position]=\"tooltipPosition()\" [visible]=\"tooltipVisible()\"></ax-chart-tooltip>\n</div>\n", styles: ["ax-gauge-chart{display:block;width:100%;height:100%;min-height:0;box-sizing:border-box;--ax-comp-gauge-chart-bg-color: 0, 0, 0, 0;--ax-comp-gauge-chart-text-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-gauge-chart-track-color: var(--ax-sys-color-dark-surface);--ax-comp-gauge-chart-needle-color: var(--ax-sys-color-primary-500)}ax-gauge-chart .ax-gauge-chart{position:relative;width:100%;height:100%;box-sizing:border-box;padding:clamp(.5rem,1.2vw,.875rem);overflow:hidden;color:rgb(var(--ax-comp-gauge-chart-text-color));background-color:rgba(var(--ax-comp-gauge-chart-bg-color));border-radius:.5rem}ax-gauge-chart .ax-gauge-chart svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden}ax-gauge-chart .ax-gauge-chart svg g:has(text){font-family:inherit}ax-gauge-chart .ax-gauge-chart-no-data-message{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:1rem;width:100%;height:100%;color:rgb(var(--ax-comp-gauge-chart-text-color));background-color:rgb(var(--ax-comp-gauge-chart-bg-color))}ax-gauge-chart .ax-gauge-chart-no-data-icon{margin-bottom:.75rem;color:rgba(var(--ax-comp-gauge-chart-text-color),.6)}ax-gauge-chart .ax-gauge-chart-no-data-text{font-weight:600;color:rgb(var(--ax-comp-gauge-chart-text-color))}\n"] }]
760
+ args: [{ selector: 'ax-gauge-chart', encapsulation: ViewEncapsulation.None, imports: [AXChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-gauge-chart\" role=\"img\" #chartContainer>\n <ax-chart-tooltip [data]=\"tooltipData()\" [position]=\"tooltipPosition()\" [visible]=\"tooltipVisible()\"></ax-chart-tooltip>\n</div>\n", styles: [".ax-chart-empty{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:flex;align-items:center;justify-content:center;box-sizing:border-box;padding:1rem;pointer-events:none}.ax-chart-empty__card{text-align:center;padding:1.5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-surface));width:80%;max-width:300px;box-sizing:border-box}.ax-chart-empty__icon{opacity:.6;margin-bottom:.75rem}.ax-chart-empty__title{font-size:1rem;font-weight:600;margin-bottom:.5rem}.ax-chart-empty__help{font-size:.8rem;opacity:.6}ax-gauge-chart{display:block;width:100%;height:100%;min-height:clamp(220px,38vw,360px);box-sizing:border-box;--ax-comp-gauge-chart-bg-color: 0, 0, 0, 0;--ax-comp-gauge-chart-text-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-gauge-chart-track-color: var(--ax-sys-color-dark-surface);--ax-comp-gauge-chart-needle-color: var(--ax-sys-color-primary-500)}ax-gauge-chart .ax-gauge-chart{position:relative;width:100%;height:100%;min-height:0;box-sizing:border-box;padding:clamp(.5rem,1.2vw,.875rem);overflow:hidden;color:rgb(var(--ax-comp-gauge-chart-text-color));background-color:rgba(var(--ax-comp-gauge-chart-bg-color));border-radius:.5rem}ax-gauge-chart .ax-gauge-chart svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden}ax-gauge-chart .ax-gauge-chart svg g:has(text){font-family:inherit}\n"] }]
742
761
  }], ctorParameters: () => [] });
743
762
 
744
763
  /**