@skdx/angular-charts 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/fesm2022/skdx-angular-charts-area.mjs +14 -32
  2. package/fesm2022/skdx-angular-charts-area.mjs.map +1 -1
  3. package/fesm2022/skdx-angular-charts-bar.mjs +14 -38
  4. package/fesm2022/skdx-angular-charts-bar.mjs.map +1 -1
  5. package/fesm2022/skdx-angular-charts-boxplot.mjs +10 -10
  6. package/fesm2022/skdx-angular-charts-boxplot.mjs.map +1 -1
  7. package/fesm2022/skdx-angular-charts-candlestick.mjs +10 -10
  8. package/fesm2022/skdx-angular-charts-candlestick.mjs.map +1 -1
  9. package/fesm2022/skdx-angular-charts-chord.mjs +10 -10
  10. package/fesm2022/skdx-angular-charts-chord.mjs.map +1 -1
  11. package/fesm2022/skdx-angular-charts-funnel.mjs +11 -43
  12. package/fesm2022/skdx-angular-charts-funnel.mjs.map +1 -1
  13. package/fesm2022/skdx-angular-charts-gantt.mjs +10 -10
  14. package/fesm2022/skdx-angular-charts-gantt.mjs.map +1 -1
  15. package/fesm2022/skdx-angular-charts-heatmap.mjs +4 -69
  16. package/fesm2022/skdx-angular-charts-heatmap.mjs.map +1 -1
  17. package/fesm2022/skdx-angular-charts-internal.mjs +235 -411
  18. package/fesm2022/skdx-angular-charts-internal.mjs.map +1 -1
  19. package/fesm2022/skdx-angular-charts-line.mjs +13 -15
  20. package/fesm2022/skdx-angular-charts-line.mjs.map +1 -1
  21. package/fesm2022/skdx-angular-charts-map.mjs +2 -8
  22. package/fesm2022/skdx-angular-charts-map.mjs.map +1 -1
  23. package/fesm2022/skdx-angular-charts-pie.mjs +11 -21
  24. package/fesm2022/skdx-angular-charts-pie.mjs.map +1 -1
  25. package/fesm2022/skdx-angular-charts-radar.mjs +14 -77
  26. package/fesm2022/skdx-angular-charts-radar.mjs.map +1 -1
  27. package/fesm2022/skdx-angular-charts-sankey.mjs +10 -10
  28. package/fesm2022/skdx-angular-charts-sankey.mjs.map +1 -1
  29. package/fesm2022/skdx-angular-charts-scatter.mjs +13 -15
  30. package/fesm2022/skdx-angular-charts-scatter.mjs.map +1 -1
  31. package/fesm2022/skdx-angular-charts-sparkline.mjs +11 -55
  32. package/fesm2022/skdx-angular-charts-sparkline.mjs.map +1 -1
  33. package/fesm2022/skdx-angular-charts-sunburst.mjs +10 -10
  34. package/fesm2022/skdx-angular-charts-sunburst.mjs.map +1 -1
  35. package/fesm2022/skdx-angular-charts-treemap.mjs +2 -10
  36. package/fesm2022/skdx-angular-charts-treemap.mjs.map +1 -1
  37. package/fesm2022/skdx-angular-charts-waterfall.mjs +10 -10
  38. package/fesm2022/skdx-angular-charts-waterfall.mjs.map +1 -1
  39. package/fesm2022/skdx-angular-charts.mjs.map +1 -1
  40. package/package.json +2 -11
  41. package/types/skdx-angular-charts-area.d.ts +1 -1
  42. package/types/skdx-angular-charts-bar.d.ts +1 -1
  43. package/types/skdx-angular-charts-funnel.d.ts +1 -1
  44. package/types/skdx-angular-charts-heatmap.d.ts +1 -8
  45. package/types/skdx-angular-charts-internal.d.ts +26 -200
  46. package/types/skdx-angular-charts-line.d.ts +1 -1
  47. package/types/skdx-angular-charts-pie.d.ts +1 -1
  48. package/types/skdx-angular-charts-radar.d.ts +1 -8
  49. package/types/skdx-angular-charts-scatter.d.ts +1 -1
  50. package/types/skdx-angular-charts-sparkline.d.ts +1 -1
  51. package/types/skdx-angular-charts.d.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, ChangeDetectionStrategy, Component, signal, computed, Directive } from '@angular/core';
2
+ import { input, ChangeDetectionStrategy, Component, signal, Directive } from '@angular/core';
3
3
 
4
4
  /*
5
5
  * Copyright (c) 2026 SkandaDX
@@ -261,19 +261,6 @@ function buildFrame(input, yExtent) {
261
261
  categories,
262
262
  };
263
263
  }
264
- /** Attributes for the `<svg>` root every framework renders. */
265
- function svgAttrs(frame, kind, label) {
266
- return {
267
- viewBox: `0 0 ${frame.width} ${frame.height}`,
268
- width: frame.width,
269
- height: frame.height,
270
- role: 'img',
271
- 'aria-label': label,
272
- 'data-skdx-chart': kind,
273
- 'font-family': CHART_COLORS.fontFamily,
274
- 'font-size': CHART_COLORS.fontSize,
275
- };
276
- }
277
264
  const values = (input) => input.series.flatMap((s) => s.data);
278
265
  /** Tooltip text for a value in a series: `Series, Category: 1,234`. */
