Allow field select, dynamically remap values to fit the plot

This commit is contained in:
Piotr Rogowski 2021-04-08 23:13:38 +02:00
parent 7b5992d180
commit 005d2186f8
No known key found for this signature in database
GPG Key ID: F40F61D5587F5673
2 changed files with 78 additions and 20 deletions

View File

@ -2,6 +2,7 @@
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
@ -37,7 +38,10 @@ import {
AppState,
UIState,
} from '../types/state';
import { Config } from '../types/config';
import {
Config,
OutputChannel,
} from '../types/config';
import store from '../store';
import {
formatBytes,
@ -89,6 +93,19 @@ const Log = ({ ui, config }: { ui: UIState, config: Config }) => {
'MAP',
]);
const prepareSelectedFields = useMemo(() => selectedFields.map((field) => {
const name = field.toString();
// TODO: parse [Datalog]
return {
name,
units: '&', // out.units,
scale: 1, // out.scale,
transform: 1, // out.transform,
};
}), [selectedFields]);
useEffect(() => {
const worker = new MlgParserWorker();
const controller = new AbortController();
@ -158,7 +175,9 @@ const Log = ({ ui, config }: { ui: UIState, config: Config }) => {
<Checkbox.Group onChange={setSelectedFields} value={selectedFields}>
{fields.map((field) => (
<Row key={field.name}>
<Checkbox key={field.name} value={field.name}>{field.name}</Checkbox>
<Checkbox key={field.name} value={field.name}>
{field.name}{field.units && ` (${field.units})`}
</Checkbox>
</Row>
))}
</Checkbox.Group>
@ -216,6 +235,7 @@ const Log = ({ ui, config }: { ui: UIState, config: Config }) => {
data={logs!.records as LogEntry[]}
width={canvasWidth}
height={600}
selectedFields={prepareSelectedFields}
/>
}
</div>

View File

@ -19,6 +19,7 @@ import {
import {
colorHsl,
msToTime,
remap,
} from '../../utils/number';
export interface LogEntry {
@ -37,14 +38,23 @@ enum Colors {
BG = '#222629',
}
export interface SelectedField {
name: string;
units: string;
scale: string | number;
transform: string | number;
};
const Canvas = ({
data,
width,
height,
selectedFields,
}: {
data: LogEntry[],
width: number,
height: number,
selectedFields: SelectedField[],
}) => {
const [zoom, setZoom] = useState(1);
const [pan, setPan] = useState(0);
@ -66,13 +76,10 @@ const Canvas = ({
const plot = useCallback(() => {
const canvas = canvasRef.current!;
const fieldsToPlot = [
{ name: 'RPM', scale: 0.1 },
{ name: 'TPS', scale: 5 },
{ name: 'AFR Target', scale: 2 },
{ name: 'AFR', scale: 2 },
{ name: 'MAP', scale: 5 },
];
const hsl = (fieldIndex: number, allFields: number) => {
const [hue] = colorHsl(0, allFields - 1, fieldIndex);
return `hsl(${hue}, 80%, 50%)`;
};
const ctx = canvas.getContext('2d')!;
const lastEntry = data[data.length - 1];
const maxTime = (lastEntry.Time as number) / (zoom < 1 ? 1 : zoom);
@ -86,10 +93,27 @@ const Canvas = ({
const resolution = Math.round(data.length / 1000 / zoom) || 1; // 1..x where 1 is max
setRightBoundary(-(scaledWidth - areaWidth));
const hsl = (fieldIndex: number) => {
const [hue] = colorHsl(0, fieldsToPlot.length - 1, fieldIndex);
return `hsl(${hue}, 80%, 50%)`;
};
// find max values for each selected field so we can calculate scale
const fieldsToPlot: { [index: string]: { min: number, max: number, scale: number, transform: number } } = {};
data.forEach((record) => {
selectedFields.forEach((field) => {
const value = record[field.name];
if (!fieldsToPlot[field.name]) {
fieldsToPlot[field.name] = {
min: 0,
max: 0,
scale: field.scale as number,
transform: field.transform as number,
};
}
if (value > fieldsToPlot[field.name].max) {
fieldsToPlot[field.name].max = record[field.name] as number;
}
if (value < fieldsToPlot[field.name].min) {
fieldsToPlot[field.name].min = record[field.name] as number;
}
});
});
// basic settings
ctx.font = '14px Arial';
@ -113,12 +137,12 @@ const Canvas = ({
ctx.fillText(text, left, top);
};
const plotField = (field: string, yScale: number, color: string) => {
const plotField = (field: string, min: number, max: number, color: string) => {
ctx.strokeStyle = color;
ctx.beginPath();
// initial value
ctx.moveTo(start, areaHeight - (firstEntry[field] as number * yScale));
ctx.moveTo(start, areaHeight - remap(firstEntry[field] as number, min, max, 0, areaHeight));
let index = 0;
data.forEach((entry) => {
@ -129,11 +153,12 @@ const Canvas = ({
// draw marker on top of the record
if (entry.type === 'marker') {
// TODO: draw actual marker
return;
}
const time = (entry.Time as number) * xScale; // scale time to max width
const value = areaHeight - (entry[field] as number * yScale); // scale the value
const value = areaHeight - remap(entry[field] as number, min, max, 0, areaHeight); // scale the value
ctx.lineTo(start + time, value);
});
@ -163,9 +188,15 @@ const Canvas = ({
}
let top = 0;
fieldsToPlot.forEach(({ name }, fieldIndex) => {
Object.keys(fieldsToPlot).forEach((name, fieldIndex) => {
top += 20;
drawText(left, top, `${name}: ${data[index][name]}`, hsl(fieldIndex), textAlign);
drawText(
left,
top,
`${name}: ${data[index][name]}`,
hsl(fieldIndex, Object.keys(fieldsToPlot).length),
textAlign,
);
});
// draw Time
@ -184,9 +215,14 @@ const Canvas = ({
// clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
fieldsToPlot.forEach(({ name, scale }, fieldIndex) => plotField(name, scale, hsl(fieldIndex)));
Object.keys(fieldsToPlot).forEach((name, fieldIndex) => plotField(
name,
fieldsToPlot[name].min,
fieldsToPlot[name].max,
hsl(fieldIndex, Object.keys(fieldsToPlot).length)),
);
drawIndicator();
}, [data, zoom, pan, rightBoundary, indicatorPos]);
}, [data, zoom, pan, rightBoundary, selectedFields, indicatorPos]);
const onWheel = (e: WheelEvent) => {
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
@ -222,6 +258,8 @@ const Canvas = ({
};
const keyboardListener = useCallback((e: KeyboardEvent) => {
// TODO:
// onKeyLeft
if (isUp(e)) {
setZoom((current) => current + 0.1);
}