Use TimeChart for plotting logs (#317)
This commit is contained in:
parent
3feb2b864e
commit
6e616cd85d
File diff suppressed because it is too large
Load Diff
|
@ -34,8 +34,6 @@
|
||||||
"@sentry/tracing": "^6.16.1",
|
"@sentry/tracing": "^6.16.1",
|
||||||
"@speedy-tuner/types": "^0.2.1",
|
"@speedy-tuner/types": "^0.2.1",
|
||||||
"antd": "^4.17.3",
|
"antd": "^4.17.3",
|
||||||
"d3": "^7.0.4",
|
|
||||||
"d3fc": "^15.2.4",
|
|
||||||
"mlg-converter": "^0.5.1",
|
"mlg-converter": "^0.5.1",
|
||||||
"pako": "^2.0.4",
|
"pako": "^2.0.4",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
|
@ -45,7 +43,8 @@
|
||||||
"react-router-dom": "^5.2.1",
|
"react-router-dom": "^5.2.1",
|
||||||
"react-scripts": "^4.0.3",
|
"react-scripts": "^4.0.3",
|
||||||
"react-table-drag-select": "^0.3.1",
|
"react-table-drag-select": "^0.3.1",
|
||||||
"recharts": "^2.1.6"
|
"recharts": "^2.1.8",
|
||||||
|
"timechart": "^1.0.0-beta.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@craco/craco": "^6.4.3",
|
"@craco/craco": "^6.4.3",
|
||||||
|
|
|
@ -102,8 +102,7 @@ html, body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plot {
|
.log-canvas {
|
||||||
&:active {
|
color: @text;
|
||||||
cursor: grab;
|
--background-overlay: transparent;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo
|
||||||
const [logs, setLogs] = useState<ParserResult>();
|
const [logs, setLogs] = useState<ParserResult>();
|
||||||
const [fields, setFields] = useState<DatalogEntry[]>([]);
|
const [fields, setFields] = useState<DatalogEntry[]>([]);
|
||||||
const [selectedFields, setSelectedFields] = useState<CheckboxValueType[]>([
|
const [selectedFields, setSelectedFields] = useState<CheckboxValueType[]>([
|
||||||
'rpm',
|
// 'rpm',
|
||||||
'tps',
|
'tps',
|
||||||
'afrTarget',
|
'afrTarget',
|
||||||
'afr',
|
'afr',
|
||||||
|
@ -229,7 +229,7 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo
|
||||||
<LogCanvas
|
<LogCanvas
|
||||||
data={loadedLogs || (logs!.records as Logs)}
|
data={loadedLogs || (logs!.records as Logs)}
|
||||||
width={canvasWidth}
|
width={canvasWidth}
|
||||||
height={600}
|
height={800}
|
||||||
selectedFields={prepareSelectedFields}
|
selectedFields={prepareSelectedFields}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
|
|
|
@ -3,21 +3,12 @@ import {
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
Logs,
|
Logs,
|
||||||
LogEntry,
|
LogEntry,
|
||||||
} from '@speedy-tuner/types';
|
} from '@speedy-tuner/types';
|
||||||
import {
|
import TimeChart from 'timechart';
|
||||||
scaleLinear,
|
|
||||||
max,
|
|
||||||
zoom,
|
|
||||||
zoomTransform,
|
|
||||||
select,
|
|
||||||
ZoomTransform,
|
|
||||||
} from 'd3';
|
|
||||||
import { seriesCanvasLine } from 'd3fc';
|
|
||||||
import { colorHsl } from '../../utils/number';
|
import { colorHsl } from '../../utils/number';
|
||||||
|
|
||||||
// enum Colors {
|
// enum Colors {
|
||||||
|
@ -58,8 +49,7 @@ export interface PlottableField {
|
||||||
};
|
};
|
||||||
|
|
||||||
const LogCanvas = ({ data, width, height, selectedFields }: Props) => {
|
const LogCanvas = ({ data, width, height, selectedFields }: Props) => {
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
const canvasRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [zoomState, setZoomState] = useState<ZoomTransform | 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);
|
||||||
|
@ -71,6 +61,7 @@ const LogCanvas = ({ data, width, height, selectedFields }: Props) => {
|
||||||
const filtered = useMemo(() => data.filter(fieldsOnly), [data]);
|
const filtered = useMemo(() => data.filter(fieldsOnly), [data]);
|
||||||
|
|
||||||
// find max values for each selected field so we can calculate scale
|
// find max values for each selected field so we can calculate scale
|
||||||
|
// TODO: unused
|
||||||
const fieldsToPlot = useMemo(() => {
|
const fieldsToPlot = useMemo(() => {
|
||||||
const temp: { [index: string]: PlottableField } = {};
|
const temp: { [index: string]: PlottableField } = {};
|
||||||
|
|
||||||
|
@ -102,76 +93,43 @@ const LogCanvas = ({ data, width, height, selectedFields }: Props) => {
|
||||||
return temp;
|
return temp;
|
||||||
}, [filtered, selectedFields]);
|
}, [filtered, selectedFields]);
|
||||||
|
|
||||||
const xValue = useCallback((entry: LogEntry): number => (entry.Time || 0) as number, []);
|
|
||||||
const yValue = useCallback((entry: LogEntry, field: SelectedField): number => {
|
|
||||||
if (!(field.label in entry)) {
|
|
||||||
console.error(`Field [${field.label}] doesn't exist in this log file.`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry[field.label] as number;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const xScale = useMemo(() => {
|
|
||||||
const tempXScale = scaleLinear()
|
|
||||||
.domain([0, max(filtered, xValue) as number])
|
|
||||||
.range([0, width]);
|
|
||||||
let newXScale = tempXScale;
|
|
||||||
|
|
||||||
if (zoomState) {
|
|
||||||
newXScale = zoomState.rescaleX(tempXScale);
|
|
||||||
tempXScale.domain(newXScale.domain());
|
|
||||||
}
|
|
||||||
|
|
||||||
return newXScale;
|
|
||||||
}, [filtered, width, xValue, zoomState]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = select(canvasRef.current);
|
let chart: TimeChart;
|
||||||
const context = (canvas.node() as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
context.clearRect(0, 0, width, height);
|
if (canvasRef.current) {
|
||||||
context.lineWidth = 2;
|
const series = selectedFields.map((field) => ({
|
||||||
|
name: field.label,
|
||||||
|
color: hsl(selectedFields.indexOf(field), selectedFields.length),
|
||||||
|
data: data.map((entry) => ({
|
||||||
|
x: entry.Time as number,
|
||||||
|
y: entry[field.label] as number,
|
||||||
|
})).filter((entry) => entry.x !== undefined || entry.y !== undefined),
|
||||||
|
}));
|
||||||
|
|
||||||
const linesRaw = () => selectedFields.forEach((field, index) => {
|
chart = new TimeChart(canvasRef.current as HTMLDivElement, {
|
||||||
const yScale = (() => {
|
series,
|
||||||
const yField = (fieldsToPlot || {})[field.label] || { min: 0, max: 0 };
|
lineWidth: 2,
|
||||||
|
tooltip: true,
|
||||||
|
legend: false,
|
||||||
|
zoom: {
|
||||||
|
x: { autoRange: true },
|
||||||
|
y: { autoRange: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return scaleLinear()
|
return () => {
|
||||||
.domain([yField.min, yField.max])
|
if (chart) {
|
||||||
.range([height, 0]);
|
chart.dispose();
|
||||||
})();
|
}
|
||||||
|
};
|
||||||
seriesCanvasLine()
|
}, [data, fieldsToPlot, filtered, hsl, selectedFields, width, height]);
|
||||||
.xScale(xScale)
|
|
||||||
.yScale(yScale)
|
|
||||||
.crossValue((entry: LogEntry) => xValue(entry))
|
|
||||||
.mainValue((entry: LogEntry) => yValue(entry, field))
|
|
||||||
.context(context)
|
|
||||||
// eslint-disable-next-line no-return-assign
|
|
||||||
.decorate((ctx: CanvasRenderingContext2D) => {
|
|
||||||
ctx.strokeStyle = hsl(index, selectedFields.length);
|
|
||||||
})(filtered);
|
|
||||||
});
|
|
||||||
|
|
||||||
const zoomed = () => setZoomState(zoomTransform(canvas.node() as any));
|
|
||||||
|
|
||||||
const zoomBehavior = zoom()
|
|
||||||
.scaleExtent([1, 1000]) // zoom boundaries
|
|
||||||
.translateExtent([[0, 0], [width, height]]) // pan boundaries
|
|
||||||
.extent([[0, 0], [width, height]])
|
|
||||||
.on('zoom', zoomed);
|
|
||||||
|
|
||||||
canvas.call(zoomBehavior as any);
|
|
||||||
|
|
||||||
linesRaw();
|
|
||||||
}, [data, fieldsToPlot, filtered, height, hsl, selectedFields, width, xScale, xValue, yValue]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<canvas
|
<div
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
width={width}
|
style={{ width, height }}
|
||||||
height={height}
|
className="log-canvas"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue