@acorex/charts 21.0.1-next.7 → 21.0.1-next.70
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/fesm2022/acorex-charts-bar-chart.mjs +37 -23
- package/fesm2022/acorex-charts-bar-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-chart-legend.mjs +3 -3
- package/fesm2022/acorex-charts-chart-legend.mjs.map +1 -1
- package/fesm2022/acorex-charts-chart-tooltip.mjs +13 -4
- package/fesm2022/acorex-charts-chart-tooltip.mjs.map +1 -1
- package/fesm2022/acorex-charts-donut-chart.mjs +83 -99
- package/fesm2022/acorex-charts-donut-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-funnel-chart.mjs +275 -0
- package/fesm2022/acorex-charts-funnel-chart.mjs.map +1 -0
- package/fesm2022/acorex-charts-gauge-chart.mjs +157 -86
- package/fesm2022/acorex-charts-gauge-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-heatmap-chart.mjs +281 -0
- package/fesm2022/acorex-charts-heatmap-chart.mjs.map +1 -0
- package/fesm2022/acorex-charts-hierarchy-chart.mjs +3 -3
- package/fesm2022/acorex-charts-hierarchy-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-line-chart.mjs +37 -23
- package/fesm2022/acorex-charts-line-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts.mjs +3 -3
- package/fesm2022/acorex-charts.mjs.map +1 -1
- package/funnel-chart/README.md +3 -0
- package/heatmap-chart/README.md +3 -0
- package/package.json +19 -13
- package/{bar-chart/index.d.ts → types/acorex-charts-bar-chart.d.ts} +7 -6
- package/{chart-tooltip/index.d.ts → types/acorex-charts-chart-tooltip.d.ts} +1 -0
- package/{donut-chart/index.d.ts → types/acorex-charts-donut-chart.d.ts} +12 -11
- package/types/acorex-charts-funnel-chart.d.ts +108 -0
- package/{gauge-chart/index.d.ts → types/acorex-charts-gauge-chart.d.ts} +16 -5
- package/types/acorex-charts-heatmap-chart.d.ts +111 -0
- package/{hierarchy-chart/index.d.ts → types/acorex-charts-hierarchy-chart.d.ts} +4 -3
- package/{line-chart/index.d.ts → types/acorex-charts-line-chart.d.ts} +5 -1
- /package/{chart-legend/index.d.ts → types/acorex-charts-chart-legend.d.ts} +0 -0
- /package/{index.d.ts → types/acorex-charts.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acorex-charts-funnel-chart.mjs","sources":["../../../../packages/charts/funnel-chart/src/lib/funnel-chart.config.ts","../../../../packages/charts/funnel-chart/src/lib/funnel-chart.component.ts","../../../../packages/charts/funnel-chart/src/lib/funnel-chart.component.html","../../../../packages/charts/funnel-chart/src/acorex-charts-funnel-chart.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { AXFunnelChartOption } from './funnel-chart.type';\n\nexport const AXFunnelChartDefaultConfig: AXFunnelChartOption = {\n margin: { top: 20, right: 160, bottom: 20, left: 160 },\n neckWidth: 0.3,\n showLabels: true,\n labelOffset: 24,\n showTooltip: true,\n animationDuration: 1000,\n animationEasing: 'cubic-out',\n startColor: 'rgb(var(--ax-sys-color-primary-50))',\n endColor: 'rgb(var(--ax-sys-color-primary-950))',\n messages: {\n noData: 'No funnel data available',\n noDataIcon: 'fa-light fa-filter-list',\n },\n};\n\nexport const AX_FUNNEL_CHART_CONFIG = new InjectionToken<AXFunnelChartOption>('AX_FUNNEL_CHART_CONFIG', {\n providedIn: 'root',\n factory: () => AXFunnelChartDefaultConfig,\n});\n","import { AXChartComponent, AXChartComponentBase, computeTooltipPosition, getEasingFunction } from '@acorex/charts';\nimport { AXChartTooltipComponent, AXChartTooltipData } from '@acorex/charts/chart-tooltip';\nimport { AXPlatform } from '@acorex/core/platform';\nimport {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { map, Subscription } from 'rxjs';\nimport { AX_FUNNEL_CHART_CONFIG } from './funnel-chart.config';\nimport { AXFunnelChartOption, AXFunnelData } from './funnel-chart.type';\n\n@Component({\n selector: 'ax-funnel-chart',\n templateUrl: './funnel-chart.component.html',\n styleUrls: ['./funnel-chart.component.scss'],\n encapsulation: ViewEncapsulation.None,\n imports: [AXChartTooltipComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AXFunnelChartComponent extends AXChartComponent implements OnDestroy, AXChartComponentBase {\n // Inputs\n data = input<AXFunnelData[]>([]);\n options = input<AXFunnelChartOption>({});\n\n // Outputs\n /** Emitted when a funnel segment is clicked */\n segmentClick = output<AXFunnelData>();\n\n private readonly chartContainerEl = viewChild.required<ElementRef<HTMLDivElement>>('chartContainer');\n\n // SVG State\n private svgElement: SVGSVGElement | null = null;\n protected d3!: typeof import('d3');\n private _initialized = signal(false);\n private _rendered = signal(false);\n private platformService = inject(AXPlatform);\n protected isRtl = signal(this.platformService.isRtl());\n private directionSub?: Subscription;\n\n // Tooltip Signals\n private _tooltipVisible = signal(false);\n private _tooltipPosition = signal({ x: 0, y: 0 });\n private _tooltipData = signal<AXChartTooltipData>({ title: '', value: '' });\n private _tooltipRafId: number | null = null;\n\n protected tooltipVisible = this._tooltipVisible.asReadonly();\n protected tooltipPosition = this._tooltipPosition.asReadonly();\n protected tooltipData = this._tooltipData.asReadonly();\n\n private configToken = inject(AX_FUNNEL_CHART_CONFIG);\n\n protected effectiveOptions = computed(() => ({\n ...this.configToken,\n ...this.options(),\n }));\n\n constructor() {\n super();\n\n afterNextRender(() => {\n this._initialized.set(true);\n this.loadD3();\n this.directionSub = this.platformService.directionChange\n .pipe(map((i) => i.data === 'rtl'))\n .subscribe((isRtl) => {\n this.isRtl.set(isRtl);\n if (this._rendered()) {\n this.updateChart();\n }\n });\n });\n\n effect(() => {\n // Trigger update on data or option change\n this.data();\n this.effectiveOptions();\n if (this._rendered()) {\n this.updateChart();\n }\n });\n }\n\n ngOnDestroy(): void {\n this.directionSub?.unsubscribe();\n this.cleanupChart();\n }\n\n protected async loadD3(): Promise<void> {\n if (this.d3) return;\n try {\n this.d3 = await import('d3');\n if (this._initialized() && this.chartContainerEl()) {\n this.createChart();\n this._rendered.set(true);\n }\n } catch (error) {\n console.error('AXFunnelChart: Failed to load D3.js', error);\n }\n }\n\n public createChart(): void {\n if (this.svgElement) this.svgElement.remove();\n\n const data = [...this.data()].sort((a, b) => b.value - a.value);\n if (!data.length) {\n this.hideTooltip();\n return;\n }\n\n const container = this.chartContainerEl().nativeElement;\n const width = container.clientWidth;\n const height = container.clientHeight;\n const opt = this.effectiveOptions();\n const margin = opt.margin;\n const isRtl = this.isRtl();\n const labelOffset = opt.labelOffset ?? 24;\n\n this.svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n const svg = this.d3\n .select(this.svgElement)\n .attr('width', '100%')\n .attr('height', '100%')\n .attr('viewBox', `0 0 ${width} ${height}`)\n .attr('preserveAspectRatio', 'xMidYMid meet');\n\n container.appendChild(this.svgElement);\n\n const innerWidth = width - margin.left - margin.right;\n const innerHeight = height - margin.top - margin.bottom;\n\n const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);\n\n const sliceHeight = innerHeight / data.length;\n const maxValue = data[0].value;\n const minValue = data[data.length - 1]?.value ?? maxValue;\n const easing = getEasingFunction(this.d3, opt.animationEasing);\n\n data.forEach((d, i) => {\n const topVal = d.value;\n const bottomVal = data[i + 1]?.value ?? d.value * opt.neckWidth;\n\n const topW = (topVal / maxValue) * innerWidth;\n const bottomW = (bottomVal / maxValue) * innerWidth;\n\n const xTop = (innerWidth - topW) / 2;\n const xBottom = (innerWidth - bottomW) / 2;\n const yTop = i * sliceHeight;\n const yBottom = (i + 1) * sliceHeight;\n\n const pathData = `M ${xTop},${yTop} L ${xTop + topW},${yTop} L ${xBottom + bottomW},${yBottom} L ${xBottom},${yBottom} Z`;\n\n const sliceGroup = g.append('g').attr('class', 'funnel-slice-container');\n\n const computedColor = this.resolveSliceColor(d, i, opt, minValue, maxValue);\n const path = sliceGroup\n .append('path')\n .attr('class', 'funnel-slice')\n .attr('d', pathData)\n .attr('fill', computedColor)\n .style('opacity', 0)\n .on('mouseenter', (event) => {\n svg.classed('is-dimmed', true);\n this.d3.select(event.currentTarget).classed('is-active', true);\n this.showTooltip(event, d, computedColor);\n })\n .on('mousemove', (event) => this.updateTooltipPosition(event))\n .on('click', () => this.segmentClick.emit(d))\n .on('mouseleave', (event) => {\n svg.classed('is-dimmed', false);\n this.d3.select(event.currentTarget).classed('is-active', false);\n this.hideTooltip();\n });\n\n path\n .transition()\n .duration(opt.animationDuration)\n .delay(i * 80)\n .ease(easing)\n .style('opacity', 1);\n\n if (opt.showLabels) {\n const labelGroup = sliceGroup.append('g').attr('class', 'funnel-label-group').style('opacity', 0);\n const labelX = isRtl ? innerWidth / 2 - topW / 2 - labelOffset : innerWidth / 2 + topW / 2 + labelOffset;\n // In RTL documents, SVG `text-anchor: end` can expand *into* the plot area because \"end\"\n // becomes the logical left edge. Using `start` makes the label expand away from the slice\n // on both LTR (to the right) and RTL (to the left).\n const anchor = 'start';\n\n labelGroup\n .append('text')\n .attr('class', 'funnel-label-name')\n .attr('x', labelX)\n .attr('y', yTop + sliceHeight / 2 - 5)\n .attr('text-anchor', anchor)\n .text(d.name);\n\n labelGroup\n .append('text')\n .attr('class', 'funnel-label-value')\n .attr('x', labelX)\n .attr('y', yTop + sliceHeight / 2 + 15)\n .attr('text-anchor', anchor)\n .text(d.value.toLocaleString());\n\n labelGroup\n .transition()\n .duration(600)\n .delay(400 + i * 80)\n .style('opacity', 1);\n }\n });\n }\n\n private resolveSliceColor(\n item: AXFunnelData,\n index: number,\n opt: AXFunnelChartOption,\n minValue: number,\n maxValue: number,\n ): string {\n if (item.color) return item.color;\n\n const palette = opt.colors?.filter(Boolean) ?? [];\n if (palette.length > 0) {\n const key = String(item.id ?? item.name ?? index);\n const idx = this.hashStringToUint32(key) % palette.length;\n return palette[idx] ?? opt.startColor ?? '#1e1b4b';\n }\n\n const startColor = opt.startColor ?? '#1e1b4b';\n const endColor = opt.endColor ?? '#818cf8';\n const resolvedStartColor = this.resolveCssColor(startColor);\n const resolvedEndColor = this.resolveCssColor(endColor);\n const range = maxValue - minValue;\n const t = range === 0 ? 1 : (item.value - minValue) / range;\n const clamped = Math.max(0, Math.min(1, t));\n return this.d3.interpolateRgb(resolvedStartColor, resolvedEndColor)(clamped);\n }\n\n private hashStringToUint32(input: string): number {\n let hash = 5381;\n for (let i = 0; i < input.length; i++) {\n hash = (hash * 33) ^ input.charCodeAt(i);\n }\n return hash >>> 0;\n }\n\n private resolveCssColor(color: string): string {\n const container = this.chartContainerEl().nativeElement;\n const probe = document.createElement('span');\n probe.style.color = color;\n probe.style.position = 'absolute';\n probe.style.left = '-9999px';\n probe.style.top = '-9999px';\n container.appendChild(probe);\n const computed = getComputedStyle(probe).color;\n probe.remove();\n return computed || color;\n }\n\n public updateChart(): void {\n this.createChart();\n }\n\n private showTooltip(event: MouseEvent, item: AXFunnelData, color: string): void {\n if (!this.effectiveOptions().showTooltip) return;\n this._tooltipData.set({ title: item.name, value: item.value.toLocaleString(), color });\n this._tooltipVisible.set(true);\n this.updateTooltipPosition(event);\n }\n\n private updateTooltipPosition(event: MouseEvent): void {\n if (this._tooltipRafId) cancelAnimationFrame(this._tooltipRafId);\n this._tooltipRafId = requestAnimationFrame(() => {\n const containerEl = this.chartContainerEl().nativeElement;\n const rect = containerEl.getBoundingClientRect();\n const tooltipEl = containerEl.querySelector('.chart-tooltip') as HTMLElement;\n const tooltipRect = tooltipEl?.getBoundingClientRect() ?? null;\n this._tooltipPosition.set(computeTooltipPosition(rect, tooltipRect, event.clientX + 10, event.clientY - 10, 10));\n });\n }\n\n private hideTooltip(): void {\n this._tooltipVisible.set(false);\n }\n\n public cleanupChart(): void {\n if (this._tooltipRafId) cancelAnimationFrame(this._tooltipRafId);\n if (this.svgElement) {\n this.svgElement.remove();\n this.svgElement = null;\n }\n }\n\n\n}\n","<div class=\"ax-funnel-chart-container\" role=\"img\" #chartContainer>\n @if (data()?.length === 0) {\n <div class=\"ax-funnel-no-data\">\n <i [class]=\"effectiveOptions().messages?.noDataIcon\"></i>\n <p class=\"ax-funnel-no-data-text\">{{ effectiveOptions().messages?.noData }}</p>\n </div>\n }\n</div>\n\n<ax-chart-tooltip [data]=\"tooltipData()\" [position]=\"tooltipPosition()\" [visible]=\"tooltipVisible()\">\n</ax-chart-tooltip>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;AAGO,MAAM,0BAA0B,GAAwB;AAC7D,IAAA,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE;AACtD,IAAA,SAAS,EAAE,GAAG;AACd,IAAA,UAAU,EAAE,IAAI;AAChB,IAAA,WAAW,EAAE,EAAE;AACf,IAAA,WAAW,EAAE,IAAI;AACjB,IAAA,iBAAiB,EAAE,IAAI;AACvB,IAAA,eAAe,EAAE,WAAW;AAC5B,IAAA,UAAU,EAAE,qCAAqC;AACjD,IAAA,QAAQ,EAAE,sCAAsC;AAChD,IAAA,QAAQ,EAAE;AACR,QAAA,MAAM,EAAE,0BAA0B;AAClC,QAAA,UAAU,EAAE,yBAAyB;AACtC,KAAA;;MAGU,sBAAsB,GAAG,IAAI,cAAc,CAAsB,wBAAwB,EAAE;AACtG,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,0BAA0B;AAC1C,CAAA;;ACQK,MAAO,sBAAuB,SAAQ,gBAAgB,CAAA;;AAE1D,IAAA,IAAI,GAAG,KAAK,CAAiB,EAAE,gDAAC;AAChC,IAAA,OAAO,GAAG,KAAK,CAAsB,EAAE,mDAAC;;;IAIxC,YAAY,GAAG,MAAM,EAAgB;AAEpB,IAAA,gBAAgB,GAAG,SAAS,CAAC,QAAQ,CAA6B,gBAAgB,CAAC;;IAG5F,UAAU,GAAyB,IAAI;AACrC,IAAA,EAAE;AACJ,IAAA,YAAY,GAAG,MAAM,CAAC,KAAK,wDAAC;AAC5B,IAAA,SAAS,GAAG,MAAM,CAAC,KAAK,qDAAC;AACzB,IAAA,eAAe,GAAG,MAAM,CAAC,UAAU,CAAC;IAClC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,OAAA,EAAA,CAAA,GAAA,EAAA,CAAA,CAAC;AAC9C,IAAA,YAAY;;AAGZ,IAAA,eAAe,GAAG,MAAM,CAAC,KAAK,2DAAC;AAC/B,IAAA,gBAAgB,GAAG,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,4DAAC;AACzC,IAAA,YAAY,GAAG,MAAM,CAAqB,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,wDAAC;IACnE,aAAa,GAAkB,IAAI;AAEjC,IAAA,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE;AAClD,IAAA,eAAe,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE;AACpD,IAAA,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE;AAE9C,IAAA,WAAW,GAAG,MAAM,CAAC,sBAAsB,CAAC;AAE1C,IAAA,gBAAgB,GAAG,QAAQ,CAAC,OAAO;QAC3C,GAAG,IAAI,CAAC,WAAW;QACnB,GAAG,IAAI,CAAC,OAAO,EAAE;AAClB,KAAA,CAAC,4DAAC;AAEH,IAAA,WAAA,GAAA;AACE,QAAA,KAAK,EAAE;QAEP,eAAe,CAAC,MAAK;AACnB,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;YAC3B,IAAI,CAAC,MAAM,EAAE;AACb,YAAA,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC;AACtC,iBAAA,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC;AACjC,iBAAA,SAAS,CAAC,CAAC,KAAK,KAAI;AACnB,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;AACrB,gBAAA,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE;oBACpB,IAAI,CAAC,WAAW,EAAE;gBACpB;AACF,YAAA,CAAC,CAAC;AACN,QAAA,CAAC,CAAC;QAEF,MAAM,CAAC,MAAK;;YAEV,IAAI,CAAC,IAAI,EAAE;YACX,IAAI,CAAC,gBAAgB,EAAE;AACvB,YAAA,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE;gBACpB,IAAI,CAAC,WAAW,EAAE;YACpB;AACF,QAAA,CAAC,CAAC;IACJ;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE;QAChC,IAAI,CAAC,YAAY,EAAE;IACrB;AAEU,IAAA,MAAM,MAAM,GAAA;QACpB,IAAI,IAAI,CAAC,EAAE;YAAE;AACb,QAAA,IAAI;YACF,IAAI,CAAC,EAAE,GAAG,MAAM,OAAO,IAAI,CAAC;YAC5B,IAAI,IAAI,CAAC,YAAY,EAAE,IAAI,IAAI,CAAC,gBAAgB,EAAE,EAAE;gBAClD,IAAI,CAAC,WAAW,EAAE;AAClB,gBAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;YAC1B;QACF;QAAE,OAAO,KAAK,EAAE;AACd,YAAA,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,KAAK,CAAC;QAC7D;IACF;IAEO,WAAW,GAAA;QAChB,IAAI,IAAI,CAAC,UAAU;AAAE,YAAA,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;QAE7C,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;AAC/D,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAChB,IAAI,CAAC,WAAW,EAAE;YAClB;QACF;QAEA,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC,aAAa;AACvD,QAAA,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW;AACnC,QAAA,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY;AACrC,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,EAAE;AACnC,QAAA,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM;AACzB,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;AAC1B,QAAA,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,IAAI,EAAE;QAEzC,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,eAAe,CAAC,4BAA4B,EAAE,KAAK,CAAC;AAC/E,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC;AACd,aAAA,MAAM,CAAC,IAAI,CAAC,UAAU;AACtB,aAAA,IAAI,CAAC,OAAO,EAAE,MAAM;AACpB,aAAA,IAAI,CAAC,QAAQ,EAAE,MAAM;aACrB,IAAI,CAAC,SAAS,EAAE,CAAA,IAAA,EAAO,KAAK,CAAA,CAAA,EAAI,MAAM,EAAE;AACxC,aAAA,IAAI,CAAC,qBAAqB,EAAE,eAAe,CAAC;AAE/C,QAAA,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;QAEtC,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK;QACrD,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM;QAEvD,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAA,UAAA,EAAa,MAAM,CAAC,IAAI,CAAA,CAAA,EAAI,MAAM,CAAC,GAAG,CAAA,CAAA,CAAG,CAAC;AAEtF,QAAA,MAAM,WAAW,GAAG,WAAW,GAAG,IAAI,CAAC,MAAM;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK;AAC9B,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,QAAQ;AACzD,QAAA,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,eAAe,CAAC;QAE9D,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;AACpB,YAAA,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK;AACtB,YAAA,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,SAAS;YAE/D,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,QAAQ,IAAI,UAAU;YAC7C,MAAM,OAAO,GAAG,CAAC,SAAS,GAAG,QAAQ,IAAI,UAAU;YAEnD,MAAM,IAAI,GAAG,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC;YACpC,MAAM,OAAO,GAAG,CAAC,UAAU,GAAG,OAAO,IAAI,CAAC;AAC1C,YAAA,MAAM,IAAI,GAAG,CAAC,GAAG,WAAW;YAC5B,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,WAAW;YAErC,MAAM,QAAQ,GAAG,CAAA,EAAA,EAAK,IAAI,IAAI,IAAI,CAAA,GAAA,EAAM,IAAI,GAAG,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,GAAA,EAAM,OAAO,GAAG,OAAO,CAAA,CAAA,EAAI,OAAO,MAAM,OAAO,CAAA,CAAA,EAAI,OAAO,CAAA,EAAA,CAAI;AAEzH,YAAA,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,wBAAwB,CAAC;AAExE,YAAA,MAAM,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC;YAC3E,MAAM,IAAI,GAAG;iBACV,MAAM,CAAC,MAAM;AACb,iBAAA,IAAI,CAAC,OAAO,EAAE,cAAc;AAC5B,iBAAA,IAAI,CAAC,GAAG,EAAE,QAAQ;AAClB,iBAAA,IAAI,CAAC,MAAM,EAAE,aAAa;AAC1B,iBAAA,KAAK,CAAC,SAAS,EAAE,CAAC;AAClB,iBAAA,EAAE,CAAC,YAAY,EAAE,CAAC,KAAK,KAAI;AAC1B,gBAAA,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC;AAC9B,gBAAA,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC;gBAC9D,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,EAAE,aAAa,CAAC;AAC3C,YAAA,CAAC;AACA,iBAAA,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC;AAC5D,iBAAA,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3C,iBAAA,EAAE,CAAC,YAAY,EAAE,CAAC,KAAK,KAAI;AAC1B,gBAAA,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC;AAC/B,gBAAA,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC;gBAC/D,IAAI,CAAC,WAAW,EAAE;AACpB,YAAA,CAAC,CAAC;YAEJ;AACG,iBAAA,UAAU;AACV,iBAAA,QAAQ,CAAC,GAAG,CAAC,iBAAiB;AAC9B,iBAAA,KAAK,CAAC,CAAC,GAAG,EAAE;iBACZ,IAAI,CAAC,MAAM;AACX,iBAAA,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;AAEtB,YAAA,IAAI,GAAG,CAAC,UAAU,EAAE;gBAClB,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;gBACjG,MAAM,MAAM,GAAG,KAAK,GAAG,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,WAAW,GAAG,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,WAAW;;;;gBAIxG,MAAM,MAAM,GAAG,OAAO;gBAEtB;qBACG,MAAM,CAAC,MAAM;AACb,qBAAA,IAAI,CAAC,OAAO,EAAE,mBAAmB;AACjC,qBAAA,IAAI,CAAC,GAAG,EAAE,MAAM;qBAChB,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,WAAW,GAAG,CAAC,GAAG,CAAC;AACpC,qBAAA,IAAI,CAAC,aAAa,EAAE,MAAM;AAC1B,qBAAA,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBAEf;qBACG,MAAM,CAAC,MAAM;AACb,qBAAA,IAAI,CAAC,OAAO,EAAE,oBAAoB;AAClC,qBAAA,IAAI,CAAC,GAAG,EAAE,MAAM;qBAChB,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,WAAW,GAAG,CAAC,GAAG,EAAE;AACrC,qBAAA,IAAI,CAAC,aAAa,EAAE,MAAM;qBAC1B,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;gBAEjC;AACG,qBAAA,UAAU;qBACV,QAAQ,CAAC,GAAG;AACZ,qBAAA,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE;AAClB,qBAAA,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;YACxB;AACF,QAAA,CAAC,CAAC;IACJ;IAEQ,iBAAiB,CACvB,IAAkB,EAClB,KAAa,EACb,GAAwB,EACxB,QAAgB,EAChB,QAAgB,EAAA;QAEhB,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK;AAEjC,QAAA,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;AACjD,QAAA,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE;AACtB,YAAA,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC;AACjD,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM;YACzD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,IAAI,SAAS;QACpD;AAEA,QAAA,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,SAAS;AAC9C,QAAA,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,SAAS;QAC1C,MAAM,kBAAkB,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC;QAC3D,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC;AACvD,QAAA,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ;QACjC,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,IAAI,KAAK;AAC3D,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3C,QAAA,OAAO,IAAI,CAAC,EAAE,CAAC,cAAc,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAC,OAAO,CAAC;IAC9E;AAEQ,IAAA,kBAAkB,CAAC,KAAa,EAAA;QACtC,IAAI,IAAI,GAAG,IAAI;AACf,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACrC,YAAA,IAAI,GAAG,CAAC,IAAI,GAAG,EAAE,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;QAC1C;QACA,OAAO,IAAI,KAAK,CAAC;IACnB;AAEQ,IAAA,eAAe,CAAC,KAAa,EAAA;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC,aAAa;QACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC;AAC5C,QAAA,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK;AACzB,QAAA,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU;AACjC,QAAA,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,SAAS;AAC5B,QAAA,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,SAAS;AAC3B,QAAA,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC;QAC5B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,KAAK;QAC9C,KAAK,CAAC,MAAM,EAAE;QACd,OAAO,QAAQ,IAAI,KAAK;IAC1B;IAEO,WAAW,GAAA;QAChB,IAAI,CAAC,WAAW,EAAE;IACpB;AAEQ,IAAA,WAAW,CAAC,KAAiB,EAAE,IAAkB,EAAE,KAAa,EAAA;AACtE,QAAA,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,WAAW;YAAE;QAC1C,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC;AACtF,QAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;AAC9B,QAAA,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC;IACnC;AAEQ,IAAA,qBAAqB,CAAC,KAAiB,EAAA;QAC7C,IAAI,IAAI,CAAC,aAAa;AAAE,YAAA,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC;AAChE,QAAA,IAAI,CAAC,aAAa,GAAG,qBAAqB,CAAC,MAAK;YAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC,aAAa;AACzD,YAAA,MAAM,IAAI,GAAG,WAAW,CAAC,qBAAqB,EAAE;YAChD,MAAM,SAAS,GAAG,WAAW,CAAC,aAAa,CAAC,gBAAgB,CAAgB;YAC5E,MAAM,WAAW,GAAG,SAAS,EAAE,qBAAqB,EAAE,IAAI,IAAI;YAC9D,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,OAAO,GAAG,EAAE,EAAE,KAAK,CAAC,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;AAClH,QAAA,CAAC,CAAC;IACJ;IAEQ,WAAW,GAAA;AACjB,QAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC;IACjC;IAEO,YAAY,GAAA;QACjB,IAAI,IAAI,CAAC,aAAa;AAAE,YAAA,oBAAoB,CAAC,IAAI,CAAC,aAAa,CAAC;AAChE,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE;AACnB,YAAA,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;AACxB,YAAA,IAAI,CAAC,UAAU,GAAG,IAAI;QACxB;IACF;uGAjRW,sBAAsB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAtB,sBAAsB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,YAAA,EAAA,cAAA,EAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,kBAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAAA,CAAA,gBAAA,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC9BnC,0cAWA,EAAA,MAAA,EAAA,CAAA,6sDAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDgBY,uBAAuB,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,gBAAA,EAAA,OAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FAGtB,sBAAsB,EAAA,UAAA,EAAA,CAAA;kBARlC,SAAS;+BACE,iBAAiB,EAAA,aAAA,EAGZ,iBAAiB,CAAC,IAAI,EAAA,OAAA,EAC5B,CAAC,uBAAuB,CAAC,EAAA,eAAA,EACjB,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,0cAAA,EAAA,MAAA,EAAA,CAAA,6sDAAA,CAAA,EAAA;2VAWoC,gBAAgB,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AEvCrG;;AAEG;;;;"}
|
|
@@ -61,6 +61,8 @@ class AXGaugeChartComponent extends AXChartComponent {
|
|
|
61
61
|
title: '',
|
|
62
62
|
value: '',
|
|
63
63
|
}, ...(ngDevMode ? [{ debugName: "_tooltipData" }] : []));
|
|
64
|
+
_tooltipRafId = null;
|
|
65
|
+
_pendingTooltipCoords = null;
|
|
64
66
|
// Expose tooltip signals as read-only for the template
|
|
65
67
|
tooltipVisible = this._tooltipVisible.asReadonly();
|
|
66
68
|
tooltipPosition = this._tooltipPosition.asReadonly();
|
|
@@ -79,6 +81,10 @@ class AXGaugeChartComponent extends AXChartComponent {
|
|
|
79
81
|
HALF_CIRCLE_RADIANS = Math.PI;
|
|
80
82
|
QUARTER_CIRCLE_RADIANS = Math.PI / 2;
|
|
81
83
|
DEGREES_PER_RADIAN = 180 / Math.PI;
|
|
84
|
+
CHART_EDGE_PADDING = 12;
|
|
85
|
+
TICK_LABEL_CHAR_WIDTH_RATIO = 0.62;
|
|
86
|
+
TICK_LABEL_SIDE_PADDING = 6;
|
|
87
|
+
LABEL_TICK_CLEARANCE = 4;
|
|
82
88
|
constructor() {
|
|
83
89
|
super();
|
|
84
90
|
// Dynamically load D3 and initialize the chart when the component is ready
|
|
@@ -163,29 +169,36 @@ class AXGaugeChartComponent extends AXChartComponent {
|
|
|
163
169
|
// Calculate margin as percentage of size (5-8% with reasonable bounds)
|
|
164
170
|
const marginRatio = 0.08;
|
|
165
171
|
const margin = Math.max(5, Math.min(size * marginRatio, 30));
|
|
166
|
-
|
|
167
|
-
const
|
|
172
|
+
const tickCount = this.getTickCount(size);
|
|
173
|
+
const tickFontPx = this.getTickFontSize(size);
|
|
174
|
+
const maxTickLabelWidth = this.estimateMaxTickLabelWidth(tickCount, minValue, maxValue, tickFontPx);
|
|
175
|
+
const tickLength = Math.max(4, Math.min((size / 2) * 0.12, 14));
|
|
176
|
+
// Keep labels closer to the gauge arc while preserving readability.
|
|
177
|
+
const labelGap = Math.max(6, Math.min((size / 2) * 0.06, 14));
|
|
178
|
+
const labelOffsetFromTickEnd = labelGap + tickFontPx * 0.35;
|
|
179
|
+
const tickLabelSpace = tickLength + labelOffsetFromTickEnd + tickFontPx;
|
|
168
180
|
const totalVerticalSpace = size / 2 + tickLabelSpace;
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
const estimatedTickFontSize = Math.max(14, Math.min(18, Math.floor(size / 35)));
|
|
172
|
-
const horizontalPadding = Math.max(20, estimatedTickFontSize * 1.5);
|
|
181
|
+
// Labels at edge ticks use start/end anchors, so they can extend by near full width.
|
|
182
|
+
const horizontalPadding = Math.max(18, maxTickLabelWidth + 8);
|
|
173
183
|
const totalWidth = size + horizontalPadding * 2;
|
|
184
|
+
const viewBoxWidth = totalWidth + this.CHART_EDGE_PADDING * 2;
|
|
185
|
+
const viewBoxHeight = totalVerticalSpace + this.CHART_EDGE_PADDING * 2;
|
|
174
186
|
// Set up SVG with responsive viewBox that accounts for overflow
|
|
175
187
|
const svg = this.d3
|
|
176
188
|
.select(this.svgElement)
|
|
177
189
|
.attr('width', '100%')
|
|
178
190
|
.attr('height', '100%')
|
|
179
|
-
.attr('viewBox', `0 0 ${
|
|
191
|
+
.attr('viewBox', `0 0 ${viewBoxWidth} ${viewBoxHeight}`)
|
|
180
192
|
.attr('preserveAspectRatio', 'xMidYMid meet');
|
|
181
193
|
// Create a group for the chart centered horizontally with padding, positioned to show only the top half
|
|
182
194
|
const chartGroup = svg
|
|
183
195
|
.append('g')
|
|
184
|
-
.attr('transform', `translate(${size / 2 + horizontalPadding}, ${size / 2 - margin})`);
|
|
196
|
+
.attr('transform', `translate(${size / 2 + horizontalPadding + this.CHART_EDGE_PADDING}, ${size / 2 - margin + this.CHART_EDGE_PADDING})`);
|
|
185
197
|
// Define gauge parameters
|
|
186
198
|
const radius = size / 2 - margin;
|
|
187
|
-
const desiredGaugeWidth = typeof gaugeWidth === 'number' && !Number.isNaN(gaugeWidth) ? gaugeWidth :
|
|
188
|
-
|
|
199
|
+
const desiredGaugeWidth = typeof gaugeWidth === 'number' && !Number.isNaN(gaugeWidth) ? gaugeWidth : 30;
|
|
200
|
+
// Respect explicit gaugeWidth in px with sane absolute bounds.
|
|
201
|
+
const clampedGaugeWidth = Math.max(6, Math.min(desiredGaugeWidth, radius * 0.7));
|
|
189
202
|
const innerRadius = radius - clampedGaugeWidth;
|
|
190
203
|
const outerRadius = radius;
|
|
191
204
|
// Create gradient definitions
|
|
@@ -197,7 +210,7 @@ class AXGaugeChartComponent extends AXChartComponent {
|
|
|
197
210
|
this.drawThresholds(chartGroup, innerRadius, outerRadius, minValue, maxValue, thresholds, cornerRadius);
|
|
198
211
|
}
|
|
199
212
|
// Draw tick marks
|
|
200
|
-
this.drawTicks(chartGroup, outerRadius, minValue, maxValue, size);
|
|
213
|
+
this.drawTicks(chartGroup, outerRadius, minValue, maxValue, size, tickCount, tickFontPx);
|
|
201
214
|
// Draw the dial/needle with animation
|
|
202
215
|
this.drawDial(chartGroup, radius, this.value(), minValue, maxValue, animationDuration);
|
|
203
216
|
// Draw the value display (after the dial so it's on top)
|
|
@@ -393,22 +406,24 @@ class AXGaugeChartComponent extends AXChartComponent {
|
|
|
393
406
|
});
|
|
394
407
|
this._tooltipVisible.set(true);
|
|
395
408
|
}
|
|
396
|
-
/**
|
|
397
|
-
* Updates the tooltip position based on the mouse event
|
|
398
|
-
* For gauge charts, we need simpler coordinate handling since D3 events are already in container coordinates
|
|
399
|
-
*/
|
|
400
409
|
updateTooltipPosition(event) {
|
|
401
|
-
|
|
402
|
-
if (
|
|
410
|
+
this._pendingTooltipCoords = { x: event.clientX, y: event.clientY };
|
|
411
|
+
if (this._tooltipRafId != null)
|
|
403
412
|
return;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
413
|
+
this._tooltipRafId = requestAnimationFrame(() => {
|
|
414
|
+
this._tooltipRafId = null;
|
|
415
|
+
const coords = this._pendingTooltipCoords;
|
|
416
|
+
if (!coords)
|
|
417
|
+
return;
|
|
418
|
+
const containerEl = this.chartContainerEl()?.nativeElement;
|
|
419
|
+
if (!containerEl)
|
|
420
|
+
return;
|
|
421
|
+
const rect = containerEl.getBoundingClientRect();
|
|
422
|
+
const tooltipEl = containerEl.querySelector('.chart-tooltip');
|
|
423
|
+
const tooltipRect = tooltipEl?.getBoundingClientRect() ?? null;
|
|
424
|
+
const pos = computeTooltipPosition(rect, tooltipRect, coords.x + 10, coords.y - 10, this.TOOLTIP_GAP);
|
|
425
|
+
this._tooltipPosition.set(pos);
|
|
426
|
+
});
|
|
412
427
|
}
|
|
413
428
|
/**
|
|
414
429
|
* Hides the tooltip
|
|
@@ -420,6 +435,11 @@ class AXGaugeChartComponent extends AXChartComponent {
|
|
|
420
435
|
* Cleans up chart resources
|
|
421
436
|
*/
|
|
422
437
|
cleanupChart() {
|
|
438
|
+
if (this._tooltipRafId != null) {
|
|
439
|
+
cancelAnimationFrame(this._tooltipRafId);
|
|
440
|
+
this._tooltipRafId = null;
|
|
441
|
+
}
|
|
442
|
+
this._pendingTooltipCoords = null;
|
|
423
443
|
if (this.svgElement) {
|
|
424
444
|
this.svgElement.remove();
|
|
425
445
|
this.svgElement = null;
|
|
@@ -476,79 +496,130 @@ class AXGaugeChartComponent extends AXChartComponent {
|
|
|
476
496
|
/**
|
|
477
497
|
* Draws tick marks and labels around the gauge
|
|
478
498
|
*/
|
|
479
|
-
drawTicks(chartGroup, radius, minValue, maxValue, size) {
|
|
480
|
-
// Dynamically choose tick count based on chart size (reduced for better spacing)
|
|
481
|
-
let tickCount = 5;
|
|
482
|
-
if (size < 200)
|
|
483
|
-
tickCount = 3; // Very small: only show min, mid, max
|
|
484
|
-
else if (size < 300)
|
|
485
|
-
tickCount = 4;
|
|
486
|
-
else if (size < 400)
|
|
487
|
-
tickCount = 5;
|
|
488
|
-
else if (size > 520)
|
|
489
|
-
tickCount = 7;
|
|
490
|
-
// Scale tick length and label offset proportionally to size
|
|
499
|
+
drawTicks(chartGroup, radius, minValue, maxValue, size, tickCount, tickFontPx) {
|
|
491
500
|
const tickLength = Math.max(4, Math.min(radius * 0.12, 14));
|
|
492
|
-
|
|
493
|
-
const labelOffset = Math.max(22, Math.min(radius * 0.28, 45));
|
|
494
|
-
// Create a group for the ticks
|
|
501
|
+
const baseLabelOffset = this.getBaseLabelOffset(radius, tickLength, tickFontPx);
|
|
495
502
|
const tickGroup = chartGroup.append('g').attr('class', 'ticks');
|
|
496
|
-
// Generate tick values
|
|
497
503
|
const tickValues = [];
|
|
498
504
|
const step = (maxValue - minValue) / (tickCount - 1);
|
|
499
505
|
for (let i = 0; i < tickCount; i++) {
|
|
500
506
|
tickValues.push(minValue + i * step);
|
|
501
507
|
}
|
|
502
|
-
|
|
503
|
-
tickValues.forEach((tick) => {
|
|
504
|
-
// Calculate angle for this tick
|
|
508
|
+
const tickMetrics = tickValues.map((tick) => {
|
|
505
509
|
const angle = this.scaleValueToAngle(tick, minValue, maxValue);
|
|
506
|
-
const radians = angle - this.QUARTER_CIRCLE_RADIANS;
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
// Draw tick line
|
|
510
|
+
const radians = angle - this.QUARTER_CIRCLE_RADIANS;
|
|
511
|
+
return {
|
|
512
|
+
radians,
|
|
513
|
+
cos: Math.cos(radians),
|
|
514
|
+
sin: Math.sin(radians),
|
|
515
|
+
label: this.formatTickValue(tick, minValue, maxValue),
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
tickMetrics.forEach((metric) => {
|
|
516
519
|
tickGroup
|
|
517
520
|
.append('line')
|
|
518
|
-
.attr('x1',
|
|
519
|
-
.attr('y1',
|
|
520
|
-
.attr('x2',
|
|
521
|
-
.attr('y2',
|
|
521
|
+
.attr('x1', metric.cos * (radius + 5))
|
|
522
|
+
.attr('y1', metric.sin * (radius + 5))
|
|
523
|
+
.attr('x2', metric.cos * (radius + tickLength))
|
|
524
|
+
.attr('y2', metric.sin * (radius + tickLength))
|
|
522
525
|
.attr('stroke', 'rgba(var(--ax-comp-gauge-chart-text-color), 0.5)')
|
|
523
526
|
.attr('stroke-width', 2);
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
527
|
+
});
|
|
528
|
+
const textSelection = tickGroup
|
|
529
|
+
.selectAll('text.gauge-tick-label')
|
|
530
|
+
.data(tickMetrics)
|
|
531
|
+
.enter()
|
|
532
|
+
.append('text')
|
|
533
|
+
.attr('class', 'gauge-tick-label')
|
|
534
|
+
.attr('x', 0)
|
|
535
|
+
.attr('y', 0)
|
|
536
|
+
.attr('fill', 'rgba(var(--ax-comp-gauge-chart-text-color), 0.7)')
|
|
537
|
+
.style('font-size', `${tickFontPx}px`)
|
|
538
|
+
.text((d) => d.label);
|
|
539
|
+
textSelection.each((d, index, nodes) => {
|
|
540
|
+
const node = nodes[index];
|
|
541
|
+
let bboxWidth = this.estimateLabelWidth(d.label, tickFontPx);
|
|
542
|
+
let bboxHeight = tickFontPx;
|
|
543
|
+
try {
|
|
544
|
+
const bbox = node.getBBox();
|
|
545
|
+
if (bbox.width > 0)
|
|
546
|
+
bboxWidth = bbox.width;
|
|
547
|
+
if (bbox.height > 0)
|
|
548
|
+
bboxHeight = bbox.height;
|
|
536
549
|
}
|
|
537
|
-
|
|
538
|
-
//
|
|
539
|
-
formattedValue = formatLargeNumber(Math.round(tick));
|
|
550
|
+
catch {
|
|
551
|
+
// Keep fallback estimates.
|
|
540
552
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
.text(formattedValue);
|
|
553
|
+
const anchor = this.getTickLabelAnchor(d.cos);
|
|
554
|
+
const nearEdgeExtent = this.getNearEdgeProjection(anchor, d.cos, d.sin, bboxWidth, bboxHeight);
|
|
555
|
+
const requiredOffset = tickLength + this.LABEL_TICK_CLEARANCE + nearEdgeExtent;
|
|
556
|
+
const effectiveLabelOffset = Math.max(baseLabelOffset, requiredOffset);
|
|
557
|
+
const nudge = tickFontPx * 0.1;
|
|
558
|
+
const x = d.cos * (radius + effectiveLabelOffset) + (anchor === 'start' ? nudge : anchor === 'end' ? -nudge : 0);
|
|
559
|
+
const y = d.sin * (radius + effectiveLabelOffset);
|
|
560
|
+
this.d3.select(node).attr('x', x).attr('y', y).attr('text-anchor', anchor).attr('dominant-baseline', 'middle');
|
|
550
561
|
});
|
|
551
562
|
}
|
|
563
|
+
getTickCount(size) {
|
|
564
|
+
if (size < 200)
|
|
565
|
+
return 3;
|
|
566
|
+
if (size < 300)
|
|
567
|
+
return 4;
|
|
568
|
+
if (size < 400)
|
|
569
|
+
return 5;
|
|
570
|
+
if (size > 520)
|
|
571
|
+
return 7;
|
|
572
|
+
return 5;
|
|
573
|
+
}
|
|
574
|
+
getTickFontSize(size) {
|
|
575
|
+
return Math.max(14, Math.min(18, Math.floor(size / 35)));
|
|
576
|
+
}
|
|
577
|
+
getBaseLabelOffset(radius, tickLength, tickFontPx) {
|
|
578
|
+
const labelGap = Math.max(6, Math.min(radius * 0.06, 14));
|
|
579
|
+
return tickLength + labelGap + tickFontPx * 0.2;
|
|
580
|
+
}
|
|
581
|
+
getTickLabelAnchor(cosValue) {
|
|
582
|
+
if (cosValue > 0.35)
|
|
583
|
+
return 'start';
|
|
584
|
+
if (cosValue < -0.35)
|
|
585
|
+
return 'end';
|
|
586
|
+
return 'middle';
|
|
587
|
+
}
|
|
588
|
+
getNearEdgeProjection(anchor, cosValue, sinValue, bboxWidth, bboxHeight) {
|
|
589
|
+
const absCos = Math.abs(cosValue);
|
|
590
|
+
const absSin = Math.abs(sinValue);
|
|
591
|
+
const halfHeightProjection = absSin * (bboxHeight / 2);
|
|
592
|
+
// For start/end anchored labels on edge ticks, the nearest side is close to the anchor point.
|
|
593
|
+
if (anchor === 'start' || anchor === 'end') {
|
|
594
|
+
return halfHeightProjection + absCos * this.TICK_LABEL_SIDE_PADDING;
|
|
595
|
+
}
|
|
596
|
+
// For centered labels, nearest side includes half text width projection.
|
|
597
|
+
return absCos * (bboxWidth / 2) + halfHeightProjection;
|
|
598
|
+
}
|
|
599
|
+
formatTickValue(tick, minValue, maxValue) {
|
|
600
|
+
const valueRange = maxValue - minValue;
|
|
601
|
+
if (valueRange <= 10) {
|
|
602
|
+
return tick.toFixed(2).replace(/\.?0+$/, '');
|
|
603
|
+
}
|
|
604
|
+
if (valueRange < 1000) {
|
|
605
|
+
return tick % 1 === 0 ? tick.toString() : tick.toFixed(1);
|
|
606
|
+
}
|
|
607
|
+
return formatLargeNumber(Math.round(tick));
|
|
608
|
+
}
|
|
609
|
+
estimateLabelWidth(label, tickFontPx) {
|
|
610
|
+
return label.length * tickFontPx * this.TICK_LABEL_CHAR_WIDTH_RATIO + this.TICK_LABEL_SIDE_PADDING;
|
|
611
|
+
}
|
|
612
|
+
estimateMaxTickLabelWidth(tickCount, minValue, maxValue, tickFontPx) {
|
|
613
|
+
if (tickCount <= 1)
|
|
614
|
+
return tickFontPx;
|
|
615
|
+
const step = (maxValue - minValue) / (tickCount - 1);
|
|
616
|
+
let maxChars = 0;
|
|
617
|
+
for (let i = 0; i < tickCount; i++) {
|
|
618
|
+
const label = this.formatTickValue(minValue + i * step, minValue, maxValue);
|
|
619
|
+
maxChars = Math.max(maxChars, label.length);
|
|
620
|
+
}
|
|
621
|
+
return maxChars * tickFontPx * this.TICK_LABEL_CHAR_WIDTH_RATIO + this.TICK_LABEL_SIDE_PADDING;
|
|
622
|
+
}
|
|
552
623
|
/**
|
|
553
624
|
* Draws the value and label text in the center
|
|
554
625
|
*/
|
|
@@ -662,12 +733,12 @@ class AXGaugeChartComponent extends AXChartComponent {
|
|
|
662
733
|
radiansToDegrees(radians) {
|
|
663
734
|
return radians * this.DEGREES_PER_RADIAN;
|
|
664
735
|
}
|
|
665
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
666
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "
|
|
736
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXGaugeChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
737
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.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></div>\n<ax-chart-tooltip [data]=\"tooltipData()\" [position]=\"tooltipPosition()\" [visible]=\"tooltipVisible()\"></ax-chart-tooltip>\n", styles: ["ax-gauge-chart{display:block;width:100%;height:100%;min-height:clamp(220px,38vw,360px);--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 });
|
|
667
738
|
}
|
|
668
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
739
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AXGaugeChartComponent, decorators: [{
|
|
669
740
|
type: Component,
|
|
670
|
-
args: [{ selector: 'ax-gauge-chart', encapsulation: ViewEncapsulation.None, imports: [AXChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-gauge-chart\" role=\"img\" #chartContainer></div>\n<ax-chart-tooltip [data]=\"tooltipData()\" [position]=\"tooltipPosition()\" [visible]=\"tooltipVisible()\"></ax-chart-tooltip>\n", styles: ["ax-gauge-chart{display:block;width:100%;height:100%;min-height:
|
|
741
|
+
args: [{ selector: 'ax-gauge-chart', encapsulation: ViewEncapsulation.None, imports: [AXChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-gauge-chart\" role=\"img\" #chartContainer></div>\n<ax-chart-tooltip [data]=\"tooltipData()\" [position]=\"tooltipPosition()\" [visible]=\"tooltipVisible()\"></ax-chart-tooltip>\n", styles: ["ax-gauge-chart{display:block;width:100%;height:100%;min-height:clamp(220px,38vw,360px);--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"] }]
|
|
671
742
|
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], chartContainerEl: [{ type: i0.ViewChild, args: ['chartContainer', { isSignal: true }] }] } });
|
|
672
743
|
|
|
673
744
|
/**
|