Small logs improvements (#71)

* Calculate resolution based on available pixels on the screen

* Marker drawing attempt

* Update dependencies

* Add todos
This commit is contained in:
Piotr Rogowski 2021-05-01 20:38:55 +02:00 committed by GitHub
parent b614ac7b57
commit 231178712a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 77 deletions

34
package-lock.json generated
View File

@ -9,7 +9,7 @@
"license": "MIT",
"dependencies": {
"@reduxjs/toolkit": "^1.5.1",
"antd": "^4.15.2",
"antd": "^4.15.3",
"electron-squirrel-startup": "^1.0.0",
"js-yaml": "^4.1.0 ",
"mlg-converter": "^0.5.0",
@ -4085,9 +4085,9 @@
}
},
"node_modules/antd": {
"version": "4.15.2",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.15.2.tgz",
"integrity": "sha512-9IwlR022xSQrG+iJG8d7adSo9gy4n7AuS5x0/OxDi/V3CYSTTfaE24jp1wnmgmXOVAT/77Llg9OEbKcSyFim2g==",
"version": "4.15.4",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.15.4.tgz",
"integrity": "sha512-1c0ykHGomcd7QhEeRtynxN3i7fb7JBdnEq2/Yqhf7yzMIhGSfZm+h+A2lTqMOMheCVL6q2ie7lxqhtNLq6sWoQ==",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.6.2",
@ -4106,7 +4106,7 @@
"rc-dropdown": "~3.2.0",
"rc-field-form": "~1.20.0",
"rc-image": "~5.2.4",
"rc-input-number": "~7.0.1",
"rc-input-number": "~7.1.0",
"rc-mentions": "~1.5.0",
"rc-menu": "~8.10.0",
"rc-motion": "^2.4.0",
@ -19036,13 +19036,13 @@
}
},
"node_modules/rc-input-number": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.0.3.tgz",
"integrity": "sha512-y0nVqVANWyxQbm/vdhz1p5E1V5Y6Yd2+3MGKntSzCxrYgw0F7/COXkbRdcTECnXwiDv8ZrbYQ1pTP3u43PqE4Q==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.1.0.tgz",
"integrity": "sha512-ewgtKZaDmwbOWX8DXBGV+amp1IiGS8G+5xDqn85CK1BiQMwsQdrmMEqNkbTdxO8EmYbwN1iQQ4t82IkAaIoa3A==",
"dependencies": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.5",
"rc-util": "^5.0.1"
"rc-util": "^5.9.8"
},
"peerDependencies": {
"react": ">=16.9.0",
@ -29544,9 +29544,9 @@
}
},
"antd": {
"version": "4.15.2",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.15.2.tgz",
"integrity": "sha512-9IwlR022xSQrG+iJG8d7adSo9gy4n7AuS5x0/OxDi/V3CYSTTfaE24jp1wnmgmXOVAT/77Llg9OEbKcSyFim2g==",
"version": "4.15.4",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.15.4.tgz",
"integrity": "sha512-1c0ykHGomcd7QhEeRtynxN3i7fb7JBdnEq2/Yqhf7yzMIhGSfZm+h+A2lTqMOMheCVL6q2ie7lxqhtNLq6sWoQ==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.6.2",
@ -29565,7 +29565,7 @@
"rc-dropdown": "~3.2.0",
"rc-field-form": "~1.20.0",
"rc-image": "~5.2.4",
"rc-input-number": "~7.0.1",
"rc-input-number": "~7.1.0",
"rc-mentions": "~1.5.0",
"rc-menu": "~8.10.0",
"rc-motion": "^2.4.0",
@ -41363,13 +41363,13 @@
}
},
"rc-input-number": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.0.3.tgz",
"integrity": "sha512-y0nVqVANWyxQbm/vdhz1p5E1V5Y6Yd2+3MGKntSzCxrYgw0F7/COXkbRdcTECnXwiDv8ZrbYQ1pTP3u43PqE4Q==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.1.0.tgz",
"integrity": "sha512-ewgtKZaDmwbOWX8DXBGV+amp1IiGS8G+5xDqn85CK1BiQMwsQdrmMEqNkbTdxO8EmYbwN1iQQ4t82IkAaIoa3A==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.5",
"rc-util": "^5.0.1"
"rc-util": "^5.9.8"
}
},
"rc-mentions": {

View File

@ -36,7 +36,7 @@
},
"dependencies": {
"@reduxjs/toolkit": "^1.5.1",
"antd": "^4.15.2",
"antd": "^4.15.3",
"electron-squirrel-startup": "^1.0.0",
"js-yaml": "^4.1.0 ",
"mlg-converter": "^0.5.0",

View File

@ -21,6 +21,7 @@ import {
colorHsl,
formatNumber,
msToTime,
round,
remap,
} from '../../utils/number';
@ -100,16 +101,19 @@ const Canvas = ({
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 maxIndex = useMemo(() => lastIndex / (zoom < 1 ? 1 : zoom), [lastIndex, zoom]);
const timeScale = useMemo(() => areaWidth / maxTime, [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)),
() => startTime >= 0 ? 0 : -(startTime * maxIndex / areaWidth),
[areaWidth, maxIndex, startTime],
);
const pixelsOnScreen = (maxIndex - startIndex) / areaWidth;
// map available pixels to the number of data entries
const resolution = pixelsOnScreen < 1 ? 1 : Math.round(pixelsOnScreen);
// find max values for each selected field so we can calculate scale
const fieldsToPlot = useMemo(() => {
@ -142,39 +146,15 @@ const Canvas = ({
const fieldsKeys = useMemo(() => Object.keys(fieldsToPlot), [fieldsToPlot]);
// 1..x where 1 is max
const resolution = useMemo(() =>
Math.round(maxIndex / 5_000 / zoom) || 1, [maxIndex, zoom]);
const dataWindow = useMemo(() => {
const sliced = data.slice(startIndex, startIndex + maxIndex); // slice data
// skip n-th element to reduce number of data points
if (resolution > 1) {
return sliced.filter((_, index) => index % resolution === 0);
}
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]);
return sliced;
}, [data, maxIndex, resolution, startIndex]);
const drawText = useCallback((left: number, top: number, text: string, color: string, textAlign = 'left') => {
ctx.textAlign = textAlign as any;
@ -184,17 +164,65 @@ const Canvas = ({
ctx.fillText(text, left, top);
}, [ctx]);
const drawMarker = useCallback((position: number) => {
const prevStyle = ctx.strokeStyle;
ctx.strokeStyle = Colors.RED;
ctx.setLineDash([5]);
ctx.beginPath();
ctx.moveTo(position, 0);
ctx.lineTo(position, canvasHeight);
ctx.stroke();
ctx.setLineDash([]);
ctx.strokeStyle = prevStyle;
}, [canvasHeight, ctx]);
const plotField = useCallback((field: string, min: number, max: number, color: string) => {
ctx.strokeStyle = color;
ctx.beginPath();
// initial position
const initialValue = areaHeight - remap(firstEntry[field] as number, min, max, 0, areaHeight);
ctx.moveTo(startTime, initialValue);
dataWindow.forEach((entry, index) => {
const lastRecord: LogEntry = dataWindow[index - 1] ?? { Time: 0 };
// scale time to max width
const time = (entry.Time ? entry.Time : lastRecord.Time) as number * timeScale;
// scale the value
const value = areaHeight - remap(entry[field] as number, min, max, 0, areaHeight);
const position = Math.round(startTime + time);
switch (entry.type) {
case 'field':
ctx.lineTo(position, Math.round(value));
break;
case 'marker':
drawText(position, areaHeight / 2, `Marker at: ${lastRecord.Time}`, Colors.GREEN);
// drawMarker(position); // TODO: fix moveTo
break;
default:
break;
}
});
ctx.stroke();
}, [areaHeight, ctx, dataWindow, drawText, firstEntry, startTime, timeScale]);
const drawIndicator = useCallback(() => {
ctx.setLineDash([5]);
ctx.strokeStyle = Colors.WHITE;
ctx.beginPath();
// switch to time
let index = Math.round(indicatorPos * (data.length - 1) / areaWidth);
// remap indicator position to index in the data array
// FIXME: this is bad
let index = Math.floor(remap(indicatorPos, 0, areaWidth, startIndex, maxIndex));
if (index < 0) {
index = 0;
}
// TODO:
// 1px = 1 index % resolution
// index = indicatorPos || 0;
const currentData = data[index];
ctx.moveTo(indicatorPos, 0);
@ -227,14 +255,24 @@ const Canvas = ({
drawText(
left,
areaHeight + 20,
msToTime(Math.round(currentData.Time as number * 1000)),
`${round(currentData.Time as number, 3)}s`,
// msToTime(Math.round(currentData.Time as number * 1000)),
Colors.GREY, textAlign,
);
// TODO: DEBUG
// 1px = 1 index % resolution
drawText(
left,
areaHeight - 20,
`${index} - ${indicatorPos}`,
Colors.RED, textAlign,
);
ctx.lineTo(indicatorPos, canvasHeight);
ctx.stroke();
ctx.setLineDash([]);
}, [areaHeight, areaWidth, canvasHeight, ctx, data, drawText, fieldsKeys, fieldsToPlot, hsl, indicatorPos]);
}, [areaHeight, areaWidth, canvasHeight, ctx, data, drawText, fieldsKeys, fieldsToPlot, hsl, indicatorPos, maxIndex, startIndex]);
const plot = useCallback(() => {
if (!ctx) {
@ -266,23 +304,9 @@ const Canvas = ({
fieldsToPlot[name].max,
hsl(fieldIndex, fieldsKeys.length)),
);
drawIndicator();
}, [
ctx,
scaledWidth,
areaWidth,
areaHeight,
zoom,
pan,
rightBoundary,
canvasWidth,
canvasHeight,
fieldsKeys,
drawIndicator,
plotField,
fieldsToPlot,
hsl,
]);
}, [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)) {
@ -318,8 +342,6 @@ const Canvas = ({
};
const keyboardListener = useCallback((e: KeyboardEvent) => {
// TODO:
// onKeyLeft
if (isUp(e)) {
setZoom((current) => current + 0.1);
}

View File

@ -1,3 +1,5 @@
export type HslType = [number, number, number];
export const formatBytes = (bytes: number, decimals = 2): string => {
if (bytes === 0) return '0 Bytes';
@ -10,6 +12,7 @@ export const formatBytes = (bytes: number, decimals = 2): string => {
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
};
// fix this pad
export const leftPad = (n: number, z = 2) => (`00${n}`).slice(-z);
export const msToTime = (input: number) => {
@ -26,8 +29,6 @@ export const msToTime = (input: number) => {
export const remap = (x: number, inMin: number, inMax: number, outMin: number, outMax: number) => (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
export type HslType = [number, number, number];
export const colorHsl = (min: number, max: number, value: number): HslType => {
const saturation = 60;
const lightness = 40;
@ -46,6 +47,7 @@ export const colorHsl = (min: number, max: number, value: number): HslType => {
// eslint-disable-next-line prefer-template
export const round = (value: number, digits: number | string) => +(Math.round(value + `e+${digits}` as any) + `e-${digits}`);
// TODO: move this or rename to MS
export const formatNumber = (value: number, format: string): string => {
if (format === '%d') {
return `${Math.round(value)}`;