Skip to content
This repository was archived by the owner on Jul 27, 2025. It is now read-only.

Commit 869462a

Browse files
committed
Dynamic y-axis baseline value for chart scales
1 parent e4a82d8 commit 869462a

File tree

1 file changed

+40
-14
lines changed

1 file changed

+40
-14
lines changed

app/javascript/controllers/time_series_chart_controller.js

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -510,29 +510,55 @@ export default class extends Controller {
510510
get _d3YScale() {
511511
const dataMin = d3.min(this._normalDataPoints, this._getDatumValue);
512512
const dataMax = d3.max(this._normalDataPoints, this._getDatumValue);
513-
514-
// Use 0 as baseline, but allow negative values if they exist
515-
const yMin = Math.min(0, dataMin);
516-
517-
// Handle edge case where all values are the same (including all zeros)
518-
const range = dataMax - yMin;
519-
if (range === 0) {
520-
// If all values are 0, show 0-100 scale. Otherwise center the value with padding.
513+
514+
// Handle edge case where all values are the same
515+
if (dataMin === dataMax) {
521516
const padding = dataMax === 0 ? 100 : Math.abs(dataMax) * 0.5;
522517
return d3
523518
.scaleLinear()
524519
.rangeRound([this._d3ContainerHeight, 0])
525-
.domain([yMin - padding, dataMax + padding]);
520+
.domain([dataMin - padding, dataMax + padding]);
521+
}
522+
523+
const dataRange = dataMax - dataMin;
524+
const avgValue = (dataMax + dataMin) / 2;
525+
526+
// Calculate relative change as a percentage
527+
const relativeChange = avgValue !== 0 ? dataRange / Math.abs(avgValue) : 1;
528+
529+
// Dynamic baseline calculation
530+
let yMin;
531+
let yMax;
532+
533+
// For small relative changes (< 10%), use a tighter scale
534+
if (relativeChange < 0.1 && dataMin > 0) {
535+
// Start axis at a percentage below the minimum, not at 0
536+
const baselinePadding = dataRange * 2; // Show 2x the data range below min
537+
yMin = Math.max(0, dataMin - baselinePadding);
538+
yMax = dataMax + dataRange * 0.5; // Add 50% padding above
539+
} else {
540+
// For larger changes or when data crosses zero, use more context
541+
// Always include 0 when data is negative or close to 0
542+
if (dataMin < 0 || (dataMin >= 0 && dataMin < avgValue * 0.1)) {
543+
yMin = Math.min(0, dataMin * 1.1);
544+
} else {
545+
// Otherwise use dynamic baseline
546+
yMin = dataMin - dataRange * 0.3;
547+
}
548+
yMax = dataMax + dataRange * 0.1;
549+
}
550+
551+
// Adjust padding for labels if needed
552+
if (this.useLabelsValue) {
553+
const extraPadding = (yMax - yMin) * 0.1;
554+
yMin -= extraPadding;
555+
yMax += extraPadding;
526556
}
527-
528-
// Add padding to prevent overlapping with labels and for visual breathing room
529-
const topPadding = range * 0.1;
530-
const bottomPadding = range * (this.useLabelsValue ? 0.15 : 0.05);
531557

532558
return d3
533559
.scaleLinear()
534560
.rangeRound([this._d3ContainerHeight, 0])
535-
.domain([yMin - bottomPadding, dataMax + topPadding]);
561+
.domain([yMin, yMax]);
536562
}
537563

538564
_setupResizeObserver() {

0 commit comments

Comments
 (0)