diff --git a/package-lock.json b/package-lock.json
index c752bda..2081631 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index fdc3bbb..7dd85fc 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/public/logs/trigger/composite_1.csv b/public/logs/trigger/composite_1.csv
index db1536c..d8d6352 100644
--- a/public/logs/trigger/composite_1.csv
+++ b/public/logs/trigger/composite_1.csv
@@ -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
diff --git a/public/logs/trigger/composite_1.csv.gz b/public/logs/trigger/composite_1.csv.gz
index bcf8144..bc6f516 100644
Binary files a/public/logs/trigger/composite_1.csv.gz and b/public/logs/trigger/composite_1.csv.gz differ
diff --git a/public/logs/trigger/3.csv b/public/logs/trigger/tooth_3.csv
similarity index 100%
rename from public/logs/trigger/3.csv
rename to public/logs/trigger/tooth_3.csv
diff --git a/public/logs/trigger/tooth_3.csv.gz b/public/logs/trigger/tooth_3.csv.gz
new file mode 100644
index 0000000..8bfdde3
Binary files /dev/null and b/public/logs/trigger/tooth_3.csv.gz differ
diff --git a/src/components/CanvasHelp.tsx b/src/components/CanvasHelp.tsx
new file mode 100644
index 0000000..da12439
--- /dev/null
+++ b/src/components/CanvasHelp.tsx
@@ -0,0 +1,29 @@
+import {
+ Popover,
+ Space,
+ Typography,
+} from 'antd';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+
+const { Text, Title } = Typography;
+
+const CanvasHelp = () => (
+
+
+ Navigation
+ Pinch to zoom
+ Drag to pan
+ Ctrl + wheel scroll to zoom X axis
+ Hold Shift to speed up zoom 5 times
+
+ }
+ >
+
+
+
+);
+
+export default CanvasHelp;
diff --git a/src/components/Diagnose.tsx b/src/components/Diagnose.tsx
index 8c0382a..d96d7ca 100644
--- a/src/components/Diagnose.tsx
+++ b/src/components/Diagnose.tsx
@@ -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();
+ const [toothLogs, setToothLogs] = useState();
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
} key="files">
- composite.csv
+ tooth.csv
+ composite.csv
@@ -200,13 +143,22 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loa
- {logs
+ {toothLogs && logs
?
-
+ (
+ <>
+
+
+ >
+ )
:
@@ -93,14 +92,14 @@ const Curve = ({
value={`${yLabel} (${yUnits})`}
position="left"
angle={-90}
- style={{ fill: mainColor }}
+ style={{ fill: Colors.TEXT }}
/>
`${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}
/>
diff --git a/src/components/Log/LogCanvas.tsx b/src/components/Log/LogCanvas.tsx
index 1c06c2c..f591785 100644
--- a/src/components/Log/LogCanvas.tsx
+++ b/src/components/Log/LogCanvas.tsx
@@ -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 (
<>
-
-
- Navigation
- Pinch to zoom
- Drag to pan
- Ctrl + wheel scroll to zoom X axis
- Hold Shift to speed up zoom 5 times
-
- }
- >
-
-
-
+
{
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 (
<>
-
-
- Navigation
- Pinch to zoom
- Drag to pan
- Ctrl + wheel scroll to zoom X axis
- Hold Shift to speed up zoom 5 times
-
- }
- >
-
-
-
+
{
+ const { sm } = useBreakpoint();
+ const [options, setOptions] = useState();
+ const [plotData, setPlotData] = useState();
+
+ 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 ;
+ }
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default ToothCanvas;
diff --git a/src/utils/api.ts b/src/utils/api.ts
index cffd865..eab8bc4 100644
--- a/src/utils/api.ts
+++ b/src/utils/api.ts
@@ -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);
diff --git a/src/utils/colors.ts b/src/utils/colors.ts
new file mode 100644
index 0000000..a42b474
--- /dev/null
+++ b/src/utils/colors.ts
@@ -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',
+}
diff --git a/src/utils/logs/TriggerLogsParser.ts b/src/utils/logs/TriggerLogsParser.ts
new file mode 100644
index 0000000..08377cc
--- /dev/null
+++ b/src/utils/logs/TriggerLogsParser.ts
@@ -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;