@skdx/angular-charts 0.1.0 → 0.32.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 +32 -14
  2. package/fesm2022/skdx-angular-charts-area.mjs.map +1 -1
  3. package/fesm2022/skdx-angular-charts-bar.mjs +38 -14
  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 +43 -11
  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 +69 -4
  16. package/fesm2022/skdx-angular-charts-heatmap.mjs.map +1 -1
  17. package/fesm2022/skdx-angular-charts-internal.mjs +411 -235
  18. package/fesm2022/skdx-angular-charts-internal.mjs.map +1 -1
  19. package/fesm2022/skdx-angular-charts-line.mjs +15 -13
  20. package/fesm2022/skdx-angular-charts-line.mjs.map +1 -1
  21. package/fesm2022/skdx-angular-charts-map.mjs +8 -2
  22. package/fesm2022/skdx-angular-charts-map.mjs.map +1 -1
  23. package/fesm2022/skdx-angular-charts-pie.mjs +21 -11
  24. package/fesm2022/skdx-angular-charts-pie.mjs.map +1 -1
  25. package/fesm2022/skdx-angular-charts-radar.mjs +77 -14
  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 +15 -13
  30. package/fesm2022/skdx-angular-charts-scatter.mjs.map +1 -1
  31. package/fesm2022/skdx-angular-charts-sparkline.mjs +55 -11
  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 +10 -2
  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 +11 -2
  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 +8 -1
  45. package/types/skdx-angular-charts-internal.d.ts +200 -26
  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 +8 -1
  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, Directive } from '@angular/core';
2
+ import { input, ChangeDetectionStrategy, Component, signal, computed, Directive } from '@angular/core';
3
3
 
4
4
  /*
5
5
  * Copyright (c) 2026 SkandaDX
@@ -261,6 +261,19 @@ 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
+ }
264
277
  const values = (input) => input.series.flatMap((s) => s.data);
265
278
  /** Tooltip text for a value in a series: `Series, Category: 1,234`. */
266
279
  const markTitle = (series, category, value) => `${series}, ${category}: ${formatValue(value)}`;
@@ -279,75 +292,6 @@ function hoverTooltip(target, svg, clientX, clientY, width, height) {
279
292
  return tooltipShapes(x, y, title, width, height);
280
293
  }
281
294
 
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
-
351
295
  /*
352
296
  * Copyright (c) 2026 SkandaDX
353
297
  * This file is part of the SkandaDX organization.
@@ -378,18 +322,19 @@ function layoutBarChart(input) {
378
322
  const y0 = y(from);
379
323
  const y1 = y(to);
380
324
  bars.push({
381
- kind: 'rect',
382
- part: 'bar',
325
+ series: s.name,
326
+ category,
327
+ value,
328
+ color,
329
+ title: markTitle(s.name, category, value),
383
330
  x: px(x.start(ci) + (input.stacked ? 0 : si * groupWidth)),
384
331
  y: px(Math.min(y0, y1)),
385
332
  width: px(groupWidth),
386
333
  height: px(Math.abs(y1 - y0)),
387
- fill: color,
388
- title: markTitle(s.name, category, value),
389
334
  });
390
335
  });
391
336
  });
392
- return { ...frame, shapes: [...frameShapes(frame), ...bars, ...frame.shapes] };
337
+ return { ...frame, bars };
393
338
  }
394
339
  function stackedExtent(input) {
395
340
  let min = 0;
@@ -416,52 +361,39 @@ function stackedExtent(input) {
416
361
  * This file is part of the SkandaDX organization.
417
362
  * Licensed under the MIT License. See LICENSE in the repository root.
418
363
  */
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) {
364
+ function polylines(input, { frame, x, y, categories }) {
421
365
  const points = [];
422
366
  const lines = [];
423
- const areas = [];
367
+ const ends = [];
424
368
  input.series.forEach((s, si) => {
425
369
  const color = seriesColor(si, s.color);
426
370
  const coords = s.data.map((value, ci) => {
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)));
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);
429
379
  return p;
430
380
  });
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
- }
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))) });
454
383
  });
455
- return {
456
- ...frame,
457
- shapes: [...frameShapes(frame), ...areas, ...lines, ...points, ...frame.shapes],
458
- };
384
+ return { frame, lines, points, ends };
459
385
  }
460
386
  function layoutLineChart(input) {
461
- return polylines(input, buildFrame(input, extent(values(input), false)), false);
387
+ const { frame, lines, points } = polylines(input, buildFrame(input, extent(values(input), false)));
388
+ return { ...frame, lines, points };
462
389
  }
463
390
  function layoutAreaChart(input) {
464
- return polylines(input, buildFrame(input, extent(values(input), true)), true);
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 };
465
397
  }
466
398
 
