@forgeroute/ui 0.4.77 → 0.4.79

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,6 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, Component, DestroyRef, NgZone, PLATFORM_ID, computed, effect, inject, input, viewChild } from "@angular/core";
3
3
  import { ArcElement, BarController, BarElement, CategoryScale, Chart, DoughnutController, LinearScale, Tooltip } from "chart.js";
4
+ import { UiLabelsService, uiCurrencyFormatter, uiFormatNumber, uiLocale } from "@forgeroute/ui";
4
5
  import { isPlatformBrowser } from "@angular/common";
5
6
  let registered$1 = false;
6
7
  function registerChartParts$1() {
@@ -143,7 +144,8 @@ var BarChartComponent = class BarChartComponent {
143
144
  this.unitPlural = input("", ...ngDevMode ? [{ debugName: "unitPlural" }] : /* istanbul ignore next */ []);
144
145
  this.valueFormat = input("number", ...ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []);
145
146
  this.currency = input("USD", ...ngDevMode ? [{ debugName: "currency" }] : /* istanbul ignore next */ []);
146
- this.locale = input("en-US", ...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
147
+ this.locale = input(null, ...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
148
+ this._ui = inject(UiLabelsService);
147
149
  this.decimals = input(null, ...ngDevMode ? [{ debugName: "decimals" }] : /* istanbul ignore next */ []);
148
150
  this.series = input([], ...ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []);
149
151
  this.stacked = input(false, ...ngDevMode ? [{ debugName: "stacked" }] : /* istanbul ignore next */ []);
@@ -155,7 +157,7 @@ var BarChartComponent = class BarChartComponent {
155
157
  this.formatter = computed(() => {
156
158
  const kind = this.valueFormat();
157
159
  if (kind !== "currency" && kind !== "percent") return null;
158
- const locale = safeLocale(this.locale());
160
+ const locale = safeLocale(this.locale() ?? this._ui.locale());
159
161
  const d = this.decimals();
160
162
  const fixed = typeof d === "number" && Number.isFinite(d) ? Math.min(6, Math.max(0, Math.trunc(d))) : null;
161
163
  const digits = (min, max) => ({
@@ -173,18 +175,19 @@ var BarChartComponent = class BarChartComponent {
173
175
  }
174
176
  };
175
177
  if (kind === "currency") {
176
- const base = {
177
- style: "currency",
178
- currency: safeCurrency(this.currency())
178
+ const currency = safeCurrency(this.currency());
179
+ const makeMoney = (extra) => {
180
+ try {
181
+ return uiCurrencyFormatter(currency, locale, {
182
+ signDisplay: "negative",
183
+ ...extra
184
+ });
185
+ } catch {
186
+ return uiCurrencyFormatter(currency, locale, extra);
187
+ }
179
188
  };
180
- const full = make(fixed === null ? base : {
181
- ...base,
182
- ...digits(fixed, fixed)
183
- });
184
- const whole = fixed === null ? make({
185
- ...base,
186
- ...digits(0, 0)
187
- }) : full;
189
+ const full = makeMoney(fixed === null ? {} : digits(fixed, fixed));
190
+ const whole = fixed === null ? makeMoney(digits(0, 0)) : full;
188
191
  return {
189
192
  value: (n) => formatWith(full, n),
190
193
  tick: (n) => formatWith(Number.isInteger(n) ? whole : full, n)
@@ -205,7 +208,7 @@ var BarChartComponent = class BarChartComponent {
205
208
  const count = this.labels().length;
206
209
  return list.map((raw, i) => {
207
210
  const s = raw && typeof raw === "object" ? raw : {};
208
- const name = typeof s.name === "string" && s.name.trim() ? s.name.trim() : `Series ${i + 1}`;
211
+ const name = typeof s.name === "string" && s.name.trim() ? s.name.trim() : this._ui.t("charts.seriesN", { n: i + 1 });
209
212
  const color = (typeof s.color === "string" && s.color.trim() ? s.color : "") || BAR_CHART_SERIES_PALETTE[i % BAR_CHART_SERIES_PALETTE.length];
210
213
  const hover = (typeof s.hoverColor === "string" && s.hoverColor.trim() ? s.hoverColor : "") || (darkenColor(color) ?? color);
211
214
  const source = Array.isArray(s.values) ? s.values : [];
@@ -719,10 +722,14 @@ const PIE_CHART_PALETTE = [
719
722
  const DOUGHNUT_CUTOUT = "60%";
720
723
  const PIE_CUTOUT = "0%";
721
724
  const EMPTY_COLOR = "#e2e8f0";
722
- function formatPercent(value, total) {
725
+ function formatPercent(value, total, locale = uiLocale()) {
723
726
  if (!(total > 0) || !(value > 0)) return "0%";
724
727
  const rounded = Math.round(value / total * 1e3) / 10;
725
- return rounded === 0 ? "<0.1%" : `${rounded}%`;
728
+ const num = (n) => uiFormatNumber(n, locale, {
729
+ maximumFractionDigits: 1,
730
+ useGrouping: false
731
+ });
732
+ return rounded === 0 ? `<${num(.1)}%` : `${num(rounded)}%`;
726
733
  }
727
734
  function prefersReducedMotion() {
728
735
  try {
@@ -733,6 +740,7 @@ function prefersReducedMotion() {
733
740
  }
734
741
  var PieChartComponent = class PieChartComponent {
735
742
  constructor() {
743
+ this.ui = inject(UiLabelsService);
736
744
  this.labels = input([], ...ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []);
737
745
  this.values = input([], ...ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []);
738
746
  this.doughnut = input(false, ...ngDevMode ? [{ debugName: "doughnut" }] : /* istanbul ignore next */ []);
@@ -766,7 +774,7 @@ var PieChartComponent = class PieChartComponent {
766
774
  index,
767
775
  label: labels[index],
768
776
  value,
769
- percent: formatPercent(value / scale, total),
777
+ percent: formatPercent(value / scale, total, this.ui.locale()),
770
778
  color: palette[index % palette.length]
771
779
  });
772
780
  });
@@ -787,7 +795,7 @@ var PieChartComponent = class PieChartComponent {
787
795
  const empty = this.isEmpty();
788
796
  const s = this.slices();
789
797
  return {
790
- labels: empty ? ["No data"] : s.map((x) => x.label),
798
+ labels: empty ? [this.ui.t("chartsPieChart.noData")] : s.map((x) => x.label),
791
799
  data: empty ? [1] : s.map((x) => x.value),
792
800
  colors: empty ? [EMPTY_COLOR] : s.map((x) => x.color),
793
801
  cutout: this.doughnut() ? DOUGHNUT_CUTOUT : PIE_CUTOUT,
@@ -961,7 +969,7 @@ var PieChartComponent = class PieChartComponent {
961
969
  [attr.role]="ariaLabel() ? 'img' : null" [attr.aria-label]="ariaLabel() || null">
962
970
  <canvas #canvas></canvas>
963
971
  @if (isEmpty()) {
964
- <div class="pc-center"><span class="pc-center-label">No data</span></div>
972
+ <div class="pc-center"><span class="pc-center-label">{{ ui.t('chartsPieChart.noData') }}</span></div>
965
973
  } @else if (showCenter()) {
966
974
  <div class="pc-center">
967
975
  @if (centerValue()) { <span class="pc-center-value">{{ centerValue() }}</span> }
@@ -982,7 +990,7 @@ var PieChartComponent = class PieChartComponent {
982
990
  }
983
991
  <ul class="pc-sr-only" role="list">
984
992
  @if (showCenter() && (centerLabel() || centerValue())) { <li>{{ centerLabel() }} {{ centerValue() }}</li> }
985
- @if (isEmpty()) { <li>No data</li> }
993
+ @if (isEmpty()) { <li>{{ ui.t('chartsPieChart.noData') }}</li> }
986
994
  @for (s of slices(); track s.index) {
987
995
  <li>{{ s.label }}: {{ valueText(s.value) }}@if (showPercent()) { ({{ s.percent }}) }</li>
988
996
  }
@@ -1010,7 +1018,7 @@ i0.ɵɵngDeclareClassMetadata({
1010
1018
  [attr.role]="ariaLabel() ? 'img' : null" [attr.aria-label]="ariaLabel() || null">
1011
1019
  <canvas #canvas></canvas>
1012
1020
  @if (isEmpty()) {
1013
- <div class="pc-center"><span class="pc-center-label">No data</span></div>
1021
+ <div class="pc-center"><span class="pc-center-label">{{ ui.t('chartsPieChart.noData') }}</span></div>
1014
1022
  } @else if (showCenter()) {
1015
1023
  <div class="pc-center">
1016
1024
  @if (centerValue()) { <span class="pc-center-value">{{ centerValue() }}</span> }
@@ -1031,7 +1039,7 @@ i0.ɵɵngDeclareClassMetadata({
1031
1039
  }
1032
1040
  <ul class="pc-sr-only" role="list">
1033
1041
  @if (showCenter() && (centerLabel() || centerValue())) { <li>{{ centerLabel() }} {{ centerValue() }}</li> }
1034
- @if (isEmpty()) { <li>No data</li> }
1042
+ @if (isEmpty()) { <li>{{ ui.t('chartsPieChart.noData') }}</li> }
1035
1043
  @for (s of slices(); track s.index) {
1036
1044
  <li>{{ s.label }}: {{ valueText(s.value) }}@if (showPercent()) { ({{ s.percent }}) }</li>
1037
1045
  }
@@ -1 +1 @@
1
- {"version":3,"file":"forgeroute-ui-charts.mjs","names":[],"sources":["../../../projects/forgeroute-ui/charts/src/bar-chart/bar-chart.component.ts","../../../projects/forgeroute-ui/charts/src/pie-chart/pie-chart.component.ts"],"sourcesContent":["import {\n Component, ChangeDetectionStrategy, DestroyRef, ElementRef, NgZone, computed, effect, inject, input, viewChild,\n} from '@angular/core';\nimport { BarController, BarElement, CategoryScale, Chart, LinearScale, Tooltip } from 'chart.js';\nimport type { ChartDataset, Plugin } from 'chart.js';\n\n// Registered when a chart is first created (not at import time) so apps that never use ui-bar-chart can drop chart.js from their bundle.\nlet registered = false;\nfunction registerChartParts(): void {\n if (registered) return;\n Chart.register(BarController, BarElement, CategoryScale, LinearScale, Tooltip);\n registered = true;\n}\n\nconst HEX_RE = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;\nconst RGB_RE = /^rgba?\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*(?:,\\s*(\\d*\\.?\\d+)\\s*)?\\)$/i;\nconst HOVER_DARKEN = 0.8;\n\n/**\n * A darker shade (each channel x 0.8) of a hex (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`) or comma-separated `rgb()` / `rgba()` colour,\n * returned as `rgb(r, g, b)` or `rgba(r, g, b, a)`. Returns `null` for anything else (named colours, `var(--x)`, `hsl()`, out-of-range\n * channels, malformed text) because those can't be computed without a browser.\n */\nexport function darkenColor(input: string): string | null {\n const c = (input ?? '').trim();\n let r: number, g: number, b: number, a: number | null = null;\n const hex = HEX_RE.exec(c);\n if (hex) {\n let h = hex[1];\n if (h.length <= 4) h = h.split('').map(ch => ch + ch).join('');\n r = parseInt(h.slice(0, 2), 16); g = parseInt(h.slice(2, 4), 16); b = parseInt(h.slice(4, 6), 16);\n if (h.length === 8) a = Math.round((parseInt(h.slice(6, 8), 16) / 255) * 1000) / 1000;\n } else {\n const m = RGB_RE.exec(c);\n if (!m) return null;\n r = Number(m[1]); g = Number(m[2]); b = Number(m[3]);\n if (r > 255 || g > 255 || b > 255) return null;\n if (m[4] !== undefined) { a = Number(m[4]); if (a > 1) return null; }\n }\n const d = (n: number) => Math.round(n * HOVER_DARKEN);\n return a === null ? `rgb(${d(r)}, ${d(g)}, ${d(b)})` : `rgba(${d(r)}, ${d(g)}, ${d(b)}, ${a})`;\n}\n\n/** How the chart writes numbers: one for value labels and tooltips, one for the y axis ticks. */\ninterface ValueFormatter {\n value: (n: number) => string;\n tick: (n: number) => string;\n}\n\nexport type BarChartValueFormat = 'number' | 'currency' | 'percent';\n\n/** One series of a grouped / stacked bar chart: its legend name, one value per label, and optional colours (any CSS colour). */\nexport interface BarChartSeries { name: string; values: number[]; color?: string; hoverColor?: string; }\n\n/**\n * Default series colours of a multi-series chart (cycled). Same values as the pie chart's palette (kept as a local copy so a bar-only\n * app never pulls the pie chart's chart.js parts into its bundle); each has at least 3:1 contrast against white.\n */\nexport const BAR_CHART_SERIES_PALETTE: readonly string[] = [\n '#2563eb', '#16a34a', '#d27006', '#dc2626', '#7c3aed', '#0891b2',\n '#db2777', '#4d7c0f', '#475569', '#0d9488', '#c2410c', '#4f46e5', '#a21caf', '#b45309',\n];\n\n/** A multi-series chart draws value labels only up to this many bars in total (labels x series); denser groups overlap. */\nexport const BAR_CHART_MAX_GROUPED_VALUE_LABELS = 24;\n\n/** A series after clean-up: trimmed or fallback name, colours resolved, exactly one number per label. */\ninterface ResolvedSeries { name: string; data: number[]; color: string; hover: string; }\n\n/** Intl.NumberFormat that never throws: a rejected option set returns null. */\nfunction tryFormat(locale: string, options?: Intl.NumberFormatOptions): Intl.NumberFormat | null {\n try { return new Intl.NumberFormat(locale, options); } catch { return null; }\n}\n\n/** The given locale when Intl accepts it (malformed tags are rejected), otherwise `en-US`. */\nfunction safeLocale(locale: string): string {\n const l = (locale ?? '').trim();\n return l && tryFormat(l) ? l : 'en-US';\n}\n\n/** The given ISO 4217 code (trimmed, upper-cased) when Intl accepts it (not exactly 3 letters = rejected), otherwise `USD`. */\nfunction safeCurrency(currency: string): string {\n const c = (currency ?? '').trim().toUpperCase();\n return c && tryFormat('en-US', { style: 'currency', currency: c }) ? c : 'USD';\n}\n\n/** Format one number; non-finite values read as plain text (NaN, Infinity), zero never shows a sign (no \"-$0.00\"). */\nfunction formatWith(nf: Intl.NumberFormat, n: number, scale = 1): string {\n if (!Number.isFinite(n)) return String(n);\n return nf.format(n === 0 ? 0 : n / scale);\n}\n\n/** Value-label formatters per chart. Kept out of `chart.options`: Chart.js treats a function option as scriptable and would call it with its own context. */\nconst VALUE_LABEL_FORMATS = new WeakMap<object, (n: number) => string>();\n\n/** Draws each bar's value above it (nothing for a 0 bar), written by `valueFormat` when that is not 'number'. Local to ui-bar-chart so no chart.js plugin package is needed. */\nconst VALUE_LABELS: Plugin<'bar'> = {\n id: 'uiBarChartValueLabels',\n afterDatasetsDraw(chart) {\n const opts = (chart.options.plugins as Record<string, { enabled?: boolean } | undefined>)['uiBarChartValueLabels'];\n const format = VALUE_LABEL_FORMATS.get(chart);\n if (opts?.enabled === false) return;\n const ctx = chart.ctx;\n ctx.save();\n ctx.fillStyle = '#475569';\n ctx.font = '600 11px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'bottom';\n // one dataset (single series) or several (grouped): dataset by dataset, hidden datasets skipped\n chart.data.datasets.forEach((dataset, di) => {\n if (!chart.isDatasetVisible(di)) return;\n chart.getDatasetMeta(di).data.forEach((bar, i) => {\n const value = dataset.data[i];\n if (typeof value === 'number' && Number.isFinite(value) && value !== 0) ctx.fillText(format ? format(value) : String(value), bar.x, bar.y - 2);\n });\n });\n ctx.restore();\n },\n};\n\n/**\n * Bar chart of one series (e.g. calls per day). Pass the category `labels` and the matching `values`.\n *\n * <ui-bar-chart [labels]=\"['8/1', '8/2']\" [values]=\"[3, 0]\" ariaLabel=\"Daily call volume\"\n * unitSingular=\"call\" unitPlural=\"calls\" />\n *\n * - Bars show their value above them (`showValues`, a 0 bar shows nothing), the y axis starts at 0 and counts whole numbers.\n * - The tooltip reads \"<value> <unit>\" (`unitSingular` for 1, `unitPlural` otherwise; no unit = just the number).\n * - With an `ariaLabel` the chart is an image for assistive tech (`role=\"img\"` + that label; without a label no role is set, as an\n * unnamed image is an accessibility error); render the same figures as text next to it\n * (e.g. a visually hidden list) when the exact values matter.\n * - Per-bar colours: `colors` gives bar i the colour `colors[i]` (any CSS colour, passed through untouched). Bars without an entry\n * (list shorter than the bars, or a blank/whitespace entry) use `color`; extra entries are ignored. With no `colors` every bar uses\n * `color` exactly as before. Colours are decorative only: never rely on colour alone, the values are always available as the\n * value labels and tooltips (and in your own text alternative).\n *\n * <ui-bar-chart [labels]=\"['New', 'Won', 'Lost']\" [values]=\"[4, 2, 1]\" [colors]=\"['#3b82f6', '#16a34a', '#dc2626']\" />\n *\n * - Number format: `valueFormat` ('number' default | 'currency' | 'percent') writes the value labels, the tooltip and the y axis ticks.\n * 'number' is the original behaviour (raw numbers, whole-number ticks, \"<value> <unit>\" tooltip); `currency`, `locale` and `decimals`\n * are ignored for it.\n *\n * <ui-bar-chart [labels]=\"months\" [values]=\"revenue\" valueFormat=\"currency\" ariaLabel=\"Revenue by month\" />\n * <ui-bar-chart [labels]=\"months\" [values]=\"revenue\" valueFormat=\"currency\" currency=\"EUR\" locale=\"de-DE\" [decimals]=\"0\" />\n * <ui-bar-chart [labels]=\"months\" [values]=\"rates\" valueFormat=\"percent\" />\n *\n * - 'currency': `$1,234.50` (`currency` ISO 4217, default USD; `locale` default en-US). Zero is `$0.00`, negatives `-$5.00`.\n * Default decimals are the currency's own (2 for USD). Axis ticks are not forced to whole numbers (values may be fractional);\n * with no `decimals` a whole-number tick drops its cents (`$500`) and a fractional one keeps them (`$0.50`); an explicit `decimals`\n * is used for every tick.\n * - 'percent': the values are ALREADY percentages on the 0-100 scale, not fractions: 12.5 is `12.5%`, 100 is `100%`. Default is up\n * to 1 decimal (12 -> `12%`, 12.5 -> `12.5%`); `decimals` fixes the number of decimals (0 -> `13%`). Ticks use the same format.\n * - The tooltip of 'currency' / 'percent' is just the formatted figure (`$1,234.50`, `12.5%`); `unitSingular` / `unitPlural` are\n * ignored. With 'number' the unit text is unchanged.\n * - `decimals` (integer, clamped to 0-6; null or non-numeric = format default). An invalid `currency` or `locale` never throws:\n * it falls back to USD / en-US. NaN and Infinity read as \"NaN\" / \"Infinity\" in the tooltip and get no value label.\n * - `ariaLabel` is never formatted by the chart: if the description carries figures, format them in the host.\n *\n * - Hover: a bar with its own colour darkens it (see `darkenColor`: hex or comma-separated rgb()/rgba() get a shade 20% darker; a\n * named colour, `var(--x)` or other unparseable value stays unchanged on hover). Bars using the fallback `color` use `hoverColor`.\n * - Several series (grouped or stacked): pass `series` (it takes precedence over `values`, `color`, `hoverColor` and `colors`, which are\n * ignored). Every series shares `labels`; bars of one label sit side by side (`stacked` = true piles them up instead).\n *\n * <ui-bar-chart [labels]=\"months\"\n * [series]=\"[{ name: 'Income', values: income, color: '#22c55e' }, { name: 'Expenses', values: expenses, color: '#ef4444' }]\"\n * valueFormat=\"currency\" ariaLabel=\"Income and expenses by month\" />\n *\n * - A series has `name` (blank = \"Series N\"), `values` (one per label: missing = 0, extra ignored, a non-number reads as 0; NaN and\n * Infinity are kept as in a single series), optional `color` (default: `BAR_CHART_SERIES_PALETTE`, cycled) and `hoverColor`\n * (default: a darker shade of the colour, see `darkenColor`; unparseable colours keep the colour on hover).\n * - Legend: an HTML list below the chart (swatch + name, hidden from assistive tech, wraps on phones), shown for any non-empty\n * `series` while `showLegend` is true (default). A single-series chart (`values`) never shows one.\n * - Tooltip: \"<series name>: <value>\" with the same `valueFormat` / unit rules as above (units per value for 'number').\n * - Value labels: each bar's value above it (`showValues`), only while labels x series <= 24 bars (`BAR_CHART_MAX_GROUPED_VALUE_LABELS`)\n * and never when `stacked` (the segments would overlap their labels). Tooltips and the hidden list always carry the values.\n * - Accessibility: the chart wrapper is unchanged (`role=\"img\"` + `ariaLabel`); additionally a visually hidden list holds one entry per\n * label and series (\"Jan, Income: $100.00\"), label by label, written as plain text.\n * - Changing `series`, `stacked` or the formats at runtime updates the same chart; going back to no `series` restores the single-series chart.\n *\n * - Height is `height` px (shorter on phones: 200px up to 768px wide, 160px up to 480px wide, when the default 240 is used);\n * the width follows the container. Needs the optional peer dependency `chart.js` (>= 4).\n */\n@Component({\n selector: 'ui-bar-chart',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"bc-wrap\" [class.bc-default-height]=\"height() === 240\" [style.height.px]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\" [attr.aria-label]=\"ariaLabel() || null\">\n <canvas #canvas></canvas>\n </div>\n @if (legendVisible()) {\n <ul class=\"bc-legend\" aria-hidden=\"true\">\n @for (g of resolvedSeries(); track $index) {\n <li class=\"bc-legend-item\">\n <span class=\"bc-swatch\" [style.background]=\"g.color\"></span>\n <span class=\"bc-legend-label\">{{ g.name }}</span>\n </li>\n }\n </ul>\n }\n @if (srRows().length) {\n <ul class=\"bc-sr-only\" role=\"list\">\n @for (row of srRows(); track $index) { <li>{{ row }}</li> }\n </ul>\n }\n `,\n styles: [`\n :host { display: block; position: relative; }\n .bc-wrap { position: relative; }\n .bc-legend { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 16px; }\n .bc-legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.8125rem; color: #334155; max-width: 100%; min-width: 0; }\n .bc-legend-label { min-width: 0; overflow-wrap: anywhere; word-break: break-word; }\n .bc-swatch {\n width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0;\n -webkit-print-color-adjust: exact; print-color-adjust: exact; forced-color-adjust: none;\n }\n .bc-sr-only {\n position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;\n clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; list-style: none;\n }\n @media (max-width: 768px) { .bc-wrap.bc-default-height { height: 200px !important; } }\n @media (max-width: 480px) { .bc-wrap.bc-default-height { height: 160px !important; } .bc-legend { gap: 4px 12px; } }\n `],\n})\nexport class BarChartComponent {\n /** Category labels, left to right (one per bar). */\n readonly labels = input<string[]>([]);\n /** The bar values, in the order of `labels`. */\n readonly values = input<number[]>([]);\n /** Bar colour (any CSS colour). */\n readonly color = input('#3b82f6');\n /** Colour per bar by index (any CSS colour); missing or blank entries fall back to `color`, extra entries are ignored. Decorative only. */\n readonly colors = input<string[]>([]);\n /** Hover colour of the bars that use `color` (bars with their own `colors` entry get a darker shade of it). */\n readonly hoverColor = input('#2563eb');\n /** Chart height in px. */\n readonly height = input(240);\n /** Spoken description of the chart. */\n readonly ariaLabel = input('');\n /** Show each bar's value above it. */\n readonly showValues = input(true);\n /** Tooltip unit for exactly 1 (e.g. \"call\"). */\n readonly unitSingular = input('');\n /** Tooltip unit for any other value (e.g. \"calls\"). */\n readonly unitPlural = input('');\n /** How numbers are written on the value labels, tooltip and y axis: 'number' (default, raw numbers), 'currency' or 'percent' (values on the 0-100 scale). */\n readonly valueFormat = input<BarChartValueFormat>('number');\n /** ISO 4217 currency code for `valueFormat=\"currency\"` (default USD; an invalid code falls back to USD). */\n readonly currency = input('USD');\n /** BCP 47 locale for 'currency' / 'percent' (default en-US; an invalid tag falls back to en-US). */\n readonly locale = input('en-US');\n /** Fixed number of decimals for 'currency' / 'percent' (integer 0-6). null = the format's default. Ignored for 'number'. */\n readonly decimals = input<number | null>(null);\n /** Several series sharing `labels` (grouped side by side, or stacked). Non-empty = takes precedence over `values`, `color`, `hoverColor` and `colors`. Default: none (single series). */\n readonly series = input<BarChartSeries[]>([]);\n /** Multi-series only: true stacks the series on top of each other instead of grouping them side by side. */\n readonly stacked = input(false);\n /** Multi-series only: show the HTML legend below the chart (a single series never has one). */\n readonly showLegend = input(true);\n\n private readonly canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n private readonly zone = inject(NgZone);\n private chart: Chart<'bar'> | null = null;\n private numberTickCallback: unknown;\n /** Whether the chart currently holds grouped datasets (null = not created yet). */\n private multiMode: boolean | null = null;\n\n /** null for 'number' (original behaviour); otherwise the formatters, rebuilt only when an input changes. */\n protected readonly formatter = computed((): ValueFormatter | null => {\n const kind = this.valueFormat();\n if (kind !== 'currency' && kind !== 'percent') return null;\n const locale = safeLocale(this.locale());\n const d = this.decimals();\n const fixed = typeof d === 'number' && Number.isFinite(d) ? Math.min(6, Math.max(0, Math.trunc(d))) : null;\n const digits = (min: number, max: number): Intl.NumberFormatOptions => ({ minimumFractionDigits: min, maximumFractionDigits: max });\n // signDisplay 'negative' = no sign on a value that rounds to zero (no \"-$0.00\")\n // ('negative' is newer than the bundled TypeScript lib typings and than some older browsers: fall back to the default sign display there)\n const make = (extra: Intl.NumberFormatOptions) => {\n try { return new Intl.NumberFormat(locale, { signDisplay: 'negative', ...extra } as unknown as Intl.NumberFormatOptions); }\n catch { return new Intl.NumberFormat(locale, extra); }\n };\n if (kind === 'currency') {\n const currency = safeCurrency(this.currency());\n const base: Intl.NumberFormatOptions = { style: 'currency', currency };\n const full = make(fixed === null ? base : { ...base, ...digits(fixed, fixed) });\n const whole = fixed === null ? make({ ...base, ...digits(0, 0) }) : full;\n return {\n value: n => formatWith(full, n),\n tick: n => formatWith(Number.isInteger(n) ? whole : full, n),\n };\n }\n // percent: the values are 0-100 already, Intl multiplies by 100, so divide first\n const pct = make({ style: 'percent', ...(fixed === null ? digits(0, 1) : digits(fixed, fixed)) });\n return { value: n => formatWith(pct, n, 100), tick: n => formatWith(pct, n, 100) };\n });\n\n /** The `series` input cleaned up for drawing; empty = single-series mode. */\n protected readonly resolvedSeries = computed((): ResolvedSeries[] => {\n const list = this.series();\n if (!Array.isArray(list)) return [];\n const count = this.labels().length;\n return list.map((raw, i): ResolvedSeries => {\n const s: Partial<BarChartSeries> = raw && typeof raw === 'object' ? raw : {};\n const name = typeof s.name === 'string' && s.name.trim() ? s.name.trim() : `Series ${i + 1}`;\n const ownColor = typeof s.color === 'string' && s.color.trim() ? s.color : '';\n const color = ownColor || BAR_CHART_SERIES_PALETTE[i % BAR_CHART_SERIES_PALETTE.length];\n const ownHover = typeof s.hoverColor === 'string' && s.hoverColor.trim() ? s.hoverColor : '';\n const hover = ownHover || (darkenColor(color) ?? color);\n const source = Array.isArray(s.values) ? s.values : [];\n const data = Array.from({ length: count }, (_, k) => (typeof source[k] === 'number' ? source[k] : 0));\n return { name, data, color, hover };\n });\n });\n\n protected readonly legendVisible = computed(() => this.resolvedSeries().length > 0 && this.showLegend());\n\n /** Visually hidden text of a multi-series chart: one entry per label and series, label by label. */\n protected readonly srRows = computed((): string[] => {\n const groups = this.resolvedSeries();\n if (!groups.length) return [];\n const rows: string[] = [];\n this.labels().forEach((label, k) => groups.forEach(g => rows.push(`${label}, ${g.name}: ${this.tooltipText(g.data[k])}`)));\n return rows;\n });\n\n private readonly tooltipText = (value: number): string => {\n const f = this.formatter();\n if (f) return f.value(value);\n const unit = value === 1 ? this.unitSingular() : this.unitPlural();\n return unit ? `${value} ${unit}` : String(value);\n };\n\n protected readonly model = computed(() => ({\n groups: this.resolvedSeries(),\n stacked: this.stacked(),\n labels: this.labels(),\n data: this.values(),\n color: this.color(),\n colors: this.colors(),\n hoverColor: this.hoverColor(),\n showValues: this.showValues(),\n formatter: this.formatter(),\n }));\n\n /** Fill and hover colours: single values when no `colors` are set (unchanged behaviour), otherwise one entry per bar. */\n private static fills(s: { data: number[]; color: string; colors: string[]; hoverColor: string }): { bg: string | string[]; hover: string | string[] } {\n if (!s.colors?.length) return { bg: s.color, hover: s.hoverColor };\n const bg: string[] = [], hover: string[] = [];\n for (let i = 0; i < s.data.length; i++) {\n const own = typeof s.colors[i] === 'string' ? s.colors[i].trim() : '';\n if (!own) { bg.push(s.color); hover.push(s.hoverColor); continue; }\n bg.push(s.colors[i]);\n hover.push(darkenColor(own) ?? s.colors[i]);\n }\n return { bg, hover };\n }\n\n private static groupDataset(g: ResolvedSeries): ChartDataset<'bar'> {\n return { label: g.name, data: g.data, backgroundColor: g.color, hoverBackgroundColor: g.hover, borderRadius: 3, borderSkipped: false };\n }\n\n constructor() {\n effect(() => {\n const s = this.model();\n const multi = s.groups.length > 0;\n const fills = BarChartComponent.fills(s);\n const freshDatasets = (): ChartDataset<'bar'>[] => multi\n ? s.groups.map(BarChartComponent.groupDataset)\n : [{ data: s.data, backgroundColor: fills.bg, hoverBackgroundColor: fills.hover, borderRadius: 3, borderSkipped: false }];\n const canvas = this.canvas().nativeElement;\n if (!this.chart) {\n registerChartParts();\n this.zone.runOutsideAngular(() => {\n this.chart = new Chart<'bar'>(canvas, {\n type: 'bar',\n data: { labels: s.labels, datasets: freshDatasets() },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: false,\n layout: { padding: { top: 20 } },\n plugins: {\n legend: { display: false },\n tooltip: {\n callbacks: {\n label: (item) => {\n const text = this.tooltipText(Number(item.raw));\n // grouped charts name the series (\"Income: $1,200.00\"); a single series stays just the figure\n return this.multiMode ? `${item.dataset?.label ?? ''}: ${text}` : text;\n },\n },\n },\n },\n scales: {\n y: { beginAtZero: true, ticks: { precision: 0, color: '#94a3b8', font: { size: 11 } }, grid: { color: '#e2e8f0' }, border: { display: false } },\n x: { grid: { display: false }, ticks: { maxRotation: 0, color: '#94a3b8', font: { size: 10 } }, border: { display: false } },\n },\n },\n plugins: [VALUE_LABELS],\n });\n });\n }\n const chart = this.chart!;\n if (this.numberTickCallback === undefined) {\n this.numberTickCallback = (chart.options.scales as Record<string, { ticks: { callback?: unknown } }>)['y'].ticks.callback;\n }\n chart.data.labels = s.labels;\n if (this.multiMode !== null && this.multiMode !== multi) chart.data.datasets = freshDatasets(); // single <-> grouped: start from clean datasets\n this.multiMode = multi;\n if (multi) {\n // same dataset objects by position, new ones appended, surplus ones removed\n const datasets = chart.data.datasets;\n s.groups.forEach((g, i) => { const next = BarChartComponent.groupDataset(g); if (datasets[i]) Object.assign(datasets[i], next); else datasets.push(next); });\n datasets.splice(s.groups.length);\n } else {\n const ds = chart.data.datasets[0];\n ds.data = s.data;\n ds.backgroundColor = fills.bg;\n ds.hoverBackgroundColor = fills.hover;\n }\n const axes = chart.options.scales as Record<string, { stacked?: boolean }>;\n for (const axis of ['x', 'y']) {\n if (multi || axes[axis].stacked !== undefined) axes[axis].stacked = multi ? s.stacked : undefined;\n }\n // the value labels plugin reads these at draw time (a grouped chart: only while not too dense and not stacked)\n const labelsOn = multi ? s.showValues && !s.stacked && s.labels.length * s.groups.length <= BAR_CHART_MAX_GROUPED_VALUE_LABELS : s.showValues;\n (chart.options.plugins as Record<string, unknown>)['uiBarChartValueLabels'] = { enabled: labelsOn };\n if (s.formatter) VALUE_LABEL_FORMATS.set(chart, s.formatter.value); else VALUE_LABEL_FORMATS.delete(chart);\n // 'number' keeps the original whole-number ticks and default labels; currency / percent format the ticks and may be fractional\n const ticks = (chart.options.scales as Record<string, { ticks: { precision?: number; callback?: unknown } }>)['y'].ticks;\n if (s.formatter) {\n const tick = s.formatter.tick;\n ticks.precision = undefined;\n ticks.callback = (v: number | string) => tick(Number(v));\n } else {\n ticks.precision = 0;\n // put back chart.js's own tick labelling (remembered when the chart was created)\n if (this.numberTickCallback !== undefined) ticks.callback = this.numberTickCallback;\n }\n chart.update('none');\n });\n inject(DestroyRef).onDestroy(() => { this.chart?.destroy(); this.chart = null; });\n }\n}\n","import {\n Component, ChangeDetectionStrategy, DestroyRef, ElementRef, NgZone, PLATFORM_ID, computed, effect, inject, input, viewChild,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { ArcElement, Chart, DoughnutController, Tooltip } from 'chart.js';\n\n// Registered when a chart is first created (not at import time), like ui-bar-chart. A pie is a doughnut with no cutout,\n// so only the doughnut controller is needed.\nlet registered = false;\nfunction registerChartParts(): void {\n if (registered) return;\n Chart.register(DoughnutController, ArcElement, Tooltip);\n registered = true;\n}\n\n/** Built-in slice colours (cycled). Every colour has at least 3:1 contrast against white and against a light page grey (#f7f7f5). */\nexport const PIE_CHART_PALETTE: readonly string[] = [\n '#2563eb', '#16a34a', '#d27006', '#dc2626', '#7c3aed', '#0891b2',\n '#db2777', '#4d7c0f', '#475569', '#0d9488', '#c2410c', '#4f46e5', '#a21caf', '#b45309',\n];\n\n/** Cutout of a doughnut (share of the radius left empty); a pie has none. */\nconst DOUGHNUT_CUTOUT = '60%';\nconst PIE_CUTOUT = '0%';\nconst EMPTY_COLOR = '#e2e8f0';\n\n/** One drawn slice. `index` is the position in the input `labels`, so colours stay stable when another slice drops to 0. */\nexport interface PieSlice { index: number; label: string; value: number; percent: string; color: string; }\n\n/** \"62.5%\", \"100%\", \"<0.1%\" (a non-zero share that rounds to nothing). Rounded to 1 decimal. */\nexport function formatPercent(value: number, total: number): string {\n if (!(total > 0) || !(value > 0)) return '0%';\n const rounded = Math.round((value / total) * 1000) / 10;\n return rounded === 0 ? '<0.1%' : `${rounded}%`;\n}\n\nfunction prefersReducedMotion(): boolean {\n try { return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches; } catch { return false; }\n}\n\n/**\n * Pie or doughnut chart of one series (e.g. payments by method).\n *\n * <ui-pie-chart [labels]=\"['Card', 'Cash']\" [values]=\"[5, 3]\" [doughnut]=\"true\" centerLabel=\"Payments\" centerValue=\"8\"\n * ariaLabel=\"Payments by method\" unitSingular=\"payment\" unitPlural=\"payments\" />\n *\n * - Values must be finite and non-negative; anything else counts as 0. Slices with 0 are not drawn and not in the legend.\n * Extra labels without a value count as 0; extra values without a label are ignored.\n * - Empty or all-zero data shows a neutral grey ring with \"No data\" (no tooltip).\n * - The tooltip reads \"<value> <unit> (<share>)\" (`unitSingular` for exactly 1, `unitPlural` otherwise; no unit = just the number;\n * `showPercent=false` drops the share). The legend is real HTML below the chart and wraps on phones.\n * - With an `ariaLabel` the chart is an image for assistive tech (`role=\"img\"` + label). A visually hidden list next to it always\n * gives every slice's label, value and share (plus the centre text), because an image hides its children.\n * - Animation is off when the user prefers reduced motion. Height as ui-bar-chart: `height` px, default 240 shrinks to 200px up\n * to 768px wide and 160px up to 480px wide. Needs the optional peer dependency `chart.js` (>= 4).\n * - No click output: a slice is not keyboard reachable, so selecting is left to a list or table next to the chart.\n */\n@Component({\n selector: 'ui-pie-chart',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"pc-wrap\" [class.pc-default-height]=\"height() === 240\" [style.height.px]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\" [attr.aria-label]=\"ariaLabel() || null\">\n <canvas #canvas></canvas>\n @if (isEmpty()) {\n <div class=\"pc-center\"><span class=\"pc-center-label\">No data</span></div>\n } @else if (showCenter()) {\n <div class=\"pc-center\">\n @if (centerValue()) { <span class=\"pc-center-value\">{{ centerValue() }}</span> }\n @if (centerLabel()) { <span class=\"pc-center-label\">{{ centerLabel() }}</span> }\n </div>\n }\n </div>\n @if (showLegend() && !isEmpty()) {\n <ul class=\"pc-legend\" aria-hidden=\"true\">\n @for (s of slices(); track s.index) {\n <li class=\"pc-legend-item\">\n <span class=\"pc-swatch\" [style.background]=\"s.color\"></span>\n <span class=\"pc-legend-label\">{{ s.label }}</span>\n @if (showPercent()) { <span class=\"pc-legend-percent\">{{ s.percent }}</span> }\n </li>\n }\n </ul>\n }\n <ul class=\"pc-sr-only\" role=\"list\">\n @if (showCenter() && (centerLabel() || centerValue())) { <li>{{ centerLabel() }} {{ centerValue() }}</li> }\n @if (isEmpty()) { <li>No data</li> }\n @for (s of slices(); track s.index) {\n <li>{{ s.label }}: {{ valueText(s.value) }}@if (showPercent()) { ({{ s.percent }}) }</li>\n }\n </ul>\n `,\n styles: [`\n :host { display: block; position: relative; }\n .pc-wrap { position: relative; }\n .pc-center {\n position: absolute; top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%);\n display: flex; flex-direction: column; align-items: center; text-align: center; pointer-events: none; max-width: 40%;\n }\n .pc-center-value { font-size: 1.5rem; font-weight: 700; color: #0f172a; line-height: 1.1; }\n .pc-center-label { font-size: 0.75rem; color: #475569; }\n .pc-legend { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 16px; }\n .pc-legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.8125rem; color: #334155; max-width: 100%; min-width: 0; }\n .pc-legend-label { min-width: 0; overflow-wrap: anywhere; word-break: break-word; }\n .pc-swatch {\n width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0;\n -webkit-print-color-adjust: exact; print-color-adjust: exact; forced-color-adjust: none;\n }\n .pc-legend-percent { color: #64748b; }\n .pc-sr-only {\n position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;\n clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; list-style: none;\n }\n @media (max-width: 768px) { .pc-wrap.pc-default-height { height: 200px !important; } }\n @media (max-width: 480px) {\n .pc-wrap.pc-default-height { height: 160px !important; }\n .pc-legend { gap: 4px 12px; }\n .pc-center-value { font-size: 1.125rem; }\n }\n `],\n})\nexport class PieChartComponent {\n /** Slice names. */\n readonly labels = input<string[]>([]);\n /** Slice values, in the order of `labels`. */\n readonly values = input<number[]>([]);\n /** true = ring with a centre hole instead of a full pie. */\n readonly doughnut = input(false);\n /** Per-slice CSS colours (cycled when shorter than the labels). Default: the built-in palette. */\n readonly colors = input<string[]>([]);\n /** Chart height in px (legend not included). */\n readonly height = input(240);\n /** Spoken description of the chart. */\n readonly ariaLabel = input('');\n /** Show the legend below the chart. */\n readonly showLegend = input(true);\n /** Show each slice's share in the tooltip, the legend and the hidden list. */\n readonly showPercent = input(true);\n /** Tooltip unit for exactly 1 (e.g. \"payment\"). */\n readonly unitSingular = input('');\n /** Tooltip unit for any other value (e.g. \"payments\"). */\n readonly unitPlural = input('');\n /** Doughnut only: small text in the hole (e.g. \"Total\"). */\n readonly centerLabel = input('');\n /** Doughnut only: large text in the hole (e.g. \"128\"). */\n readonly centerValue = input('');\n\n private readonly canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n private readonly zone = inject(NgZone);\n private readonly browser = isPlatformBrowser(inject(PLATFORM_ID));\n private chart: Chart<'doughnut'> | null = null;\n\n /** Drawn slices (value > 0) with their colour and share. */\n protected readonly slices = computed((): PieSlice[] => {\n const labels = this.labels();\n const values = this.values();\n const palette = this.colors().length ? this.colors() : PIE_CHART_PALETTE;\n const clean = labels.map((_, i) => { const v = values[i]; return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : 0; });\n let total = clean.reduce((a, b) => a + b, 0);\n // Huge values can overflow the sum to Infinity; shares are scale-free, so work on values relative to the largest.\n const scale = Number.isFinite(total) ? 1 : Math.max(...clean);\n if (scale !== 1) total = clean.reduce((a, b) => a + b / scale, 0);\n const out: PieSlice[] = [];\n clean.forEach((value, index) => {\n if (value > 0) out.push({ index, label: labels[index], value, percent: formatPercent(value / scale, total), color: palette[index % palette.length] });\n });\n return out;\n });\n\n protected readonly isEmpty = computed(() => this.slices().length === 0);\n protected readonly showCenter = computed(() => this.doughnut() && !this.isEmpty() && !!(this.centerLabel() || this.centerValue()));\n\n /** \"<value> <unit>\" (`unitSingular` for exactly 1, `unitPlural` otherwise; no unit = just the number). */\n protected readonly valueText = (value: number): string => {\n const unit = value === 1 ? this.unitSingular() : this.unitPlural();\n return unit ? `${value} ${unit}` : String(value);\n };\n\n private readonly tooltipText = (dataIndex: number, value: number): string => {\n const base = this.valueText(value);\n const slice = this.slices()[dataIndex];\n return this.showPercent() && slice ? `${base} (${slice.percent})` : base;\n };\n\n private readonly chartData = computed(() => {\n const empty = this.isEmpty();\n const s = this.slices();\n return {\n labels: empty ? ['No data'] : s.map(x => x.label),\n data: empty ? [1] : s.map(x => x.value),\n colors: empty ? [EMPTY_COLOR] : s.map(x => x.color),\n cutout: this.doughnut() ? DOUGHNUT_CUTOUT : PIE_CUTOUT,\n tooltip: !empty,\n };\n });\n\n constructor() {\n effect(() => {\n const d = this.chartData();\n if (!this.browser) return; // server rendering: no canvas to draw on\n const canvas = this.canvas().nativeElement;\n const reduced = prefersReducedMotion();\n if (!this.chart) {\n registerChartParts();\n this.zone.runOutsideAngular(() => {\n this.chart = new Chart(canvas, {\n type: 'doughnut',\n data: { labels: d.labels, datasets: [{ data: d.data, backgroundColor: d.colors, borderColor: '#ffffff', borderWidth: 2, hoverOffset: 4 }] },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: reduced ? false : { duration: 400 },\n cutout: d.cutout,\n plugins: {\n legend: { display: false },\n tooltip: { enabled: d.tooltip, callbacks: { label: (item) => this.tooltipText(item.dataIndex, Number(item.raw)) } },\n },\n },\n });\n });\n }\n const chart = this.chart!;\n chart.data.labels = d.labels;\n const ds = chart.data.datasets[0];\n ds.data = d.data;\n ds.backgroundColor = d.colors;\n chart.options.cutout = d.cutout;\n chart.options.animation = reduced ? false : { duration: 400 };\n chart.options.plugins!.tooltip!.enabled = d.tooltip;\n // outside the zone: an animated update would otherwise trigger change detection on every frame\n this.zone.runOutsideAngular(() => chart.update(reduced ? 'none' : undefined));\n });\n inject(DestroyRef).onDestroy(() => { this.chart?.destroy(); this.chart = null; });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"forgeroute-ui-charts.mjs","names":[],"sources":["../../../projects/forgeroute-ui/charts/src/bar-chart/bar-chart.component.ts","../../../projects/forgeroute-ui/charts/src/pie-chart/pie-chart.component.ts"],"sourcesContent":["import {\n Component, ChangeDetectionStrategy, DestroyRef, ElementRef, NgZone, computed, effect, inject, input, viewChild,\n} from '@angular/core';\nimport { BarController, BarElement, CategoryScale, Chart, LinearScale, Tooltip } from 'chart.js';\nimport type { ChartDataset, Plugin } from 'chart.js';\nimport { UiLabelsService, uiCurrencyFormatter } from '@forgeroute/ui';\n\n// Registered when a chart is first created (not at import time) so apps that never use ui-bar-chart can drop chart.js from their bundle.\nlet registered = false;\nfunction registerChartParts(): void {\n if (registered) return;\n Chart.register(BarController, BarElement, CategoryScale, LinearScale, Tooltip);\n registered = true;\n}\n\nconst HEX_RE = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;\nconst RGB_RE = /^rgba?\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*(?:,\\s*(\\d*\\.?\\d+)\\s*)?\\)$/i;\nconst HOVER_DARKEN = 0.8;\n\n/**\n * A darker shade (each channel x 0.8) of a hex (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`) or comma-separated `rgb()` / `rgba()` colour,\n * returned as `rgb(r, g, b)` or `rgba(r, g, b, a)`. Returns `null` for anything else (named colours, `var(--x)`, `hsl()`, out-of-range\n * channels, malformed text) because those can't be computed without a browser.\n */\nexport function darkenColor(input: string): string | null {\n const c = (input ?? '').trim();\n let r: number, g: number, b: number, a: number | null = null;\n const hex = HEX_RE.exec(c);\n if (hex) {\n let h = hex[1];\n if (h.length <= 4) h = h.split('').map(ch => ch + ch).join('');\n r = parseInt(h.slice(0, 2), 16); g = parseInt(h.slice(2, 4), 16); b = parseInt(h.slice(4, 6), 16);\n if (h.length === 8) a = Math.round((parseInt(h.slice(6, 8), 16) / 255) * 1000) / 1000;\n } else {\n const m = RGB_RE.exec(c);\n if (!m) return null;\n r = Number(m[1]); g = Number(m[2]); b = Number(m[3]);\n if (r > 255 || g > 255 || b > 255) return null;\n if (m[4] !== undefined) { a = Number(m[4]); if (a > 1) return null; }\n }\n const d = (n: number) => Math.round(n * HOVER_DARKEN);\n return a === null ? `rgb(${d(r)}, ${d(g)}, ${d(b)})` : `rgba(${d(r)}, ${d(g)}, ${d(b)}, ${a})`;\n}\n\n/** How the chart writes numbers: one for value labels and tooltips, one for the y axis ticks. */\ninterface ValueFormatter {\n value: (n: number) => string;\n tick: (n: number) => string;\n}\n\nexport type BarChartValueFormat = 'number' | 'currency' | 'percent';\n\n/** One series of a grouped / stacked bar chart: its legend name, one value per label, and optional colours (any CSS colour). */\nexport interface BarChartSeries { name: string; values: number[]; color?: string; hoverColor?: string; }\n\n/**\n * Default series colours of a multi-series chart (cycled). Same values as the pie chart's palette (kept as a local copy so a bar-only\n * app never pulls the pie chart's chart.js parts into its bundle); each has at least 3:1 contrast against white.\n */\nexport const BAR_CHART_SERIES_PALETTE: readonly string[] = [\n '#2563eb', '#16a34a', '#d27006', '#dc2626', '#7c3aed', '#0891b2',\n '#db2777', '#4d7c0f', '#475569', '#0d9488', '#c2410c', '#4f46e5', '#a21caf', '#b45309',\n];\n\n/** A multi-series chart draws value labels only up to this many bars in total (labels x series); denser groups overlap. */\nexport const BAR_CHART_MAX_GROUPED_VALUE_LABELS = 24;\n\n/** A series after clean-up: trimmed or fallback name, colours resolved, exactly one number per label. */\ninterface ResolvedSeries { name: string; data: number[]; color: string; hover: string; }\n\n/** Intl.NumberFormat that never throws: a rejected option set returns null. */\nfunction tryFormat(locale: string, options?: Intl.NumberFormatOptions): Intl.NumberFormat | null {\n try { return new Intl.NumberFormat(locale, options); } catch { return null; }\n}\n\n/** The given locale when Intl accepts it (malformed tags are rejected), otherwise `en-US`. */\nfunction safeLocale(locale: string): string {\n const l = (locale ?? '').trim();\n return l && tryFormat(l) ? l : 'en-US';\n}\n\n/** The given ISO 4217 code (trimmed, upper-cased) when Intl accepts it (not exactly 3 letters = rejected), otherwise `USD`. */\nfunction safeCurrency(currency: string): string {\n const c = (currency ?? '').trim().toUpperCase();\n return c && tryFormat('en-US', { style: 'currency', currency: c }) ? c : 'USD';\n}\n\n/** Format one number; non-finite values read as plain text (NaN, Infinity), zero never shows a sign (no \"-$0.00\"). */\nfunction formatWith(nf: Pick<Intl.NumberFormat, 'format'>, n: number, scale = 1): string {\n if (!Number.isFinite(n)) return String(n);\n return nf.format(n === 0 ? 0 : n / scale);\n}\n\n/** Value-label formatters per chart. Kept out of `chart.options`: Chart.js treats a function option as scriptable and would call it with its own context. */\nconst VALUE_LABEL_FORMATS = new WeakMap<object, (n: number) => string>();\n\n/** Draws each bar's value above it (nothing for a 0 bar), written by `valueFormat` when that is not 'number'. Local to ui-bar-chart so no chart.js plugin package is needed. */\nconst VALUE_LABELS: Plugin<'bar'> = {\n id: 'uiBarChartValueLabels',\n afterDatasetsDraw(chart) {\n const opts = (chart.options.plugins as Record<string, { enabled?: boolean } | undefined>)['uiBarChartValueLabels'];\n const format = VALUE_LABEL_FORMATS.get(chart);\n if (opts?.enabled === false) return;\n const ctx = chart.ctx;\n ctx.save();\n ctx.fillStyle = '#475569';\n ctx.font = '600 11px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'bottom';\n // one dataset (single series) or several (grouped): dataset by dataset, hidden datasets skipped\n chart.data.datasets.forEach((dataset, di) => {\n if (!chart.isDatasetVisible(di)) return;\n chart.getDatasetMeta(di).data.forEach((bar, i) => {\n const value = dataset.data[i];\n if (typeof value === 'number' && Number.isFinite(value) && value !== 0) ctx.fillText(format ? format(value) : String(value), bar.x, bar.y - 2);\n });\n });\n ctx.restore();\n },\n};\n\n/**\n * Bar chart of one series (e.g. calls per day). Pass the category `labels` and the matching `values`.\n *\n * <ui-bar-chart [labels]=\"['8/1', '8/2']\" [values]=\"[3, 0]\" ariaLabel=\"Daily call volume\"\n * unitSingular=\"call\" unitPlural=\"calls\" />\n *\n * - Bars show their value above them (`showValues`, a 0 bar shows nothing), the y axis starts at 0 and counts whole numbers.\n * - The tooltip reads \"<value> <unit>\" (`unitSingular` for 1, `unitPlural` otherwise; no unit = just the number).\n * - With an `ariaLabel` the chart is an image for assistive tech (`role=\"img\"` + that label; without a label no role is set, as an\n * unnamed image is an accessibility error); render the same figures as text next to it\n * (e.g. a visually hidden list) when the exact values matter.\n * - Per-bar colours: `colors` gives bar i the colour `colors[i]` (any CSS colour, passed through untouched). Bars without an entry\n * (list shorter than the bars, or a blank/whitespace entry) use `color`; extra entries are ignored. With no `colors` every bar uses\n * `color` exactly as before. Colours are decorative only: never rely on colour alone, the values are always available as the\n * value labels and tooltips (and in your own text alternative).\n *\n * <ui-bar-chart [labels]=\"['New', 'Won', 'Lost']\" [values]=\"[4, 2, 1]\" [colors]=\"['#3b82f6', '#16a34a', '#dc2626']\" />\n *\n * - Number format: `valueFormat` ('number' default | 'currency' | 'percent') writes the value labels, the tooltip and the y axis ticks.\n * 'number' is the original behaviour (raw numbers, whole-number ticks, \"<value> <unit>\" tooltip); `currency`, `locale` and `decimals`\n * are ignored for it.\n *\n * <ui-bar-chart [labels]=\"months\" [values]=\"revenue\" valueFormat=\"currency\" ariaLabel=\"Revenue by month\" />\n * <ui-bar-chart [labels]=\"months\" [values]=\"revenue\" valueFormat=\"currency\" currency=\"EUR\" locale=\"de-DE\" [decimals]=\"0\" />\n * <ui-bar-chart [labels]=\"months\" [values]=\"rates\" valueFormat=\"percent\" />\n *\n * - 'currency': `$1,234.50` (`currency` ISO 4217, default USD; `locale` default en-US). Zero is `$0.00`, negatives `-$5.00`.\n * Default decimals are the currency's own (2 for USD). Axis ticks are not forced to whole numbers (values may be fractional);\n * with no `decimals` a whole-number tick drops its cents (`$500`) and a fractional one keeps them (`$0.50`); an explicit `decimals`\n * is used for every tick.\n * - 'percent': the values are ALREADY percentages on the 0-100 scale, not fractions: 12.5 is `12.5%`, 100 is `100%`. Default is up\n * to 1 decimal (12 -> `12%`, 12.5 -> `12.5%`); `decimals` fixes the number of decimals (0 -> `13%`). Ticks use the same format.\n * - The tooltip of 'currency' / 'percent' is just the formatted figure (`$1,234.50`, `12.5%`); `unitSingular` / `unitPlural` are\n * ignored. With 'number' the unit text is unchanged.\n * - `decimals` (integer, clamped to 0-6; null or non-numeric = format default). An invalid `currency` or `locale` never throws:\n * it falls back to USD / en-US. NaN and Infinity read as \"NaN\" / \"Infinity\" in the tooltip and get no value label.\n * - `ariaLabel` is never formatted by the chart: if the description carries figures, format them in the host.\n *\n * - Hover: a bar with its own colour darkens it (see `darkenColor`: hex or comma-separated rgb()/rgba() get a shade 20% darker; a\n * named colour, `var(--x)` or other unparseable value stays unchanged on hover). Bars using the fallback `color` use `hoverColor`.\n * - Several series (grouped or stacked): pass `series` (it takes precedence over `values`, `color`, `hoverColor` and `colors`, which are\n * ignored). Every series shares `labels`; bars of one label sit side by side (`stacked` = true piles them up instead).\n *\n * <ui-bar-chart [labels]=\"months\"\n * [series]=\"[{ name: 'Income', values: income, color: '#22c55e' }, { name: 'Expenses', values: expenses, color: '#ef4444' }]\"\n * valueFormat=\"currency\" ariaLabel=\"Income and expenses by month\" />\n *\n * - A series has `name` (blank = \"Series N\"), `values` (one per label: missing = 0, extra ignored, a non-number reads as 0; NaN and\n * Infinity are kept as in a single series), optional `color` (default: `BAR_CHART_SERIES_PALETTE`, cycled) and `hoverColor`\n * (default: a darker shade of the colour, see `darkenColor`; unparseable colours keep the colour on hover).\n * - Legend: an HTML list below the chart (swatch + name, hidden from assistive tech, wraps on phones), shown for any non-empty\n * `series` while `showLegend` is true (default). A single-series chart (`values`) never shows one.\n * - Tooltip: \"<series name>: <value>\" with the same `valueFormat` / unit rules as above (units per value for 'number').\n * - Value labels: each bar's value above it (`showValues`), only while labels x series <= 24 bars (`BAR_CHART_MAX_GROUPED_VALUE_LABELS`)\n * and never when `stacked` (the segments would overlap their labels). Tooltips and the hidden list always carry the values.\n * - Accessibility: the chart wrapper is unchanged (`role=\"img\"` + `ariaLabel`); additionally a visually hidden list holds one entry per\n * label and series (\"Jan, Income: $100.00\"), label by label, written as plain text.\n * - Changing `series`, `stacked` or the formats at runtime updates the same chart; going back to no `series` restores the single-series chart.\n *\n * - Height is `height` px (shorter on phones: 200px up to 768px wide, 160px up to 480px wide, when the default 240 is used);\n * the width follows the container. Needs the optional peer dependency `chart.js` (>= 4).\n */\n@Component({\n selector: 'ui-bar-chart',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"bc-wrap\" [class.bc-default-height]=\"height() === 240\" [style.height.px]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\" [attr.aria-label]=\"ariaLabel() || null\">\n <canvas #canvas></canvas>\n </div>\n @if (legendVisible()) {\n <ul class=\"bc-legend\" aria-hidden=\"true\">\n @for (g of resolvedSeries(); track $index) {\n <li class=\"bc-legend-item\">\n <span class=\"bc-swatch\" [style.background]=\"g.color\"></span>\n <span class=\"bc-legend-label\">{{ g.name }}</span>\n </li>\n }\n </ul>\n }\n @if (srRows().length) {\n <ul class=\"bc-sr-only\" role=\"list\">\n @for (row of srRows(); track $index) { <li>{{ row }}</li> }\n </ul>\n }\n `,\n styles: [`\n :host { display: block; position: relative; }\n .bc-wrap { position: relative; }\n .bc-legend { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 16px; }\n .bc-legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.8125rem; color: #334155; max-width: 100%; min-width: 0; }\n .bc-legend-label { min-width: 0; overflow-wrap: anywhere; word-break: break-word; }\n .bc-swatch {\n width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0;\n -webkit-print-color-adjust: exact; print-color-adjust: exact; forced-color-adjust: none;\n }\n .bc-sr-only {\n position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;\n clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; list-style: none;\n }\n @media (max-width: 768px) { .bc-wrap.bc-default-height { height: 200px !important; } }\n @media (max-width: 480px) { .bc-wrap.bc-default-height { height: 160px !important; } .bc-legend { gap: 4px 12px; } }\n `],\n})\nexport class BarChartComponent {\n /** Category labels, left to right (one per bar). */\n readonly labels = input<string[]>([]);\n /** The bar values, in the order of `labels`. */\n readonly values = input<number[]>([]);\n /** Bar colour (any CSS colour). */\n readonly color = input('#3b82f6');\n /** Colour per bar by index (any CSS colour); missing or blank entries fall back to `color`, extra entries are ignored. Decorative only. */\n readonly colors = input<string[]>([]);\n /** Hover colour of the bars that use `color` (bars with their own `colors` entry get a darker shade of it). */\n readonly hoverColor = input('#2563eb');\n /** Chart height in px. */\n readonly height = input(240);\n /** Spoken description of the chart. */\n readonly ariaLabel = input('');\n /** Show each bar's value above it. */\n readonly showValues = input(true);\n /** Tooltip unit for exactly 1 (e.g. \"call\"). */\n readonly unitSingular = input('');\n /** Tooltip unit for any other value (e.g. \"calls\"). */\n readonly unitPlural = input('');\n /** How numbers are written on the value labels, tooltip and y axis: 'number' (default, raw numbers), 'currency' or 'percent' (values on the 0-100 scale). */\n readonly valueFormat = input<BarChartValueFormat>('number');\n /** ISO 4217 currency code for `valueFormat=\"currency\"` (default USD; an invalid code falls back to USD). */\n readonly currency = input('USD');\n /** BCP 47 locale for 'currency' / 'percent'. `null` (default) = the app's active locale (`UiLabelsService.setLocale`, `en-US` until set); an invalid tag falls back to en-US. */\n readonly locale = input<string | null>(null);\n private readonly _ui = inject(UiLabelsService);\n /** Fixed number of decimals for 'currency' / 'percent' (integer 0-6). null = the format's default. Ignored for 'number'. */\n readonly decimals = input<number | null>(null);\n /** Several series sharing `labels` (grouped side by side, or stacked). Non-empty = takes precedence over `values`, `color`, `hoverColor` and `colors`. Default: none (single series). */\n readonly series = input<BarChartSeries[]>([]);\n /** Multi-series only: true stacks the series on top of each other instead of grouping them side by side. */\n readonly stacked = input(false);\n /** Multi-series only: show the HTML legend below the chart (a single series never has one). */\n readonly showLegend = input(true);\n\n private readonly canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n private readonly zone = inject(NgZone);\n private chart: Chart<'bar'> | null = null;\n private numberTickCallback: unknown;\n /** Whether the chart currently holds grouped datasets (null = not created yet). */\n private multiMode: boolean | null = null;\n\n /** null for 'number' (original behaviour); otherwise the formatters, rebuilt only when an input changes. */\n protected readonly formatter = computed((): ValueFormatter | null => {\n const kind = this.valueFormat();\n if (kind !== 'currency' && kind !== 'percent') return null;\n const locale = safeLocale(this.locale() ?? this._ui.locale());\n const d = this.decimals();\n const fixed = typeof d === 'number' && Number.isFinite(d) ? Math.min(6, Math.max(0, Math.trunc(d))) : null;\n const digits = (min: number, max: number): Intl.NumberFormatOptions => ({ minimumFractionDigits: min, maximumFractionDigits: max });\n // signDisplay 'negative' = no sign on a value that rounds to zero (no \"-$0.00\")\n // ('negative' is newer than the bundled TypeScript lib typings and than some older browsers: fall back to the default sign display there)\n const make = (extra: Intl.NumberFormatOptions) => {\n try { return new Intl.NumberFormat(locale, { signDisplay: 'negative', ...extra } as unknown as Intl.NumberFormatOptions); }\n catch { return new Intl.NumberFormat(locale, extra); }\n };\n if (kind === 'currency') {\n const currency = safeCurrency(this.currency());\n // US dollars: \"$12.50\", or \"US$12.50\" in a locale whose own money also uses \"$\" (es-MX, en-AU); see uiCurrencyFormatter\n const makeMoney = (extra: Intl.NumberFormatOptions) => {\n try { return uiCurrencyFormatter(currency, locale, { signDisplay: 'negative', ...extra } as unknown as Intl.NumberFormatOptions); }\n catch { return uiCurrencyFormatter(currency, locale, extra); }\n };\n const full = makeMoney(fixed === null ? {} : digits(fixed, fixed));\n const whole = fixed === null ? makeMoney(digits(0, 0)) : full;\n return {\n value: n => formatWith(full, n),\n tick: n => formatWith(Number.isInteger(n) ? whole : full, n),\n };\n }\n // percent: the values are 0-100 already, Intl multiplies by 100, so divide first\n const pct = make({ style: 'percent', ...(fixed === null ? digits(0, 1) : digits(fixed, fixed)) });\n return { value: n => formatWith(pct, n, 100), tick: n => formatWith(pct, n, 100) };\n });\n\n /** The `series` input cleaned up for drawing; empty = single-series mode. */\n protected readonly resolvedSeries = computed((): ResolvedSeries[] => {\n const list = this.series();\n if (!Array.isArray(list)) return [];\n const count = this.labels().length;\n return list.map((raw, i): ResolvedSeries => {\n const s: Partial<BarChartSeries> = raw && typeof raw === 'object' ? raw : {};\n const name = typeof s.name === 'string' && s.name.trim() ? s.name.trim() : this._ui.t('charts.seriesN', { n: i + 1 });\n const ownColor = typeof s.color === 'string' && s.color.trim() ? s.color : '';\n const color = ownColor || BAR_CHART_SERIES_PALETTE[i % BAR_CHART_SERIES_PALETTE.length];\n const ownHover = typeof s.hoverColor === 'string' && s.hoverColor.trim() ? s.hoverColor : '';\n const hover = ownHover || (darkenColor(color) ?? color);\n const source = Array.isArray(s.values) ? s.values : [];\n const data = Array.from({ length: count }, (_, k) => (typeof source[k] === 'number' ? source[k] : 0));\n return { name, data, color, hover };\n });\n });\n\n protected readonly legendVisible = computed(() => this.resolvedSeries().length > 0 && this.showLegend());\n\n /** Visually hidden text of a multi-series chart: one entry per label and series, label by label. */\n protected readonly srRows = computed((): string[] => {\n const groups = this.resolvedSeries();\n if (!groups.length) return [];\n const rows: string[] = [];\n this.labels().forEach((label, k) => groups.forEach(g => rows.push(`${label}, ${g.name}: ${this.tooltipText(g.data[k])}`)));\n return rows;\n });\n\n private readonly tooltipText = (value: number): string => {\n const f = this.formatter();\n if (f) return f.value(value);\n const unit = value === 1 ? this.unitSingular() : this.unitPlural();\n return unit ? `${value} ${unit}` : String(value);\n };\n\n protected readonly model = computed(() => ({\n groups: this.resolvedSeries(),\n stacked: this.stacked(),\n labels: this.labels(),\n data: this.values(),\n color: this.color(),\n colors: this.colors(),\n hoverColor: this.hoverColor(),\n showValues: this.showValues(),\n formatter: this.formatter(),\n }));\n\n /** Fill and hover colours: single values when no `colors` are set (unchanged behaviour), otherwise one entry per bar. */\n private static fills(s: { data: number[]; color: string; colors: string[]; hoverColor: string }): { bg: string | string[]; hover: string | string[] } {\n if (!s.colors?.length) return { bg: s.color, hover: s.hoverColor };\n const bg: string[] = [], hover: string[] = [];\n for (let i = 0; i < s.data.length; i++) {\n const own = typeof s.colors[i] === 'string' ? s.colors[i].trim() : '';\n if (!own) { bg.push(s.color); hover.push(s.hoverColor); continue; }\n bg.push(s.colors[i]);\n hover.push(darkenColor(own) ?? s.colors[i]);\n }\n return { bg, hover };\n }\n\n private static groupDataset(g: ResolvedSeries): ChartDataset<'bar'> {\n return { label: g.name, data: g.data, backgroundColor: g.color, hoverBackgroundColor: g.hover, borderRadius: 3, borderSkipped: false };\n }\n\n constructor() {\n effect(() => {\n const s = this.model();\n const multi = s.groups.length > 0;\n const fills = BarChartComponent.fills(s);\n const freshDatasets = (): ChartDataset<'bar'>[] => multi\n ? s.groups.map(BarChartComponent.groupDataset)\n : [{ data: s.data, backgroundColor: fills.bg, hoverBackgroundColor: fills.hover, borderRadius: 3, borderSkipped: false }];\n const canvas = this.canvas().nativeElement;\n if (!this.chart) {\n registerChartParts();\n this.zone.runOutsideAngular(() => {\n this.chart = new Chart<'bar'>(canvas, {\n type: 'bar',\n data: { labels: s.labels, datasets: freshDatasets() },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: false,\n layout: { padding: { top: 20 } },\n plugins: {\n legend: { display: false },\n tooltip: {\n callbacks: {\n label: (item) => {\n const text = this.tooltipText(Number(item.raw));\n // grouped charts name the series (\"Income: $1,200.00\"); a single series stays just the figure\n return this.multiMode ? `${item.dataset?.label ?? ''}: ${text}` : text;\n },\n },\n },\n },\n scales: {\n y: { beginAtZero: true, ticks: { precision: 0, color: '#94a3b8', font: { size: 11 } }, grid: { color: '#e2e8f0' }, border: { display: false } },\n x: { grid: { display: false }, ticks: { maxRotation: 0, color: '#94a3b8', font: { size: 10 } }, border: { display: false } },\n },\n },\n plugins: [VALUE_LABELS],\n });\n });\n }\n const chart = this.chart!;\n if (this.numberTickCallback === undefined) {\n this.numberTickCallback = (chart.options.scales as Record<string, { ticks: { callback?: unknown } }>)['y'].ticks.callback;\n }\n chart.data.labels = s.labels;\n if (this.multiMode !== null && this.multiMode !== multi) chart.data.datasets = freshDatasets(); // single <-> grouped: start from clean datasets\n this.multiMode = multi;\n if (multi) {\n // same dataset objects by position, new ones appended, surplus ones removed\n const datasets = chart.data.datasets;\n s.groups.forEach((g, i) => { const next = BarChartComponent.groupDataset(g); if (datasets[i]) Object.assign(datasets[i], next); else datasets.push(next); });\n datasets.splice(s.groups.length);\n } else {\n const ds = chart.data.datasets[0];\n ds.data = s.data;\n ds.backgroundColor = fills.bg;\n ds.hoverBackgroundColor = fills.hover;\n }\n const axes = chart.options.scales as Record<string, { stacked?: boolean }>;\n for (const axis of ['x', 'y']) {\n if (multi || axes[axis].stacked !== undefined) axes[axis].stacked = multi ? s.stacked : undefined;\n }\n // the value labels plugin reads these at draw time (a grouped chart: only while not too dense and not stacked)\n const labelsOn = multi ? s.showValues && !s.stacked && s.labels.length * s.groups.length <= BAR_CHART_MAX_GROUPED_VALUE_LABELS : s.showValues;\n (chart.options.plugins as Record<string, unknown>)['uiBarChartValueLabels'] = { enabled: labelsOn };\n if (s.formatter) VALUE_LABEL_FORMATS.set(chart, s.formatter.value); else VALUE_LABEL_FORMATS.delete(chart);\n // 'number' keeps the original whole-number ticks and default labels; currency / percent format the ticks and may be fractional\n const ticks = (chart.options.scales as Record<string, { ticks: { precision?: number; callback?: unknown } }>)['y'].ticks;\n if (s.formatter) {\n const tick = s.formatter.tick;\n ticks.precision = undefined;\n ticks.callback = (v: number | string) => tick(Number(v));\n } else {\n ticks.precision = 0;\n // put back chart.js's own tick labelling (remembered when the chart was created)\n if (this.numberTickCallback !== undefined) ticks.callback = this.numberTickCallback;\n }\n chart.update('none');\n });\n inject(DestroyRef).onDestroy(() => { this.chart?.destroy(); this.chart = null; });\n }\n}\n","import {\n Component, ChangeDetectionStrategy, DestroyRef, ElementRef, NgZone, PLATFORM_ID, computed, effect, inject, input, viewChild,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { ArcElement, Chart, DoughnutController, Tooltip } from 'chart.js';\nimport { UiLabelsService, uiFormatNumber, uiLocale } from '@forgeroute/ui';\n\n// Registered when a chart is first created (not at import time), like ui-bar-chart. A pie is a doughnut with no cutout,\n// so only the doughnut controller is needed.\nlet registered = false;\nfunction registerChartParts(): void {\n if (registered) return;\n Chart.register(DoughnutController, ArcElement, Tooltip);\n registered = true;\n}\n\n/** Built-in slice colours (cycled). Every colour has at least 3:1 contrast against white and against a light page grey (#f7f7f5). */\nexport const PIE_CHART_PALETTE: readonly string[] = [\n '#2563eb', '#16a34a', '#d27006', '#dc2626', '#7c3aed', '#0891b2',\n '#db2777', '#4d7c0f', '#475569', '#0d9488', '#c2410c', '#4f46e5', '#a21caf', '#b45309',\n];\n\n/** Cutout of a doughnut (share of the radius left empty); a pie has none. */\nconst DOUGHNUT_CUTOUT = '60%';\nconst PIE_CUTOUT = '0%';\nconst EMPTY_COLOR = '#e2e8f0';\n\n/** One drawn slice. `index` is the position in the input `labels`, so colours stay stable when another slice drops to 0. */\nexport interface PieSlice { index: number; label: string; value: number; percent: string; color: string; }\n\n/**\n * \"62.5%\", \"100%\", \"<0.1%\" (a non-zero share that rounds to nothing). Rounded to 1 decimal, written with the decimal mark of `locale`\n * (default: the app's active locale, so \"62,5%\" in a locale that uses a comma).\n */\nexport function formatPercent(value: number, total: number, locale: string = uiLocale()): string {\n if (!(total > 0) || !(value > 0)) return '0%';\n const rounded = Math.round((value / total) * 1000) / 10;\n const num = (n: number) => uiFormatNumber(n, locale, { maximumFractionDigits: 1, useGrouping: false });\n return rounded === 0 ? `<${num(0.1)}%` : `${num(rounded)}%`;\n}\n\nfunction prefersReducedMotion(): boolean {\n try { return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches; } catch { return false; }\n}\n\n/**\n * Pie or doughnut chart of one series (e.g. payments by method).\n *\n * <ui-pie-chart [labels]=\"['Card', 'Cash']\" [values]=\"[5, 3]\" [doughnut]=\"true\" centerLabel=\"Payments\" centerValue=\"8\"\n * ariaLabel=\"Payments by method\" unitSingular=\"payment\" unitPlural=\"payments\" />\n *\n * - Values must be finite and non-negative; anything else counts as 0. Slices with 0 are not drawn and not in the legend.\n * Extra labels without a value count as 0; extra values without a label are ignored.\n * - Empty or all-zero data shows a neutral grey ring with \"No data\" (no tooltip).\n * - The tooltip reads \"<value> <unit> (<share>)\" (`unitSingular` for exactly 1, `unitPlural` otherwise; no unit = just the number;\n * `showPercent=false` drops the share). The legend is real HTML below the chart and wraps on phones.\n * - With an `ariaLabel` the chart is an image for assistive tech (`role=\"img\"` + label). A visually hidden list next to it always\n * gives every slice's label, value and share (plus the centre text), because an image hides its children.\n * - Animation is off when the user prefers reduced motion. Height as ui-bar-chart: `height` px, default 240 shrinks to 200px up\n * to 768px wide and 160px up to 480px wide. Needs the optional peer dependency `chart.js` (>= 4).\n * - No click output: a slice is not keyboard reachable, so selecting is left to a list or table next to the chart.\n */\n@Component({\n selector: 'ui-pie-chart',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"pc-wrap\" [class.pc-default-height]=\"height() === 240\" [style.height.px]=\"height()\"\n [attr.role]=\"ariaLabel() ? 'img' : null\" [attr.aria-label]=\"ariaLabel() || null\">\n <canvas #canvas></canvas>\n @if (isEmpty()) {\n <div class=\"pc-center\"><span class=\"pc-center-label\">{{ ui.t('chartsPieChart.noData') }}</span></div>\n } @else if (showCenter()) {\n <div class=\"pc-center\">\n @if (centerValue()) { <span class=\"pc-center-value\">{{ centerValue() }}</span> }\n @if (centerLabel()) { <span class=\"pc-center-label\">{{ centerLabel() }}</span> }\n </div>\n }\n </div>\n @if (showLegend() && !isEmpty()) {\n <ul class=\"pc-legend\" aria-hidden=\"true\">\n @for (s of slices(); track s.index) {\n <li class=\"pc-legend-item\">\n <span class=\"pc-swatch\" [style.background]=\"s.color\"></span>\n <span class=\"pc-legend-label\">{{ s.label }}</span>\n @if (showPercent()) { <span class=\"pc-legend-percent\">{{ s.percent }}</span> }\n </li>\n }\n </ul>\n }\n <ul class=\"pc-sr-only\" role=\"list\">\n @if (showCenter() && (centerLabel() || centerValue())) { <li>{{ centerLabel() }} {{ centerValue() }}</li> }\n @if (isEmpty()) { <li>{{ ui.t('chartsPieChart.noData') }}</li> }\n @for (s of slices(); track s.index) {\n <li>{{ s.label }}: {{ valueText(s.value) }}@if (showPercent()) { ({{ s.percent }}) }</li>\n }\n </ul>\n `,\n styles: [`\n :host { display: block; position: relative; }\n .pc-wrap { position: relative; }\n .pc-center {\n position: absolute; top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%);\n display: flex; flex-direction: column; align-items: center; text-align: center; pointer-events: none; max-width: 40%;\n }\n .pc-center-value { font-size: 1.5rem; font-weight: 700; color: #0f172a; line-height: 1.1; }\n .pc-center-label { font-size: 0.75rem; color: #475569; }\n .pc-legend { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 16px; }\n .pc-legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.8125rem; color: #334155; max-width: 100%; min-width: 0; }\n .pc-legend-label { min-width: 0; overflow-wrap: anywhere; word-break: break-word; }\n .pc-swatch {\n width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0;\n -webkit-print-color-adjust: exact; print-color-adjust: exact; forced-color-adjust: none;\n }\n .pc-legend-percent { color: #64748b; }\n .pc-sr-only {\n position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;\n clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; list-style: none;\n }\n @media (max-width: 768px) { .pc-wrap.pc-default-height { height: 200px !important; } }\n @media (max-width: 480px) {\n .pc-wrap.pc-default-height { height: 160px !important; }\n .pc-legend { gap: 4px 12px; }\n .pc-center-value { font-size: 1.125rem; }\n }\n `],\n})\nexport class PieChartComponent {\n protected readonly ui = inject(UiLabelsService);\n /** Slice names. */\n readonly labels = input<string[]>([]);\n /** Slice values, in the order of `labels`. */\n readonly values = input<number[]>([]);\n /** true = ring with a centre hole instead of a full pie. */\n readonly doughnut = input(false);\n /** Per-slice CSS colours (cycled when shorter than the labels). Default: the built-in palette. */\n readonly colors = input<string[]>([]);\n /** Chart height in px (legend not included). */\n readonly height = input(240);\n /** Spoken description of the chart. */\n readonly ariaLabel = input('');\n /** Show the legend below the chart. */\n readonly showLegend = input(true);\n /** Show each slice's share in the tooltip, the legend and the hidden list. */\n readonly showPercent = input(true);\n /** Tooltip unit for exactly 1 (e.g. \"payment\"). */\n readonly unitSingular = input('');\n /** Tooltip unit for any other value (e.g. \"payments\"). */\n readonly unitPlural = input('');\n /** Doughnut only: small text in the hole (e.g. \"Total\"). */\n readonly centerLabel = input('');\n /** Doughnut only: large text in the hole (e.g. \"128\"). */\n readonly centerValue = input('');\n\n private readonly canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n private readonly zone = inject(NgZone);\n private readonly browser = isPlatformBrowser(inject(PLATFORM_ID));\n private chart: Chart<'doughnut'> | null = null;\n\n /** Drawn slices (value > 0) with their colour and share. */\n protected readonly slices = computed((): PieSlice[] => {\n const labels = this.labels();\n const values = this.values();\n const palette = this.colors().length ? this.colors() : PIE_CHART_PALETTE;\n const clean = labels.map((_, i) => { const v = values[i]; return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : 0; });\n let total = clean.reduce((a, b) => a + b, 0);\n // Huge values can overflow the sum to Infinity; shares are scale-free, so work on values relative to the largest.\n const scale = Number.isFinite(total) ? 1 : Math.max(...clean);\n if (scale !== 1) total = clean.reduce((a, b) => a + b / scale, 0);\n const out: PieSlice[] = [];\n clean.forEach((value, index) => {\n if (value > 0) out.push({ index, label: labels[index], value, percent: formatPercent(value / scale, total, this.ui.locale()), color: palette[index % palette.length] });\n });\n return out;\n });\n\n protected readonly isEmpty = computed(() => this.slices().length === 0);\n protected readonly showCenter = computed(() => this.doughnut() && !this.isEmpty() && !!(this.centerLabel() || this.centerValue()));\n\n /** \"<value> <unit>\" (`unitSingular` for exactly 1, `unitPlural` otherwise; no unit = just the number). */\n protected readonly valueText = (value: number): string => {\n const unit = value === 1 ? this.unitSingular() : this.unitPlural();\n return unit ? `${value} ${unit}` : String(value);\n };\n\n private readonly tooltipText = (dataIndex: number, value: number): string => {\n const base = this.valueText(value);\n const slice = this.slices()[dataIndex];\n return this.showPercent() && slice ? `${base} (${slice.percent})` : base;\n };\n\n private readonly chartData = computed(() => {\n const empty = this.isEmpty();\n const s = this.slices();\n return {\n labels: empty ? [this.ui.t('chartsPieChart.noData')] : s.map(x => x.label),\n data: empty ? [1] : s.map(x => x.value),\n colors: empty ? [EMPTY_COLOR] : s.map(x => x.color),\n cutout: this.doughnut() ? DOUGHNUT_CUTOUT : PIE_CUTOUT,\n tooltip: !empty,\n };\n });\n\n constructor() {\n effect(() => {\n const d = this.chartData();\n if (!this.browser) return; // server rendering: no canvas to draw on\n const canvas = this.canvas().nativeElement;\n const reduced = prefersReducedMotion();\n if (!this.chart) {\n registerChartParts();\n this.zone.runOutsideAngular(() => {\n this.chart = new Chart(canvas, {\n type: 'doughnut',\n data: { labels: d.labels, datasets: [{ data: d.data, backgroundColor: d.colors, borderColor: '#ffffff', borderWidth: 2, hoverOffset: 4 }] },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: reduced ? false : { duration: 400 },\n cutout: d.cutout,\n plugins: {\n legend: { display: false },\n tooltip: { enabled: d.tooltip, callbacks: { label: (item) => this.tooltipText(item.dataIndex, Number(item.raw)) } },\n },\n },\n });\n });\n }\n const chart = this.chart!;\n chart.data.labels = d.labels;\n const ds = chart.data.datasets[0];\n ds.data = d.data;\n ds.backgroundColor = d.colors;\n chart.options.cutout = d.cutout;\n chart.options.animation = reduced ? false : { duration: 400 };\n chart.options.plugins!.tooltip!.enabled = d.tooltip;\n // outside the zone: an animated update would otherwise trigger change detection on every frame\n this.zone.runOutsideAngular(() => chart.update(reduced ? 'none' : undefined));\n });\n inject(DestroyRef).onDestroy(() => { this.chart?.destroy(); this.chart = null; });\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, Component, ElementRef, computed, inject, input, output, signal } from "@angular/core";
3
+ import { UiLabelsService, usStateName } from "@forgeroute/ui";
3
4
  const US_MAP_VIEWBOX = "192 9 1028 746";
4
5
  const US_STATE_PATHS = [
5
6
  {
@@ -418,17 +419,13 @@ const SMALL_STATE_IDS = [
418
419
  ];
419
420
  const STATES_BY_NAME = [...US_STATE_PATHS].sort((a, b) => a.name.localeCompare(b.name, "en-US"));
420
421
  const VALID_IDS = new Set(US_STATE_PATHS.map((s) => s.id));
421
- let numberFormat = null;
422
- function formatNumber(n) {
423
- numberFormat ??= new Intl.NumberFormat("en-US");
424
- return numberFormat.format(n);
425
- }
426
422
  const normId = (v) => {
427
423
  const id = (v ?? "").trim().toUpperCase();
428
424
  return VALID_IDS.has(id) ? id : null;
429
425
  };
430
426
  var UsStateMapComponent = class UsStateMapComponent {
431
427
  constructor() {
428
+ this.labels = inject(UiLabelsService);
432
429
  this.data = input({}, ...ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []);
433
430
  this.selectedState = input(null, ...ngDevMode ? [{ debugName: "selectedState" }] : /* istanbul ignore next */ []);
434
431
  this.focusState = input(null, ...ngDevMode ? [{ debugName: "focusState" }] : /* istanbul ignore next */ []);
@@ -438,7 +435,11 @@ var UsStateMapComponent = class UsStateMapComponent {
438
435
  this.colorHigh = input(DEFAULT_HIGH, ...ngDevMode ? [{ debugName: "colorHigh" }] : /* istanbul ignore next */ []);
439
436
  this.showLegend = input(true, ...ngDevMode ? [{ debugName: "showLegend" }] : /* istanbul ignore next */ []);
440
437
  this.showSmallStateButtons = input(false, ...ngDevMode ? [{ debugName: "showSmallStateButtons" }] : /* istanbul ignore next */ []);
441
- this.ariaLabel = input("US state map", ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
438
+ this.ariaLabelInput = input(null, {
439
+ ...ngDevMode ? { debugName: "ariaLabelInput" } : /* istanbul ignore next */ {},
440
+ alias: "ariaLabel"
441
+ });
442
+ this.ariaLabel = computed(() => this.ariaLabelInput() ?? this.labels.t("mapsUsStateMap.ariaLabel"), ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
442
443
  this.maxWidth = input("42rem", ...ngDevMode ? [{ debugName: "maxWidth" }] : /* istanbul ignore next */ []);
443
444
  this.height = input("", ...ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []);
444
445
  this.stateClick = output();
@@ -472,19 +473,25 @@ var UsStateMapComponent = class UsStateMapComponent {
472
473
  const values = this.values();
473
474
  const max = this.max();
474
475
  const low = this.low(), high = this.high();
476
+ let collator;
477
+ try {
478
+ collator = new Intl.Collator(this.labels.locale());
479
+ } catch {
480
+ collator = new Intl.Collator("en-US");
481
+ }
475
482
  return STATES_BY_NAME.map((s) => {
476
483
  const value = values.get(s.id) ?? 0;
477
484
  const valueText = this.valueText(value);
478
485
  return {
479
486
  id: s.id,
480
- name: s.name,
487
+ name: usStateName(s.id, s.name),
481
488
  path: s.path,
482
489
  value,
483
490
  valueText,
484
- label: `${s.name}, ${valueText}`,
491
+ label: `${usStateName(s.id, s.name)}, ${valueText}`,
485
492
  fill: value > 0 && max > 0 ? mixColors(low, high, value / max) : mixColors(low, high, 0)
486
493
  };
487
- });
494
+ }).sort((a, b) => collator.compare(a.name, b.name));
488
495
  }, ...ngDevMode ? [{ debugName: "views" }] : /* istanbul ignore next */ []);
489
496
  this.smallViews = computed(() => this.views().filter((v) => SMALL_STATE_IDS.includes(v.id)), ...ngDevMode ? [{ debugName: "smallViews" }] : /* istanbul ignore next */ []);
490
497
  this.selectedId = computed(() => normId(this.selectedState()), ...ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []);
@@ -501,7 +508,7 @@ var UsStateMapComponent = class UsStateMapComponent {
501
508
  }, ...ngDevMode ? [{ debugName: "tip" }] : /* istanbul ignore next */ []);
502
509
  this.gradient = computed(() => `linear-gradient(to right, ${mixColors(this.low(), this.high(), 0)}, ${mixColors(this.low(), this.high(), 1)})`, ...ngDevMode ? [{ debugName: "gradient" }] : /* istanbul ignore next */ []);
503
510
  this.maxText = computed(() => this.valueText(this.max()), ...ngDevMode ? [{ debugName: "maxText" }] : /* istanbul ignore next */ []);
504
- this.legendLabel = computed(() => `Colour scale from 0 to ${this.maxText()}`, ...ngDevMode ? [{ debugName: "legendLabel" }] : /* istanbul ignore next */ []);
511
+ this.legendLabel = computed(() => this.labels.t("mapsUsStateMap.legend", { max: this.maxText() }), ...ngDevMode ? [{ debugName: "legendLabel" }] : /* istanbul ignore next */ []);
505
512
  }
506
513
  unit(n) {
507
514
  const one = this.unitSingular().trim();
@@ -510,7 +517,8 @@ var UsStateMapComponent = class UsStateMapComponent {
510
517
  }
511
518
  valueText(n) {
512
519
  const unit = this.unit(n);
513
- return unit ? `${formatNumber(n)} ${unit}` : formatNumber(n);
520
+ const number = this.labels.formatNumber(n);
521
+ return unit ? `${number} ${unit}` : number;
514
522
  }
515
523
  opacityOf(id) {
516
524
  const selected = this.selectedId();
@@ -667,8 +675,8 @@ var UsStateMapComponent = class UsStateMapComponent {
667
675
  isRequired: false,
668
676
  transformFunction: null
669
677
  },
670
- ariaLabel: {
671
- classPropertyName: "ariaLabel",
678
+ ariaLabelInput: {
679
+ classPropertyName: "ariaLabelInput",
672
680
  publicName: "ariaLabel",
673
681
  isSignal: true,
674
682
  isRequired: false,
@@ -732,7 +740,7 @@ var UsStateMapComponent = class UsStateMapComponent {
732
740
  </div>
733
741
 
734
742
  @if (showSmallStateButtons()) {
735
- <div class="usm-chips" role="group" aria-label="Small states">
743
+ <div class="usm-chips" role="group" [attr.aria-label]="labels.t('mapsUsStateMap.smallStates')">
736
744
  @for (s of smallViews(); track s.id) {
737
745
  <button type="button" class="usm-chip" [class.usm-chip-selected]="s.id === selectedId()"
738
746
  [attr.data-chip]="s.id" [attr.aria-label]="s.label" [attr.aria-pressed]="s.id === selectedId() ? 'true' : 'false'"
@@ -751,7 +759,7 @@ var UsStateMapComponent = class UsStateMapComponent {
751
759
  </div>
752
760
  }
753
761
 
754
- <ul class="usm-sr-only" aria-label="Values by state">
762
+ <ul class="usm-sr-only" [attr.aria-label]="labels.t('mapsUsStateMap.valuesByState')">
755
763
  @for (s of views(); track s.id) { <li>{{ s.label }}</li> }
756
764
  </ul>
757
765
  </div>
@@ -814,7 +822,7 @@ i0.ɵɵngDeclareClassMetadata({
814
822
  </div>
815
823
 
816
824
  @if (showSmallStateButtons()) {
817
- <div class="usm-chips" role="group" aria-label="Small states">
825
+ <div class="usm-chips" role="group" [attr.aria-label]="labels.t('mapsUsStateMap.smallStates')">
818
826
  @for (s of smallViews(); track s.id) {
819
827
  <button type="button" class="usm-chip" [class.usm-chip-selected]="s.id === selectedId()"
820
828
  [attr.data-chip]="s.id" [attr.aria-label]="s.label" [attr.aria-pressed]="s.id === selectedId() ? 'true' : 'false'"
@@ -833,7 +841,7 @@ i0.ɵɵngDeclareClassMetadata({
833
841
  </div>
834
842
  }
835
843
 
836
- <ul class="usm-sr-only" aria-label="Values by state">
844
+ <ul class="usm-sr-only" [attr.aria-label]="labels.t('mapsUsStateMap.valuesByState')">
837
845
  @for (s of views(); track s.id) { <li>{{ s.label }}</li> }
838
846
  </ul>
839
847
  </div>
@@ -914,7 +922,7 @@ i0.ɵɵngDeclareClassMetadata({
914
922
  required: false
915
923
  }]
916
924
  }],
917
- ariaLabel: [{
925
+ ariaLabelInput: [{
918
926
  type: i0.Input,
919
927
  args: [{
920
928
  isSignal: true,