@porscheinformatik/clr-addons 19.18.2 → 19.18.4

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.
@@ -1083,6 +1083,36 @@ function renderDots(g, series, x, y, onClick) {
1083
1083
  * This software is released under MIT license.
1084
1084
  * The full license information can be found in LICENSE in the root directory of this project.
1085
1085
  */
1086
+ /**
1087
+ * Computes sensible tick values for a linear Y scale.
1088
+ *
1089
+ * When the domain contains enough integer values (≥ 3) the ticks are filtered
1090
+ * to integers only (avoids fractional labels for count data). For small-range
1091
+ * domains – e.g. 0–1 ratios / probabilities – the raw D3 ticks are returned
1092
+ * unchanged so that decimal labels are still visible.
1093
+ */
1094
+ function computeYTickValues(y, count = 5) {
1095
+ const raw = y.ticks(count);
1096
+ const integers = raw.filter(Number.isInteger);
1097
+ // Keep integer-only ticks only when there are at least 3 of them; otherwise
1098
+ // the domain is too small (e.g. 0–1) and we need the decimal ticks.
1099
+ return integers.length >= 3 ? integers : raw;
1100
+ }
1101
+ /**
1102
+ * Returns a D3 tick-format function that matches the decision made in
1103
+ * {@link computeYTickValues}:
1104
+ *
1105
+ * - **Integer / large-value domain** (≥ 3 integer ticks): SI prefix format
1106
+ * (`~s`), e.g. `1k`, `200`.
1107
+ * - **Small / decimal domain** (< 3 integer ticks, e.g. 0–1 ratios): plain
1108
+ * fixed-point format (`.3~f`) – avoids the SI "milli" prefix (`m`) that
1109
+ * D3 would otherwise apply to values < 1.
1110
+ */
1111
+ function computeYTickFormat(y, count = 5) {
1112
+ const raw = y.ticks(count);
1113
+ const integers = raw.filter(Number.isInteger);
1114
+ return integers.length >= 3 ? format('~s') : format('.3~f');
1115
+ }
1086
1116
  /**
1087
1117
  * Draws the X and Y axes (with integer-only Y ticks and grid lines) plus
1088
1118
  * optional description labels onto the given D3 group.
@@ -1102,9 +1132,10 @@ function drawXYAxes(g, x, y, opts) {
1102
1132
  .style('font-size', '11px')
1103
1133
  .style('fill', 'var(--clr-color-neutral-600, #666)');
1104
1134
  // Y axis
1105
- const tickValues = y.ticks(5).filter((t) => Number.isInteger(t));
1135
+ const tickValues = computeYTickValues(y);
1136
+ const tickFormat = computeYTickFormat(y);
1106
1137
  g.append('g')
1107
- .call(axisLeft(y).tickValues(tickValues).tickSize(-width).tickFormat(format('~s')))
1138
+ .call(axisLeft(y).tickValues(tickValues).tickSize(-width).tickFormat(tickFormat))
1108
1139
  .selectAll('text')
1109
1140
  .style('font-size', '11px')
1110
1141
  .style('fill', 'var(--clr-color-neutral-600, #666)');
@@ -1478,6 +1509,7 @@ class ComboChartComponent extends ChartBase {
1478
1509
  const lineItems = this.lineSeries().map(s => ({ label: s.label, color: s.color }));
1479
1510
  return [...barItems, ...lineItems];
1480
1511
  });
1512
+ this._clipIdCounter = 0;
1481
1513
  }
1482
1514
  // ── Lifecycle ────────────────────────────────────────────────────────────────
1483
1515
  ngOnChanges(_changes) {
@@ -1535,6 +1567,19 @@ class ComboChartComponent extends ChartBase {
1535
1567
  .domain([0, maxLine || 1])
1536
1568
  .nice(this.yLineMax() === undefined ? undefined : 0)
1537
1569
  .range([height, 0]);
1570
+ // ── SVG ClipPath ─────────────────────────────────────────────────────────
1571
+ // Ensures bars/lines that exceed the Y-axis maximum (yBarMax/yLineMax) are
1572
+ // visually clipped at the chart boundary without modifying the data values.
1573
+ const clipId = `combo-clip-${this._clipIdCounter++}`;
1574
+ this.svg
1575
+ .append('defs')
1576
+ .append('clipPath')
1577
+ .attr('id', clipId)
1578
+ .append('rect')
1579
+ .attr('x', 0)
1580
+ .attr('y', 0)
1581
+ .attr('width', width)
1582
+ .attr('height', height);
1538
1583
  const g = this.svg
1539
1584
  .attr('width', width)
1540
1585
  .attr('height', height)
@@ -1551,12 +1596,12 @@ class ComboChartComponent extends ChartBase {
1551
1596
  styleGridLines(g);
1552
1597
  // ── Right Y axis for line series ─────────────────────────────────────────
1553
1598
  if (hasLines) {
1554
- const lineTickValues = yLine.ticks(5).filter((t) => Number.isInteger(t));
1599
+ const lineTickValues = computeYTickValues(yLine);
1600
+ const lineTickFormat = computeYTickFormat(yLine);
1555
1601
  const rightAxisG = g
1556
1602
  .append('g')
1557
1603
  .attr('transform', `translate(${width},0)`)
1558
- .call(axisRight(yLine).tickValues(lineTickValues).tickFormat(format('~s')).tickSize(-width) // draw grid lines across the chart
1559
- );
1604
+ .call(axisRight(yLine).tickValues(lineTickValues).tickFormat(lineTickFormat).tickSize(-width));
1560
1605
  rightAxisG.selectAll('text').style('font-size', '11px').style('fill', 'var(--clr-color-neutral-600, #666)');
1561
1606
  // Style the line-axis grid lines with a distinct dashed color
1562
1607
  rightAxisG
@@ -1577,8 +1622,11 @@ class ComboChartComponent extends ChartBase {
1577
1622
  .text(this.yLineAxisLabel());
1578
1623
  }
1579
1624
  }
1580
- this.drawBars(g, x, yBar);
1581
- this.drawLines(g, x, yLine);
1625
+ // Wrap chart content in a clipped group so elements exceeding the Y max
1626
+ // are not visible beyond the chart boundary.
1627
+ const contentGroup = g.append('g').attr('clip-path', `url(#${clipId})`);
1628
+ this.drawBars(contentGroup, x, yBar);
1629
+ this.drawLines(contentGroup, x, yLine);
1582
1630
  }
1583
1631
  drawBars(g, x, y) {
1584
1632
  // Running stack base (cumulative sum) per X key across all bar series