Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi region/indicator time series #1136

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
4 changes: 3 additions & 1 deletion src/blocks/HistoryLineTooltip.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
export let sensor;

export let prop = 'displayName';

$: items = item && view ? view.data('values').filter((d) => d.time_value === item.time_value) : [];
</script>

Expand All @@ -26,7 +28,7 @@
<table>
{#each items as i}
<tr>
<th>{i.displayName}</th>
<th>{i[prop]}</th>
<td>
<SensorValue {sensor} value={i.value} medium />
</td>
Expand Down
58 changes: 58 additions & 0 deletions src/blocks/SensorsLineTooltip.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script>
import { formatDateShortDayOfWeekAbbr } from '../formats';
import SensorValue from '../components/SensorValue.svelte';

export let hidden = false;
/**
* @type {import('../../data').EpiDataRow}
*/
export let item;

/**
* @type {View}
*/
export let view;

/**
* @type {import('../../stores/params').Sensor[]}
*/
export let sensors;

export let prop = 'sensorName';

$: items = item && view ? view.data('values').filter((d) => d.time_value === item.time_value) : [];
</script>

<div aria-label="tooltip" class="tooltip" class:hidden>
<h5>{formatDateShortDayOfWeekAbbr(item.date_value)}</h5>
<table>
{#each items as i, index}
<tr>
<th>{i[prop]}</th>
<td>
<SensorValue sensor={sensors[index]} value={i.value} medium />
</td>
</tr>
{/each}
</table>
</div>

<style>
.hidden {
display: none;
}

h5 {
margin: 0;
padding: 0;
}

th {
text-align: left;
max-width: 15em;
}
td {
text-align: right;
vertical-align: top;
}
</style>
2 changes: 2 additions & 0 deletions src/components/RegionSearch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
selectOnClick
{selectedItem}
{selectedItems}
clear={selectedItems == null}
labelFieldName="displayName"
keywordFunction={combineKeywords}
maxItemsToShowInList={15}
on:change
on:add
on:remove
>
<svelte:fragment slot="entry" let:label let:item let:onClick>
<a class="search-box-link" href="?region={item ? item.id : ''}" on:click|preventDefault={onClick}>
Expand Down
22 changes: 14 additions & 8 deletions src/components/Search.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@
/>
{#if clear}
<button
type="button"
class="uk-search-icon clear-button"
class:hidden={!text}
class:modern
Expand All @@ -349,6 +350,7 @@
<div class="search-tag">
<span>{labelFunction(selectedItem)}</span>
<button
type="button"
class=""
data-uk-icon="icon: close"
on:click={() => removeItem(selectedItem)}
Expand Down Expand Up @@ -376,14 +378,18 @@
on:keypress={onKeyPress}
/>
{/if}
<button
class="uk-search-icon clear-button"
class:modern
class:hidden={selectedItems.length === 0}
on:click={onResetItem}
title="Clear Search Field"
data-uk-icon="icon: close"
/>

{#if clear}
<button
type="button"
class="uk-search-icon clear-button"
class:modern
class:hidden={selectedItems.length === 0}
on:click={onResetItem}
title="Clear Search Field"
data-uk-icon="icon: close"
/>
{/if}
{/if}

<div class="uk-dropdown uk-dropdown-bottom-left search-box-list" class:uk-open={opened} bind:this={listRef}>
Expand Down
2 changes: 2 additions & 0 deletions src/components/SensorSearch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
icon="database"
{selectedItem}
{selectedItems}
clear={selectedItems == null}
selectOnClick
labelFieldName="name"
keywordFunction={combineKeywords}
maxItemsToShowInList={11}
on:change
on:add
on:remove
>
<svelte:fragment slot="entry" let:label let:item let:onClick>
<a
Expand Down
37 changes: 37 additions & 0 deletions src/data/sensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,40 @@ export function resolveDefaultRegion(sensor: Sensor): Region {
}
return nationInfo;
}

/**
* checks whether multiple regions of the same sensor can be shown on a single axis without harm
* @param sensor
* @returns
*/
export function isComparableAcrossRegions(sensor: Sensor): boolean {
return sensor.format === 'fraction' || sensor.format === 'per100k' || sensor.format === 'percent';
}

/**
* groups sensor by their compatibility, i.e. can be shown on the same axis
*/
export function toSignalCompatibilityGroups(sensors: readonly Sensor[]): Sensor[][] {
if (sensors.length === 1) {
return [sensors.slice()];
}
const byFormat = new Map<string, Sensor[]>();
const byFormatList: Sensor[][] = [];
for (const sensor of sensors) {
if (isComparableAcrossRegions(sensor)) {
// can combine with others of same format
const entry = byFormat.get(sensor.format);
if (entry) {
entry.push(sensor);
} else {
const list = [sensor];
byFormatList.push(list);
byFormat.set(sensor.format, list);
}
} else {
// need to be alone
byFormatList.push([sensor]);
}
}
return byFormatList;
}
4 changes: 4 additions & 0 deletions src/modes/dashboard/WidgetConfigurator.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import RegionPicker from './config/RegionPicker.svelte';
import SensorPicker from './config/SensorPicker.svelte';
import SensorsPicker from './config/SensorsPicker.svelte';
import RegionsPicker from './config/RegionsPicker.svelte';
import TimeFramePicker from './config/TimeFramePicker.svelte';
import { findWidget } from './state';

Expand Down Expand Up @@ -35,6 +36,9 @@
{#if hasConfig.has('region')}
<RegionPicker {region} value={config.region || ''} />
{/if}
{#if hasConfig.has('regions')}
<RegionsPicker {region} value={config.regions || ''} />
{/if}
{#if hasConfig.has('level')}
<LevelPicker {region} value={config.level} />
{/if}
Expand Down
26 changes: 26 additions & 0 deletions src/modes/dashboard/WidgetFactory.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@
import SensorTableWidget from './widgets/SensorTableWidget.svelte';
import AnomaliesWidget from './widgets/AnomaliesWidget.svelte';
import ZoomedMapChartWidget from './widgets/ZoomedMapChartWidget.svelte';
import SensorLineChartWidget from './widgets/SensorLineChartWidget.svelte';
import RegionLineChartWidget from './widgets/RegionLineChartWidget.svelte';
import {
resolveDate,
resolveRegion,
resolveRegionLevel,
resolveRegions,
resolveSensor,
resolveSensors,
resolveSensorParams,
resolveTimeFrame,
} from './configResolver';

Expand Down Expand Up @@ -54,6 +58,28 @@
id={c.id}
initialState={c.state}
/>
{:else if c.type === 'sensorsline'}
<SensorLineChartWidget
sensors={resolveSensorParams(sensor, c.config.sensors)}
timeFrame={resolveTimeFrame(sensor, date, c.config.timeFrame)}
region={resolveRegion(region, c.config.region)}
bind:highlight
on:action={trackAction}
on:state={trackState}
id={c.id}
initialState={c.state}
/>
{:else if c.type === 'regionsline'}
<RegionLineChartWidget
sensor={resolveSensor(sensor, c.config.sensor)}
timeFrame={resolveTimeFrame(sensor, date, c.config.timeFrame)}
regions={resolveRegions(region, c.config.regions)}
bind:highlight
on:action={trackAction}
on:state={trackState}
id={c.id}
initialState={c.state}
/>
{:else if c.type === 'map'}
<MapChartWidget
sensor={resolveSensor(sensor, c.config.sensor)}
Expand Down
65 changes: 65 additions & 0 deletions src/modes/dashboard/config/RegionsPicker.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<script>
import RegionSearch from '../../../components/RegionSearch.svelte';
import { getInfoByName } from '../../../data/regions';
import { sortedNameInfos } from '../utils';

/**
* @type {import("../../../stores/params").RegionParam}
*/
export let region;

/**
* @type {string[]}
*/
export let value = [];

let syncedValues = value ? (Array.isArray(value) ? value : [value]) : [''];

$: defaultRegion = {
id: '',
displayName: `Use Configured: ${region.displayName}`,
};
$: allItems = [defaultRegion, ...sortedNameInfos];

function toKey(e) {
return !e.detail || e.detail.id === '' ? '' : `${e.detail.id}@${e.detail.level}`;
}

$: selectedItems = syncedValues.map((d) => (!d ? defaultRegion : getInfoByName(d)));
</script>

<div class="regions-picker">
<label for="widget-adder-r" class="uk-form-label">Geographic Regions</label>
{#each syncedValues as s}
<input type="hidden" value={s} name="regions" />
{/each}
<RegionSearch
items={allItems}
{selectedItems}
on:change={(e) => {
syncedValues = e.detail ? [toKey(e)] : [''];
}}
on:add={(e) => {
if (syncedValues.length === 1 && syncedValues[0] === '') {
// replace default
syncedValues = [toKey(e)];
} else {
syncedValues = [...syncedValues, toKey(e)];
}
}}
on:remove={(e) => {
if (syncedValues.length === 1) {
syncedValues = [''];
} else {
const key = toKey(e);
syncedValues = syncedValues.filter((d) => d !== key);
}
}}
/>
</div>

<style>
.regions-picker :global(.search-multiple) {
max-width: 35em;
}
</style>
9 changes: 8 additions & 1 deletion src/modes/dashboard/config/SensorsPicker.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,18 @@
syncedValues = [...syncedValues, e.detail.key];
}
}}
on:remove={(e) => {
if (syncedValues.length === 1) {
syncedValues = [''];
} else {
syncedValues = syncedValues.filter((d) => d !== e.detail.key);
}
}}
/>
</div>

<style>
.sensors-picker :global(.serach-multiple) {
.sensors-picker :global(.search-multiple) {
max-width: 35em;
}
</style>
Loading