Skip to content

Commit

Permalink
Axes highlight on livegraph
Browse files Browse the repository at this point in the history
  • Loading branch information
r59q committed Mar 17, 2024
1 parent 76a64eb commit 308edaa
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/components/graphs/LiveGraph.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
});
chart.streamTo(<HTMLCanvasElement>canvas, 0);
chart.stop();
}
};
// On mount draw smoothieChart
onMount(() => {
Expand Down
186 changes: 186 additions & 0 deletions src/components/graphs/LiveGraphHighlighted.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<!--
(c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors
SPDX-License-Identifier: MIT
-->

<script lang="ts">
import { state } from '../../script/stores/uiStore';
import { onMount } from 'svelte';
import { type Unsubscriber } from 'svelte/store';
import { SmoothieChart, TimeSeries } from 'smoothie';
import DimensionLabels from './DimensionLabels.svelte';
import LiveData from '../../script/domain/stores/LiveData';
import StaticConfiguration from '../../StaticConfiguration';
import SmoothedLiveData from '../../script/livedata/SmoothedLiveData';
import { classifier } from '../../script/stores/Stores';
import Axes from '../../script/domain/Axes';
/**
* TimesSeries, but with the data array added.
* `data[i][0]` is the timestamp,
* `data[i][1]` is the value,
*/
type TimeSeriesWithData = TimeSeries & { data: number[][] };
// Updates width to ensure that the canvas fills the whole screen
export let width: number;
export let liveData: LiveData<any>;
export let maxValue: number;
export let minValue: number;
export let axis: Axes;
let axisColors = StaticConfiguration.liveGraphColors;
// Smoothes real-time data by using the 3 most recent data points
const smoothedLiveData = new SmoothedLiveData(liveData, 3);
var canvas: HTMLCanvasElement | undefined = undefined;
var chart: SmoothieChart | undefined;
const lines: TimeSeriesWithData[] = [];
for (let i = 0; i < smoothedLiveData.getSeriesSize(); i++) {
lines.push(new TimeSeries() as TimeSeriesWithData);
}
let recordLines = new TimeSeries();
const lineWidth = 2;
const init = () => {
chart = new SmoothieChart({
maxValue,
minValue,
millisPerPixel: 7,
grid: {
fillStyle: '#ffffff00',
strokeStyle: 'rgba(48,48,48,0.20)',
millisPerLine: 3000,
borderVisible: false,
},
interpolation: 'linear',
});
let i = 0;
for (const line of lines) {
const getOpacity = () => {
if (i === 0 && axis === Axes.X) {
return 'ff';
}
if (i === 1 && axis === Axes.Y) {
return 'ff';
}
if (i === 2 && axis === Axes.Z) {
return 'ff';
}
return '30';
};
chart.addTimeSeries(line, {
lineWidth,
strokeStyle: axisColors[i] + getOpacity(),
});
i++;
}
chart.addTimeSeries(recordLines, {
lineWidth: 3,
strokeStyle: '#4040ff44',
fillStyle: '#0000ff07',
});
chart.streamTo(<HTMLCanvasElement>canvas, 0);
chart.stop();
};
// On mount draw smoothieChart
onMount(() => {
init();
});
// Start and stop chart when microbit connect/disconnect
const model = classifier.getModel();
$: {
if (chart !== undefined) {
if ($state.isInputReady) {
if (!$model.isTraining) {
chart.start();
} else {
chart.stop();
}
} else {
chart.stop();
}
}
}
// Draw on graph to display that users are recording
// The jagged edges problem is caused by repeating the recordingStarted function.
// We will simply block the recording from starting, while it's recording
let blockRecordingStart = false;
$: recordingStarted($state.isRecording);
// Function to clearly diplay the area in which users are recording
function recordingStarted(isRecording: boolean): void {
if (!isRecording || blockRecordingStart) {
return;
}
// Set start line
recordLines.append(new Date().getTime() - 1, minValue, false);
recordLines.append(new Date().getTime(), maxValue, false);
// Wait a second and set end line
blockRecordingStart = true;
setTimeout(() => {
recordLines.append(new Date().getTime() - 1, maxValue, false);
recordLines.append(new Date().getTime(), minValue, false);
blockRecordingStart = false;
}, StaticConfiguration.recordingDuration);
}
// When state changes, update the state of the canvas
$: {
const isConnected = $state.isInputReady;
updateCanvas(isConnected);
}
let unsubscribeFromData: Unsubscriber | undefined;
// If state is connected. Start updating the graph whenever there is new data
// From the Micro:Bit
function updateCanvas(isConnected: boolean) {
if (isConnected) {
unsubscribeFromData = smoothedLiveData.subscribe(data => {
addDataToGraphLines(data);
});
// Else if we're currently subscribed to data. Unsubscribe.
// This means that the micro:bit has been disconnected
} else if (unsubscribeFromData !== undefined) {
unsubscribeFromData();
unsubscribeFromData = undefined;
}
}
const addDataToGraphLines = (data: any) => {
const t = new Date().getTime();
let i = 0;
for (const property in data) {
const line: TimeSeriesWithData = lines[i];
if (!line) {
break;
}
const newValue = data[property];
line.append(t, newValue, false);
i++;
}
};
</script>

<main class="flex">
<canvas bind:this={canvas} height="160" id="smoothie-chart" width={width - 30} />
<DimensionLabels
hidden={!$state.isInputConnected}
{minValue}
graphHeight={160}
{maxValue}
liveData={smoothedLiveData} />
</main>
43 changes: 38 additions & 5 deletions src/components/graphs/MicrobitLiveGraph.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,47 @@

<script lang="ts">
import StaticConfiguration from '../../StaticConfiguration';
import { Feature, hasFeature } from '../../script/FeatureToggles';
import Axes from '../../script/domain/Axes';
import { liveAccelerometerData } from '../../script/stores/Stores';
import { highlightedAxis } from '../../script/stores/uiStore';
import LiveGraph from './LiveGraph.svelte';
import LiveGraphHighlighted from './LiveGraphHighlighted.svelte';
export let width: number;
$: showhighlit = hasFeature(Feature.KNN_MODEL) && $highlightedAxis !== undefined;
</script>

<LiveGraph
minValue={StaticConfiguration.liveGraphValueBounds.min}
maxValue={StaticConfiguration.liveGraphValueBounds.max}
liveData={liveAccelerometerData}
{width} />
{#if showhighlit}
{#if $highlightedAxis === Axes.X}
<LiveGraphHighlighted
minValue={StaticConfiguration.liveGraphValueBounds.min}
maxValue={StaticConfiguration.liveGraphValueBounds.max}
liveData={liveAccelerometerData}
axis={Axes.X}
{width} />
{/if}
{#if $highlightedAxis === Axes.Y}
<LiveGraphHighlighted
minValue={StaticConfiguration.liveGraphValueBounds.min}
maxValue={StaticConfiguration.liveGraphValueBounds.max}
liveData={liveAccelerometerData}
axis={Axes.Y}
{width} />
{/if}
{#if $highlightedAxis === Axes.Z}
<LiveGraphHighlighted
minValue={StaticConfiguration.liveGraphValueBounds.min}
maxValue={StaticConfiguration.liveGraphValueBounds.max}
liveData={liveAccelerometerData}
axis={Axes.Z}
{width} />
{/if}
{:else}
<LiveGraph
minValue={StaticConfiguration.liveGraphValueBounds.min}
maxValue={StaticConfiguration.liveGraphValueBounds.max}
liveData={liveAccelerometerData}
{width} />
{/if}
111 changes: 58 additions & 53 deletions src/components/graphs/knngraph/AxesFilterVector.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
[liveAccelerometerData, highlightedAxis],
stores => {
const axis = stores[1];
if (!axis) {
return Array(classifier.getFilters().count()).fill(0);
}
try {
const seriesTimestamped = liveAccelerometerData
.getBuffer()
Expand All @@ -40,62 +43,64 @@

<div>
<div>
<div class="flex flex-row space-x-1">
<div class="flex flex-col">
<div class="flex flex-row space-x-2">
<StandardButton
small
outlined={$highlightedAxis !== Axes.X}
onClick={() => ($highlightedAxis = Axes.X)}>X</StandardButton>
{#if $highlightedAxis}
<div class="flex flex-row space-x-1">
<div class="flex flex-col">
<div class="flex flex-row space-x-2">
<StandardButton
small
outlined={$highlightedAxis !== Axes.X}
onClick={() => ($highlightedAxis = Axes.X)}>X</StandardButton>
</div>
<div class="flex flex-row space-x-2">
<StandardButton
small
outlined={$highlightedAxis !== Axes.Y}
onClick={() => ($highlightedAxis = Axes.Y)}>Y</StandardButton>
</div>
<div class="flex flex-row space-x-2">
<StandardButton
small
outlined={$highlightedAxis !== Axes.Z}
onClick={() => ($highlightedAxis = Axes.Z)}>Z</StandardButton>
</div>
</div>
<div class="flex flex-row space-x-2">
<StandardButton
small
outlined={$highlightedAxis !== Axes.Y}
onClick={() => ($highlightedAxis = Axes.Y)}>Y</StandardButton>
<div class="flex flex-col justify-around">
<img
src={'imgs/vector_lines_x.png'}
class:hidden={$highlightedAxis !== Axes.X}
alt="x vector line" />
<img
src={'imgs/vector_lines_y.png'}
class:hidden={$highlightedAxis !== Axes.Y}
alt="y vector line" />
<img
src={'imgs/vector_lines_z.png'}
class:hidden={$highlightedAxis !== Axes.Z}
alt="z vector line" />
</div>
<div class="flex flex-row space-x-2">
<StandardButton
small
outlined={$highlightedAxis !== Axes.Z}
onClick={() => ($highlightedAxis = Axes.Z)}>Z</StandardButton>
<div class="flex flex-col justify-around">
<p>MAX</p>
<p>MIN</p>
<p>MEAN</p>
</div>
<div class="flex flex-col justify-around">
<img src={'imgs/right_arrow_blue.svg'} alt="right arrow icon" width="20px" />
<img src={'imgs/right_arrow_blue.svg'} alt="right arrow icon" width="20px" />
<img src={'imgs/right_arrow_blue.svg'} alt="right arrow icon" width="20px" />
</div>
<div class="flex flex-col justify-around">
<img src={'imgs/left_bracket_blue.png'} alt="left bracket" />
</div>
<div class="flex flex-col justify-around w-12">
{#each $liveFilteredAxesData as val}
<p>{val.toFixed(3)}</p>
{/each}
</div>
<div class="flex flex-col justify-around">
<img src={'imgs/right_bracket_blue.png'} alt="left bracket" />
</div>
</div>
<div class="flex flex-col justify-around">
<img
src={'imgs/vector_lines_x.png'}
class:hidden={$highlightedAxis !== Axes.X}
alt="x vector line" />
<img
src={'imgs/vector_lines_y.png'}
class:hidden={$highlightedAxis !== Axes.Y}
alt="y vector line" />
<img
src={'imgs/vector_lines_z.png'}
class:hidden={$highlightedAxis !== Axes.Z}
alt="z vector line" />
</div>
<div class="flex flex-col justify-around">
<p>MAX</p>
<p>MIN</p>
<p>MEAN</p>
</div>
<div class="flex flex-col justify-around">
<img src={'imgs/right_arrow_blue.svg'} alt="right arrow icon" width="20px" />
<img src={'imgs/right_arrow_blue.svg'} alt="right arrow icon" width="20px" />
<img src={'imgs/right_arrow_blue.svg'} alt="right arrow icon" width="20px" />
</div>
<div class="flex flex-col justify-around">
<img src={'imgs/left_bracket_blue.png'} alt="left bracket" />
</div>
<div class="flex flex-col justify-around w-12">
{#each $liveFilteredAxesData as val}
<p>{val.toFixed(3)}</p>
{/each}
</div>
<div class="flex flex-col justify-around">
<img src={'imgs/right_bracket_blue.png'} alt="left bracket" />
</div>
</div>
{/if}
</div>
</div>
1 change: 0 additions & 1 deletion src/components/graphs/knngraph/KNNModelGraphController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { classifier, liveAccelerometerData } from '../../../script/stores/Stores
import { MicrobitAccelerometerData } from '../../../script/livedata/MicrobitAccelerometerData';
import { TimestampedData } from '../../../script/domain/LiveDataBuffer';
import Axes from '../../../script/domain/Axes';
import PerformanceProfileTimer from '../../../script/utils/PerformanceProfileTimer';

type SampleData = {
value: number[];
Expand Down
Loading

0 comments on commit 308edaa

Please sign in to comment.