Skip to content

Commit

Permalink
Add graph line settings to aid accessibility (#548)
Browse files Browse the repository at this point in the history
* Use foundation fork of Smoothie to support line style
* Add graph line style settings
* Add graph line thickness setting
* Add preview graph to settings dialog

---------

Co-authored-by: Matt Hillsdon <[email protected]>
  • Loading branch information
microbit-robert and microbit-matt-hillsdon authored Nov 29, 2024
1 parent ff7c692 commit 4af9ec3
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 29 deletions.
26 changes: 25 additions & 1 deletion lang/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,30 @@
"defaultMessage": "Default (red, blue, green)",
"description": "Graph colour scheme option"
},
"graph-line-scheme": {
"defaultMessage": "Graph line style",
"description": "Graph line scheme setting label"
},
"graph-line-scheme-accessible": {
"defaultMessage": "Accessible lines (solid, dashed, dots)",
"description": "Graph line scheme option"
},
"graph-line-scheme-solid": {
"defaultMessage": "Solid lines",
"description": "Graph line scheme option"
},
"graph-line-weight": {
"defaultMessage": "Graph line thickness",
"description": "Graph line weight setting label"
},
"graph-line-weight-default": {
"defaultMessage": "Default",
"description": "Graph line weight option"
},
"graph-line-weight-thick": {
"defaultMessage": "Thick",
"description": "Graph line weight option"
},
"help-label": {
"defaultMessage": "Help",
"description": "Help icon aria label"
Expand Down Expand Up @@ -1659,4 +1683,4 @@
"defaultMessage": "unplug and replug the USB cable",
"description": "WebUSB error dialog"
}
}
}
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@microbit/makecode-embed": "^0.0.0-alpha.7",
"@microbit/microbit-connection": "^0.0.0-alpha.29",
"@microbit/ml-header-generator": "^0.4.3",
"@microbit/smoothie": "^1.37.0-microbit.2",
"@tensorflow/tfjs": "^4.20.0",
"@types/w3c-web-serial": "^1.0.6",
"@types/w3c-web-usb": "^1.0.6",
Expand All @@ -81,7 +82,6 @@
"react-intl": "^6.6.8",
"react-router": "^6.24.0",
"react-router-dom": "^6.24.0",
"smoothie": "^1.36.1",
"zustand": "^4.5.5"
}
}
42 changes: 35 additions & 7 deletions src/components/LiveGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { HStack, usePrevious } from "@chakra-ui/react";
import { useSize } from "@chakra-ui/react-use-size";
import { AccelerometerDataEvent } from "@microbit/microbit-connection";
import { useEffect, useMemo, useRef, useState } from "react";
import { SmoothieChart, TimeSeries } from "smoothie";
import { SmoothieChart, TimeSeries } from "@microbit/smoothie";
import { useConnectActions } from "../connect-actions-hooks";
import { ConnectionStatus } from "../connect-status-hooks";
import { useConnectionStage } from "../connection-stage-hooks";
import { useGraphColors } from "../hooks/use-graph-colors";
import { maxAccelerationScaleForGraphs } from "../mlConfig";
import { useSettings, useStore } from "../store";
import LiveGraphLabels from "./LiveGraphLabels";
import { useGraphLineStyles } from "../hooks/use-graph-line-styles";

