Base work for tooth logs (#325)
This commit is contained in:
parent
0f1d35093c
commit
117ab6615d
|
@ -24,7 +24,9 @@
|
|||
"react-scripts": "^4.0.3",
|
||||
"react-table-drag-select": "^0.3.1",
|
||||
"recharts": "^2.1.8",
|
||||
"timechart": "^1.0.0-beta.4"
|
||||
"timechart": "^1.0.0-beta.4",
|
||||
"uplot": "^1.6.18",
|
||||
"uplot-react": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^6.4.3",
|
||||
|
@ -21479,6 +21481,23 @@
|
|||
"yarn": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/uplot": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.18.tgz",
|
||||
"integrity": "sha512-x7+bFfIZ8rMjOmDGhUlJCkYWiZX617xQWNfT94JUhidliRtzMHKIX0xUiN92TZ/7il6xMf9oLwbhsz7nbqF1YQ=="
|
||||
},
|
||||
"node_modules/uplot-react": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/uplot-react/-/uplot-react-1.1.1.tgz",
|
||||
"integrity": "sha512-zCvwyZVm4nfYDi+KjaK0FppqftGzga/x+u0h2baRWj1vXMB9/hfJ1qb9gXAdXMfp17C9Rk57HoZDE9MewNWLfg==",
|
||||
"engines": {
|
||||
"node": ">=8.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.6",
|
||||
"uplot": "^1.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
@ -40124,6 +40143,17 @@
|
|||
"resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
|
||||
"integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="
|
||||
},
|
||||
"uplot": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.18.tgz",
|
||||
"integrity": "sha512-x7+bFfIZ8rMjOmDGhUlJCkYWiZX617xQWNfT94JUhidliRtzMHKIX0xUiN92TZ/7il6xMf9oLwbhsz7nbqF1YQ=="
|
||||
},
|
||||
"uplot-react": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/uplot-react/-/uplot-react-1.1.1.tgz",
|
||||
"integrity": "sha512-zCvwyZVm4nfYDi+KjaK0FppqftGzga/x+u0h2baRWj1vXMB9/hfJ1qb9gXAdXMfp17C9Rk57HoZDE9MewNWLfg==",
|
||||
"requires": {}
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
|
|
@ -45,7 +45,9 @@
|
|||
"react-scripts": "^4.0.3",
|
||||
"react-table-drag-select": "^0.3.1",
|
||||
"recharts": "^2.1.8",
|
||||
"timechart": "^1.0.0-beta.4"
|
||||
"timechart": "^1.0.0-beta.4",
|
||||
"uplot": "^1.6.18",
|
||||
"uplot-react": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^6.4.3",
|
||||
|
|
|
@ -123,6 +123,7 @@ Flag,Flag,Flag,Flag,ms,ms,ms,ms
|
|||
0.0,0.0,0.0,1.0,564200.94,564200.94,35.864,564200.94
|
||||
0.0,1.0,1.0,1.0,564250.3,564250.3,49.376,564250.3
|
||||
1.0,1.0,0.0,1.0,564258.0,564258.0,7.728,564258.0
|
||||
MARK 000
|
||||
1.0,0.0,1.0,1.0,564286.5,564286.5,28.484,564286.5
|
||||
0.0,0.0,0.0,1.0,564294.0,564294.0,7.468,564294.0
|
||||
1.0,0.0,0.0,1.0,564351.06,564351.06,57.056,564351.06
|
||||
|
@ -382,4 +383,4 @@ Flag,Flag,Flag,Flag,ms,ms,ms,ms
|
|||
1.0,0.0,0.0,1.0,568452.75,568452.75,57.408,568452.75
|
||||
0.0,0.0,0.0,1.0,568488.8,568488.8,36.024,568488.8
|
||||
0.0,1.0,1.0,1.0,568538.44,568538.44,49.624,568538.44
|
||||
MARK 000
|
||||
MARK 001
|
||||
|
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,29 @@
|
|||
import {
|
||||
Popover,
|
||||
Space,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
const CanvasHelp = () => (
|
||||
<div style={{ marginTop: -20, marginBottom: 10, textAlign: 'left', marginLeft: 20 }}>
|
||||
<Popover
|
||||
placement="bottom"
|
||||
content={
|
||||
<Space direction="vertical">
|
||||
<Title level={5}>Navigation</Title>
|
||||
<Text>Pinch to zoom</Text>
|
||||
<Text>Drag to pan</Text>
|
||||
<Text>Ctrl + wheel scroll to zoom X axis</Text>
|
||||
<Text>Hold Shift to speed up zoom 5 times</Text>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CanvasHelp;
|
|
@ -13,6 +13,7 @@ import {
|
|||
Steps,
|
||||
Space,
|
||||
Divider,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import {
|
||||
FileTextOutlined,
|
||||
|
@ -28,15 +29,23 @@ import {
|
|||
Config,
|
||||
Logs,
|
||||
} from '@speedy-tuner/types';
|
||||
import { loadCompositeLogs } from '../utils/api';
|
||||
import {
|
||||
loadCompositeLogs,
|
||||
loadToothLogs,
|
||||
} from '../utils/api';
|
||||
import store from '../store';
|
||||
import { formatBytes } from '../utils/number';
|
||||
import CompositeCanvas from './TriggerLog/CompositeCanvas';
|
||||
import { isNumber } from '../utils/tune/expression';
|
||||
import TriggerLogsParser, {
|
||||
CompositeLogEntry,
|
||||
ToothLogEntry,
|
||||
} from '../utils/logs/TriggerLogsParser';
|
||||
import ToothCanvas from './TriggerLog/ToothCanvas';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
const { Content } = Layout;
|
||||
const { Step } = Steps;
|
||||
|
||||
const edgeUnknown = 'Unknown';
|
||||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
|
@ -46,19 +55,6 @@ const mapStateToProps = (state: AppState) => ({
|
|||
loadedLogs: state.logs,
|
||||
});
|
||||
|
||||
// TODO: extract this to types package
|
||||
interface CompositeLogEntry {
|
||||
type: 'trigger' | 'marker';
|
||||
primaryLevel: number;
|
||||
secondaryLevel: number;
|
||||
trigger: number;
|
||||
sync: number;
|
||||
refTime: number;
|
||||
maxTime: number;
|
||||
toothTime: number;
|
||||
time: number;
|
||||
}
|
||||
|
||||
const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLogs: Logs }) => {
|
||||
const { lg } = useBreakpoint();
|
||||
const { Sider } = Layout;
|
||||
|
@ -83,6 +79,7 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loa
|
|||
},
|
||||
};
|
||||
const [logs, setLogs] = useState<CompositeLogEntry[]>();
|
||||
const [toothLogs, setToothLogs] = useState<ToothLogEntry[]>();
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
|
@ -90,79 +87,24 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loa
|
|||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const raw = await loadCompositeLogs((percent, total, edge) => {
|
||||
const compositeRaw = await loadCompositeLogs((percent, total, edge) => {
|
||||
setProgress(percent);
|
||||
setFileSize(formatBytes(total));
|
||||
setEdgeLocation(edge || edgeUnknown);
|
||||
}, signal);
|
||||
|
||||
setFileSize(formatBytes(raw.byteLength));
|
||||
|
||||
const buff = pako.inflate(new Uint8Array(raw));
|
||||
const string = (new TextDecoder()).decode(buff);
|
||||
const result: CompositeLogEntry[] = [];
|
||||
const toothRaw = await loadToothLogs(undefined, signal);
|
||||
|
||||
setFileSize(formatBytes(compositeRaw.byteLength));
|
||||
setStep(1);
|
||||
|
||||
// TODO: extract this, make a parser class
|
||||
string.split('\n').forEach((line, index) => {
|
||||
const trimmed = line.trim();
|
||||
const parser = new TriggerLogsParser();
|
||||
const resultComposite = parser.parse(pako.inflate(new Uint8Array(compositeRaw))).getCompositeLogs();
|
||||
const resultTooth = parser.parse(pako.inflate(new Uint8Array(toothRaw))).getToothLogs();
|
||||
|
||||
// skip comments
|
||||
if (trimmed.startsWith('#')) {
|
||||
return;
|
||||
}
|
||||
setLogs(resultComposite);
|
||||
setToothLogs(resultTooth);
|
||||
|
||||
// markers
|
||||
if (trimmed.startsWith('MARK')) {
|
||||
const previous = result[result.length - 1] || {
|
||||
primaryLevel: 0,
|
||||
secondaryLevel: 0,
|
||||
trigger: 0,
|
||||
sync: 0,
|
||||
refTime: 0,
|
||||
maxTime: 0,
|
||||
toothTime: 0,
|
||||
time: 0,
|
||||
};
|
||||
|
||||
result.push({
|
||||
type: 'marker',
|
||||
primaryLevel: previous.primaryLevel,
|
||||
secondaryLevel: previous.secondaryLevel,
|
||||
trigger: previous.trigger,
|
||||
sync: previous.sync,
|
||||
refTime: previous.refTime,
|
||||
maxTime: previous.maxTime,
|
||||
toothTime: previous.toothTime,
|
||||
time: previous.time,
|
||||
});
|
||||
}
|
||||
|
||||
const split = trimmed.split(',');
|
||||
if (!isNumber(split[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const time = Number(split[7]);
|
||||
if (!time) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.push({
|
||||
type: 'trigger',
|
||||
primaryLevel: Number(split[0]),
|
||||
secondaryLevel: Number(split[1]),
|
||||
trigger: Number(split[2]),
|
||||
sync: Number(split[3]),
|
||||
refTime: Number(split[4]),
|
||||
maxTime: Number(split[5]),
|
||||
toothTime: Number(split[6]),
|
||||
time,
|
||||
});
|
||||
});
|
||||
|
||||
setLogs(result);
|
||||
setStep(2);
|
||||
} catch (error) {
|
||||
setFetchError(error as Error);
|
||||
|
@ -191,7 +133,8 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loa
|
|||
<Tabs defaultActiveKey="files" style={{ marginLeft: 20 }}>
|
||||
<TabPane tab={<FileTextOutlined />} key="files">
|
||||
<PerfectScrollbar options={{ suppressScrollX: true }}>
|
||||
composite.csv
|
||||
<Typography.Paragraph>tooth.csv</Typography.Paragraph>
|
||||
<Typography.Paragraph>composite.csv</Typography.Paragraph>
|
||||
</PerfectScrollbar>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
@ -200,13 +143,22 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loa
|
|||
<Layout style={{ width: '100%', textAlign: 'center', marginTop: 50 }}>
|
||||
<Content>
|
||||
<div ref={contentRef} style={{ width: '100%', marginRight: margin }}>
|
||||
{logs
|
||||
{toothLogs && logs
|
||||
?
|
||||
<CompositeCanvas
|
||||
data={logs!}
|
||||
width={canvasWidth}
|
||||
height={canvasWidth * 0.4}
|
||||
/>
|
||||
(
|
||||
<>
|
||||
<ToothCanvas
|
||||
data={toothLogs!}
|
||||
width={canvasWidth}
|
||||
height={canvasWidth * 0.3}
|
||||
/>
|
||||
<CompositeCanvas
|
||||
data={logs!}
|
||||
width={canvasWidth}
|
||||
height={canvasWidth * 0.3}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
:
|
||||
<Space
|
||||
direction="vertical"
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
ResponsiveContainer,
|
||||
Label,
|
||||
} from 'recharts';
|
||||
import { Colors } from '../../utils/colors';
|
||||
import LandscapeNotice from './LandscapeNotice';
|
||||
import Table from './Table';
|
||||
|
||||
|
@ -54,8 +55,6 @@ const Curve = ({
|
|||
const [data, setData] = useState(mapData([yData, xData]));
|
||||
const { sm } = useBreakpoint();
|
||||
const margin = 15;
|
||||
const mainColor = '#ccc';
|
||||
const tooltipBg = '#2E3338';
|
||||
const animationDuration = 500;
|
||||
|
||||
if (!sm) {
|
||||
|
@ -85,7 +84,7 @@ const Curve = ({
|
|||
<Label
|
||||
value={`${xLabel} (${xUnits})`}
|
||||
position="bottom"
|
||||
style={{ fill: mainColor }}
|
||||
style={{ fill: Colors.TEXT }}
|
||||
/>
|
||||
</XAxis>
|
||||
<YAxis domain={['auto', 'auto']}>
|
||||
|
@ -93,14 +92,14 @@ const Curve = ({
|
|||
value={`${yLabel} (${yUnits})`}
|
||||
position="left"
|
||||
angle={-90}
|
||||
style={{ fill: mainColor }}
|
||||
style={{ fill: Colors.TEXT }}
|
||||
/>
|
||||
</YAxis>
|
||||
<Tooltip
|
||||
labelFormatter={(value) => `${xLabel} : ${value} ${xUnits}`}
|
||||
formatter={(value: number) => [`${value} ${yUnits}`, yLabel]}
|
||||
contentStyle={{
|
||||
backgroundColor: tooltipBg,
|
||||
backgroundColor: Colors.MAIN,
|
||||
border: 0,
|
||||
boxShadow: '0 3px 6px -4px rgb(0 0 0 / 12%), 0 6px 16px 0 rgb(0 0 0 / 8%), 0 9px 28px 8px rgb(0 0 0 / 5%)',
|
||||
borderRadius: 5,
|
||||
|
@ -111,7 +110,7 @@ const Curve = ({
|
|||
strokeWidth={3}
|
||||
type="linear"
|
||||
dataKey="y"
|
||||
stroke="#1e88ea"
|
||||
stroke={Colors.ACCENT}
|
||||
animationDuration={animationDuration}
|
||||
/>
|
||||
</LineChart>
|
||||
|
|
|
@ -5,31 +5,13 @@ import {
|
|||
useRef,
|
||||
} from 'react';
|
||||
import { Logs } from '@speedy-tuner/types';
|
||||
import {
|
||||
Popover,
|
||||
Space,
|
||||
Typography,
|
||||
Grid,
|
||||
} from 'antd';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { Grid } from 'antd';
|
||||
import TimeChart from 'timechart';
|
||||
import { EventsPlugin } from 'timechart/dist/lib/plugins_extra/events';
|
||||
import { colorHsl } from '../../utils/number';
|
||||
import LandscapeNotice from '../Dialog/LandscapeNotice';
|
||||
import CanvasHelp from '../CanvasHelp';
|
||||
|
||||
// enum Colors {
|
||||
// RED = '#f32450',
|
||||
// CYAN = '#8dd3c7',
|
||||
// YELLOW = '#ffff00',
|
||||
// PURPLE = '#bebada',
|
||||
// GREEN = '#77de3c',
|
||||
// BLUE = '#2fe3ff',
|
||||
// GREY = '#334455',
|
||||
// WHITE = '#fff',
|
||||
// BG = '#222629',
|
||||
// }
|
||||
|
||||
const { Text } = Typography;
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
export interface SelectedField {
|
||||
|
@ -155,22 +137,7 @@ const LogCanvas = ({ data, width, height, selectedFields }: Props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginTop: -20, marginBottom: 10, textAlign: 'left', marginLeft: 20 }}>
|
||||
<Popover
|
||||
placement="bottom"
|
||||
content={
|
||||
<Space direction="vertical">
|
||||
<Typography.Title level={5}>Navigation</Typography.Title>
|
||||
<Text>Pinch to zoom</Text>
|
||||
<Text>Drag to pan</Text>
|
||||
<Text>Ctrl + wheel scroll to zoom X axis</Text>
|
||||
<Text>Hold Shift to speed up zoom 5 times</Text>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</Popover>
|
||||
</div>
|
||||
<CanvasHelp />
|
||||
<div
|
||||
ref={canvasRef}
|
||||
style={{ width, height }}
|
||||
|
|
|
@ -2,54 +2,19 @@ import {
|
|||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import {
|
||||
Popover,
|
||||
Space,
|
||||
Typography,
|
||||
Grid,
|
||||
} from 'antd';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { Grid } from 'antd';
|
||||
import TimeChart from 'timechart';
|
||||
import { EventsPlugin } from 'timechart/dist/lib/plugins_extra/events';
|
||||
import LandscapeNotice from '../Dialog/LandscapeNotice';
|
||||
import {
|
||||
CompositeLogEntry,
|
||||
EntryType,
|
||||
} from '../../utils/logs/TriggerLogsParser';
|
||||
import CanvasHelp from '../CanvasHelp';
|
||||
import { Colors } from '../../utils/colors';
|
||||
|
||||
enum Colors {
|
||||
RED = '#f32450',
|
||||
CYAN = '#8dd3c7',
|
||||
YELLOW = '#ffff00',
|
||||
PURPLE = '#bebada',
|
||||
GREEN = '#77de3c',
|
||||
BLUE = '#2fe3ff',
|
||||
GREY = '#334455',
|
||||
WHITE = '#fff',
|
||||
BG = '#222629',
|
||||
}
|
||||
|
||||
const { Text } = Typography;
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
export interface SelectedField {
|
||||
name: string;
|
||||
label: string;
|
||||
units: string;
|
||||
scale: string | number;
|
||||
transform: string | number;
|
||||
format: string;
|
||||
};
|
||||
|
||||
// TODO: extract this to types package
|
||||
interface CompositeLogEntry {
|
||||
type: 'trigger' | 'marker';
|
||||
primaryLevel: number;
|
||||
secondaryLevel: number;
|
||||
trigger: number;
|
||||
sync: number;
|
||||
refTime: number;
|
||||
maxTime: number;
|
||||
toothTime: number;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
data: CompositeLogEntry[];
|
||||
width: number;
|
||||
|
@ -73,14 +38,14 @@ const CompositeCanvas = ({ data, width, height }: Props) => {
|
|||
const sync: DataPoint[] = [];
|
||||
|
||||
data.forEach((entry, index) => {
|
||||
if (entry.type === 'marker') {
|
||||
if (entry.type === EntryType.MARKER) {
|
||||
markers.push({
|
||||
x: index,
|
||||
name: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (entry.type === 'trigger') {
|
||||
if (entry.type === EntryType.TRIGGER) {
|
||||
const prevSecondary = data[index - 1] ? data[index - 1].secondaryLevel : 0;
|
||||
const currentSecondary = (entry.secondaryLevel + 3) * 2; // apply scale
|
||||
|
||||
|
@ -166,22 +131,7 @@ const CompositeCanvas = ({ data, width, height }: Props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginTop: -20, marginBottom: 10, textAlign: 'left', marginLeft: 20 }}>
|
||||
<Popover
|
||||
placement="bottom"
|
||||
content={
|
||||
<Space direction="vertical">
|
||||
<Typography.Title level={5}>Navigation</Typography.Title>
|
||||
<Text>Pinch to zoom</Text>
|
||||
<Text>Drag to pan</Text>
|
||||
<Text>Ctrl + wheel scroll to zoom X axis</Text>
|
||||
<Text>Hold Shift to speed up zoom 5 times</Text>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</Popover>
|
||||
</div>
|
||||
<CanvasHelp />
|
||||
<div
|
||||
ref={canvasRef}
|
||||
style={{ width, height }}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Grid } from 'antd';
|
||||
import UplotReact from 'uplot-react';
|
||||
import uPlot from 'uplot';
|
||||
import LandscapeNotice from '../Dialog/LandscapeNotice';
|
||||
import {
|
||||
ToothLogEntry,
|
||||
EntryType,
|
||||
} from '../../utils/logs/TriggerLogsParser';
|
||||
import CanvasHelp from '../CanvasHelp';
|
||||
|
||||
import 'uplot/dist/uPlot.min.css';
|
||||
import { Colors } from '../../utils/colors';
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
const { bars } = uPlot.paths;
|
||||
|
||||
interface Props {
|
||||
data: ToothLogEntry[];
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
const ToothCanvas = ({ data, width, height }: Props) => {
|
||||
const { sm } = useBreakpoint();
|
||||
const [options, setOptions] = useState<uPlot.Options>();
|
||||
const [plotData, setPlotData] = useState<uPlot.AlignedData>();
|
||||
|
||||
useEffect(() => {
|
||||
const xData: number[] = [];
|
||||
const yData: (number | null)[] = [];
|
||||
|
||||
data.forEach((entry: ToothLogEntry, index) => {
|
||||
if (entry.type === EntryType.TRIGGER) {
|
||||
yData.push(entry.toothTime);
|
||||
xData.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
setPlotData([
|
||||
xData,
|
||||
yData,
|
||||
]);
|
||||
|
||||
setOptions({
|
||||
title: 'Tooth logs',
|
||||
width,
|
||||
height,
|
||||
scales: {
|
||||
x: { time: false },
|
||||
},
|
||||
series: [
|
||||
{
|
||||
label: 'Event',
|
||||
},
|
||||
{
|
||||
label: 'Tooth time',
|
||||
points: { show: false },
|
||||
stroke: Colors.ACCENT,
|
||||
fill: Colors.ACCENT,
|
||||
scale: 'toothTime',
|
||||
value: (self, rawValue) => `${rawValue.toLocaleString()}μs`,
|
||||
paths: bars!({ size: [0.6, 100] }),
|
||||
},
|
||||
],
|
||||
axes: [
|
||||
{
|
||||
stroke: Colors.TEXT,
|
||||
grid: { stroke: Colors.MAIN_LIGHT },
|
||||
},
|
||||
{
|
||||
scale: 'toothTime',
|
||||
label: '',
|
||||
stroke: Colors.TEXT,
|
||||
grid: { stroke: Colors.MAIN_LIGHT },
|
||||
},
|
||||
],
|
||||
cursor: {
|
||||
drag: { y: false },
|
||||
},
|
||||
});
|
||||
}, [data, width, height, sm]);
|
||||
|
||||
if (!sm) {
|
||||
return <LandscapeNotice />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<CanvasHelp />
|
||||
<UplotReact
|
||||
options={options!}
|
||||
data={plotData!}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToothCanvas;
|
|
@ -90,8 +90,15 @@ export const loadLogs = (onProgress?: onProgressType, signal?: AbortSignal) =>
|
|||
|
||||
export const loadCompositeLogs = (onProgress?: onProgressType, signal?: AbortSignal) =>
|
||||
fetchWithProgress(
|
||||
'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/composite_1.csv.gz',
|
||||
'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/composite_1_2.csv.gz',
|
||||
// 'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/2.csv.gz',
|
||||
onProgress,
|
||||
signal,
|
||||
).then((response) => response);
|
||||
|
||||
export const loadToothLogs = (onProgress?: onProgressType, signal?: AbortSignal) =>
|
||||
fetchWithProgress(
|
||||
'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/tooth_3.csv.gz',
|
||||
onProgress,
|
||||
signal,
|
||||
).then((response) => response);
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
// eslint-disable-next-line import/prefer-default-export
|
||||
export enum Colors {
|
||||
RED = '#f32450',
|
||||
CYAN = '#8dd3c7',
|
||||
YELLOW = '#ffff00',
|
||||
PURPLE = '#bebada',
|
||||
GREEN = '#77de3c',
|
||||
BLUE = '#2fe3ff',
|
||||
GREY = '#334455',
|
||||
|
||||
// dark theme
|
||||
ACCENT = '#1e88ea',
|
||||
TEXT = '#ddd',
|
||||
MAIN = '#222629',
|
||||
MAIN_DARK = '#191C1E',
|
||||
MAIN_LIGHT = '#2E3338',
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
import { isNumber } from '../tune/expression';
|
||||
|
||||
export enum EntryType {
|
||||
TRIGGER = 'trigger',
|
||||
MARKER = 'marker',
|
||||
}
|
||||
|
||||
export interface CompositeLogEntry {
|
||||
type: EntryType;
|
||||
primaryLevel: number;
|
||||
secondaryLevel: number;
|
||||
trigger: number;
|
||||
sync: number;
|
||||
refTime: number;
|
||||
maxTime: number;
|
||||
toothTime: number;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface ToothLogEntry {
|
||||
type: EntryType;
|
||||
toothTime: number;
|
||||
time: number;
|
||||
}
|
||||
|
||||
class TriggerLogsParser {
|
||||
COMMENT_PREFIX = '#';
|
||||
|
||||
MARKER_PREFIX = 'MARK';
|
||||
|
||||
isTooth: boolean = false;
|
||||
|
||||
isComposite: boolean = false;
|
||||
|
||||
resultComposite: CompositeLogEntry[] = [];
|
||||
|
||||
resultTooth: ToothLogEntry[] = [];
|
||||
|
||||
parse(buffer: ArrayBuffer): TriggerLogsParser {
|
||||
const raw = (new TextDecoder()).decode(buffer);
|
||||
this.parseCompositeLogs(raw);
|
||||
this.parseToothLogs(raw);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
getCompositeLogs(): CompositeLogEntry[] {
|
||||
return this.resultComposite;
|
||||
}
|
||||
|
||||
getToothLogs(): ToothLogEntry[] {
|
||||
return this.resultTooth;
|
||||
}
|
||||
|
||||
private parseToothLogs(raw: string): void {
|
||||
this.resultTooth = [];
|
||||
|
||||
raw.split('\n').forEach((line) => {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed.startsWith(this.COMMENT_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith(this.MARKER_PREFIX)) {
|
||||
const previous = this.resultTooth[this.resultTooth.length - 1] || {
|
||||
toothTime: 0,
|
||||
time: 0,
|
||||
};
|
||||
|
||||
this.resultTooth.push({
|
||||
type: EntryType.MARKER,
|
||||
toothTime: previous.toothTime,
|
||||
time: previous.time,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const split = trimmed.split(',');
|
||||
if (!isNumber(split[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const time = Number(split[1]);
|
||||
if (!time) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resultTooth.push({
|
||||
type: EntryType.TRIGGER,
|
||||
toothTime: Number(split[0]),
|
||||
time,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private parseCompositeLogs(raw: string): void {
|
||||
this.resultComposite = [];
|
||||
|
||||
raw.split('\n').forEach((line) => {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed.startsWith(this.COMMENT_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith(this.MARKER_PREFIX)) {
|
||||
const previous = this.resultComposite[this.resultComposite.length - 1] || {
|
||||
primaryLevel: 0,
|
||||
secondaryLevel: 0,
|
||||
trigger: 0,
|
||||
sync: 0,
|
||||
refTime: 0,
|
||||
maxTime: 0,
|
||||
toothTime: 0,
|
||||
time: 0,
|
||||
};
|
||||
|
||||
this.resultComposite.push({
|
||||
type: EntryType.MARKER,
|
||||
primaryLevel: previous.primaryLevel,
|
||||
secondaryLevel: previous.secondaryLevel,
|
||||
trigger: previous.trigger,
|
||||
sync: previous.sync,
|
||||
refTime: previous.refTime,
|
||||
maxTime: previous.maxTime,
|
||||
toothTime: previous.toothTime,
|
||||
time: previous.time,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const split = trimmed.split(',');
|
||||
if (!isNumber(split[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const time = Number(split[7]);
|
||||
if (!time) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resultComposite.push({
|
||||
type: EntryType.TRIGGER,
|
||||
primaryLevel: Number(split[0]),
|
||||
secondaryLevel: Number(split[1]),
|
||||
trigger: Number(split[2]),
|
||||
sync: Number(split[3]),
|
||||
refTime: Number(split[4]),
|
||||
maxTime: Number(split[5]),
|
||||
toothTime: Number(split[6]),
|
||||
time,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default TriggerLogsParser;
|
Loading…
Reference in New Issue