279
266
  const markTitle = (series, category, value) => `${series}, ${category}: ${formatValue(value)}`;
@@ -292,6 +279,75 @@ function hoverTooltip(target, svg, clientX, clientY, width, height) {
292
279
  return tooltipShapes(x, y, title, width, height);
293
280
  }
294
281
 
282
+ /*
283
+ * Copyright (c) 2026 SkandaDX
284
+ * This file is part of the SkandaDX organization.
285
+ * Licensed under the MIT License. See LICENSE in the repository root.
286
+ */
287
+ /** Grid, axis line and tick labels of an axis frame, as shapes. */
288
+ function frameShapes(frame) {
289
+ const { plot, xTicks, yTicks, baseline } = frame;
290
+ const right = plot.x + plot.width;
291
+ return [
292
+ ...yTicks.map((t) => ({
293
+ kind: 'line',
294
+ part: 'grid',
295
+ x1: plot.x,
296
+ x2: right,
297
+ y1: t.pos,
298
+ y2: t.pos,
299
+ stroke: CHART_COLORS.grid,
300
+ })),
301
+ {
302
+ kind: 'line',
303
+ part: 'axis',
304
+ x1: plot.x,
305
+ x2: right,
306
+ y1: baseline,
307
+ y2: baseline,
308
+ stroke: CHART_COLORS.axis,
309
+ },
310
+ ...yTicks.map((t) => ({
311
+ kind: 'text',
312
+ part: 'y-label',
313
+ x: plot.x - 8,
314
+ y: t.pos,
315
+ text: t.label,
316
+ anchor: 'end',
317
+ baseline: 'middle',
318
+ fill: CHART_COLORS.label,
319
+ })),
320
+ ...xTicks.map((t) => ({
321
+ kind: 'text',
322
+ part: 'x-label',
323
+ x: t.pos,
324
+ y: plot.y + plot.height + 20,
325
+ text: t.label,
326
+ anchor: 'middle',
327
+ fill: CHART_COLORS.label,
328
+ })),
329
+ ];
330
+ }
331
+ const label = (x, y, text, extra = {}) => ({
332
+ kind: 'text',
333
+ part: 'label',
334
+ x,
335
+ y,
336
+ text,
337
+ fill: CHART_COLORS.label,
338
+ ...extra,
339
+ });
340
+ /** A dot with its native tooltip. */
341
+ const point = (cx, cy, r, fill, title) => ({
342
+ kind: 'circle',
343
+ part: 'point',
344
+ cx,
345
+ cy,
346
+ r,
347
+ fill,
348
+ title,
349
+ });
350
+
295
351
  /*
296
352
  * Copyright (c) 2026 SkandaDX
297
353
  * This file is part of the SkandaDX organization.
@@ -322,19 +378,18 @@ function layoutBarChart(input) {
322
378
  const y0 = y(from);
323
379
  const y1 = y(to);
324
380
  bars.push({
325
- series: s.name,
326
- category,
327
- value,
328
- color,
329
- title: markTitle(s.name, category, value),
381
+ kind: 'rect',
382
+ part: 'bar',
330
383
  x: px(x.start(ci) + (input.stacked ? 0 : si * groupWidth)),
331
384
  y: px(Math.min(y0, y1)),
332
385
  width: px(groupWidth),
333
386
  height: px(Math.abs(y1 - y0)),
387
+ fill: color,
388
+ title: markTitle(s.name, category, value),
334
389
  });
335
390
  });
336
391
  });
337
- return { ...frame, bars };
392
+ return { ...frame, shapes: [...frameShapes(frame), ...bars, ...frame.shapes] };
338
393
  }
339
394
  function stackedExtent(input) {
340
395
  let min = 0;
@@ -361,39 +416,52 @@ function stackedExtent(input) {
361
416
  * This file is part of the SkandaDX organization.
362
417
  * Licensed under the MIT License. See LICENSE in the repository root.
363
418
  */
364
- function polylines(input, { frame, x, y, categories }) {
419
+ /** A stroke per series and a dot per value; `close` turns a stroke into a filled area. */
420
+ function polylines(input, { frame, x, y, categories }, area) {
365
421
  const points = [];
366
422
  const lines = [];
367
- const ends = [];
423
+ const areas = [];
368
424
  input.series.forEach((s, si) => {
369
425
  const color = seriesColor(si, s.color);
370
426
  const coords = s.data.map((value, ci) => {
371
- const p = {
372
- series: s.name,
373
- color,
374
- title: markTitle(s.name, categories[ci], value),
375
- x: px(x.center(ci)),
376
- y: px(y(value)),
377
- };
378
- points.push(p);
427
+ const p = { x: px(x.center(ci)), y: px(y(value)) };
428
+ points.push(point(p.x, p.y, 3, color, markTitle(s.name, categories[ci], value)));
379
429
  return p;
380
430
  });
381
- lines.push({ series: s.name, color, d: pathThrough(coords, input.curve) });
382
- ends.push({ first: px(x.center(0)), last: px(x.center(Math.max(0, s.data.length - 1))) });
431
+ const d = pathThrough(coords, input.curve);
432
+ lines.push({
433
+ kind: 'path',
434
+ part: 'line',
435
+ d,
436
+ stroke: color,
437
+ strokeWidth: 2,
438
+ strokeLinecap: 'round',
439
+ strokeLinejoin: 'round',
440
+ });
441
+ if (area) {
442
+ const first = coords[0];
443
+ const last = coords[coords.length - 1];
444
+ areas.push({
445
+ kind: 'path',
446
+ part: 'area',
447
+ d: d && first && last
448
+ ? `${d}L${last.x} ${frame.baseline}L${first.x} ${frame.baseline}Z`
449
+ : '',
450
+ fill: color,
451
+ fillOpacity: 0.25,
452
+ });
453
+ }
383
454
  });
384
- return { frame, lines, points, ends };
455
+ return {
456
+ ...frame,
457
+ shapes: [...frameShapes(frame), ...areas, ...lines, ...points, ...frame.shapes],
458
+ };
385
459
  }
386
460
  function layoutLineChart(input) {
387
- const { frame, lines, points } = polylines(input, buildFrame(input, extent(values(input), false)));
388
- return { ...frame, lines, points };
461
+ return polylines(input, buildFrame(input, extent(values(input), false)), false);
389
462
  }
390
463
  function layoutAreaChart(input) {
391
- const { frame, lines, points, ends } = polylines(input, buildFrame(input, extent(values(input), true)));
392
- const areas = lines.map((l, i) => {
393
- const { first, last } = ends[i];
394
- return { ...l, d: l.d ? `${l.d}L${last} ${frame.baseline}L${first} ${frame.baseline}Z` : '' };
395
- });
396
- return { ...frame, lines, points, areas };
464
+ return polylines(input, buildFrame(input, extent(values(input), true)), true);
397
465
  }
398
466
 
399
467
  /*
@@ -407,25 +475,14 @@ function layoutScatterChart(input) {
407
475
  const pairs = input.series.flatMap((s) => s.data);
408
476
  const x = axis(extent(pairs.map((p) => p[0]), false), [plot.x, plot.x + plot.width]);
409
477
  const y = axis(extent(pairs.map((p) => p[1]), false), [plot.y + plot.height, plot.y]);
478
+ const legend = input.series.map((s, i) => ({ name: s.name, color: seriesColor(i, s.color) }));
479
+ const c = canvas(input, width, height, legend);
410
480
  const points = input.series.flatMap((s, si) => {
411
481
  const color = seriesColor(si, s.color);
412
- return s.data.map(([vx, vy]) => ({
413
- series: s.name,
414
- color,
415
- title: `${s.name}: ${formatValue(vx)}, ${formatValue(vy)}`,
416
- x: px(x.scale(vx)),
417
- y: px(y.scale(vy)),
418
- }));
482
+ return s.data.map(([vx, vy]) => point(px(x.scale(vx)), px(y.scale(vy)), 3, color, `${s.name}: ${formatValue(vx)}, ${formatValue(vy)}`));
419
483
  });
420
- const legend = input.series.map((s, i) => ({ name: s.name, color: seriesColor(i, s.color) }));
421
- return {
422
- ...canvas(input, width, height, legend),
423
- plot,
424
- xTicks: x.ticks,
425
- yTicks: y.ticks,
426
- baseline: y.zero,
427
- points,
428
- };
484
+ const frame = { ...c, plot, xTicks: x.ticks, yTicks: y.ticks, baseline: y.zero };
485
+ return { ...c, shapes: [...frameShapes(frame), ...points, ...c.shapes] };
429
486
  }
430
487
 
431
488
  /*
@@ -465,29 +522,23 @@ function layoutPieChart(input) {
465
522
  if (input.labels && percent > 0.03) {
466
523
  const mid = (a0 + angle) / 2;
467
524
  const lr = (r + r0) / 2;
468
- labels.push({
469
- kind: 'text',
525
+ labels.push(label(px(cx + lr * Math.sin(mid)), px(cy - lr * Math.cos(mid)), `${d.name} ${Math.round(percent * 100)}%`, {
470
526
  part: 'slice-label',
471
- x: px(cx + lr * Math.sin(mid)),
472
- y: px(cy - lr * Math.cos(mid)),
473
- text: `${d.name} ${Math.round(percent * 100)}%`,
474
527
  anchor: 'middle',
475
528
  baseline: 'middle',
476
529
  fill: 'var(--skdx-color-text-on-accent)',
477
- });
530
+ }));
478
531
  }
479
532
  return {
480
- name: d.name,
481
- value: d.value,
482
- percent,
483
- series: d.name,
484
- color: seriesColor(i, d.color),
485
- title: `${d.name}: ${formatValue(d.value)} (${Math.round(percent * 100)}%)`,
533
+ kind: 'path',
534
+ part: 'slice',
486
535
  d: percent > 0 ? arc(cx, cy, r, r0, a0, angle) : '',
536
+ fill: seriesColor(i, d.color),
537
+ title: `${d.name}: ${formatValue(d.value)} (${Math.round(percent * 100)}%)`,
487
538
  };
488
539
  });
489
- const c = canvas(input, width, height, slices.map((s) => ({ name: s.name, color: s.color })));
490
- return { ...c, shapes: [...c.shapes, ...labels], slices };
540
+ const c = canvas(input, width, height, input.data.map((d, i) => ({ name: d.name, color: seriesColor(i, d.color) })));
541
+ return { ...c, shapes: [...slices, ...c.shapes, ...labels] };
491
542
  }
492
543
 
493
544
  /*
@@ -505,15 +556,25 @@ function layoutSparkline(input) {
505
556
  const line = pathThrough(coords, input.curve);
506
557
  const last = coords[coords.length - 1];
507
558
  const first = coords[0];
559
+ const color = seriesColor(0, input.color);
508
560
  return {
509
561
  width,
510
562
  height,
511
- shapes: [],
512
563
  tooltip: input.tooltip ?? true,
513
- color: seriesColor(0, input.color),
514
- line,
515
- area: line && first && last ? `${line}L${last.x} ${height - PAD$3}L${first.x} ${height - PAD$3}Z` : '',
516
- last,
564
+ shapes: [
565
+ {
566
+ kind: 'path',
567
+ part: 'area',
568
+ d: line && first && last
569
+ ? `${line}L${last.x} ${height - PAD$3}L${first.x} ${height - PAD$3}Z`
570
+ : '',
571
+ fill: color,
572
+ fillOpacity: 0.15,
573
+ },
574
+ { kind: 'path', part: 'line', d: line, stroke: color, strokeWidth: 1.5 },
575
+ // The most recent point, drawn as a dot.
576
+ ...(last ? [point(last.x, last.y, 2, color)] : []),
577
+ ],
517
578
  };
518
579
  }
519
580
 
@@ -553,44 +614,62 @@ function layoutRadarChart(input) {
553
614
  return `${p.x} ${p.y}`;
554
615
  })
555
616
  .join('L')}Z`;
617
+ // Concentric grid polygons, one per tick, innermost first, then a spoke per category.
618
+ const rings = ticks.filter((t) => t > 0);
619
+ const grid = [
620
+ ...rings.map((t) => ({
621
+ kind: 'path',
622
+ part: 'grid',
623
+ d: polygon(() => t),
624
+ stroke: CHART_COLORS.grid,
625
+ })),
626
+ ...input.categories.map((_, i) => {
627
+ const p = at(i, max);
628
+ return {
629
+ kind: 'line',
630
+ part: 'grid',
631
+ x1: px(cx),
632
+ y1: px(cy),
633
+ x2: p.x,
634
+ y2: p.y,
635
+ stroke: CHART_COLORS.grid,
636
+ };
637
+ }),
638
+ ...rings.map((t) => label(px(cx + 4), at(0, t).y, input.normalize ? `${t}%` : formatValue(t), {
639
+ part: 'y-label',
640
+ baseline: 'middle',
641
+ })),
642
+ ...input.categories.map((text, i) => {
643
+ const cos = Math.cos(angle(i));
644
+ const r = radius + 14;
645
+ return label(px(cx + r * cos), px(cy + r * Math.sin(angle(i))), text, {
646
+ part: 'x-label',
647
+ anchor: cos > 0.1 ? 'start' : cos < -0.1 ? 'end' : 'middle',
648
+ baseline: 'middle',
649
+ });
650
+ }),
651
+ ];
556
652
  const points = [];
557
653
  const polygons = input.series.map((s, si) => {
558
654
  const color = seriesColor(si, s.color);
559
655
  input.categories.forEach((category, i) => {
560
656
  const value = s.data[i] ?? 0;
561
- points.push({
562
- series: s.name,
563
- color,
564
- title: markTitle(s.name, category, value),
565
- ...at(i, norm(si, value)),
566
- });
657
+ const p = at(i, norm(si, value));
658
+ points.push(point(p.x, p.y, 3, color, markTitle(s.name, category, value)));
567
659
  });
568
- return { series: s.name, color, d: polygon((i) => norm(si, s.data[i] ?? 0)) };
660
+ return {
661
+ kind: 'path',
662
+ part: 'area',
663
+ d: polygon((i) => norm(si, s.data[i] ?? 0)),
664
+ fill: color,
665
+ fillOpacity: 0.25,
666
+ stroke: color,
667
+ strokeWidth: 2,
668
+ };
569
669
  });
570
- return {
571
- ...canvas(input, width, height, polygons.map((p) => ({ name: p.series, color: p.color }))),
572
- rings: ticks
573
- .filter((t) => t > 0)
574
- .map((t) => ({
575
- d: polygon(() => t),
576
- label: { x: px(cx + 4), y: at(0, t).y, label: input.normalize ? `${t}%` : formatValue(t) },
577
- })),
578
- spokes: input.categories.map((_, i) => ({ x1: px(cx), y1: px(cy), ...toXY(at(i, max)) })),
579
- axisLabels: input.categories.map((label, i) => {
580
- const cos = Math.cos(angle(i));
581
- const r = radius + 14;
582
- return {
583
- x: px(cx + r * cos),
584
- y: px(cy + r * Math.sin(angle(i))),
585
- label,
586
- anchor: cos > 0.1 ? 'start' : cos < -0.1 ? 'end' : 'middle',
587
- };
588
- }),
589
- polygons,
590
- points,
591
- };
670
+ const c = canvas(input, width, height, input.series.map((s, i) => ({ name: s.name, color: seriesColor(i, s.color) })));
671
+ return { ...c, shapes: [...grid, ...polygons, ...points, ...c.shapes] };
592
672
  }
593
- const toXY = (p) => ({ x2: p.x, y2: p.y });
594
673
 
595
674
  /*
596
675
  * Copyright (c) 2026 SkandaDX
@@ -610,6 +689,7 @@ function layoutHeatmap(input) {
610
689
  const negative = input.negativeColor ?? 'var(--skdx-color-danger-500)';
611
690
  const span = Math.max(Math.abs(min), Math.abs(max));
612
691
  // Diverging: opacity is the distance from zero, color the sign.
692
+ // Otherwise 0.15..1, the value's place between the smallest and largest cell.
613
693
  const opacity = (v) => input.diverging
614
694
  ? span === 0
615
695
  ? 1
@@ -624,34 +704,34 @@ function layoutHeatmap(input) {
624
704
  if (value === undefined)
625
705
  return;
626
706
  cells.push({
627
- series: row,
628
- row,
629
- column,
630
- value,
631
- color: input.diverging && value < 0 ? negative : color,
632
- opacity: opacity(value),
633
- title: `${row}, ${column}: ${formatValue(value)}`,
707
+ kind: 'rect',
708
+ part: 'cell',
634
709
  x: px(plot.x + ci * cellW),
635
710
  y: px(plot.y + ri * cellH),
636
711
  width: px(cellW),
637
712
  height: px(cellH),
713
+ fill: input.diverging && value < 0 ? negative : color,
714
+ opacity: opacity(value),
715
+ title: `${row}, ${column}: ${formatValue(value)}`,
638
716
  });
639
717
  });
640
718
  });
719
+ const c = canvas(input, width, height);
641
720
  return {
642
- ...canvas(input, width, height),
643
- plot,
644
- cells,
645
- rowLabels: input.rows.map((label, i) => ({
646
- x: plot.x - 8,
647
- y: px(plot.y + (i + 0.5) * cellH),
648
- label,
649
- })),
650
- columnLabels: input.columns.map((label, i) => ({
651
- x: px(plot.x + (i + 0.5) * cellW),
652
- y: plot.y - 8,
653
- label,
654
- })),
721
+ ...c,
722
+ shapes: [
723
+ ...input.rows.map((text, i) => label(plot.x - 8, px(plot.y + (i + 0.5) * cellH), text, {
724
+ part: 'y-label',
725
+ anchor: 'end',
726
+ baseline: 'middle',
727
+ })),
728
+ ...input.columns.map((text, i) => label(px(plot.x + (i + 0.5) * cellW), plot.y - 8, text, {
729
+ part: 'x-label',
730
+ anchor: 'middle',
731
+ })),
732
+ ...cells,
733
+ ...c.shapes,
734
+ ],
655
735
  };
656
736
  }
657
737
 
@@ -671,90 +751,34 @@ function layoutFunnelChart(input) {
671
751
  const offset = legendTop(input);
672
752
  const stageH = n === 0 ? 0 : (height - offset - 2 * PAD$2) / n;
673
753
  const widthOf = (value) => (max > 0 ? (Math.max(0, value) / max) * full : 0);
674
- const stages = input.data.map((d, i) => {
754
+ const legend = input.data.map((d, i) => ({ name: d.name, color: seriesColor(i, d.color) }));
755
+ const stages = input.data.flatMap((d, i) => {
675
756
  const top = widthOf(d.value);
676
757
  const bottom = widthOf(input.data[i + 1]?.value ?? d.value);
677
758
  const y0 = offset + PAD$2 + i * stageH;
678
759
  const y1 = y0 + stageH - GAP;
679
760
  const cx = width / 2;
680
- return {
681
- name: d.name,
682
- value: d.value,
683
- percent: first > 0 ? Math.max(0, d.value) / first : 0,
684
- series: d.name,
685
- color: seriesColor(i, d.color),
686
- title: `${d.name}: ${formatValue(d.value)} (${Math.round((first > 0 ? d.value / first : 0) * 100)}%)`,
687
- d: `M${px(cx - top / 2)} ${px(y0)}L${px(cx + top / 2)} ${px(y0)}L${px(cx + bottom / 2)} ${px(y1)}L${px(cx - bottom / 2)} ${px(y1)}Z`,
688
- x: px(cx),
689
- y: px((y0 + y1) / 2),
690
- label: `${d.name} · ${formatValue(d.value)}`,
691
- };
761
+ const percent = first > 0 ? d.value / first : 0;
762
+ return [
763
+ {
764
+ kind: 'path',
765
+ part: 'stage',
766
+ d: `M${px(cx - top / 2)} ${px(y0)}L${px(cx + top / 2)} ${px(y0)}L${px(cx + bottom / 2)} ${px(y1)}L${px(cx - bottom / 2)} ${px(y1)}Z`,
767
+ fill: seriesColor(i, d.color),
768
+ title: `${d.name}: ${formatValue(d.value)} (${Math.round(percent * 100)}%)`,
769
+ },
770
+ label(px(cx), px((y0 + y1) / 2), `${d.name} · ${formatValue(d.value)}`, {
771
+ part: 'stage-label',
772
+ anchor: 'middle',
773
+ baseline: 'middle',
774
+ fill: 'var(--skdx-color-text-on-accent)',
775
+ }),
776
+ ];
692
777
  });
693
- return {
694
- ...canvas(input, width, height, stages.map((s) => ({ name: s.name, color: s.color }))),
695
- stages,
696
- };
778
+ const c = canvas(input, width, height, legend);
779
+ return { ...c, shapes: [...stages, ...c.shapes] };
697
780
  }
698
781
 
699
- /*
700
- * Copyright (c) 2026 SkandaDX
701
- * This file is part of the SkandaDX organization.
702
- * Licensed under the MIT License. See LICENSE in the repository root.
703
- */
704
- /** Grid, axis line and tick labels of an axis frame, as shapes. */
705
- function frameShapes(frame) {
706
- const { plot, xTicks, yTicks, baseline } = frame;
707
- const right = plot.x + plot.width;
708
- return [
709
- ...yTicks.map((t) => ({
710
- kind: 'line',
711
- part: 'grid',
712
- x1: plot.x,
713
- x2: right,
714
- y1: t.pos,
715
- y2: t.pos,
716
- stroke: CHART_COLORS.grid,
717
- })),
718
- {
719
- kind: 'line',
720
- part: 'axis',
721
- x1: plot.x,
722
- x2: right,
723
- y1: baseline,
724
- y2: baseline,
725
- stroke: CHART_COLORS.axis,
726
- },
727
- ...yTicks.map((t) => ({
728
- kind: 'text',
729
- part: 'y-label',
730
- x: plot.x - 8,
731
- y: t.pos,
732
- text: t.label,
733
- anchor: 'end',
734
- baseline: 'middle',
735
- fill: CHART_COLORS.label,
736
- })),
737
- ...xTicks.map((t) => ({
738
- kind: 'text',
739
- part: 'x-label',
740
- x: t.pos,
741
- y: plot.y + plot.height + 20,
742
- text: t.label,
743
- anchor: 'middle',
744
- fill: CHART_COLORS.label,
745
- })),
746
- ];
747
- }
748
- const label = (x, y, text, extra = {}) => ({
749
- kind: 'text',
750
- part: 'label',
751
- x,
752
- y,
753
- text,
754
- fill: CHART_COLORS.label,
755
- ...extra,
756
- });
757
-
758
782
  /*
759
783
  * Copyright (c) 2026 SkandaDX
760
784
  * This file is part of the SkandaDX organization.
@@ -979,10 +1003,8 @@ function layoutTreemap(input) {
979
1003
  });
980
1004
  };
981
1005
  tile(input.data, 0, top, width, height - top, undefined);
982
- return {
983
- ...canvas(input, width, height, legend),
984
- shapes: [...shapes, ...canvas(input, width, height, legend).shapes],
985
- };
1006
+ const c = canvas(input, width, height, legend);
1007
+ return { ...c, shapes: [...shapes, ...c.shapes] };
986
1008
  }
987
1009
 
988
1010
  /*
@@ -1457,7 +1479,10 @@ class SkdxChartShapesComponent {
1457
1479
  [attr.fill]="s.fill ?? 'none'"
1458
1480
  [attr.stroke]="s.stroke"
1459
1481
  [attr.stroke-width]="s.strokeWidth"
1482
+ [attr.stroke-linecap]="s.strokeLinecap"
1483
+ [attr.stroke-linejoin]="s.strokeLinejoin"
1460
1484
  [attr.opacity]="s.opacity"
1485
+ [attr.fill-opacity]="s.fillOpacity"
1461
1486
  >
1462
1487
  @if (s.title) {
1463
1488
  <svg:title>{{ s.title }}</svg:title>
@@ -1540,7 +1565,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1540
1565
  [attr.fill]="s.fill ?? 'none'"
1541
1566
  [attr.stroke]="s.stroke"
1542
1567
  [attr.stroke-width]="s.strokeWidth"
1568
+ [attr.stroke-linecap]="s.strokeLinecap"
1569
+ [attr.stroke-linejoin]="s.strokeLinejoin"
1543
1570
  [attr.opacity]="s.opacity"
1571
+ [attr.fill-opacity]="s.fillOpacity"
1544
1572
  >
1545
1573
  @if (s.title) {
1546
1574
  <svg:title>{{ s.title }}</svg:title>
@@ -1652,7 +1680,6 @@ class SkdxChartRootComponent {
1652
1680
  (pointerleave)="tip.set([])"
1653
1681
  >
1654
1682
  <title>{{ label() }}</title>
1655
- <ng-content></ng-content>
1656
1683
  <svg:g skdxChartShapes [shapes]="layout().shapes"></svg:g>
1657
1684
  <svg:g skdxChartShapes [shapes]="tip()"></svg:g>
1658
1685
  </svg>
@@ -1682,7 +1709,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1682
1709
  (pointerleave)="tip.set([])"
1683
1710
  >
1684
1711
  <title>{{ label() }}</title>
1685
- <ng-content></ng-content>
1686
1712
  <svg:g skdxChartShapes [shapes]="layout().shapes"></svg:g>
1687
1713
  <svg:g skdxChartShapes [shapes]="tip()"></svg:g>
1688
1714
  </svg>
@@ -1690,155 +1716,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1690
1716
  }]
1691
1717
  }], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: true }] }], kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }] } });
1692
1718
  /** The `<svg>` root, grid, axis and tick labels every axis chart shares; marks are projected. */
1693
- class SkdxChartFrameComponent {
1694
- constructor() {
1695
- this.layout = input.required(/* @ts-ignore */
1696
- ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
1697
- this.kind = input.required(/* @ts-ignore */
1698
- ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
1699
- this.label = input.required(/* @ts-ignore */
1700
- ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1701
- this.colors = CHART_COLORS;
1702
- this.plot = computed(() => this.layout().plot, /* @ts-ignore */
1703
- ...(ngDevMode ? [{ debugName: "plot" }] : /* istanbul ignore next */ []));
1704
- this.right = computed(() => this.plot().x + this.plot().width, /* @ts-ignore */
1705
- ...(ngDevMode ? [{ debugName: "right" }] : /* istanbul ignore next */ []));
1706
- }
1707
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SkdxChartFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1708
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: SkdxChartFrameComponent, isStandalone: true, selector: "skdx-chart-frame", inputs: { layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: true, transformFunction: null }, kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null } }, host: { styleAttribute: "display: inline-block" }, ngImport: i0, template: `
1709
- <skdx-chart-root [layout]="layout()" [kind]="kind()" [label]="label()">
1710
- <svg:g data-skdx-part="grid" [attr.stroke]="colors.grid">
1711
- @for (t of layout().yTicks; track t.pos) {
1712
- <svg:line
1713
- [attr.x1]="plot().x"
1714
- [attr.x2]="right()"
1715
- [attr.y1]="t.pos"
1716
- [attr.y2]="t.pos"
1717
- ></svg:line>
1718
- }
1719
- </svg:g>
1720
- <svg:line
1721
- data-skdx-part="axis"
1722
- [attr.x1]="plot().x"
1723
- [attr.x2]="right()"
1724
- [attr.y1]="layout().baseline"
1725
- [attr.y2]="layout().baseline"
1726
- [attr.stroke]="colors.axis"
1727
- ></svg:line>
1728
- <svg:g
1729
- data-skdx-part="y-labels"
1730
- [attr.fill]="colors.label"
1731
- text-anchor="end"
1732
- dominant-baseline="middle"
1733
- >
1734
- @for (t of layout().yTicks; track t.pos) {
1735
- <svg:text [attr.x]="plot().x - 8" [attr.y]="t.pos">{{ t.label }}</svg:text>
1736
- }
1737
- </svg:g>
1738
- <svg:g data-skdx-part="x-labels" [attr.fill]="colors.label" text-anchor="middle">
1739
- @for (t of layout().xTicks; track $index) {
1740
- <svg:text [attr.x]="t.pos" [attr.y]="plot().y + plot().height + 20">
1741
- {{ t.label }}
1742
- </svg:text>
1743
- }
1744
- </svg:g>
1745
- <ng-content></ng-content>
1746
- </skdx-chart-root>
1747
- `, isInline: true, dependencies: [{ kind: "component", type: SkdxChartRootComponent, selector: "skdx-chart-root", inputs: ["layout", "kind", "label"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1748
- }
1749
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SkdxChartFrameComponent, decorators: [{
1750
- type: Component,
1751
- args: [{
1752
- selector: 'skdx-chart-frame',
1753
- standalone: true,
1754
- changeDetection: ChangeDetectionStrategy.OnPush,
1755
- host: { style: 'display: inline-block' },
1756
- imports: [SkdxChartRootComponent],
1757
- template: `
1758
- <skdx-chart-root [layout]="layout()" [kind]="kind()" [label]="label()">
1759
- <svg:g data-skdx-part="grid" [attr.stroke]="colors.grid">
1760
- @for (t of layout().yTicks; track t.pos) {
1761
- <svg:line
1762
- [attr.x1]="plot().x"
1763
- [attr.x2]="right()"
1764
- [attr.y1]="t.pos"
1765
- [attr.y2]="t.pos"
1766
- ></svg:line>
1767
- }
1768
- </svg:g>
1769
- <svg:line
1770
- data-skdx-part="axis"
1771
- [attr.x1]="plot().x"
1772
- [attr.x2]="right()"
1773
- [attr.y1]="layout().baseline"
1774
- [attr.y2]="layout().baseline"
1775
- [attr.stroke]="colors.axis"
1776
- ></svg:line>
1777
- <svg:g
1778
- data-skdx-part="y-labels"
1779
- [attr.fill]="colors.label"
1780
- text-anchor="end"
1781
- dominant-baseline="middle"
1782
- >
1783
- @for (t of layout().yTicks; track t.pos) {
1784
- <svg:text [attr.x]="plot().x - 8" [attr.y]="t.pos">{{ t.label }}</svg:text>
1785
- }
1786
- </svg:g>
1787
- <svg:g data-skdx-part="x-labels" [attr.fill]="colors.label" text-anchor="middle">
1788
- @for (t of layout().xTicks; track $index) {
1789
- <svg:text [attr.x]="t.pos" [attr.y]="plot().y + plot().height + 20">
1790
- {{ t.label }}
1791
- </svg:text>
1792
- }
1793
- </svg:g>
1794
- <ng-content></ng-content>
1795
- </skdx-chart-root>
1796
- `,
1797
- }]
1798
- }], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: true }] }], kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }] } });
1799
- /** A dot per value with its native tooltip. */
1800
- class SkdxChartPointsComponent {
1801
- constructor() {
1802
- this.points = input.required(/* @ts-ignore */
1803
- ...(ngDevMode ? [{ debugName: "points" }] : /* istanbul ignore next */ []));
1804
- }
1805
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SkdxChartPointsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1806
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: SkdxChartPointsComponent, isStandalone: true, selector: "g[skdxChartPoints]", inputs: { points: { classPropertyName: "points", publicName: "points", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
1807
- @for (p of points(); track $index) {
1808
- <svg:circle
1809
- data-skdx-part="point"
1810
- [attr.cx]="p.x"
1811
- [attr.cy]="p.y"
1812
- r="3"
1813
- [attr.fill]="p.color"
1814
- >
1815
- <svg:title>{{ p.title }}</svg:title>
1816
- </svg:circle>
1817
- }
1818
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1819
- }
1820
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SkdxChartPointsComponent, decorators: [{
1821
- type: Component,
1822
- args: [{
1823
- selector: 'g[skdxChartPoints]',
1824
- standalone: true,
1825
- changeDetection: ChangeDetectionStrategy.OnPush,
1826
- template: `
1827
- @for (p of points(); track $index) {
1828
- <svg:circle
1829
- data-skdx-part="point"
1830
- [attr.cx]="p.x"
1831
- [attr.cy]="p.y"
1832
- r="3"
1833
- [attr.fill]="p.color"
1834
- >
1835
- <svg:title>{{ p.title }}</svg:title>
1836
- </svg:circle>
1837
- }
1838
- `,
1839
- }]
1840
- }], propDecorators: { points: [{ type: i0.Input, args: [{ isSignal: true, alias: "points", required: true }] }] } });
1841
- /** Size and label inputs every chart takes. */
1842
1719
  class SkdxSizedBase {
1843
1720
  constructor() {
1844
1721
  this.width = input(/* @ts-ignore */
@@ -1888,59 +1765,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1888
1765
  type: Directive
1889
1766
  }], propDecorators: { series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: true }] }], categories: [{ type: i0.Input, args: [{ isSignal: true, alias: "categories", required: false }] }] } });
