Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions angular-client/src/pages/graph-page/graph/graph.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { TestBed } from '@angular/core/testing';
import CustomGraphComponent from './graph.component';

// Regression coverage for #630: with "Multiple Y-Axes" on, the y-axis set must always
// match the currently selected topics. applyMultiYAxisConfigs is the seam that rebuilds
// options.yaxis from the current data map, and updateChart now calls it every render so
// deselecting a topic prunes its axis. These tests drive that seam directly (the full
// component needs ApexCharts + a live DOM chart container, out of scope for a unit test).
describe('CustomGraphComponent — multi y-axis sync (#630)', () => {
function makeComponent(): CustomGraphComponent {
const c = TestBed.createComponent(CustomGraphComponent).componentInstance;
// applyMultiYAxisConfigs only reads `data` and writes `options.yaxis`.
(c as unknown as { options: { yaxis: unknown[] } }).options = { yaxis: [] };
return c;
}

function applyAxes(c: CustomGraphComponent): void {
(c as unknown as { applyMultiYAxisConfigs: () => void }).applyMultiYAxisConfigs();
}

it('builds one y-axis per topic in the current data map', () => {
const c = makeComponent();
c.data = new Map([
['BMS/Pack/Voltage 0', []],
['BMS/Pack/Current 0', []]
]);

applyAxes(c);

expect(c.options.yaxis.length).toBe(2);
});

it('prunes a y-axis when its topic is deselected (removed from the data map)', () => {
const c = makeComponent();
c.data = new Map([
['BMS/Pack/Voltage 0', []],
['BMS/Pack/Current 0', []]
]);
applyAxes(c);
expect(c.options.yaxis.length).toBe(2);

// Deselecting a topic removes its series from the data map...
c.data.delete('BMS/Pack/Current 0');
applyAxes(c);

// ...so its y-axis must go too, leaving axes matched to the remaining series.
expect(c.options.yaxis.length).toBe(1);
expect((c.options.yaxis[0] as { title: { text: string } }).title.text).toBe('BMS/Pack/Voltage ');
});

it('alternates y-axis sides so stacked axes stay readable', () => {
const c = makeComponent();
c.data = new Map([
['A 0', []],
['B 0', []],
['C 0', []]
]);

applyAxes(c);

const sides = (c.options.yaxis as { opposite: boolean }[]).map((y) => y.opposite);
expect(sides).toEqual([false, true, false]);
});
});
56 changes: 33 additions & 23 deletions angular-client/src/pages/graph-page/graph/graph.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, effect, input, OnDestroy, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { Component, effect, input, OnDestroy, OnInit, ChangeDetectionStrategy, untracked } from '@angular/core';
import ApexCharts from 'apexcharts';
import {
ApexXAxis,
Expand Down Expand Up @@ -102,29 +102,10 @@ export default class CustomGraphComponent implements OnInit, OnDestroy {
});

effect(() => {
// Re-run whenever the toggle flips. Per-topic axes are also rebuilt in updateChart
// (see applyMultiYAxisConfigs) so they stay in sync as topics are selected/deselected.
if (this.showMultipleYAxes()) {
const yaxisConfigs: Partial<ApexYAxis>[] = Array.from(this.data.keys()).map((key, index) => ({
title: {
text: key.replace('0', ''),
style: {
color: 'grey',
fontSize: '20px',
fontWeight: 'bold'
}
},
labels: {
style: {
colors: '#fff'
}
},
opposite: index % 2 !== 0 // Alternate sides for each y-axis
}));

// Update y-axis configurations
if (this.chart) {
this.options.yaxis = yaxisConfigs;
this.chart.updateOptions(this.options);
}
this.applyMultiYAxisConfigs();
} else {
this.options.yaxis = [
{
Expand All @@ -135,6 +116,8 @@ export default class CustomGraphComponent implements OnInit, OnDestroy {
}
}
];
}
if (this.chart) {
this.chart.updateOptions(this.options);
}
});
Expand Down Expand Up @@ -183,6 +166,13 @@ export default class CustomGraphComponent implements OnInit, OnDestroy {
yaxis: index
}));

// Keep the per-topic y-axes matched to the current series set: deselecting a topic
// drops its series here, so its y-axis must be pruned too (#630). untracked() so calling
// updateChart from an effect doesn't add showMultipleYAxes as a dependency.
if (untracked(this.showMultipleYAxes)) {
this.applyMultiYAxisConfigs();
}

// Only constrain the x-axis range in real-time mode; historical mode should auto-fit all data
let effectiveRange: number | undefined = undefined;
if (this.realTime()) {
Expand Down Expand Up @@ -215,6 +205,26 @@ export default class CustomGraphComponent implements OnInit, OnDestroy {
);
};

// Build one y-axis per current topic (order matches the series' `yaxis: index`).
private applyMultiYAxisConfigs(): void {
this.options.yaxis = Array.from(this.data.keys()).map((key, index) => ({
title: {
text: key.replace('0', ''),
style: {
color: 'grey',
fontSize: '20px',
fontWeight: 'bold'
}
},
labels: {
style: {
colors: '#fff'
}
},
opposite: index % 2 !== 0 // Alternate sides for each y-axis
}));
}

private computeDataSpan(): { minX: number; maxX: number; spanMs: number } | null {
let minX = Infinity;
let maxX = -Infinity;
Expand Down