diff --git a/src/pages/workouts/components/ChartGroup.ts b/src/pages/workouts/components/ChartGroup.ts index db14efe..e9c233c 100644 --- a/src/pages/workouts/components/ChartGroup.ts +++ b/src/pages/workouts/components/ChartGroup.ts @@ -8,6 +8,10 @@ import { resetZoom } from "chartjs-plugin-zoom"; export class ChartGroup { charts: Array; private broadcasting = false; + // Guards against re-entrant hover propagation: activating a tooltip on the + // source chart redraws it synchronously, which re-invokes its tooltip + // callbacks -> setHover -> infinite recursion. + private hovering = false; constructor(charts: Array) { this.charts = charts; @@ -27,25 +31,60 @@ export class ChartGroup { * the same point order (a common time axis), so the index is identical. */ setHover(dataIndex: number): void { - for (const chart of this.charts) { - if (!chart.tooltip) { - continue; + if (this.hovering) { + return; + } + this.hovering = true; + try { + for (const chart of this.charts) { + if (!chart.tooltip) { + continue; + } + // Optimization: skip charts whose tooltip is already active at the + // same index so the source chart is not redrawn twice per hover move. + const active = chart.tooltip.getActiveElements(); + if ( + active.length === 1 && + active[0].index === dataIndex && + active[0].datasetIndex === 0 + ) { + continue; + } + chart.tooltip.setActiveElements( + [{ datasetIndex: 0, index: dataIndex }], + { + x: 0, + y: 0, + }, + ); + chart.draw(); } - chart.tooltip.setActiveElements([{ datasetIndex: 0, index: dataIndex }], { - x: 0, - y: 0, - }); - chart.draw(); + } finally { + this.hovering = false; } } + /** + * Clears the hover state on every chart. Idempotent; safe to call from + * mouseout at any time. Guarded by the same re-entrancy flag as setHover: + * if invoked while setHover is in progress, the caller's own setHover will + * have (re)established the correct state, so an early return loses nothing. + */ clearHover(): void { - for (const chart of this.charts) { - if (!chart.tooltip) { - continue; + if (this.hovering) { + return; + } + this.hovering = true; + try { + for (const chart of this.charts) { + if (!chart.tooltip) { + continue; + } + chart.tooltip.setActiveElements([], { x: 0, y: 0 }); + chart.draw(); } - chart.tooltip.setActiveElements([], { x: 0, y: 0 }); - chart.draw(); + } finally { + this.hovering = false; } } diff --git a/src/pages/workouts/components/WorkoutItem.vue b/src/pages/workouts/components/WorkoutItem.vue index bc531c3..ea2c02e 100644 --- a/src/pages/workouts/components/WorkoutItem.vue +++ b/src/pages/workouts/components/WorkoutItem.vue @@ -231,12 +231,14 @@ - +
+ +