Skip to content

Commit 10171b1

Browse files
Add fingerprints behind flag first cut (#403)
Also, added view options to data samples menu. First step to making it work. Design discussions and bits to follow (e.g. whether perceptual colour space would be better for fingerprint colours)
1 parent 742931d commit 10171b1

20 files changed

+763
-66
lines changed

lang/ui.en.json

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,22 @@
371371
"defaultMessage": "Short collections of movement data lasting approximately 2 seconds. Your data samples are only stored on your computer, they do not get sent to anyone else.",
372372
"description": "Tooltip for data samples heading"
373373
},
374+
"data-samples-view-data-features-option": {
375+
"defaultMessage": "Data features",
376+
"description": "Data samples view option"
377+
},
378+
"data-samples-view-graph-and-data-features-option": {
379+
"defaultMessage": "Graphs and data features",
380+
"description": "Data samples view option"
381+
},
382+
"data-samples-view-graph-option": {
383+
"defaultMessage": "Graphs",
384+
"description": "Data samples view option"
385+
},
386+
"data-samples-view-options-heading": {
387+
"defaultMessage": "Views",
388+
"description": "Data samples view options heading"
389+
},
374390
"delete-action-aria": {
375391
"defaultMessage": "Delete action \"{action}\"",
376392
"description": "Aria label for action delete icon"
@@ -503,6 +519,102 @@
503519
"defaultMessage": "Feedback",
504520
"description": "Action to open a feedback dialog"
505521
},
522+
"fingerprint-acc-x-tooltip": {
523+
"defaultMessage": "Sum of x-direction data for {action}",
524+
"description": "Tooltip for data feature of a recording"
525+
},
526+
"fingerprint-acc-y-tooltip": {
527+
"defaultMessage": "Sum of y-direction data for {action}",
528+
"description": "Tooltip for data feature of a recording"
529+
},
530+
"fingerprint-acc-z-tooltip": {
531+
"defaultMessage": "Sum of z-direction data for {action}",
532+
"description": "Tooltip for data feature of a recording"
533+
},
534+
"fingerprint-max-x-tooltip": {
535+
"defaultMessage": "Maximum point among all x-direction data points in {action}",
536+
"description": "Tooltip for data feature of a recording"
537+
},
538+
"fingerprint-max-y-tooltip": {
539+
"defaultMessage": "Maximum point among all y-direction data points in {action}",
540+
"description": "Tooltip for data feature of a recording"
541+
},
542+
"fingerprint-max-z-tooltip": {
543+
"defaultMessage": "Maximum point among all z-direction data points in {action}",
544+
"description": "Tooltip for data feature of a recording"
545+
},
546+
"fingerprint-mean-x-tooltip": {
547+
"defaultMessage": "Mean value for x-direction data in {action}",
548+
"description": "Tooltip for data feature of a recording"
549+
},
550+
"fingerprint-mean-y-tooltip": {
551+
"defaultMessage": "Mean value for y-direction data in {action}",
552+
"description": "Tooltip for data feature of a recording"
553+
},
554+
"fingerprint-mean-z-tooltip": {
555+
"defaultMessage": "Mean value for z-direction data in {action}",
556+
"description": "Tooltip for data feature of a recording"
557+
},
558+
"fingerprint-min-x-tooltip": {
559+
"defaultMessage": "Minimum point among all x-direction data points in {action}",
560+
"description": "Tooltip for data feature of a recording"
561+
},
562+
"fingerprint-min-y-tooltip": {
563+
"defaultMessage": "Minimum point among all y-direction data points in {action}",
564+
"description": "Tooltip for data feature of a recording"
565+
},
566+
"fingerprint-min-z-tooltip": {
567+
"defaultMessage": "Minimum point among all z-direction data points in {action}",
568+
"description": "Tooltip for data feature of a recording"
569+
},
570+
"fingerprint-peaks-x-tooltip": {
571+
"defaultMessage": "Number of extremes among all x-direction data points in {action}",
572+
"description": "Tooltip for data feature of a recording"
573+
},
574+
"fingerprint-peaks-y-tooltip": {
575+
"defaultMessage": "Number of extremes among all y-direction data points in {action}",
576+
"description": "Tooltip for data feature of a recording"
577+
},
578+
"fingerprint-peaks-z-tooltip": {
579+
"defaultMessage": "Number of extremes among all z-direction data points in {action}",
580+
"description": "Tooltip for data feature of a recording"
581+
},
582+
"fingerprint-rms-x-tooltip": {
583+
"defaultMessage": "Root mean square value for all x-direction data points in {action}",
584+
"description": "Tooltip for data feature of a recording"
585+
},
586+
"fingerprint-rms-y-tooltip": {
587+
"defaultMessage": "Root mean square value for all y-direction data points in {action}",
588+
"description": "Tooltip for data feature of a recording"
589+
},
590+
"fingerprint-rms-z-tooltip": {
591+
"defaultMessage": "Root mean square value for all z-direction data points in {action}",
592+
"description": "Tooltip for data feature of a recording"
593+
},
594+
"fingerprint-std-x-tooltip": {
595+
"defaultMessage": "Average deviation from 0 among all x-direction data points in {action}",
596+
"description": "Tooltip for data feature of a recording"
597+
},
598+
"fingerprint-std-y-tooltip": {
599+
"defaultMessage": "Average deviation from 0 among all y-direction data points in {action}",
600+
"description": "Tooltip for data feature of a recording"
601+
},
602+
"fingerprint-std-z-tooltip": {
603+
"defaultMessage": "Average deviation from 0 among all z-direction data points in {action}",
604+
"description": "Tooltip for data feature of a recording"
605+
},
606+
"fingerprint-zcr-x-tooltip": {
607+
"defaultMessage": "Rate at which acceleration transitions between positive and negative for x-direction data points in {action}",
608+
"description": "Tooltip for data feature of a recording"
609+
},
610+
"fingerprint-zcr-y-tooltip": {
611+
"defaultMessage": "Rate at which acceleration transitions between positive and negative for y-direction data points in {action}",
612+
"description": "Tooltip for data feature of a recording"
613+
},
614+
"fingerprint-zcr-z-tooltip": {
615+
"defaultMessage": "Rate at which acceleration transitions between positive and negative for z-direction data points in {action}",
616+
"description": "Tooltip for data feature of a recording"
617+
},
506618
"firmware-outdated-content1": {
507619
"defaultMessage": "Connecting to the micro:bit failed because the firmware on your micro:bit is too old.",
508620
"description": "Connection error dialog"
@@ -807,6 +919,10 @@
807919
"defaultMessage": "Record data for action \"{action}\"",
808920
"description": "Text in recording dialog"
809921
},
922+
"recording-fingerprint-label": {
923+
"defaultMessage": "heatmap showing micro:bit x, y, z accelerometer data features for one recording",
924+
"description": "Label for recording fingerprint heatmap"
925+
},
810926
"recording-graph-label": {
811927
"defaultMessage": "graph showing micro:bit x, y, z accelerometer data for one recording",
812928
"description": "Label for recording graph"

src/buffered-data-hooks.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ReactNode, createContext, useContext, useEffect, useRef } from "react";
33
import { BufferedData } from "./buffered-data";
44
import { useConnectActions } from "./connect-actions-hooks";
55
import { ConnectionStatus, useConnectStatus } from "./connect-status-hooks";
6-
import { mlSettings } from "./ml";
6+
import { mlSettings } from "./mlConfig";
77

