Remove old LogCanvas component (#329)
This commit is contained in:
parent
2e485d0493
commit
5779ed0631
|
@ -37,7 +37,7 @@ import {
|
||||||
DatalogEntry,
|
DatalogEntry,
|
||||||
} from '@speedy-tuner/types';
|
} from '@speedy-tuner/types';
|
||||||
import { loadLogs } from '../utils/api';
|
import { loadLogs } from '../utils/api';
|
||||||
import LogCanvas2 from './Log/LogCanvas2';
|
import LogCanvas from './Log/LogCanvas';
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
import {
|
import {
|
||||||
formatBytes,
|
formatBytes,
|
||||||
|
@ -88,7 +88,6 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo
|
||||||
collapsed: ui.sidebarCollapsed,
|
collapsed: ui.sidebarCollapsed,
|
||||||
onCollapse: (collapsed: boolean) => {
|
onCollapse: (collapsed: boolean) => {
|
||||||
store.dispatch({ type: 'ui/sidebarCollapsed', payload: collapsed });
|
store.dispatch({ type: 'ui/sidebarCollapsed', payload: collapsed });
|
||||||
setTimeout(calculateCanvasSize, 1);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [logs, setLogs] = useState<ParserResult>();
|
const [logs, setLogs] = useState<ParserResult>();
|
||||||
|
@ -195,7 +194,7 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo
|
||||||
worker.terminate();
|
worker.terminate();
|
||||||
window.removeEventListener('resize', calculateCanvasSize);
|
window.removeEventListener('resize', calculateCanvasSize);
|
||||||
};
|
};
|
||||||
}, [calculateCanvasSize, config.datalog, config.outputChannels, loadedLogs]);
|
}, [calculateCanvasSize, config.datalog, config.outputChannels, loadedLogs, ui.sidebarCollapsed]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -249,7 +248,7 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo
|
||||||
<div ref={contentRef} style={{ width: '100%', marginRight: margin }}>
|
<div ref={contentRef} style={{ width: '100%', marginRight: margin }}>
|
||||||
{logs || !!loadedLogs.length
|
{logs || !!loadedLogs.length
|
||||||
?
|
?
|
||||||
<LogCanvas2
|
<LogCanvas
|
||||||
data={loadedLogs || (logs!.records as Logs)}
|
data={loadedLogs || (logs!.records as Logs)}
|
||||||
width={canvasWidth}
|
width={canvasWidth}
|
||||||
height={canvasHeight}
|
height={canvasHeight}
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useState,
|
||||||
useRef,
|
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Logs } from '@speedy-tuner/types';
|
import { Logs } from '@speedy-tuner/types';
|
||||||
import { Grid } from 'antd';
|
import {
|
||||||
import TimeChart from 'timechart';
|
Grid,
|
||||||
import { EventsPlugin } from 'timechart/dist/lib/plugins_extra/events';
|
Space,
|
||||||
|
} from 'antd';
|
||||||
|
import UplotReact from 'uplot-react';
|
||||||
|
import uPlot from 'uplot';
|
||||||
import { colorHsl } from '../../utils/number';
|
import { colorHsl } from '../../utils/number';
|
||||||
import LandscapeNotice from '../Dialog/LandscapeNotice';
|
import LandscapeNotice from '../Dialog/LandscapeNotice';
|
||||||
import CanvasHelp from '../CanvasHelp';
|
import { Colors } from '../../utils/colors';
|
||||||
|
import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin';
|
||||||
|
|
||||||
|
import 'uplot/dist/uPlot.min.css';
|
||||||
|
import { isNumber } from '../../utils/tune/expression';
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
|
@ -27,7 +33,8 @@ interface Props {
|
||||||
data: Logs;
|
data: Logs;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
selectedFields: SelectedField[];
|
selectedFields1: SelectedField[];
|
||||||
|
selectedFields2: SelectedField[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface PlottableField {
|
export interface PlottableField {
|
||||||
|
@ -39,15 +46,18 @@ export interface PlottableField {
|
||||||
format: string;
|
format: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LogCanvas = ({ data, width, height, selectedFields }: Props) => {
|
const LogCanvas = ({ data, width, height, selectedFields1, selectedFields2 }: Props) => {
|
||||||
const { sm } = useBreakpoint();
|
const { sm } = useBreakpoint();
|
||||||
const canvasRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const hsl = useCallback((fieldIndex: number, allFields: number) => {
|
const hsl = useCallback((fieldIndex: number, allFields: number) => {
|
||||||
const [hue] = colorHsl(0, allFields - 1, fieldIndex);
|
const [hue] = colorHsl(0, allFields - 1, fieldIndex);
|
||||||
return `hsl(${hue}, 90%, 50%)`;
|
return `hsl(${hue}, 90%, 50%)`;
|
||||||
}, []);
|
}, []);
|
||||||
|
const [options1, setOptions1] = useState<uPlot.Options>();
|
||||||
|
const [plotData1, setPlotData1] = useState<uPlot.AlignedData>();
|
||||||
|
const [options2, setOptions2] = useState<uPlot.Options>();
|
||||||
|
const [plotData2, setPlotData2] = useState<uPlot.AlignedData>();
|
||||||
|
|
||||||
const fieldsToPlot = useMemo(() => {
|
const generateFieldsToPlot = useCallback((selectedFields: SelectedField[]) => {
|
||||||
const temp: { [index: string]: PlottableField } = {};
|
const temp: { [index: string]: PlottableField } = {};
|
||||||
|
|
||||||
data.forEach((entry) => {
|
data.forEach((entry) => {
|
||||||
|
@ -76,74 +86,100 @@ const LogCanvas = ({ data, width, height, selectedFields }: Props) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return temp;
|
return temp;
|
||||||
}, [data, selectedFields]);
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
const generatePlotConfig = useCallback((fieldsToPlot: { [index: string]: PlottableField }, selectedFieldsLength: number, plotSyncKey: string) => {
|
||||||
const markers: { x: number, name: string }[] = [];
|
const dataSeries: uPlot.Series[] = [];
|
||||||
const series = Object.keys(fieldsToPlot).map((label, index) => {
|
const xData: number[] = [];
|
||||||
|
const yData: (number | null)[][] = [];
|
||||||
|
|
||||||
|
Object.keys(fieldsToPlot).forEach((label, index) => {
|
||||||
const field = fieldsToPlot[label];
|
const field = fieldsToPlot[label];
|
||||||
|
|
||||||
return {
|
dataSeries.push({
|
||||||
name: field.units ? `${label} (${field.units})` : label,
|
label: field.units ? `${label} (${field.units})` : label,
|
||||||
color: hsl(index, selectedFields.length),
|
points: { show: false },
|
||||||
data: data.map((entry, entryIndex) => {
|
stroke: hsl(index, selectedFieldsLength),
|
||||||
|
scale: field.units,
|
||||||
|
width: 2,
|
||||||
|
value: (_self, val) => isNumber(val) ? val.toFixed(2) : 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
data.forEach((entry) => {
|
||||||
|
if (entry.type === 'field') {
|
||||||
|
xData.push(entry.Time as number);
|
||||||
|
|
||||||
let value = entry[label];
|
let value = entry[label];
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
value = (value as number * field.scale) + field.transform;
|
value = (value as number * field.scale) + field.transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.type === 'marker') {
|
if (!yData[index]) {
|
||||||
const previousEntry = data[entryIndex - 1];
|
yData[index] = [];
|
||||||
if (previousEntry && previousEntry.Time !== undefined) {
|
}
|
||||||
markers.push({
|
|
||||||
x: previousEntry.Time as number,
|
yData[index].push(value);
|
||||||
name: '',
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: entry.Time,
|
xData,
|
||||||
y: value,
|
yData,
|
||||||
} as { x: number, y: number };
|
options: {
|
||||||
}).filter((entry) => entry.x !== undefined && entry.y !== undefined),
|
width,
|
||||||
|
height,
|
||||||
|
scales: { x: { time: false } },
|
||||||
|
series: [
|
||||||
|
{ label: 'Time (s)' },
|
||||||
|
...dataSeries,
|
||||||
|
],
|
||||||
|
axes: [
|
||||||
|
{
|
||||||
|
stroke: Colors.TEXT,
|
||||||
|
grid: { stroke: Colors.MAIN_LIGHT },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cursor: {
|
||||||
|
drag: { y: false },
|
||||||
|
sync: {
|
||||||
|
key: plotSyncKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [touchZoomPlugin()],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
}, [data, height, hsl, width]);
|
||||||
let chart: TimeChart;
|
|
||||||
|
|
||||||
if (canvasRef.current && sm) {
|
useEffect(() => {
|
||||||
chart = new TimeChart(canvasRef.current, {
|
const plotSync = uPlot.sync('logs');
|
||||||
series,
|
|
||||||
lineWidth: 2,
|
|
||||||
tooltip: true,
|
|
||||||
legend: false,
|
|
||||||
zoom: {
|
|
||||||
x: { autoRange: true },
|
|
||||||
},
|
|
||||||
tooltipXLabel: 'Time (s)',
|
|
||||||
plugins: {
|
|
||||||
events: new EventsPlugin(markers),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => chart && chart.dispose();
|
const result1 = generatePlotConfig(generateFieldsToPlot(selectedFields1), selectedFields1.length, plotSync.key);
|
||||||
}, [data, fieldsToPlot, hsl, selectedFields, width, height, sm]);
|
setOptions1(result1.options);
|
||||||
|
setPlotData1([result1.xData, ...result1.yData]);
|
||||||
|
|
||||||
|
const result2 = generatePlotConfig(generateFieldsToPlot(selectedFields2), selectedFields2.length, plotSync.key);
|
||||||
|
setOptions2(result2.options);
|
||||||
|
setPlotData2([result2.xData, ...result2.yData]);
|
||||||
|
|
||||||
|
}, [data, hsl, width, height, sm, generatePlotConfig, generateFieldsToPlot, selectedFields1, selectedFields2]);
|
||||||
|
|
||||||
if (!sm) {
|
if (!sm) {
|
||||||
return <LandscapeNotice />;
|
return <LandscapeNotice />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Space direction="vertical" size="large">
|
||||||
<CanvasHelp />
|
<UplotReact
|
||||||
<div
|
options={options1!}
|
||||||
ref={canvasRef}
|
data={plotData1!}
|
||||||
style={{ width, height }}
|
|
||||||
className="log-canvas"
|
|
||||||
/>
|
/>
|
||||||
</>
|
<UplotReact
|
||||||
|
options={options2!}
|
||||||
|
data={plotData2!}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
import {
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { Logs } from '@speedy-tuner/types';
|
|
||||||
import {
|
|
||||||
Grid,
|
|
||||||
Space,
|
|
||||||
} from 'antd';
|
|
||||||
import UplotReact from 'uplot-react';
|
|
||||||
import uPlot from 'uplot';
|
|
||||||
import { colorHsl } from '../../utils/number';
|
|
||||||
import LandscapeNotice from '../Dialog/LandscapeNotice';
|
|
||||||
import { Colors } from '../../utils/colors';
|
|
||||||
import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin';
|
|
||||||
|
|
||||||
import 'uplot/dist/uPlot.min.css';
|
|
||||||
import { isNumber } from '../../utils/tune/expression';
|
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
|
||||||
|
|
||||||
export interface SelectedField {
|
|
||||||
name: string;
|
|
||||||
label: string;
|
|
||||||
units: string;
|
|
||||||
scale: string | number;
|
|
||||||
transform: string | number;
|
|
||||||
format: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
data: Logs;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
selectedFields1: SelectedField[];
|
|
||||||
selectedFields2: SelectedField[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface PlottableField {
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
scale: number;
|
|
||||||
transform: number;
|
|
||||||
units: string;
|
|
||||||
format: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LogCanvas2 = ({ data, width, height, selectedFields1, selectedFields2 }: Props) => {
|
|
||||||
const { sm } = useBreakpoint();
|
|
||||||
const hsl = useCallback((fieldIndex: number, allFields: number) => {
|
|
||||||
const [hue] = colorHsl(0, allFields - 1, fieldIndex);
|
|
||||||
return `hsl(${hue}, 90%, 50%)`;
|
|
||||||
}, []);
|
|
||||||
const [options1, setOptions1] = useState<uPlot.Options>();
|
|
||||||
const [plotData1, setPlotData1] = useState<uPlot.AlignedData>();
|
|
||||||
const [options2, setOptions2] = useState<uPlot.Options>();
|
|
||||||
const [plotData2, setPlotData2] = useState<uPlot.AlignedData>();
|
|
||||||
|
|
||||||
const generateFieldsToPlot = useCallback((selectedFields: SelectedField[]) => {
|
|
||||||
const temp: { [index: string]: PlottableField } = {};
|
|
||||||
|
|
||||||
data.forEach((entry) => {
|
|
||||||
selectedFields.forEach(({ label, scale, transform, units, format }) => {
|
|
||||||
const value = entry[label];
|
|
||||||
|
|
||||||
if (!temp[label]) {
|
|
||||||
temp[label] = {
|
|
||||||
min: 0,
|
|
||||||
max: 0,
|
|
||||||
scale: (scale || 1) as number,
|
|
||||||
transform: (transform || 0) as number,
|
|
||||||
units: units || '',
|
|
||||||
format: format || '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value > temp[label].max) {
|
|
||||||
temp[label].max = entry[label] as number;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < temp[label].min) {
|
|
||||||
temp[label].min = entry[label] as number;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return temp;
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const generatePlotConfig = useCallback((fieldsToPlot: { [index: string]: PlottableField }, selectedFieldsLength: number, plotSyncKey: string) => {
|
|
||||||
const dataSeries: uPlot.Series[] = [];
|
|
||||||
const xData: number[] = [];
|
|
||||||
const yData: (number | null)[][] = [];
|
|
||||||
|
|
||||||
Object.keys(fieldsToPlot).forEach((label, index) => {
|
|
||||||
const field = fieldsToPlot[label];
|
|
||||||
|
|
||||||
dataSeries.push({
|
|
||||||
label: field.units ? `${label} (${field.units})` : label,
|
|
||||||
points: { show: false },
|
|
||||||
stroke: hsl(index, selectedFieldsLength),
|
|
||||||
scale: field.units,
|
|
||||||
width: 2,
|
|
||||||
value: (_self, val) => isNumber(val) ? val.toFixed(2) : 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
data.forEach((entry) => {
|
|
||||||
if (entry.type !== 'marker') {
|
|
||||||
xData.push(entry.Time as number);
|
|
||||||
|
|
||||||
let value = entry[label];
|
|
||||||
|
|
||||||
if (value !== undefined) {
|
|
||||||
value = (value as number * field.scale) + field.transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!yData[index]) {
|
|
||||||
yData[index] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
yData[index].push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
xData,
|
|
||||||
yData,
|
|
||||||
options: {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
scales: { x: { time: false } },
|
|
||||||
series: [
|
|
||||||
{ label: 'Time (s)' },
|
|
||||||
...dataSeries,
|
|
||||||
],
|
|
||||||
axes: [
|
|
||||||
{
|
|
||||||
stroke: Colors.TEXT,
|
|
||||||
grid: { stroke: Colors.MAIN_LIGHT },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
cursor: {
|
|
||||||
drag: { y: false },
|
|
||||||
sync: {
|
|
||||||
key: plotSyncKey,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [touchZoomPlugin()],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, [data, height, hsl, width]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const plotSync = uPlot.sync('logs');
|
|
||||||
|
|
||||||
const result1 = generatePlotConfig(generateFieldsToPlot(selectedFields1), selectedFields1.length, plotSync.key);
|
|
||||||
setOptions1(result1.options);
|
|
||||||
setPlotData1([result1.xData, ...result1.yData]);
|
|
||||||
|
|
||||||
const result2 = generatePlotConfig(generateFieldsToPlot(selectedFields2), selectedFields2.length, plotSync.key);
|
|
||||||
setOptions2(result2.options);
|
|
||||||
setPlotData2([result2.xData, ...result2.yData]);
|
|
||||||
|
|
||||||
}, [data, hsl, width, height, sm, generatePlotConfig, generateFieldsToPlot, selectedFields1, selectedFields2]);
|
|
||||||
|
|
||||||
if (!sm) {
|
|
||||||
return <LandscapeNotice />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space direction="vertical" size="large">
|
|
||||||
<UplotReact
|
|
||||||
options={options1!}
|
|
||||||
data={plotData1!}
|
|
||||||
/>
|
|
||||||
<UplotReact
|
|
||||||
options={options2!}
|
|
||||||
data={plotData2!}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LogCanvas2;
|
|
Loading…
Reference in New Issue