Add synthetic array (waveform) DSP support + UX improvements
Adds full array/waveform support through the synthetic DSP engine: a dsp.Sample value model (scalar or []float64), array ops (index, slice, sum, mean, min, max, length, fft) with an in-tree radix-2 FFT, and static type propagation (OpOutputType) that the editor mirrors to colour wires by data type and flag invalid wirings. Stateful filters and lua stay scalar-only. Adds a waveform plot mode (x-vs-index trace). Also: errored-node hover reasons, S/N add-signal/add-node HUD shortcuts in the synthetic editor, and view-mode widgets that blend with the canvas background (chrome kept in edit mode). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,8 @@ export default function PlotWidget({ widget, onContextMenu, timeRange }: Props)
|
||||
const showLegend = legendPos !== 'none';
|
||||
|
||||
const buffers: SeriesBuffer[] = signals.map(() => ({ timestamps: [], values: [] }));
|
||||
// Latest waveform (array) value per signal — used only by plotType 'waveform'.
|
||||
const waveforms: number[][] = signals.map(() => []);
|
||||
const unsubs: (() => void)[] = [];
|
||||
const fmt = widget.options['format'] ?? '';
|
||||
|
||||
@@ -269,6 +271,24 @@ export default function PlotWidget({ widget, onContextMenu, timeRange }: Props)
|
||||
}),
|
||||
};
|
||||
}
|
||||
case 'waveform': {
|
||||
// Latest waveform (array) sample per signal, drawn as an x-vs-index
|
||||
// trace. Each update replaces the trace rather than appending.
|
||||
return {
|
||||
backgroundColor: ECHARTS_DARK.backgroundColor,
|
||||
legend: legendOpt,
|
||||
grid: { left: 60, right: 16, top: showLegend ? 28 : 12, bottom: 40 },
|
||||
xAxis: { type: 'value', name: 'Index', nameLocation: 'middle', nameGap: 24, min: 0, axisLine, axisLabel, splitLine },
|
||||
yAxis: { type: 'value', name: 'Value', axisLine, axisLabel, splitLine },
|
||||
series: signals.map((s, i) => ({
|
||||
name: s.name,
|
||||
type: 'line',
|
||||
data: (waveforms[i] ?? []).map((v, k) => [k, v]),
|
||||
lineStyle: { color: s.color ?? COLORS[i % COLORS.length] },
|
||||
showSymbol: false,
|
||||
})),
|
||||
};
|
||||
}
|
||||
case 'logic': {
|
||||
// Logic-analyser style: each signal is a 0/1 step trace stacked on its
|
||||
// own lane, with a relative-time x-axis (seconds before now).
|
||||
@@ -423,7 +443,16 @@ export default function PlotWidget({ widget, onContextMenu, timeRange }: Props)
|
||||
if (sv.value === null || sv.value === undefined || sv.ts === null) return;
|
||||
const ts = new Date(sv.ts).getTime() / 1000;
|
||||
|
||||
if (plotType === 'timeseries') {
|
||||
if (plotType === 'waveform') {
|
||||
// Array-valued sample: keep only the latest waveform and redraw.
|
||||
if (Array.isArray(sv.value)) {
|
||||
waveforms[i] = sv.value.map((x: any) => typeof x === 'number' ? x : parseFloat(String(x)));
|
||||
} else {
|
||||
const v = typeof sv.value === 'number' ? sv.value : parseFloat(String(sv.value));
|
||||
waveforms[i] = isNaN(v) ? [] : [v];
|
||||
}
|
||||
if (echart) echart.setOption(echartsOption(), { notMerge: true });
|
||||
} else if (plotType === 'timeseries') {
|
||||
const v = typeof sv.value === 'number' ? sv.value : parseFloat(String(sv.value));
|
||||
if (isNaN(v)) return;
|
||||
pushSample(buffers[i], ts, v);
|
||||
|
||||
Reference in New Issue
Block a user