From 2e485d0493f9642ed1de9c4ad74cc79f39e75f4e Mon Sep 17 00:00:00 2001 From: Piotr Rogowski Date: Mon, 20 Dec 2021 00:29:09 +0100 Subject: [PATCH] Display second log viewer (#327) --- public/index.html | 2 +- src/components/Log.tsx | 84 ++++++---- src/components/Log/LogCanvas2.tsx | 186 ++++++++++++++++++++++ src/components/TriggerLog/ToothCanvas.tsx | 4 +- src/utils/uPlot/touchZoomPlugin.ts | 8 +- 5 files changed, 249 insertions(+), 35 deletions(-) create mode 100644 src/components/Log/LogCanvas2.tsx diff --git a/public/index.html b/public/index.html index f422023..ecb976f 100644 --- a/public/index.html +++ b/public/index.html @@ -3,7 +3,7 @@ - + diff --git a/src/components/Log.tsx b/src/components/Log.tsx index 63a89df..b45c781 100644 --- a/src/components/Log.tsx +++ b/src/components/Log.tsx @@ -2,7 +2,6 @@ import { useCallback, useEffect, - useMemo, useRef, useState, } from 'react'; @@ -38,7 +37,7 @@ import { DatalogEntry, } from '@speedy-tuner/types'; import { loadLogs } from '../utils/api'; -import LogCanvas, { SelectedField } from './Log/LogCanvas'; +import LogCanvas2 from './Log/LogCanvas2'; import store from '../store'; import { formatBytes, @@ -72,8 +71,16 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo const contentRef = useRef(null); const margin = 30; const [canvasWidth, setCanvasWidth] = useState(0); + const [canvasHeight, setCanvasHeight] = useState(0); const sidebarWidth = 250; - const calculateCanvasWidth = useCallback(() => setCanvasWidth((contentRef.current?.clientWidth || 0) - margin), []); + const calculateCanvasSize = useCallback(() => { + setCanvasWidth((contentRef.current?.clientWidth || 0) - margin); + + if (window.innerHeight > 600) { + setCanvasHeight(Math.round((window.innerHeight - 250) / 2)); + } + }, []); + const siderProps = { width: sidebarWidth, collapsible: true, @@ -81,23 +88,26 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo collapsed: ui.sidebarCollapsed, onCollapse: (collapsed: boolean) => { store.dispatch({ type: 'ui/sidebarCollapsed', payload: collapsed }); - setTimeout(calculateCanvasWidth, 1); + setTimeout(calculateCanvasSize, 1); }, }; const [logs, setLogs] = useState(); const [fields, setFields] = useState([]); - const [selectedFields, setSelectedFields] = useState([ - // 'rpm', + const [selectedFields1, setSelectedFields1] = useState([ + 'rpm', 'tps', + 'map', + ]); + const [selectedFields2, setSelectedFields2] = useState([ 'afrTarget', 'afr', - 'map', + 'dwell', ]); const { isConfigReady, findOutputChannel, } = useConfig(config); - const prepareSelectedFields = useMemo(() => { + const prepareSelectedFields = useCallback((selectedFields: CheckboxValueType[]) => { if (!isConfigReady) { return []; } @@ -121,7 +131,7 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo }; }).filter((val) => !!val); - }, [config.datalog, findOutputChannel, isConfigReady, selectedFields]); + }, [config.datalog, findOutputChannel, isConfigReady]); useEffect(() => { const worker = new MlgParserWorker(); @@ -176,16 +186,16 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo setFields(Object.values(config.datalog)); } - calculateCanvasWidth(); + calculateCanvasSize(); - window.addEventListener('resize', calculateCanvasWidth); + window.addEventListener('resize', calculateCanvasSize); return () => { controller.abort(); worker.terminate(); - window.removeEventListener('resize', calculateCanvasWidth); + window.removeEventListener('resize', calculateCanvasSize); }; - }, [calculateCanvasWidth, config.datalog, config.outputChannels, loadedLogs]); + }, [calculateCanvasSize, config.datalog, config.outputChannels, loadedLogs]); return ( <> @@ -196,18 +206,35 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo !ui.sidebarCollapsed && } key="fields"> - - - {fields.map((field) => ( - - - {field.label} - {/* {field.units && ` (${field.units})`} */} - - - ))} - - +
+ + + {fields.map((field) => ( + + + {field.label} + {/* {field.units && ` (${field.units})`} */} + + + ))} + + +
+ +
+ + + {fields.map((field) => ( + + + {field.label} + {/* {field.units && ` (${field.units})`} */} + + + ))} + + +
} key="files"> @@ -222,11 +249,12 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo
{logs || !!loadedLogs.length ? - : { + 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(); + const [plotData1, setPlotData1] = useState(); + const [options2, setOptions2] = useState(); + const [plotData2, setPlotData2] = useState(); + + 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 ; + } + + return ( + + + + + ); +}; + +export default LogCanvas2; diff --git a/src/components/TriggerLog/ToothCanvas.tsx b/src/components/TriggerLog/ToothCanvas.tsx index dd4ec4f..51d7c3f 100644 --- a/src/components/TriggerLog/ToothCanvas.tsx +++ b/src/components/TriggerLog/ToothCanvas.tsx @@ -5,6 +5,7 @@ import { import { Grid } from 'antd'; import UplotReact from 'uplot-react'; import uPlot from 'uplot'; +import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin'; import LandscapeNotice from '../Dialog/LandscapeNotice'; import { ToothLogEntry, @@ -12,7 +13,6 @@ import { } from '../../utils/logs/TriggerLogsParser'; import CanvasHelp from '../CanvasHelp'; import { Colors } from '../../utils/colors'; -import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin'; import 'uplot/dist/uPlot.min.css'; @@ -63,7 +63,7 @@ const ToothCanvas = ({ data, width, height }: Props) => { stroke: Colors.ACCENT, fill: Colors.ACCENT, scale: 'toothTime', - value: (self, rawValue) => `${rawValue.toLocaleString()}μs`, + value: (_self, rawValue) => `${rawValue.toLocaleString()}μs`, paths: bars!({ size: [0.6, 100] }), }, ], diff --git a/src/utils/uPlot/touchZoomPlugin.ts b/src/utils/uPlot/touchZoomPlugin.ts index a017170..7301132 100644 --- a/src/utils/uPlot/touchZoomPlugin.ts +++ b/src/utils/uPlot/touchZoomPlugin.ts @@ -47,8 +47,8 @@ const touchZoomPlugin = () => { const yMax = Math.max(t0y, t1y); // mid points - t.y = (yMin+yMax)/2; - t.x = (xMin+xMax)/2; + t.y = (yMin + yMax) / 2; + t.x = (xMin + xMax) / 2; t.dx = xMax - xMin; t.dy = yMax - yMin; @@ -74,8 +74,8 @@ const touchZoomPlugin = () => { const xFactor = fr.d! / to.d!; const yFactor = fr.d! / to.d!; - const leftPct = left/rect.width; - const btmPct = 1 - top/rect.height; + const leftPct = left / rect.width; + const btmPct = 1 - top / rect.height; const nxRange = oxRange * xFactor; const nxMin = xVal - leftPct * nxRange;