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

Browse page selected layers #521

Merged
merged 12 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/lib/browse/LayerControls.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@
</CheckboxGroup>
</CollapsibleCard>
<CollapsibleCard label="Public transport">
<BusRoutesLayerControl />
<TramsLayerControl />
<BusStopsLayerControl />
<CheckboxGroup small>
<BusRoutesLayerControl />
<TramsLayerControl />
<BusStopsLayerControl />
</CheckboxGroup>
</CollapsibleCard>
<CollapsibleCard label="Boundaries">
<CheckboxGroup small>
Expand Down
27 changes: 27 additions & 0 deletions src/lib/browse/layers/ActiveLayersLegend.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import { activeLayers, layerLegends } from "./stores";

let container: HTMLDivElement | null = null;

function update(
container: HTMLDivElement | null,
activeLayers: Set<string>,
layerLegends: Map<string, HTMLDivElement>,
) {
if (container) {
container.innerHTML = "";
for (let l of activeLayers) {
let obj = layerLegends.get(l);
// Layers can be immediately active from the URL, before the DOM element is registered
if (obj) {
obj.style.display = "block";
container.appendChild(obj);
}
}
}
}
$: update(container, $activeLayers, $layerLegends);
</script>

<p><b>Selected layers</b></p>
<div bind:this={container} />
91 changes: 91 additions & 0 deletions src/lib/browse/layers/LayerControl.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<script lang="ts">
import { HelpButton } from "lib/common";
import { SecondaryButton, Checkbox, CheckboxGroup } from "govuk-svelte";
import { onDestroy } from "svelte";
import { activeLayers, layerLegends } from "./stores";

// Slots include: icon, controls, help

export let name: string;
export let title: string;
// This must be bound to state that controls the layer display and URL
export let show: boolean;

// If the layer is initially shown (from the URL state), make it active
if (show) {
$activeLayers.add(name);
$activeLayers = $activeLayers;
}

// The controls for each layer are defined in a layer component's optional
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a trick I've been using in many other projects. One Svelte component logically grouping some functionality can define some controls that wind up in different parts of the page. In this case, we're going a bit beyond that by centrally collecting all of the controls by name.

// "controls" slot, as well as the generic controls defined below in
// this component. The whole DOM element is bound here, then it's
// stored centrally in layerLegends . That lets ActiveLayersLegend
// later display the proper layer controls in one place.
let contents: HTMLDivElement | null = null;

$: if (contents) {
layerLegends.update((l) => {
// Initially hide the controls. Since layer components are defined under
// the left sidebar, they'd otherwise appear there.
contents!.style.display = "none";
l.set(name, contents!);
return l;
});
}

onDestroy(() => {
layerLegends.update((l) => {
l.delete(name);
return l;
});
});

function toggleActive() {
activeLayers.update((l) => {
if (l.has(name)) {
l.delete(name);
show = false;
} else {
l.add(name);
show = true;
}
return l;
});
}

function remove() {
$activeLayers.delete(name);
$activeLayers = $activeLayers;
show = false;
}
</script>

<!-- For the left sidebar -->
<Checkbox checked={$activeLayers.has(name)} on:change={toggleActive}>
{title}
</Checkbox>

<!-- For the right panel -->
<div bind:this={contents}>
<CheckboxGroup small>
<div
style="display: flex; justify-content: space-between; white-space: nowrap"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nowrap thing is very weird and messes up vertical alignment. I can't make everything behave here, but we can iterate on it later

>
<Checkbox bind:checked={show}>
<slot name="icon" />
{title}
</Checkbox>
<span>
<HelpButton>
<slot name="help" />
</HelpButton>

