Skip to content
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