@acorex/charts 22.3.20 → 22.3.22

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.
@@ -396,15 +396,7 @@ class AXBarChartComponent extends AXChartComponent {
396
396
  .domain(data.map((d) => d.id))
397
397
  .range([0, this.width])
398
398
  .padding(padding);
399
- // Create y scale (linear scale for values)
400
- // Support negative values by calculating min/max
401
- const minValue = this.d3.min(data, (d) => d.value) || 0;
402
- const maxValue = this.d3.max(data, (d) => d.value) || 0;
403
- this.yScale = this.d3
404
- .scaleLinear()
405
- .domain([Math.min(0, minValue), Math.max(0, maxValue)])
406
- .nice()
407
- .range([this.height, 0]);
399
+ this.setupYScale(data.map((item) => item.value));
408
400
  }
409
401
  /**
410
402
  * Creates x and y scales for clustered charts
@@ -433,13 +425,51 @@ class AXBarChartComponent extends AXChartComponent {
433
425
  allVisibleValues.push(item.value);
434
426
  });
435
427
  }
436
- const yMin = this.d3.min(allVisibleValues) || 0;
437
- const yMax = this.d3.max(allVisibleValues) || 0;
438
- this.yScale = this.d3
439
- .scaleLinear()
440
- .domain([Math.min(0, yMin), Math.max(0, yMax)])
441
- .nice()
442
- .range([this.height, 0]);
428
+ this.setupYScale(allVisibleValues);
429
+ }
430
+ /**
431
+ * Whole-number spans of 0–8 stay on integer ticks.
432
+ * `.nice()` on 0–1 otherwise subdivides the axis into 0.1 steps.
433
+ */
434
+ setupYScale(values) {
435
+ const finite = values.filter((value) => Number.isFinite(value));
436
+ const dataMin = finite.length > 0 ? (this.d3.min(finite) ?? 0) : 0;
437
+ const dataMax = finite.length > 0 ? (this.d3.max(finite) ?? 0) : 0;
438
+ const yMin = Math.min(0, dataMin);
439
+ const yMax = Math.max(0, dataMax);
440
+ const ySpan = yMax - yMin;
441
+ const integralDomain = finite.every((value) => Number.isInteger(value)) && Number.isInteger(yMin) && Number.isInteger(yMax);
442
+ if (integralDomain && ySpan <= 8) {
443
+ const upper = ySpan === 0 ? yMax + 1 : yMax;
444
+ this.yScale = this.d3.scaleLinear().domain([yMin, upper]).range([this.height, 0]);
445
+ return;
446
+ }
447
+ this.yScale = this.d3.scaleLinear().domain([yMin, yMax]).nice().range([this.height, 0]);
448
+ if (integralDomain) {
449
+ const [niceMin, niceMax] = this.yScale.domain();
450
+ const snappedMin = Math.floor(niceMin);
451
+ const snappedMax = Math.max(snappedMin + 1, Math.ceil(niceMax));
452
+ this.yScale.domain([snappedMin, snappedMax]);
453
+ }
454
+ }
455
+ /** Integer ticks for a short whole-number domain so D3 does not subdivide 0–1 into decimals. */
456
+ resolveYAxisTickValues() {
457
+ const [min, max] = this.yScale.domain();
458
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
459
+ return null;
460
+ }
461
+ if (!Number.isInteger(min) || !Number.isInteger(max)) {
462
+ return null;
463
+ }
464
+ const span = max - min;
465
+ if (span > 8) {
466
+ return null;
467
+ }
468
+ const ticks = [];
469
+ for (let value = min; value <= max; value += 1) {
470
+ ticks.push(value);
471
+ }
472
+ return ticks;
443
473
  }
444
474
  /**
445
475
  * Creates x and y axes with grid lines
@@ -466,17 +496,22 @@ class AXBarChartComponent extends AXChartComponent {
466
496
  }
467
497
  createYAxis(axesGroup, options) {
468
498
  // Create Y axis with smart number formatting
499
+ const yTickValues = this.resolveYAxisTickValues();
469
500
  const yAxisGenerator = this.d3.axisLeft(this.yScale).tickFormat((value) => {
470
501
  const numValue = typeof value === 'number' ? value : value.valueOf();
471
502
  return formatLargeNumber(numValue);
472
503
  });
473
- // Reduce tick count for better readability
474
- const maxValue = this.yScale.domain()[1];
475
- if (maxValue > 1e6) {
476
- yAxisGenerator.ticks(6); // Fewer ticks for large numbers
504
+ if (yTickValues) {
505
+ yAxisGenerator.tickValues(yTickValues);
477
506
  }
478
- else if (maxValue > 1e3) {
479
- yAxisGenerator.ticks(8);
507
+ else {
508
+ const maxValue = this.yScale.domain()[1];
509
+ if (maxValue > 1e6) {
510
+ yAxisGenerator.ticks(6);
511
+ }
512
+ else if (maxValue > 1e3) {
513
+ yAxisGenerator.ticks(8);
514
+ }
480
515
  }
481
516
  this.yAxis = axesGroup.append('g').attr('class', 'ax-bar-chart-axis-y').call(yAxisGenerator);
482
517
  // Style the axis text
@@ -585,13 +620,18 @@ class AXBarChartComponent extends AXChartComponent {
585
620
  return tickLabelText.length * this.DEFAULT_FONT_SIZE * this.FONT_WIDTH_MULTIPLIER + this.FONT_PADDING;
586
621
  }
587
622
  createGridLines() {
623
+ const yTickValues = this.resolveYAxisTickValues();
624
+ const gridAxis = this.d3
625
+ .axisLeft(this.yScale)
626
+ .tickSize(-this.width)
627
+ .tickFormat(() => '');
628
+ if (yTickValues) {
629
+ gridAxis.tickValues(yTickValues);
630
+ }
588
631
  this.chart
589
632
  .append('g')
590
633
  .attr('class', 'ax-bar-chart-grid')
591
- .call(this.d3
592
- .axisLeft(this.yScale)
593
- .tickSize(-this.width)
594
- .tickFormat(() => ''))
634
+ .call(gridAxis)
595
635
  .selectAll('line')
596
636
  .style('stroke', 'rgb(var(--ax-comp-bar-chart-grid-lines-color))')
597
637
  .style('stroke-opacity', 0.2);