<SecondaryButton on:click={remove}>X</SecondaryButton>
</span>
</div>
</CheckboxGroup>
{#if show}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is flexible. We might try using a collapsible card next. It should be easy to plug things in here and make them work for all layers

<slot name="controls" />
{/if}
</div>
138 changes: 38 additions & 100 deletions src/lib/browse/layers/areas/CensusOutputAreas.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
<script lang="ts">
import {
ExternalLink,
HelpButton,
Popup,
publicResourceBaseUrl,
} from "lib/common";
import { Checkbox } from "govuk-svelte";
import { ExternalLink, Popup, publicResourceBaseUrl } from "lib/common";
import { Radio } from "govuk-svelte";
import { layerId, makeColorRamp } from "lib/maplibre";
import {
FillLayer,
Expand All @@ -18,6 +13,7 @@
import OsOglLicense from "../OsOglLicense.svelte";
import SequentialLegend from "../SequentialLegend.svelte";
import { customUrlState } from "../url";
import LayerControl from "../LayerControl.svelte";

let name = "census_output_areas";
let colorScale = colors.sequential_low_to_high;
Expand All @@ -28,7 +24,7 @@
};
let defaultState = {
show: false,
kind: "",
kind: "percent_households_with_car",
};
function stringify(x: State): string | null {
return x.show ? x.kind : null;
Expand All @@ -41,26 +37,6 @@
}
let state = customUrlState(name, defaultState, stringify, parse);

// Mutually exclusive, like a radio button. We need these for checkboxes to work
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big change to this layer to use radio buttons. Probably should've been like this from the start

let showHouseholdsWithCar = $state.kind == "percent_households_with_car";
let showAverageCars = $state.kind == "average_cars_per_household";
let showPopulationDensity = $state.kind == "population_density";
$: {
if (showHouseholdsWithCar) {
$state.show = true;
$state.kind = "percent_households_with_car";
} else if (showAverageCars) {
$state.show = true;
$state.kind = "average_cars_per_household";
} else if (showPopulationDensity) {
$state.show = true;
$state.kind = "population_density";
} else {
$state.show = false;
$state.kind = "";
}
}

function onClick(e: CustomEvent<LayerClickInfo>) {
let oa = e.detail.features[0].properties!["OA21CD"];
if (
Expand Down Expand Up @@ -94,16 +70,9 @@
}
</script>

<Checkbox
bind:checked={showHouseholdsWithCar}
on:change={() => {
showAverageCars = false;
showPopulationDensity = false;
}}
>
Percent of households with a car
<span slot="right">
<HelpButton>
<LayerControl {name} title="2021 census" bind:show={$state.show}>
<span slot="help">
{#if $state.kind == "percent_households_with_car"}
<p>
Car/van availability data is from the 2021 census, via <ExternalLink
href="https://www.nomisweb.co.uk/sources/census_2021_bulk"
Expand All @@ -115,31 +84,7 @@
ONS Geography
</ExternalLink>.
</p>
<p>
License: <ExternalLink
href="http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/"
>
Open Government License
</ExternalLink>. Contains OS data &copy; Crown copyright and database
right 2023.
</p>
</HelpButton>
</span>
</Checkbox>
{#if $state.kind == "percent_households_with_car"}
<SequentialLegend {colorScale} limits={makeLimits($state.kind)} />
{/if}

<Checkbox
bind:checked={showAverageCars}
on:change={() => {
showHouseholdsWithCar = false;
showPopulationDensity = false;
}}
>
Average cars per household
<span slot="right">
<HelpButton>
{:else if $state.kind == "average_cars_per_household"}
<p>
Where the census counts "3 or more cars or vans", the average shown here
assumes 3.
Expand All @@ -155,31 +100,7 @@
ONS Geography
</ExternalLink>.
</p>
<p>
License: <ExternalLink
href="http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/"
>
Open Government License
</ExternalLink>. Contains OS data &copy; Crown copyright and database
right 2023.
</p>
</HelpButton>
</span>
</Checkbox>
{#if $state.kind == "average_cars_per_household"}
<SequentialLegend {colorScale} limits={makeLimits($state.kind)} />
{/if}

<Checkbox
bind:checked={showPopulationDensity}
on:change={() => {
showHouseholdsWithCar = false;
showAverageCars = false;
}}
>
Population density
<span slot="right">
<HelpButton>
{:else}
<p>
Population density data is from the 2021 census, via <ExternalLink
href="https://www.nomisweb.co.uk/sources/census_2021_bulk"
Expand All @@ -191,17 +112,34 @@
ONS Geography
</ExternalLink>.
</p>
<OsOglLicense />
</HelpButton>
{/if}
<OsOglLicense />
</span>
</Checkbox>
{#if $state.kind == "population_density"}
<p>(people per square kilometres)</p>
<SequentialLegend
{colorScale}
limits={makeLimits($state.kind).map((x) => x.toLocaleString())}
/>
{/if}

<span slot="controls">
<Radio
label="Dataset"
choices={[
["percent_households_with_car", "Percent of households with a car"],
["average_cars_per_household", "Average cars per household"],
["population_density", "Population density"],
]}
bind:value={$state.kind}
/>

{#if $state.kind == "percent_households_with_car"}
<SequentialLegend {colorScale} limits={makeLimits($state.kind)} />
{:else if $state.kind == "average_cars_per_household"}
<SequentialLegend {colorScale} limits={makeLimits($state.kind)} />
{:else if $state.kind == "population_density"}
<p>(people per square kilometres)</p>
<SequentialLegend
{colorScale}
limits={makeLimits($state.kind).map((x) => x.toLocaleString())}
/>
{/if}
</span>
</LayerControl>

<VectorTileSource
url={`pmtiles://${publicResourceBaseUrl()}/v1/${name}.pmtiles`}
Expand All @@ -218,7 +156,7 @@
"fill-opacity": hoverStateFilter(0.5, 0.7),
}}
layout={{
visibility: $state.kind != "" ? "visible" : "none",
visibility: $state.show ? "visible" : "none",
}}
eventsIfTopMost
manageHoverState
Expand Down Expand Up @@ -255,7 +193,7 @@
"line-width": 0.5,
}}
layout={{
visibility: $state.kind != "" ? "visible" : "none",
visibility: $state.show ? "visible" : "none",
}}
/>
</VectorTileSource>
31 changes: 14 additions & 17 deletions src/lib/browse/layers/areas/CombinedAuthorities.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script lang="ts">
import LayerControl from "../LayerControl.svelte";
import {
ColorLegend,
ExternalLink,
HelpButton,
Popup,
publicResourceBaseUrl,
} from "lib/common";
import { Checkbox } from "govuk-svelte";
import { layerId } from "lib/maplibre";
import {
FillLayer,
Expand All @@ -20,6 +19,7 @@
import { showHideLayer } from "../url";

let name = "combined_authorities";
let title = "Combined authorities";
let color = colors.combined_authorities;

let show = showHideLayer(name);
Expand All @@ -34,22 +34,19 @@
}
</script>

<Checkbox bind:checked={$show}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of boilerplate disappears

<ColorLegend {color} />
Combined authorities
<span slot="right">
<HelpButton>
<p>
Data from <ExternalLink
href="https://geoportal.statistics.gov.uk/datasets/ons::combined-authorities-december-2022-boundaries-en-buc/explore"
>
ONS Geography
</ExternalLink>, as of December 2022.
</p>
<OsOglLicense />
</HelpButton>
<LayerControl {name} {title} bind:show={$show}>
<span slot="icon"><ColorLegend {color} /></span>
<span slot="help">
<p>
Data from <ExternalLink
href="https://geoportal.statistics.gov.uk/datasets/ons::combined-authorities-december-2022-boundaries-en-buc/explore"
>
ONS Geography
</ExternalLink>, as of December 2022.
</p>
<OsOglLicense />
</span>
</Checkbox>
</LayerControl>

<GeoJSON data={`${publicResourceBaseUrl()}/v1/${name}.geojson`}>
<FillLayer
Expand Down
Loading
Loading