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:
parent
b614ac7b57
commit
231178712a
|
@ -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": {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)}`;
|
||||
|
|
Loading…
Reference in New Issue