Refactor logs, improve performance (#56)

This commit is contained in:
Piotr Rogowski 2021-04-16 22:57:53 +02:00 committed by GitHub
parent 2282962190
commit 36adb53b30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 201 additions and 155 deletions

View File

@ -10,10 +10,14 @@ settings:
- ".ts"
- ".tsx"
extends:
- eslint:recommended
- react-app
- airbnb
- plugin:jsx-a11y/recommended
- prettier
- plugin:import/errors
- plugin:import/warnings
- plugin:import/typescript
plugins:
- jsx-a11y
- prettier

60
package-lock.json generated
View File

@ -9,7 +9,7 @@
"license": "MIT",
"dependencies": {
"@reduxjs/toolkit": "^1.5.1",
"antd": "^4.15.0",
"antd": "^4.15.1",
"electron-squirrel-startup": "^1.0.0",
"js-yaml": "^4.0.0 ",
"mlg-converter": "^0.5.0",
@ -41,8 +41,8 @@
"@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.1.7",
"concurrently": "^6.0.1",
"electron": "^12.0.1",
"eslint": "^7.23.0",
"electron": "^12.0.4",
"eslint": "^7.24.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.1.0",
@ -52,7 +52,7 @@
"eslint-plugin-prettier": "^3.3.1",
"less-loader": "^6.1.0",
"prettier": "^2.2.1",
"typescript": "^4.1.5",
"typescript": "^4.2.4",
"wait-on": "^5.3.0",
"worker-loader": "^3.0.8"
}
@ -4085,9 +4085,9 @@
}
},
"node_modules/antd": {
"version": "4.15.0",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.15.0.tgz",
"integrity": "sha512-24HMixmQAhCyqb0ND5wX5DYRTbPactCT36mfVKowqgr77eT7XQ59Uu6aS513mbeiVhXcHrNlrlCKNZBSeEDgPg==",
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.15.1.tgz",
"integrity": "sha512-zTZz8GY9yERNjSnH6xWU3Rw5sC3RtHEs/LOTKcSMTtU3Q5jHXIbAHKd1C6bYLQT6Ru75p+/UyKvJoNip/ax/WQ==",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.6.2",
@ -4096,7 +4096,7 @@
"array-tree-filter": "^2.1.0",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0",
"lodash": "^4.17.20",
"lodash": "^4.17.21",
"moment": "^2.25.3",
"rc-cascader": "~1.4.0",
"rc-checkbox": "~2.3.0",
@ -7839,9 +7839,9 @@
}
},
"node_modules/electron": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-12.0.2.tgz",
"integrity": "sha512-14luh9mGzfL4e0sncyy0+kW37IU7Y0Y1tvI97FDRSW0ZBQxi5cmAwSs5dmPmNBFBIGtzkaGaEB01j9RjZuCmow==",
"version": "12.0.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-12.0.4.tgz",
"integrity": "sha512-A8Lq3YMZ1CaO1z5z5nsyFxIwkgwXLHUwL2pf9MVUHpq7fv3XUewCMD98EnLL3DdtiyCvw5KMkeT1WGsZh8qFug==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@ -8924,9 +8924,9 @@
}
},
"node_modules/eslint": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz",
"integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.24.0.tgz",
"integrity": "sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ==",
"dependencies": {
"@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.0",
@ -23715,9 +23715,9 @@
}
},
"node_modules/typescript": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -29539,9 +29539,9 @@
}
},
"antd": {
"version": "4.15.0",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.15.0.tgz",
"integrity": "sha512-24HMixmQAhCyqb0ND5wX5DYRTbPactCT36mfVKowqgr77eT7XQ59Uu6aS513mbeiVhXcHrNlrlCKNZBSeEDgPg==",
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.15.1.tgz",
"integrity": "sha512-zTZz8GY9yERNjSnH6xWU3Rw5sC3RtHEs/LOTKcSMTtU3Q5jHXIbAHKd1C6bYLQT6Ru75p+/UyKvJoNip/ax/WQ==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.6.2",
@ -29550,7 +29550,7 @@
"array-tree-filter": "^2.1.0",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0",
"lodash": "^4.17.20",
"lodash": "^4.17.21",
"moment": "^2.25.3",
"rc-cascader": "~1.4.0",
"rc-checkbox": "~2.3.0",
@ -32589,9 +32589,9 @@
"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA=="
},
"electron": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-12.0.2.tgz",
"integrity": "sha512-14luh9mGzfL4e0sncyy0+kW37IU7Y0Y1tvI97FDRSW0ZBQxi5cmAwSs5dmPmNBFBIGtzkaGaEB01j9RjZuCmow==",
"version": "12.0.4",
"resolved": "https://registry.npmjs.org/electron/-/electron-12.0.4.tgz",
"integrity": "sha512-A8Lq3YMZ1CaO1z5z5nsyFxIwkgwXLHUwL2pf9MVUHpq7fv3XUewCMD98EnLL3DdtiyCvw5KMkeT1WGsZh8qFug==",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
@ -33438,9 +33438,9 @@
}
},
"eslint": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz",
"integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.24.0.tgz",
"integrity": "sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ==",
"requires": {
"@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.0",
@ -44992,9 +44992,9 @@
}
},
"typescript": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw=="
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg=="
},
"ua-parser-js": {
"version": "0.7.27",

View File

@ -36,7 +36,7 @@
},
"dependencies": {
"@reduxjs/toolkit": "^1.5.1",
"antd": "^4.15.0",
"antd": "^4.15.1",
"electron-squirrel-startup": "^1.0.0",
"js-yaml": "^4.0.0 ",
"mlg-converter": "^0.5.0",
@ -68,8 +68,8 @@
"@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.1.7",
"concurrently": "^6.0.1",
"electron": "^12.0.1",
"eslint": "^7.23.0",
"electron": "^12.0.4",
"eslint": "^7.24.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.1.0",
@ -79,7 +79,7 @@
"eslint-plugin-prettier": "^3.3.1",
"less-loader": "^6.1.0",
"prettier": "^2.2.1",
"typescript": "^4.1.5",
"typescript": "^4.2.4",
"wait-on": "^5.3.0",
"worker-loader": "^3.0.8"
},