1890
1767
 
1891
- /*
1892
- * Copyright (c) 2026 SkandaDX
1893
- * This file is part of the SkandaDX organization.
1894
- * Licensed under the MIT License. See LICENSE in the repository root.
1895
- */
1896
- /** Line strokes plus a dot per value, shared by the line and area charts. */
1897
- class SkdxChartLinesComponent {
1898
- constructor() {
1899
- this.lines = input.required(/* @ts-ignore */
1900
- ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
1901
- this.points = input.required(/* @ts-ignore */
1902
- ...(ngDevMode ? [{ debugName: "points" }] : /* istanbul ignore next */ []));
1903
- }
1904
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SkdxChartLinesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1905
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: SkdxChartLinesComponent, isStandalone: true, selector: "g[skdxChartLines]", inputs: { lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: true, transformFunction: null }, points: { classPropertyName: "points", publicName: "points", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
1906
- @for (l of lines(); track l.series) {
1907
- <svg:path
1908
- data-skdx-part="line"
1909
- [attr.d]="l.d"
1910
- fill="none"
1911
- [attr.stroke]="l.color"
1912
- stroke-width="2"
1913
- stroke-linejoin="round"
1914
- stroke-linecap="round"
1915
- ></svg:path>
1916
- }
1917
- <svg:g skdxChartPoints [points]="points()"></svg:g>
1918
- `, isInline: true, dependencies: [{ kind: "component", type: SkdxChartPointsComponent, selector: "g[skdxChartPoints]", inputs: ["points"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1919
- }
1920
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: SkdxChartLinesComponent, decorators: [{
1921
- type: Component,
1922
- args: [{
1923
- selector: 'g[skdxChartLines]',
1924
- standalone: true,
1925
- changeDetection: ChangeDetectionStrategy.OnPush,
1926
- imports: [SkdxChartPointsComponent],
1927
- template: `
1928
- @for (l of lines(); track l.series) {
1929
- <svg:path
1930
- data-skdx-part="line"
1931
- [attr.d]="l.d"
1932
- fill="none"
1933
- [attr.stroke]="l.color"
1934
- stroke-width="2"
1935
- stroke-linejoin="round"
1936
- stroke-linecap="round"
1937
- ></svg:path>
1938
- }
1939
- <svg:g skdxChartPoints [points]="points()"></svg:g>
1940
- `,
1941
- }]
1942
- }], propDecorators: { lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: true }] }], points: [{ type: i0.Input, args: [{ isSignal: true, alias: "points", required: true }] }] } });
1943
-
1944
1768
  /*
1945
1769
  * Copyright (c) 2026 SkandaDX
1946
1770
  * This file is part of the SkandaDX organization.
@@ -1951,5 +1775,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1951
1775
  * Generated bundle index. Do not edit.
1952
1776
  */
1953
1777
 
1954
- export { CHART_COLORS, SERIES_COLORS, SkdxChartBase, SkdxChartFrameComponent, SkdxChartLinesComponent, SkdxChartPointsComponent, SkdxChartRootComponent, SkdxChartShapesComponent, SkdxSizedBase, boxStats, formatValue, hoverTooltip, layoutAreaChart, layoutBarChart, layoutBoxplot, layoutCandlestick, layoutChord, layoutFunnelChart, layoutGantt, layoutHeatmap, layoutLineChart, layoutMap, layoutPieChart, layoutRadarChart, layoutSankey, layoutScatterChart, layoutSparkline, layoutSunburst, layoutTreemap, layoutWaterfall, linearScale, logTicks, niceTicks, pathThrough, seriesColor, svgAttrs, tooltipShapes };
1778
+ export { CHART_COLORS, SkdxChartBase, SkdxChartRootComponent, SkdxChartShapesComponent, SkdxSizedBase, hoverTooltip, layoutAreaChart, layoutBarChart, layoutBoxplot, layoutCandlestick, layoutChord, layoutFunnelChart, layoutGantt, layoutHeatmap, layoutLineChart, layoutMap, layoutPieChart, layoutRadarChart, layoutSankey, layoutScatterChart, layoutSparkline, layoutSunburst, layoutTreemap, layoutWaterfall };
1955
1779
  //# sourceMappingURL=skdx-angular-charts-internal.mjs.map