Skip to content

Commit

Permalink
patch: extract chart props out into chart helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
acheronfail committed Sep 30, 2024
1 parent 1aa0e25 commit fae1e87
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 108 deletions.
1 change: 1 addition & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ _pre_commit_start:
set -e

if [ $needs_save -ne 0 ]; then
# FIXME: do not clobber existing patch file!
git diff > "{{git_temp_patch}}"
git apply --reverse "{{git_temp_patch}}"
fi
Expand Down
95 changes: 11 additions & 84 deletions src/components/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,22 @@
import { demoFile, demoRows } from '../lib/parse/float-control';
import Picker from './Picker.svelte';
import type { DragEventHandler, EventHandler } from 'svelte/elements';
import { DataSource, State, Units, type RowWithIndex } from '../lib/parse/types';
import { DataSource, State, type RowWithIndex } from '../lib/parse/types';
import Modal from './Modal.svelte';
import { riderSvg } from '../lib/map-helpers';
import settings from '../lib/settings.svelte';
import SettingsModal from './SettingsModal.svelte';
import Button from './Button.svelte';
import { ChartColours } from '../lib/chart-helpers';
import { Charts } from '../lib/chart-helpers';
import { parse, supportedMimeTypes } from '../lib/parse';
import { speedMapper } from '../lib/misc';
import { globalState } from '../lib/global.svelte';
/** source of data*/
let source = $state(DataSource.None);
/** selected file */
let file = $state<File | undefined>(import.meta.env.DEV ? demoFile : undefined);
/** parsed csv data from Float Control */
let rows = $state<RowWithIndex[]>(demoRows);
/** the units that the data is in */
let dataUnits = $state(Units.Metric);
/** the units that the user has selected */
let mapSpeed = $derived(speedMapper(dataUnits, settings.units));
/** selected index of `rows` */
let selectedRowIndex = $state(0);
/** entire view of gps points from `rows` */
Expand Down Expand Up @@ -143,7 +139,7 @@
.then((results) => {
clearTimeout(timer);
dataUnits = results.units;
globalState.unitsFromData = results.units;
source = results.source;
// FIXME: handle parse errors
if (results.error) {
Expand Down Expand Up @@ -286,95 +282,26 @@
wide:[grid-column:span_2] wide:[grid-row:unset]"
class:details-swapped={swapMapAndDetails}
>
<Details data={visibleRows[selectedIndex]} batterySpecs={settings.batterySpecs} {mapSpeed} units={settings.units} />
<Details data={visibleRows[selectedIndex]} batterySpecs={settings.batterySpecs} units={settings.units} />
</div>

<div class={chartClass}>
<Chart
data={[{ values: visibleRows.map((x) => mapSpeed(x.speed)), color: ChartColours.Speed }]}
{selectedIndex}
{setSelectedIdx}
{gapIndices}
title="Speed"
precision={1}
unit={settings.units === Units.Metric ? ' km/h' : ' mph'}
showMax
showMin="nonzero"
/>
<Chart {selectedIndex} {setSelectedIdx} {gapIndices} {...Charts.speed(visibleRows)} />
</div>
<div class={chartClass}>
<Chart
data={[{ values: visibleRows.map((x) => x.duty), color: ChartColours.DutyCycle }]}
{selectedIndex}
{setSelectedIdx}
{gapIndices}
title="Duty cycle"
unit="%"
showMax
showMin="nonzero"
/>
<Chart {selectedIndex} {setSelectedIdx} {gapIndices} {...Charts.duty(visibleRows)} />
</div>
<div class={chartClass}>
<Chart
data={[{ values: visibleRows.map((x) => x.voltage), color: ChartColours.BatteryVoltage }]}
{selectedIndex}
{setSelectedIdx}
{gapIndices}
title="Battery Voltage"
unit="V"
showMax
showMin
precision={1}
yAxis={{ suggestedMin: settings.suggestedVMin, suggestedMax: settings.suggestedVMax }}
/>
<Chart {selectedIndex} {setSelectedIdx} {gapIndices} {...Charts.batteryVoltage(visibleRows)} />
</div>
<div class={chartClass}>
<Chart
data={[{ values: visibleRows.map((x) => x.altitude), color: ChartColours.Elevation }]}
{selectedIndex}
{setSelectedIdx}
{gapIndices}
title="Elevation"
unit="m"
showMax
showMin
/>
<Chart {selectedIndex} {setSelectedIdx} {gapIndices} {...Charts.elevation(visibleRows)} />
</div>
<div class={chartClass}>
<Chart
data={[
{ values: visibleRows.map((x) => x.current_motor), color: ChartColours.CurrentMotor, label: 'Motor current' },
{
values: visibleRows.map((x) => x.current_battery),
color: ChartColours.CurrentBattery,
label: 'Battery current',
},
]}
{selectedIndex}
{setSelectedIdx}
{gapIndices}
title="I-Mot / I-Batt"
precision={1}
unit="A"
showMax
showMin="nonzero"
/>
<Chart {selectedIndex} {setSelectedIdx} {gapIndices} {...Charts.currentCombined(visibleRows)} />
</div>
<div class={chartClass}>
<Chart
data={[
{ values: visibleRows.map((x) => x.temp_motor), color: ChartColours.TempMotor, label: 'Motor temp' },
{ values: visibleRows.map((x) => x.temp_mosfet), color: ChartColours.TempMosfet, label: 'Mosfet temp' },
]}
{selectedIndex}
{setSelectedIdx}
{gapIndices}
title="T-Mot / T-Mosfet"
precision={1}
unit="°C"
showMin
showMax
/>
<Chart {selectedIndex} {setSelectedIdx} {gapIndices} {...Charts.tempCombined(visibleRows)} />
</div>
</main>

Expand Down
4 changes: 2 additions & 2 deletions src/components/App.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ test('it works', () => {
expect(screen.getByTestId('chart_duty_cycle')).toBeInTheDocument();
expect(screen.getByTestId('chart_battery_voltage')).toBeInTheDocument();
expect(screen.getByTestId('chart_elevation')).toBeInTheDocument();
expect(screen.getByTestId('chart_i-mot_/_i-batt')).toBeInTheDocument();
expect(screen.getByTestId('chart_t-mot_/_t-mosfet')).toBeInTheDocument();
expect(screen.getByTestId('chart_i-motor_/_i-battery')).toBeInTheDocument();
expect(screen.getByTestId('chart_t-motor_/_t-controller')).toBeInTheDocument();
});
21 changes: 2 additions & 19 deletions src/components/Chart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
* It's right here: https://jf.id.au/blog/how-i-built-the-best-chart-in-the-world
*/
import { ticks, type TickOptions } from '../lib/chart-helpers';
import { ticks } from '../lib/chart-helpers';
import type { MouseEventHandler, TouchEventHandler } from 'svelte/elements';
import { assert, formatFloat } from '../lib/misc';
import { untrack } from 'svelte';
import type { Props } from './Chart';
const DEFAULT_COLOUR = 'red';
Expand All @@ -30,24 +31,6 @@
const GAP_LINE_WIDTH = 1;
const GAP_LINE_DASHARRAY = '1 1';
interface Props {
data: {
color?: string;
label?: string;
values: number[];
}[];
selectedIndex: number;
setSelectedIdx: (index: number) => void;
gapIndices: number[];
yAxis?: TickOptions;
unit?: string;
title?: string;
precision?: number;
showMax?: boolean | 'nonzero';
showMin?: boolean | 'nonzero';
}
const getYValueHeight = (y: number, min: number, max: number) => ((y - min) / (max - min)) * 100;
const indexToXPct = (i: number): number => (100 / dataPointsLen) * (i + 0.5);
const valueToYPct = (y: number, min: number, max: number) => 100 - getYValueHeight(y, min, max);
Expand Down
19 changes: 19 additions & 0 deletions src/components/Chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { TickOptions } from '../lib/chart-helpers';

export interface Props {
data: {
color?: string;
label?: string;
values: number[];
}[];
selectedIndex: number;
setSelectedIdx: (index: number) => void;
gapIndices: number[];

yAxis?: TickOptions;
unit?: string;
title?: string;
precision?: number;
showMax?: boolean | 'nonzero';
showMin?: boolean | 'nonzero';
}
6 changes: 3 additions & 3 deletions src/components/Details.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
export interface Props {
data: RowWithIndex | undefined;
batterySpecs: ZBatterySpecs;
mapSpeed: (input: number) => number;
units: Units;
}
</script>
Expand All @@ -19,12 +18,13 @@
import { ChartColours } from '../lib/chart-helpers';
import { empty, State } from '../lib/parse/types';
import { formatFloat } from '../lib/misc';
import { globalState } from '../lib/global.svelte';
let { data = empty, batterySpecs, mapSpeed, units }: Props = $props();
let { data = empty, batterySpecs, units }: Props = $props();
let voltsPerCell = $derived(batterySpecs.cellCount ? data.voltage / batterySpecs.cellCount : NaN);
let cellVoltsLow = $derived(voltsPerCell && batterySpecs.cellMinVolt && voltsPerCell < batterySpecs.cellMinVolt);
let formatSpeed = $derived((x: number) => (Number.isNaN(x) ? '??' : mapSpeed(x).toFixed(1)));
let formatSpeed = $derived((x: number) => (Number.isNaN(x) ? '??' : globalState.mapSpeed(x).toFixed(1)));
const getStateColor = (state: string): string | undefined => {
switch (state.toLowerCase()) {
Expand Down
74 changes: 74 additions & 0 deletions src/lib/chart-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { Units, type RowWithIndex } from './parse/types';
import type { Props as ChartProps } from '../components/Chart';
import settings from './settings.svelte';
import { globalState } from './global.svelte';

export enum ChartColours {
Speed = '#fde68a',
DutyCycle = '#f472b6',
Expand All @@ -9,6 +14,75 @@ export enum ChartColours {
TempMosfet = '#fb7185',
}

export type ChartFactoryFn = (
visibleRows: RowWithIndex[],
) => Omit<ChartProps, 'selectedIndex' | 'setSelectedIdx' | 'gapIndices'>;

export const Charts = {
speed: (visibleRows) => ({
data: [
{
values: visibleRows.map((x) => globalState.mapSpeed(x.speed)),
color: ChartColours.Speed,
},
],
title: 'Speed',
precision: 1,
unit: settings.units === Units.Metric ? ' km/h' : ' mph',
showMax: true,
showMin: 'nonzero',
}),
duty: (visibleRows) => ({
data: [{ values: visibleRows.map((x) => x.duty), color: ChartColours.DutyCycle }],
title: 'Duty Cycle',
unit: '%',
showMax: true,
showMin: 'nonzero',
}),
elevation: (visibleRows) => ({
data: [{ values: visibleRows.map((x) => x.altitude), color: ChartColours.Elevation }],
title: 'Elevation',
unit: 'm',
showMax: true,
showMin: true,
}),
batteryVoltage: (visibleRows) => ({
data: [{ values: visibleRows.map((x) => x.voltage), color: ChartColours.BatteryVoltage }],
title: 'Battery Voltage',
unit: 'V',
showMax: true,
showMin: true,
precision: 1,
yAxis: { suggestedMin: settings.suggestedVMin, suggestedMax: settings.suggestedVMax },
}),
currentCombined: (visibleRows) => ({
data: [
{ values: visibleRows.map((x) => x.current_motor), color: ChartColours.CurrentMotor, label: 'Motor current' },
{
values: visibleRows.map((x) => x.current_battery),
color: ChartColours.CurrentBattery,
label: 'Battery current',
},
],
title: 'I-Motor / I-Battery',
unit: 'A',
showMax: true,
showMin: 'nonzero',
precision: 1,
}),
tempCombined: (visibleRows) => ({
data: [
{ values: visibleRows.map((x) => x.temp_motor), color: ChartColours.TempMotor, label: 'Motor temp' },
{ values: visibleRows.map((x) => x.temp_mosfet), color: ChartColours.TempMosfet, label: 'Controller temp' },
],
title: 'T-Motor / T-Controller',
unit: '°C',
showMax: true,
showMin: true,
precision: 1,
}),
} satisfies Record<string, ChartFactoryFn>;

export interface TickOptions {
min?: number;
max?: number;
Expand Down
8 changes: 8 additions & 0 deletions src/lib/global.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { speedMapper } from './misc';
import { Units } from './parse/types';
import settings from './settings.svelte';

export const globalState = new (class {
unitsFromData = $state(Units.Metric);
mapSpeed = $derived(speedMapper(this.unitsFromData, settings.units));
})();

0 comments on commit fae1e87

Please sign in to comment.