export const smoothenDataPoint = (curr: number, next: number) => {
// TODO: Factor out so that recording graph can do the same
Expand All @@ -20,13 +21,16 @@ export const smoothenDataPoint = (curr: number, next: number) => {
const LiveGraph = () => {
const { isConnected, status } = useConnectionStage();
const connectActions = useConnectActions();
const [{ graphColorScheme }] = useSettings();
const [{ graphColorScheme, graphLineScheme, graphLineWeight }] =
useSettings();

const colors = useGraphColors(graphColorScheme);
const lineStyles = useGraphLineStyles(graphLineScheme);
const canvasRef = useRef<HTMLCanvasElement>(null);

// When we update the chart we re-run the effect that syncs it with the connection state.
const [chart, setChart] = useState<SmoothieChart | undefined>(undefined);
const lineWidth = 2;
const lineWidth = graphLineWeight === "default" ? 2 : 3;

const liveGraphContainerRef = useRef(null);
const { width, height } = useSize(liveGraphContainerRef) ?? {
Expand Down Expand Up @@ -58,9 +62,21 @@ const LiveGraph = () => {
enableDpiScaling: false,
});

smoothieChart.addTimeSeries(lineX, { lineWidth, strokeStyle: colors.x });
smoothieChart.addTimeSeries(lineY, { lineWidth, strokeStyle: colors.y });
smoothieChart.addTimeSeries(lineZ, { lineWidth, strokeStyle: colors.z });
smoothieChart.addTimeSeries(lineX, {
lineWidth,
strokeStyle: colors.x,
lineDash: lineStyles.x,
});
smoothieChart.addTimeSeries(lineY, {
lineWidth,
strokeStyle: colors.y,
lineDash: lineStyles.y,
});
smoothieChart.addTimeSeries(lineZ, {
lineWidth,
strokeStyle: colors.z,
lineDash: lineStyles.z,
});

smoothieChart.addTimeSeries(recordLines, {
lineWidth: 3,
Expand All @@ -73,7 +89,19 @@ const LiveGraph = () => {
return () => {
smoothieChart.stop();
};
}, [colors.x, colors.y, colors.z, lineX, lineY, lineZ, recordLines]);
}, [
colors.x,
colors.y,
colors.z,
lineStyles.x,
lineStyles.y,
lineStyles.z,
lineWidth,
lineX,
lineY,
lineZ,
recordLines,
]);

useEffect(() => {
if (isConnected || status === ConnectionStatus.ReconnectingAutomatically) {
Expand Down
28 changes: 22 additions & 6 deletions src/components/RecordingGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,45 @@ import {
registerables,
} from "chart.js";
import { useEffect, useRef } from "react";
import { useGraphColors } from "../hooks/use-graph-colors";
import { useGraphLineStyles } from "../hooks/use-graph-line-styles";
import { XYZData } from "../model";
import { getConfig as getRecordingChartConfig } from "../recording-graph";
import { useGraphColors } from "../hooks/use-graph-colors";
import { useSettings } from "../store";

interface RecordingGraphProps extends BoxProps {
data: XYZData;
responsive?: boolean;
}

const RecordingGraph = ({ data, children, ...rest }: RecordingGraphProps) => {
const [{ graphColorScheme }] = useSettings();
const RecordingGraph = ({
data,
responsive = false,
children,
...rest
}: RecordingGraphProps) => {
const [{ graphColorScheme, graphLineScheme, graphLineWeight }] =
useSettings();
const canvasRef = useRef<HTMLCanvasElement>(null);
const colors = useGraphColors(graphColorScheme);
const lineStyles = useGraphLineStyles(graphLineScheme);
useEffect(() => {
Chart.unregister(...registerables);
Chart.register([LinearScale, LineController, PointElement, LineElement]);
const chart = new Chart(
canvasRef.current?.getContext("2d") ?? new HTMLCanvasElement(),
getRecordingChartConfig(data, colors)
getRecordingChartConfig(
data,
responsive,
colors,
lineStyles,
graphLineWeight
)
);
return () => {
chart.destroy();
};
}, [colors, data]);
}, [colors, data, graphLineWeight, lineStyles, responsive]);

return (
<Box
Expand All @@ -43,7 +58,8 @@ const RecordingGraph = ({ data, children, ...rest }: RecordingGraphProps) => {
position="relative"
{...rest}
>
<canvas width="158px" height="95px" ref={canvasRef} />
{/* canvas dimensions must account for parent border width */}
<canvas width="156px" height="92px" ref={canvasRef} />
{children}
</Box>
);
Expand Down
61 changes: 59 additions & 2 deletions src/components/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,26 @@ import {
ModalOverlay,
} from "@chakra-ui/modal";
import {
AspectRatio,
FormControl,
FormHelperText,
Text,
useDisclosure,
VStack,
} from "@chakra-ui/react";
import { useCallback, useMemo } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { defaultSettings, graphColorSchemeOptions } from "../settings";
import {
defaultSettings,
graphColorSchemeOptions,
graphLineSchemeOptions,
graphLineWeightOptions,
} from "../settings";
import { useSettings } from "../store";
import SelectFormControl, { createOptions } from "./SelectFormControl";
import { previewGraphData } from "../utils/preview-graph-data";
import { ConfirmDialog } from "./ConfirmDialog";
import RecordingGraph from "./RecordingGraph";
import SelectFormControl, { createOptions } from "./SelectFormControl";

interface SettingsDialogProps {
isOpen: boolean;
Expand Down Expand Up @@ -64,6 +73,16 @@ export const SettingsDialog = ({
"graph-color-scheme",
intl
),
graphLineScheme: createOptions(
graphLineSchemeOptions,
"graph-line-scheme",
intl
),
graphLineWeight: createOptions(
graphLineWeightOptions,
"graph-line-weight",
intl
),
};
}, [intl]);
return (
Expand Down Expand Up @@ -107,6 +126,44 @@ export const SettingsDialog = ({
})
}
/>
<SelectFormControl
id="graphLineScheme"
label={intl.formatMessage({ id: "graph-line-scheme" })}
options={options.graphLineScheme}
value={settings.graphLineScheme}
onChange={(graphLineScheme) =>
setSettings({
...settings,
graphLineScheme,
})
}
/>
<SelectFormControl
id="graphLineWeight"
label={intl.formatMessage({ id: "graph-line-weight" })}
options={options.graphLineWeight}
value={settings.graphLineWeight}
onChange={(graphLineWeight) =>
setSettings({
...settings,
graphLineWeight,
})
}
/>
<VStack alignItems="flex-start" w="full">
<Text>Graph preview</Text>
<AspectRatio ratio={526 / 92} w="full">
<RecordingGraph
responsive
data={previewGraphData}
role="img"
w="full"
aria-label={intl.formatMessage({
id: "recording-graph-label",
})}
/>
</AspectRatio>
</VStack>
<FormControl>
<Button variant="link" onClick={handleResetToDefault}>
<FormattedMessage id="restore-defaults-action" />
Expand Down
31 changes: 31 additions & 0 deletions src/hooks/use-graph-line-styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useMemo } from "react";
import { GraphLineScheme } from "../settings";

export interface GraphLineStyles {
x: undefined | number[];
y: undefined | number[];
z: undefined | number[];
}

export const useGraphLineStyles = (
graphLineScheme: GraphLineScheme
): GraphLineStyles => {
return useMemo(() => {
switch (graphLineScheme) {
case "accessible": {
return {
x: undefined,
y: [10, 5],
z: [2, 2],
};
}
default: {
return {
x: undefined,
y: undefined,
z: undefined,
};
}
}
}, [graphLineScheme]);
};
Loading

0 comments on commit 4af9ec3

Please sign in to comment.