467
399
  /*
@@ -475,14 +407,25 @@ function layoutScatterChart(input) {
475
407
  const pairs = input.series.flatMap((s) => s.data);
476
408
  const x = axis(extent(pairs.map((p) => p[0]), false), [plot.x, plot.x + plot.width]);
477
409
  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);
480
410
  const points = input.series.flatMap((s, si) => {
481
411
  const color = seriesColor(si, s.color);
482
- return s.data.map(([vx, vy]) => point(px(x.scale(vx)), px(y.scale(vy)), 3, color, `${s.name}: ${formatValue(vx)}, ${formatValue(vy)}`));
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
+ }));
483
419
  });
484
- const frame = { ...c, plot, xTicks: x.ticks, yTicks: y.ticks, baseline: y.zero };
485
- return { ...c, shapes: [...frameShapes(frame), ...points, ...c.shapes] };
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
+ };
486
429
  }
487
430
 
488
431
  /*
@@ -522,23 +465,29 @@ function layoutPieChart(input) {
522
465
  if (input.labels && percent > 0.03) {
523
466
  const mid = (a0 + angle) / 2;
524
467
  const lr = (r + r0) / 2;
525
- labels.push(label(px(cx + lr * Math.sin(mid)), px(cy - lr * Math.cos(mid)), `${d.name} ${Math.round(percent * 100)}%`, {
468
+ labels.push({
469
+ kind: 'text',
526
470
  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)}%`,
527
474
  anchor: 'middle',
528
475
  baseline: 'middle',
529
476
  fill: 'var(--skdx-color-text-on-accent)',
530
- }));
477
+ });
531
478
  }
532
479
  return {
533
- kind: 'path',
534
- part: 'slice',
535
- d: percent > 0 ? arc(cx, cy, r, r0, a0, angle) : '',
536
- fill: seriesColor(i, d.color),
480
+ name: d.name,
481
+ value: d.value,
482
+ percent,
483
+ series: d.name,
484
+ color: seriesColor(i, d.color),
537
485
  title: `${d.name}: ${formatValue(d.value)} (${Math.round(percent * 100)}%)`,
486
+ d: percent > 0 ? arc(cx, cy, r, r0, a0, angle) : '',
538
487
  };
539
488
  });
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] };
489
+ const c = canvas(input, width, height, slices.map((s) => ({ name: s.name, color: s.color })));
490
+ return { ...c, shapes: [...c.shapes, ...labels], slices };
542
491
  }
543
492
 
544
493
  /*
@@ -556,25 +505,15 @@ function layoutSparkline(input) {
556
505
  const line = pathThrough(coords, input.curve);
557
506
  const last = coords[coords.length - 1];
558
507
  const first = coords[0];
559
- const color = seriesColor(0, input.color);
560
508
  return {
561
509
  width,
562
510
  height,
511
+ shapes: [],
563
512
  tooltip: input.tooltip ?? true,
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
- ],
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,
578
517
  };
579
518
  }
580
519
 
@@ -614,62 +553,44 @@ function layoutRadarChart(input) {
614
553
  return `${p.x} ${p.y}`;
615
554
  })
616
555
  .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
- ];
652
556
  const points = [];
653
557
  const polygons = input.series.map((s, si) => {
654
558
  const color = seriesColor(si, s.color);
655
559
  input.categories.forEach((category, i) => {
656
560
  const value = s.data[i] ?? 0;
657
- const p = at(i, norm(si, value));
658
- points.push(point(p.x, p.y, 3, color, markTitle(s.name, category, value)));
561
+ points.push({
562
+ series: s.name,
563
+ color,
564
+ title: markTitle(s.name, category, value),
565
+ ...at(i, norm(si, value)),
566
+ });
659
567
  });
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
- };
568
+ return { series: s.name, color, d: polygon((i) => norm(si, s.data[i] ?? 0)) };
669
569
  });
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] };
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
+ };
672
592
  }
593
+ const toXY = (p) => ({ x2: p.x, y2: p.y });
673
594
 
674
595
  /*
675
596
  * Copyright (c) 2026 SkandaDX
@@ -689,7 +610,6 @@ function layoutHeatmap(input) {
689
610
  const negative = input.negativeColor ?? 'var(--skdx-color-danger-500)';
690
611
  const span = Math.max(Math.abs(min), Math.abs(max));
691
612
  // 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.
693
613
  const opacity = (v) => input.diverging
694
614
  ? span === 0
695
615
  ? 1
@@ -704,34 +624,34 @@ function layoutHeatmap(input) {
704
624
  if (value === undefined)
705
625
  return;
706
626
  cells.push({
707
- kind: 'rect',
708
- part: 'cell',
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)}`,
709
634
  x: px(plot.x + ci * cellW),
710
635
  y: px(plot.y + ri * cellH),
711
636
  width: px(cellW),
712
637
  height: px(cellH),
713
- fill: input.diverging && value < 0 ? negative : color,
714
- opacity: opacity(value),
715
- title: `${row}, ${column}: ${formatValue(value)}`,
716
638
  });
717
639
  });
718
640
  });
719
- const c = canvas(input, width, height);
720
641
  return {
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
- ],
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
+ })),
735
655
  };
736
656
  }
737
657
 
@@ -751,34 +671,90 @@ function layoutFunnelChart(input) {
751
671
  const offset = legendTop(input);
752
672
  const stageH = n === 0 ? 0 : (height - offset - 2 * PAD$2) / n;
753
673
  const widthOf = (value) => (max > 0 ? (Math.max(0, value) / max) * full : 0);
754
- const legend = input.data.map((d, i) => ({ name: d.name, color: seriesColor(i, d.color) }));
755
- const stages = input.data.flatMap((d, i) => {
674
+ const stages = input.data.map((d, i) => {
756
675
  const top = widthOf(d.value);
757
676
  const bottom = widthOf(input.data[i + 1]?.value ?? d.value);
758
677
  const y0 = offset + PAD$2 + i * stageH;
759
678
  const y1 = y0 + stageH - GAP;
760
679
  const cx = width / 2;
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
- ];
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
+ };
777
692
  });
778
- const c = canvas(input, width, height, legend);
779
- return { ...c, shapes: [...stages, ...c.shapes] };
693
+ return {
694
+ ...canvas(input, width, height, stages.map((s) => ({ name: s.name, color: s.color }))),
695
+ stages,
696
+ };
780
697
  }
781
698
 
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
+
782
758
  /*
783
759
  * Copyright (c) 2026 SkandaDX
784
760
  * This file is part of the SkandaDX organization.
@@ -1003,8 +979,10 @@ function layoutTreemap(input) {
1003
979
  });
1004
980
  };
1005
981
  tile(input.data, 0, top, width, height - top, undefined);
1006
- const c = canvas(input, width, height, legend);
1007
- return { ...c, shapes: [...shapes, ...c.shapes] };
982
+ return {
983
+ ...canvas(input, width, height, legend),
984
+ shapes: [...shapes, ...canvas(input, width, height, legend).shapes],
985
+ };
1008
986
  }
1009
987
 
1010
988
  /*
@@ -1479,10 +1457,7 @@ class SkdxChartShapesComponent {
1479
1457
  [attr.fill]="s.fill ?? 'none'"
1480
1458
  [attr.stroke]="s.stroke"
1481
1459
  [attr.stroke-width]="s.strokeWidth"
1482
- [attr.stroke-linecap]="s.strokeLinecap"
1483
- [attr.stroke-linejoin]="s.strokeLinejoin"
1484
1460
  [attr.opacity]="s.opacity"
1485
- [attr.fill-opacity]="s.fillOpacity"
1486
1461
  >
1487
1462
  @if (s.title) {
1488
1463
  <svg:title>{{ s.title }}</svg:title>
@@ -1565,10 +1540,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1565
1540
  [attr.fill]="s.fill ?? 'none'"
1566
1541
  [attr.stroke]="s.stroke"
1567
1542
  [attr.stroke-width]="s.strokeWidth"
1568
- [attr.stroke-linecap]="s.strokeLinecap"
1569
- [attr.stroke-linejoin]="s.strokeLinejoin"
1570
1543
  [attr.opacity]="s.opacity"
1571
- [attr.fill-opacity]="s.fillOpacity"
1572
1544
  >
1573
1545
  @if (s.title) {
1574
1546
  <svg:title>{{ s.title }}</svg:title>
@@ -1680,6 +1652,7 @@ class SkdxChartRootComponent {
1680
1652
  (pointerleave)="tip.set([])"
1681
1653
  >
1682
1654
  <title>{{ label() }}</title>
1655
+ <ng-content></ng-content>
1683
1656
  <svg:g skdxChartShapes [shapes]="layout().shapes"></svg:g>
1684
1657
  <svg:g skdxChartShapes [shapes]="tip()"></svg:g>
1685
1658
  </svg>
@@ -1709,6 +1682,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1709
1682
  (pointerleave)="tip.set([])"
1710
1683
  >
1711
1684
  <title>{{ label() }}</title>
1685
+ <ng-content></ng-content>
1712
1686
  <svg:g skdxChartShapes [shapes]="layout().shapes"></svg:g>
1713
1687
  <svg:g skdxChartShapes [shapes]="tip()"></svg:g>
1714
1688
  </svg>
@@ -1716,6 +1690,155 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1716
1690
  }]
1717
1691
  }], 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 }] }] } });
1718
1692
  /** 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. */
1719
1842
  class SkdxSizedBase {
1720
1843
  constructor() {
1721
1844
  this.width = input(/* @ts-ignore */
@@ -1765,6 +1888,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1765
1888
  type: Directive
1766
1889
  }], propDecorators: { series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: true }] }], categories: [{ type: i0.Input, args: [{ isSignal: true, alias: "categories", required: false }] }] } });
1767
1890
 
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
+
1768
1944
  /*
1769
1945
  * Copyright (c) 2026 SkandaDX
1770
1946
  * This file is part of the SkandaDX organization.
@@ -1775,5 +1951,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1775
1951
  * Generated bundle index. Do not edit.
1776
1952
  */
1777
1953
 
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 };
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 };
1779
1955
  //# sourceMappingURL=skdx-angular-charts-internal.mjs.map