View File

@ -1,7 +1,4 @@
import {
useEffect,
useMemo,
} from 'react';
import {
useLocation,
Switch,
@ -15,6 +12,10 @@ import {
Result,
} from 'antd';
import { connect } from 'react-redux';
import {
useEffect,
useMemo,
} from 'react';
import Dialog from './components/Dialog';
import { loadAll } from './utils/api';
import SideBar, { DialogMatchedPathType } from './components/SideBar';

View File

@ -9,6 +9,7 @@ import {
WheelEvent,
TouchEvent,
Touch,
useMemo,
} from 'react';
import {
isDown,
@ -85,33 +86,40 @@ const Canvas = ({
return value;
}, [rightBoundary]);
const plot = useCallback(() => {
const canvas = canvasRef.current!;
const hsl = (fieldIndex: number, allFields: number) => {
const [hue] = colorHsl(0, allFields - 1, fieldIndex);
return `hsl(${hue}, 90%, 50%)`;
};
const ctx = canvas.getContext('2d')!;
const lastEntry = data[data.length - 1];
const maxTime = (lastEntry.Time as number) / (zoom < 1 ? 1 : zoom);
const areaWidth = canvas.width;
const areaHeight = canvas.height - 30; // leave some space in the bottom
const xScale = areaWidth / maxTime;
const firstEntry = data[0];
const scaledWidth = areaWidth * zoom / 1;
const start = pan;
// TODO: adjust this based on FPS / preference
const resolution = Math.round(data.length / 1000 / zoom) || 1; // 1..x where 1 is max
const hsl = useCallback((fieldIndex: number, allFields: number) => {
const [hue] = colorHsl(0, allFields - 1, fieldIndex);
return `hsl(${hue}, 90%, 50%)`;
}, []);
setRightBoundary(-(scaledWidth - areaWidth));
const canvas = canvasRef.current!;
const ctx = useMemo(() => canvas && canvas.getContext('2d', { alpha: false })!, [canvas]);
const canvasWidth = canvas ? canvas.width : 0;
const canvasHeight = canvas ? canvas.height : 0;
const areaWidth = canvas ? canvasWidth : 0;
const areaHeight = canvas ? canvasHeight - 30 : 0; // leave some space in the bottom
const lastIndex = data.length - 1;
const lastEntry = useMemo(() => data[lastIndex], [data, lastIndex]);
const maxTime = useMemo(() => (lastEntry.Time as number) / (zoom < 1 ? 1 : zoom), [lastEntry.Time, zoom]);
const maxIndex = useMemo(() => Math.round(lastIndex / (zoom < 1 ? 1 : zoom)), [lastIndex, zoom]);
const timeScale = areaWidth / maxTime;
// const indexScale = areaWidth / maxIndex;
const firstEntry = data[0];
const scaledWidth = useMemo(() => areaWidth * zoom / 1, [areaWidth, zoom]);
const startTime = pan;
const startIndex = useMemo(
() => Math.round(startTime >= 0 ? 0 : -(startTime * maxIndex / areaWidth)),
[areaWidth, maxIndex, startTime],
);
// find max values for each selected field so we can calculate scale
const fieldsToPlot = useMemo(() => {
const temp: { [index: string]: PlottableField } = {};
// find max values for each selected field so we can calculate scale
const fieldsToPlot: { [index: string]: PlottableField } = {};
data.forEach((record) => {
selectedFields.forEach(({ name, scale, transform, units, format }) => {
const value = record[name];
if (!fieldsToPlot[name]) {
fieldsToPlot[name] = {
if (!temp[name]) {
temp[name] = {
min: 0,
max: 0,
scale: scale as number,
@ -120,15 +128,120 @@ const Canvas = ({
format,
};
}
if (value > fieldsToPlot[name].max) {
fieldsToPlot[name].max = record[name] as number;
if (value > temp[name].max) {
temp[name].max = record[name] as number;
}
if (value < fieldsToPlot[name].min) {
fieldsToPlot[name].min = record[name] as number;
if (value < temp[name].min) {
temp[name].min = record[name] as number;
}
});
});
const fieldsKeys = Object.keys(fieldsToPlot);
return temp;
}, [data, selectedFields]);
const fieldsKeys = useMemo(() => Object.keys(fieldsToPlot), [fieldsToPlot]);
// 1..x where 1 is max
const resolution = useMemo(() =>
Math.round(data.length / 1_000 / zoom) || 1, [data.length, zoom]);
const dataWindow = useMemo(
() => data
.slice(startIndex, startIndex + maxIndex) // slice the data array
.filter((_, index) => index % resolution === 0),
[data, maxIndex, resolution, startIndex],
);
const plotField = useCallback((field: string, min: number, max: number, color: string) => {
ctx.strokeStyle = color;
ctx.beginPath();
// initial value
ctx.moveTo(startTime, areaHeight - remap(firstEntry[field] as number, min, max, 0, areaHeight));
dataWindow.forEach((entry) => {
// draw marker on top of the record
if (entry.type === 'marker') {
// TODO: draw actual marker
return;
}
const time = (entry.Time as number) * timeScale; // scale time to max width
const value = areaHeight - remap(entry[field] as number, min, max, 0, areaHeight); // scale the value
ctx.lineTo(Math.round(startTime + time), Math.round(value));
});
ctx.stroke();
}, [areaHeight, ctx, dataWindow, firstEntry, startTime, timeScale]);
const drawText = useCallback((left: number, top: number, text: string, color: string, textAlign = 'left') => {
ctx.textAlign = textAlign as any;
ctx.fillStyle = Colors.BG;
ctx.fillText(text, left + 2, top + 2);
ctx.fillStyle = color;
ctx.fillText(text, left, top);
}, [ctx]);
const drawIndicator = useCallback(() => {
ctx.setLineDash([5]);
ctx.strokeStyle = Colors.WHITE;
ctx.beginPath();
// switch to time
let index = Math.round(indicatorPos * (data.length - 1) / areaWidth);
if (index < 0) {
index = 0;
}
const currentData = data[index];
ctx.moveTo(indicatorPos, 0);
let left = indicatorPos + 10;
let textAlign = 'left';
if (indicatorPos > areaWidth / 2) {
// flip text to the left side of the indicator
textAlign = 'right';
left = indicatorPos - 10;
}
let top = 0;
fieldsKeys.forEach((name, fieldIndex) => {
const field = fieldsToPlot[name];
const { units, scale, transform, format } = field;
const value = formatNumber((currentData[name] as number * scale) + transform, format);
top += 20;
drawText(
left,
top,
`${name}: ${value}${units ? ` (${units})` : ''}`,
hsl(fieldIndex, fieldsKeys.length),
textAlign,
);
});
// draw Time
drawText(
left,
areaHeight + 20,
msToTime(Math.round(currentData.Time as number * 1000)),
Colors.GREY, textAlign,
);
ctx.lineTo(indicatorPos, canvasHeight);
ctx.stroke();
ctx.setLineDash([]);
}, [areaHeight, areaWidth, canvasHeight, ctx, data, drawText, fieldsKeys, fieldsToPlot, hsl, indicatorPos]);
const plot = useCallback(() => {
if (!ctx) {
return;
}
setRightBoundary(-(scaledWidth - areaWidth));
// basic settings
ctx.font = '14px Arial';
@ -144,95 +257,8 @@ const Canvas = ({
return;
}
const drawText = (left: number, top: number, text: string, color: string, textAlign = 'left') => {
ctx.textAlign = textAlign as any;
ctx.fillStyle = Colors.BG;
ctx.fillText(text, left + 2, top + 2);
ctx.fillStyle = color;
ctx.fillText(text, left, top);
};
const plotField = (field: string, min: number, max: number, color: string) => {
ctx.strokeStyle = color;
ctx.beginPath();
// initial value
ctx.moveTo(start, areaHeight - remap(firstEntry[field] as number, min, max, 0, areaHeight));
let index = 0;
data.forEach((entry) => {
index++;
if (index % resolution !== 0) {
return;
}
// 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 - remap(entry[field] as number, min, max, 0, areaHeight); // scale the value
ctx.lineTo(start + time, value);
});
ctx.stroke();
};
const drawIndicator = () => {
ctx.setLineDash([5]);
ctx.strokeStyle = Colors.WHITE;
ctx.beginPath();
// switch to time
let index = Math.round(indicatorPos * (data.length - 1) / areaWidth);
if (index < 0) {
index = 0;
}
ctx.moveTo(indicatorPos, 0);
let left = indicatorPos + 10;
let textAlign = 'left';
if (indicatorPos > areaWidth / 2) {
// flip text to the left side of the indicator
textAlign = 'right';
left = indicatorPos - 10;
}
let top = 0;
fieldsKeys.forEach((name, fieldIndex) => {
const field = fieldsToPlot[name];
const { units, scale, transform, format } = field;
const value = formatNumber((data[index][name] as number * scale) + transform, format);
top += 20;
drawText(
left,
top,
`${name}: ${value}${units ? ` (${units})` : ''}`,
hsl(fieldIndex, fieldsKeys.length),
textAlign,
);
});
// draw Time
drawText(
left,
areaHeight + 20,
msToTime(Math.round(data[index].Time as number * 1000)),
Colors.GREY, textAlign,
);
ctx.lineTo(indicatorPos, canvas.height);
ctx.stroke();
ctx.setLineDash([]);
};
// clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
fieldsKeys.forEach((name, fieldIndex) => plotField(
name,
@ -241,7 +267,22 @@ const Canvas = ({
hsl(fieldIndex, fieldsKeys.length)),
);
drawIndicator();
}, [data, zoom, pan, rightBoundary, selectedFields, indicatorPos]);
}, [
ctx,
scaledWidth,
areaWidth,
areaHeight,
zoom,
pan,
rightBoundary,
canvasWidth,
canvasHeight,
fieldsKeys,
drawIndicator,
plotField,
fieldsToPlot,
hsl,
]);
const onWheel = (e: WheelEvent) => {
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {

View File

@ -1,6 +1,6 @@
import { Help as HelpType } from '../types/config';
export const help: HelpType = {
const help: HelpType = {
reqFuel: 'The base reference pulse width required to achieve stoichiometric at 100% VE and a manifold absolute pressure (MAP) of 100kPa using current settings.',
algorithm: 'Fueling calculation algorithm',
alternate: 'Whether or not the injectors should be fired at the same time.\nThis setting is ignored when Sequential is selected below, however it will still affect req_fuel value.',