88
const BufferedDataContext = createContext<BufferedData | null>(null);
99

src/components/DataRecordingGridItem.tsx

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import { useCallback, useRef } from "react";
1111
import { useIntl } from "react-intl";
1212
import { useConnectionStage } from "../connection-stage-hooks";
1313
import RecordIcon from "../images/record-icon.svg?react";
14-
import { GestureData } from "../model";
14+
import { DataSamplesView, GestureData } from "../model";
1515
import { useStore } from "../store";
1616
import { tourElClassname } from "../tours";
1717
import RecordingGraph from "./RecordingGraph";
18+
import RecordingFingerprint from "./RecordingFingerprint";
19+
import { flags } from "../flags";
1820

1921
interface DataRecordingGridItemProps {
2022
data: GestureData;
@@ -31,6 +33,7 @@ const DataRecordingGridItem = ({
3133
}: DataRecordingGridItemProps) => {
3234
const intl = useIntl();
3335
const deleteGestureRecording = useStore((s) => s.deleteGestureRecording);
36+
const view = useStore((s) => s.settings.dataSamplesView);
3437
const closeRecordingDialogFocusRef = useRef(null);
3538
const { isConnected } = useConnectionStage();
3639

@@ -81,11 +84,12 @@ const DataRecordingGridItem = ({
8184
/>
8285
</HStack>
8386
{data.recordings.map((recording, idx) => (
84-
<HStack key={idx} w="158px" position="relative">
87+
<HStack key={idx} position="relative">
8588
<CloseButton
8689
position="absolute"
8790
top={0}
8891
right={0}
92+
zIndex={1}
8993
size="sm"
9094
aria-label={intl.formatMessage({
9195
id: "delete-recording-aria",
@@ -94,13 +98,29 @@ const DataRecordingGridItem = ({
9498
handleDeleteRecording(idx);
9599
}}
96100
/>
97-
<RecordingGraph
98-
data={recording.data}
99-
role="image"
100-
aria-label={intl.formatMessage({
101-
id: "recording-graph-label",
102-
})}
103-
/>
101+
{(!flags.fingerprints ||
102+
view === DataSamplesView.Graph ||
103+
view === DataSamplesView.GraphAndDataFeatures) && (
104+
<RecordingGraph
105+
data={recording.data}
106+
role="image"
107+
aria-label={intl.formatMessage({
108+
id: "recording-graph-label",
109+
})}
110+
/>
111+
)}
112+
{flags.fingerprints &&
113+
(view === DataSamplesView.DataFeatures ||
114+
view === DataSamplesView.GraphAndDataFeatures) && (
115+
<RecordingFingerprint
116+
data={recording.data}
117+
role="image"
118+
gestureName={data.name}
119+
aria-label={intl.formatMessage({
120+
id: "recording-fingerprint-label",
121+
})}
122+
/>
123+
)}
104124
</HStack>
105125
))}
106126
</CardBody>

src/components/DataSamplesMenu.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import {
33
IconButton,
44
Menu,
55
MenuButton,
6+
MenuDivider,
67
MenuItem,
8+
MenuItemOption,
79
MenuList,
10+
MenuOptionGroup,
811
Portal,
912
} from "@chakra-ui/react";
1013
import { MdMoreVert } from "react-icons/md";
@@ -19,12 +22,16 @@ import { useStore } from "../store";
1922
import { useLogging } from "../logging/logging-hooks";
2023
import { useCallback } from "react";
2124
import { getTotalNumSamples } from "../utils/gestures";
25+
import { DataSamplesView } from "../model";
26+
import { flags } from "../flags";
2227

2328
const DataSamplesMenu = () => {
2429
const intl = useIntl();
2530
const logging = useLogging();
2631
const gestures = useStore((s) => s.gestures);
2732
const downloadDataset = useStore((s) => s.downloadDataset);
33+
const setDataSamplesView = useStore((s) => s.setDataSamplesView);
34+
const dataSamplesView = useStore((s) => s.settings.dataSamplesView);
2835
const handleDownloadDataset = useCallback(() => {
2936
logging.event({
3037
type: "dataset-save",
@@ -42,6 +49,14 @@ const DataSamplesMenu = () => {
4249
});
4350
deleteAllGestures();
4451
}, [deleteAllGestures, logging]);
52+
const handleViewChange = useCallback(
53+
(view: string | string[]) => {
54+
if (typeof view === "string") {
55+
setDataSamplesView(view as DataSamplesView);
56+
}
57+
},
58+
[setDataSamplesView]
59+
);
4560
return (
4661
<Menu>
4762
<MenuButton
@@ -55,6 +70,29 @@ const DataSamplesMenu = () => {
5570
/>
5671
<Portal>
5772
<MenuList>
73+
{flags.fingerprints && (
74+
<>
75+
<MenuOptionGroup
76+
defaultValue={dataSamplesView}
77+
title={intl.formatMessage({
78+
id: "data-samples-view-options-heading",
79+
})}
80+
type="radio"
81+
onChange={handleViewChange}
82+
>
83+
<MenuItemOption value={DataSamplesView.Graph}>
84+
<FormattedMessage id="data-samples-view-graph-option" />
85+
</MenuItemOption>
86+
<MenuItemOption value={DataSamplesView.DataFeatures}>
87+
<FormattedMessage id="data-samples-view-data-features-option" />
88+
</MenuItemOption>
89+
<MenuItemOption value={DataSamplesView.GraphAndDataFeatures}>
90+
<FormattedMessage id="data-samples-view-graph-and-data-features-option" />
91+
</MenuItemOption>
92+
</MenuOptionGroup>
93+
<MenuDivider />
94+
</>
95+
)}
5896
<LoadProjectMenuItem icon={<RiUpload2Line />} accept=".json">
5997
<FormattedMessage id="import-data-samples-action" />
6098
</LoadProjectMenuItem>

src/components/LiveGraph.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { SmoothieChart, TimeSeries } from "smoothie";
55
import { useConnectActions } from "../connect-actions-hooks";
66
import { useConnectionStage } from "../connection-stage-hooks";
77
import { AccelerometerDataEvent } from "@microbit/microbit-connection";
8-
import { mlSettings } from "../ml";
98
import { ConnectionStatus } from "../connect-status-hooks";
109
import { RiArrowDropLeftFill } from "react-icons/ri";
1110
import React from "react";
1211
import { LabelConfig, getUpdatedLabelConfig } from "../live-graph-label-config";
1312
import { useStore } from "../store";
13+
import { mlSettings } from "../mlConfig";
1414

1515
const initialLabelConfigs: LabelConfig[] = [
1616
{ label: "x", arrowHeight: 0, labelHeight: 0, color: "#f9808e", id: 0 },

src/components/RecordingDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1616
import { FormattedMessage, useIntl } from "react-intl";
1717
import { TimedXYZ } from "../buffered-data";
1818
import { useBufferedData } from "../buffered-data-hooks";
19-
import { mlSettings } from "../ml";
2019
import { GestureData, XYZData } from "../model";
2120
import { useStore } from "../store";
21+
import { mlSettings } from "../mlConfig";
2222

2323
interface CountdownStage {
2424
value: string | number;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { BoxProps, Grid, GridItem, Text, VStack } from "@chakra-ui/react";
2+
import { applyFilters } from "../ml";
3+
import { XYZData } from "../model";
4+
import { calculateColor } from "../utils/gradient-calculator";
5+
import ClickableTooltip from "./ClickableTooltip";
6+
import { FormattedMessage } from "react-intl";
7+
8+
interface RecordingFingerprintProps extends BoxProps {
9+
data: XYZData;
10+
gestureName: string;
11+
}
12+
13+
const RecordingFingerprint = ({
14+
data,
15+
gestureName,
16+
...rest
17+
}: RecordingFingerprintProps) => {
18+
const dataFeatures = applyFilters(data, { normalize: true });
19+
20+
return (
21+
<Grid
22+
w="80px"
23+
h="100%"
24+
position="relative"
25+
borderRadius="md"
26+
borderWidth={1}
27+
borderColor="gray.200"
28+
overflow="hidden"
29+
{...rest}
30+
>
31+
{Object.keys(dataFeatures).map((k, idx) => (
32+
<ClickableTooltip
33+
key={idx}
34+
label={
35+
<VStack
36+
textAlign="left"
37+
alignContent="left"
38+
alignItems="left"
39+
m={3}
40+
>
41+
<Text fontWeight="bold">
42+
<FormattedMessage
43+
id={`fingerprint-${k}-tooltip`}
44+
values={{ action: gestureName }}
45+
/>
46+
</Text>
47+
</VStack>
48+
}
49+
>
50+
<GridItem
51+
w="100%"
52+
backgroundColor={calculateColor(
53+
dataFeatures[k],
54+
{ r: 225, g: 255, b: 255 },
55+
{ r: 25, g: 125, b: 188 }
56+
)}
57+
/>
58+
</ClickableTooltip>
59+
))}
60+
</Grid>
61+
);
62+
};
63+
64+
export default RecordingFingerprint;

src/components/RecordingGraph.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const RecordingGraph = ({ data, ...rest }: RecordingGraphProps) => {
3838
borderRadius="md"
3939
borderWidth={1}
4040
borderColor="gray.200"
41-
width="100%"
41+
w="158px"
4242
height="100%"
4343
{...rest}
4444
>

0 commit comments

Comments
 (0)