Skip to content

Commit 422769a

Browse files
rzatsmelange396
andauthored
Upgrade EpiVis dashboard (#36)
* Dismiss tour once cancelled or completed * Set default as FluView * Make sure signals never appear offscreen * Update tour & autofitting * Make sure shared datasets are displayed in sidebar * Prevent left panel going offscreen * Force reactive updates * Button classes --------- Co-authored-by: Rostyslav Zatserkovnyi <[email protected]> Co-authored-by: george <[email protected]>
1 parent 6e310a1 commit 422769a

File tree

13 files changed

+172
-64
lines changed

13 files changed

+172
-64
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "www-epivis",
3-
"version": "2.0.3",
3+
"version": "2.1.0",
44
"private": true,
55
"license": "MIT",
66
"description": "",

src/App.svelte

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,48 @@
66
import type { IChart } from './store';
77
import { onMount } from 'svelte';
88
import { tour } from './tour';
9+
import { addDataSet } from './store';
10+
import { fluViewRegions } from './data/data';
11+
import { DEFAULT_ISSUE } from './components/dialogs/utils';
12+
import { importFluView } from './api/EpiData';
913
1014
let chart: Chart | null = null;
1115
$: ichart = chart as unknown as IChart | null;
1216
1317
onMount(() => {
14-
tour.start();
18+
if (!localStorage.getItem('shepherd-tour')) {
19+
tour.start();
20+
}
21+
22+
// Try fetching the default FluView dataset! (unless the URL has a shared dataset in it)
23+
const url = new URL(location.href);
24+
const hash = url.hash.slice(1);
25+
if (!hash) {
26+
let regions = fluViewRegions[0].value;
27+
let issue = DEFAULT_ISSUE;
28+
let auth: string = '';
29+
30+
importFluView({ regions, ...issue, auth }).then((ds) => {
31+
if (ds) {
32+
// add the dataset itself
33+
addDataSet(ds);
34+
// reset active datasets to fluview -> ili
35+
$activeDatasets = [ds.datasets[1]];
36+
if (chart) {
37+
chart.fitData(true);
38+
}
39+
}
40+
});
41+
}
1542
});
1643
</script>
1744

1845
<TopMenu chart={ichart} style="grid-area: menu" />
19-
<LeftMenu style="grid-area: side" />
46+
<LeftMenu chart={ichart} style="grid-area: side; max-height: 100vh; overflow: scroll" />
2047
<Chart
2148
bind:this={chart}
2249
style="grid-area: main"
2350
bind:showPoints={$isShowingPoints}
2451
bind:navMode={$navMode}
2552
initialViewport={$initialViewport}
26-
datasets={$activeDatasets}
2753
/>

src/api/EpiData.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
import DataSet, { DataGroup } from '../data/DataSet';
2222
import EpiDate from '../data/EpiDate';
2323
import EpiPoint from '../data/EpiPoint';
24+
import { get } from 'svelte/store';
25+
import { expandedDataGroups } from '../store';
2426

2527
// import DataSet from "../data/DataSet";
2628
// import EpiDate from "../data/EpiDate";
@@ -111,6 +113,17 @@ export function loadDataSet(
111113
userParams: Record<string, unknown>,
112114
columns: string[],
113115
): Promise<DataGroup | null> {
116+
const duplicates = get(expandedDataGroups).filter((d) => d.title == title);
117+
if (duplicates.length > 0) {
118+
return UIkit.modal
119+
.alert(
120+
`
121+
<div class="uk-alert uk-alert-error">
122+
Cannot import duplicate dataset: <b>${title}.</b>
123+
</div>`,
124+
)
125+
.then(() => null);
126+
}
114127
const url = new URL(ENDPOINT + `/${endpoint}/`);
115128
const params = cleanParams(userParams);
116129
Object.entries(fixedParams).forEach(([key, value]) => {

src/components/Chart.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { onMount } from 'svelte';
3+
import { activeDatasets } from '../store';
34
import type DataSet from '../data/DataSet';
45
import { DEFAULT_VIEWPORT } from '../data/DataSet';
56
import EpiDate from '../data/EpiDate';
@@ -81,7 +82,8 @@
8182
export let showPoints = false;
8283
export let interpolate = false;
8384
export let highlightedDate: EpiDate | null = null;
84-
export let datasets: DataSet[] = [];
85+
86+
$: datasets = $activeDatasets;
8587
8688
function date2x(date: number): number {
8789
return ((date - xMin) / (xMax - xMin)) * width;
@@ -381,6 +383,7 @@
381383
}
382384
383385
export function fitData(shouldAnimate = false): void {
386+
datasets = $activeDatasets; // force an update
384387
if (datasets.length === 0) {
385388
return;
386389
}

src/components/LeftMenu.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
<script lang="ts">
22
import DataSet from '../data/DataSet';
3+
import type { IChart } from '../store';
34
import { datasetTree, version } from '../store';
45
import ImportDataSetsMenu from './ImportDataSetsMenu.svelte';
56
import TreeInnerNode from './tree/TreeInnerNode.svelte';
67
import TreeLeafNode from './tree/TreeLeafNode.svelte';
78
89
export let style = '';
10+
export let chart: IChart | null;
911
</script>
1012

1113
<side class="left" {style} data-tour="browser">
1214
<ImportDataSetsMenu />
1315
<div class="tree">
1416
{#each $datasetTree.datasets as child (child.title)}
1517
{#if child instanceof DataSet}
16-
<TreeLeafNode node={child} />
18+
<TreeLeafNode {chart} node={child} />
1719
{:else}
18-
<TreeInnerNode node={child} />
20+
<TreeInnerNode {chart} node={child} />
1921
{/if}
2022
{/each}
2123
</div>

src/components/TopMenu.svelte

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
faImage,
1010
faLink,
1111
faPaintBrush,
12+
faQuestion,
1213
faReceipt,
1314
faSearchPlus,
15+
faUpDown,
1416
} from '@fortawesome/free-solid-svg-icons';
1517
import Fa from 'svelte-fa';
16-
import { activeDatasets, isShowingPoints, navMode, randomizeColors, reset, scaleMean } from '../store';
18+
import { activeDatasets, isShowingPoints, navMode, randomizeColors, reset, scaleMean, autoFit } from '../store';
1719
import type { IChart } from '../store';
1820
import { NavMode } from './chartUtils';
21+
import { tour } from '../tour';
1922
import RegressionDialog from './dialogs/RegressionDialog.svelte';
2023
import DirectLinkDialog from './dialogs/DirectLinkDialog.svelte';
2124
@@ -67,6 +70,13 @@
6770
case 's':
6871
$isShowingPoints = !$isShowingPoints;
6972
break;
73+
case 'a':
74+
$autoFit = !$autoFit;
75+
break;
76+
case 'h':
77+
tour.cancel();
78+
tour.start();
79+
break;
7080
}
7181
}
7282
</script>
@@ -92,8 +102,22 @@
92102
>
93103
<button
94104
type="button"
95-
class="uk-button uk-button-default uk-button-small"
105+
class="uk-button uk-button-small"
106+
disabled={!chart}
107+
class:uk-active={$autoFit}
108+
class:uk-button-secondary={$autoFit}
109+
class:uk-button-default={!$autoFit}
110+
on:click|preventDefault={() => ($autoFit = !$autoFit)}
111+
title="Automatically Fit Data<br/>(Keyboard Shortcut: a)"
112+
data-tour="autofit"
113+
uk-tooltip><Fa icon={faUpDown} /></button
114+
>
115+
<button
116+
type="button"
117+
class="uk-button uk-button-small"
96118
class:uk-active={$isShowingPoints}
119+
class:uk-button-secondary={$isShowingPoints}
120+
class:uk-button-default={!$isShowingPoints}
97121
on:click|preventDefault={() => ($isShowingPoints = !$isShowingPoints)}
98122
title="Show or Hide points<br/>(Keyboard Shortcut: s)"
99123
data-tour="points"
@@ -111,7 +135,7 @@
111135
class="uk-button uk-button-default uk-button-small"
112136
on:click|preventDefault={() => (doDialog = 'regress')}
113137
title="Perform Regression"
114-
uk-tootlip
138+
uk-tooltip
115139
disabled={$activeDatasets.length < 2}><Fa icon={faChartLine} /></button
116140
>
117141
<button
@@ -145,29 +169,49 @@
145169
<div class="uk-button-group" data-tour="navmode">
146170
<button
147171
type="button"
148-
class="uk-button uk-button-default uk-button-small"
172+
class="uk-button uk-button-small"
149173
class:uk-active={$navMode === NavMode.pan}
150174
title="Pan Mode<br/>(Keyboard Shortcut: p)"
175+
class:uk-button-secondary={$navMode === NavMode.pan}
176+
class:uk-button-default={$navMode !== NavMode.pan}
151177
uk-tooltip
152178
on:click|preventDefault={() => ($navMode = NavMode.pan)}><Fa icon={faArrowsAlt} /></button
153179
>
154180
<button
155181
type="button"
156-
class="uk-button uk-button-default uk-button-small"
182+
class="uk-button uk-button-small"
157183
class:uk-active={$navMode === NavMode.crop}
184+
class:uk-button-secondary={$navMode === NavMode.crop}
185+
class:uk-button-default={$navMode !== NavMode.crop}
158186
title="Crop Mode<br/>(Keyboard Shortcut: c)"
159187
uk-tooltip
160188
on:click|preventDefault={() => ($navMode = NavMode.crop)}><Fa icon={faCrop} /></button
161189
>
162190
<button
163191
type="button"
164-
class="uk-button uk-button-default uk-button-small"
192+
class="uk-button uk-button-small"
165193
class:uk-active={$navMode === NavMode.zoom}
194+
class:uk-button-secondary={$navMode === NavMode.zoom}
195+
class:uk-button-default={$navMode !== NavMode.zoom}
166196
title="Zoom Mode<br/>(Keyboard Shortcut: z)"
167197
uk-tooltip
168198
on:click|preventDefault={() => ($navMode = NavMode.zoom)}><Fa icon={faSearchPlus} /></button
169199
>
170200
</div>
201+
<div class="uk-button-group">
202+
<button
203+
type="button"
204+
class="uk-button uk-button-default uk-button-small"
205+
disabled={!chart}
206+
title="View introductory tour<br/>(Keyboard Shortcut: h)"
207+
uk-tooltip
208+
data-tour="datatour"
209+
on:click|preventDefault={() => {
210+
tour.cancel();
211+
tour.start();
212+
}}><Fa icon={faQuestion} /></button
213+
>
214+
</div>
171215
</div>
172216

173217
{#if doDialog === 'regress'}

src/components/tree/TreeInnerNode.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<script lang="ts">
22
import DataSet from '../../data/DataSet';
33
import type { DataGroup } from '../../data/DataSet';
4+
import type { IChart } from '../store';
45
import { expandedDataGroups } from '../../store';
56
import TreeLeafNode from './TreeLeafNode.svelte';
67
import Fa from 'svelte-fa';
78
import { faChevronRight, faChevronDown } from '@fortawesome/free-solid-svg-icons';
89
910
export let node: DataGroup;
11+
export let chart: IChart | null;
1012
1113
function toggleExpanded() {
1214
if (expanded) {
@@ -28,7 +30,7 @@
2830
{#if expanded}
2931
{#each node.datasets as child (child.title)}
3032
{#if child instanceof DataSet}
31-
<TreeLeafNode node={child} />
33+
<TreeLeafNode {chart} node={child} />
3234
{:else}
3335
<svelte:self node={child} />
3436
{/if}

src/components/tree/TreeLeafNode.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<script lang="ts">
22
import type DataSet from '../../data/DataSet';
3-
import { activeDatasets } from '../../store';
3+
import { activeDatasets, autoFit } from '../../store';
44
import Fa from 'svelte-fa';
55
import { faEyeSlash, faEye } from '@fortawesome/free-solid-svg-icons';
6+
import type { IChart } from '../store';
67
78
export let node: DataSet;
9+
export let chart: IChart | null;
810
911
function toggleSelected() {
1012
if (selected) {
@@ -13,6 +15,12 @@
1315
$activeDatasets = [node, ...$activeDatasets];
1416
}
1517
}
18+
$: {
19+
// runs whenever $activeDatasets is updated
20+
if ($activeDatasets && chart && $autoFit) {
21+
chart.fitData(true);
22+
}
23+
}
1624
$: selected = $activeDatasets.includes(node);
1725
$: color = $activeDatasets.includes(node) ? node.color : undefined;
1826
</script>

src/data/DataSet.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default class DataSet {
2626

2727
constructor(
2828
public readonly data: readonly EpiPoint[],
29-
public readonly title = '',
29+
public title = '',
3030
public readonly params: Record<string, unknown> | unknown[] | null = null,
3131
public color = getRandomColor(),
3232
) {
@@ -100,24 +100,6 @@ export default class DataSet {
100100
}
101101
}
102102

103-
export const SAMPLE_DATASET: DataSet = ((): DataSet => {
104-
// initial dataset, just for fun
105-
const data = new Array<EpiPoint>(365);
106-
const now = new Date();
107-
const baseIndex = new EpiDate(now.getFullYear(), now.getMonth() + 1, now.getDate()).getIndex() - 182;
108-
for (let i = 0; i < data.length; i++) {
109-
const x = 6 - (12 * i) / (data.length - 1);
110-
const xp = x * Math.PI;
111-
const v = x === 0 ? 1 : Math.sin(xp) / xp;
112-
data[i] = new EpiPoint(EpiDate.fromIndex(baseIndex + i), v);
113-
}
114-
const ds = new DataSet(data, 'EpiVis Sample');
115-
116-
//sampleDataset.lineWidth = 5;
117-
//sampleDataset.color = '#dd3311';
118-
return ds;
119-
})();
120-
121103
export class DataGroup {
122104
public parent?: DataGroup;
123105

@@ -134,7 +116,7 @@ export class DataGroup {
134116
}
135117
}
136118

137-
export const DEFAULT_GROUP: DataGroup = new DataGroup('All Datasets', [SAMPLE_DATASET]);
119+
export const DEFAULT_GROUP: DataGroup = new DataGroup('All Datasets', []);
138120

139121
export function flatten(dataset: DataSet | DataGroup): DataSet[] {
140122
if (dataset instanceof DataSet) {

0 commit comments

Comments
 (0)