Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"url": "git+https://github.com/opencor/webapp.git"
},
"type": "module",
"version": "0.20260727.0",
"version": "0.20260727.1",
"engines": {
"bun": ">=1.2.0"
},
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"./style.css": "./dist/opencor.css"
},
"version": "0.20260727.0",
"version": "0.20260727.1",
"libopencorVersion": "1.20260723.2",
"scripts": {
"build": "vite build && bun scripts/generate.version.ts",
Expand Down
105 changes: 103 additions & 2 deletions src/renderer/src/components/widgets/GraphPanelWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ defineExpose({
resize
});

const DOUBLE_CLICK_DELAY = 300;

const instanceId = Symbol('GraphPanelWidget');
const rootRef = vue.ref<HTMLElement | null>(null);
const mainDivRef = vue.ref<HTMLElement | null>(null);
Expand All @@ -150,6 +152,8 @@ let trackedMargins: IGraphPanelMargins | undefined;
let stopTrackingContainerSize: (() => void) | undefined;
let marginsRafId: number | undefined;
let resizeRafId: number | undefined;
let lastLegendClickIndex: number | undefined;
let lastLegendClickTime = 0;

// Context menu functionality.

Expand Down Expand Up @@ -710,6 +714,10 @@ const updateMarginsAsync = (): void => {
});
};

const plotlyTraceData = (): IPlotlyTraceState[] | undefined => {
return (mainDivRef.value as unknown as { data?: IPlotlyTraceState[] })?.data;
};

const updatePlot = (): void => {
plotIsReady = false;

Expand All @@ -735,7 +743,7 @@ const updatePlot = (): void => {
};

const previousTraceVisibilityByKey: Record<string, PlotlyTraceVisible> = {};
const previousPlotlyData = (mainDivRef.value as unknown as { data?: IPlotlyTraceState[] })?.data;
const previousPlotlyData = plotlyTraceData();

for (const plotlyTrace of previousPlotlyData ?? []) {
const plotlyTraceKey = traceVisibilityKey(plotlyTrace);
Expand All @@ -759,6 +767,7 @@ const updatePlot = (): void => {
x: dataTrace.x,
y: dataTrace.y,
name: dataTrace.name,
traceId: dataTrace.traceId,
visible: dataTraceKey ? previousTraceVisibilityByKey[dataTraceKey] : undefined,
line: { color: dataTrace.color },
legendrank: dataTrace.zorder
Expand Down Expand Up @@ -798,7 +807,7 @@ const updatePlot = (): void => {

responsive: true,
displayModeBar: false,
doubleClickDelay: 1000,
doubleClickDelay: DOUBLE_CLICK_DELAY,
scrollZoom: true,
showTips: false
})
Expand Down Expand Up @@ -874,6 +883,98 @@ vue.onMounted(() => {
emit('resetMargins');
}
});

// Intercept legend single-click to toggle trace visibility immediately, bypassing Plotly's setTimeout()-based
// handler.

plotlyElement.on('plotly_legendclick', (...args: unknown[]) => {
const eventData = args[0] as { curveNumber?: number } | undefined;
const curveNumber = eventData?.curveNumber;

if (curveNumber === undefined) {
return;
}

const now = Date.now();

// If this is a rapid second click on the same trace, a double-click is in progress, so skip toggling and let
// plotly_legenddoubleclick() handle isolation.

if (curveNumber === lastLegendClickIndex && now - lastLegendClickTime < DOUBLE_CLICK_DELAY) {
lastLegendClickIndex = undefined;

return false;
}

// Toggle the trace visibility immediately.

const plotlyData = plotlyTraceData();

if (!plotlyData || curveNumber >= plotlyData.length) {
return false;
}

const currentVisibility = plotlyData[curveNumber]?.visible;
const newVisibility = currentVisibility === 'legendonly' ? true : 'legendonly';

dependencies._plotlyJs.restyle(mainDivRef.value, 'visible', newVisibility, [curveNumber]);

lastLegendClickIndex = curveNumber;
lastLegendClickTime = now;

// Return false to cancel Plotly's own setTimeout()-based handler.

return false;
});

// Intercept legend double-click to implement trace isolation (show only the clicked trace) immediately, since we
// already cancelled the single-click path.

plotlyElement.on('plotly_legenddoubleclick', (...args: unknown[]) => {
const eventData = args[0] as { curveNumber?: number } | undefined;
const curveNumber = eventData?.curveNumber;

if (curveNumber === undefined) {
return false;
}

lastLegendClickIndex = undefined;

const plotlyData = plotlyTraceData();

if (!plotlyData || curveNumber >= plotlyData.length) {
return false;
}

// Collect the indices of visible (not 'legendonly') traces.

const visibleIndices: number[] = [];

for (let i = 0; i < plotlyData.length; ++i) {
if (plotlyData[i] && plotlyData[i].visible !== 'legendonly' && plotlyData[i].visible !== false) {
visibleIndices.push(i);
}
}

// If the clicked trace is the only one visible, then show all traces again. Otherwise, isolate the clicked trace
// by hiding all the other traces (by setting them to 'legendonly').

const isSoleVisible = visibleIndices.length === 1 && visibleIndices[0] === curveNumber;

if (isSoleVisible) {
dependencies._plotlyJs.restyle(mainDivRef.value, 'visible', true);
} else {
const visibilityUpdates: PlotlyTraceVisible[] = new Array(plotlyData.length);

for (let i = 0; i < plotlyData.length; ++i) {
visibilityUpdates[i] = i === curveNumber ? true : 'legendonly';
}

dependencies._plotlyJs.restyle(mainDivRef.value, 'visible', visibilityUpdates);
}

return false;
});
});
});

Expand Down
Loading