Skip to content

Commit fe549e1

Browse files
authored
Merge pull request #27 from ONSdigital/app-rebuild
App rebuild DRAFT
2 parents 85d4dd6 + 00fbf47 commit fe549e1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2910
-1556
lines changed

package-lock.json

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

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
"license": "MIT",
2222
"devDependencies": {
2323
"@mapbox/tilebelt": "^2.0.2",
24-
"@onsvisual/robo-utils": "^0.3.7",
24+
"@onsvisual/robo-utils": "^0.3.9",
2525
"@onsvisual/svelte-charts": "^0.4.11",
26-
"@onsvisual/svelte-components": "^1.0.30",
26+
"@onsvisual/svelte-components": "^1.0.31",
27+
"@onsvisual/svelte-maps": "^1.2.25",
2728
"@sveltejs/adapter-netlify": "^5.2.4",
2829
"@sveltejs/adapter-node": "^5.3.3",
2930
"@sveltejs/kit": "^2.46.5",
@@ -42,7 +43,7 @@
4243
"prettier-plugin-svelte": "^3.2.6",
4344
"svelte": "^5.0.0",
4445
"svelte-check": "^4.3.4",
45-
"svelteplot": "^0.3.7",
46+
"svelteplot": "^0.7.0",
4647
"temporal-polyfill": "^0.3.0",
4748
"throttleit": "^2.1.0",
4849
"topojson-client": "^3.1.0",

scripts/generate-data.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ function indicatorToCube(indicator, t, meta_data, tableSchema, dataset_name) {
111111
source: meta_data.metadata.source,
112112
slug: manifest_metadata_indicator[0].slug,
113113
...restOfMetadata,
114-
experimentalStatistic: meta_data.experimentalStatistic,
114+
experimentalStatistic: meta_data.metadata.experimentalStatistic,
115115
geography: meta_data.metadata.geography
116116
}
117117
}
@@ -316,9 +316,26 @@ const indicators = [];
316316
for (const file of file_paths) {
317317
indicators.push(...processFile(file,excluded_indicators));
318318
}
319-
cube.link.item = indicator_slugs.map(slug => indicators.find(ind => ind.extension.slug === slug))
319+
// Sort indicators to match order in manifest (ie. taxonomy order)
320+
cube.link.item = indicator_slugs.map(slug => indicators.find(ind => ind.extension.slug === slug));
320321

321-
// console.log(cube.link.item)
322322
const output = "./src/lib/data/json-stat.json";
323323
writeFileSync(output, JSON.stringify(cube));
324-
console.log(`Wrote ${output}.`)
324+
console.log(`Wrote ${output}.`)
325+
326+
// Generate JSON file with summary stats/data
327+
const summaryData = {
328+
count: cube.link.item.length,
329+
topics: Array.from(new Set(cube.link.item.map(ds => ds.extension.topic)))
330+
.map(t => ({slug: t.replaceAll(" ", "-"), label: t[0].toUpperCase() + t.slice(1)})),
331+
years: Array.from(
332+
new Set(cube.link.item.map(ds =>
333+
Object.keys(ds.dimension.period.category.index).map(val => +val.slice(0, 4))
334+
).flat())).sort((a, b) => a - b),
335+
geoYears: Array.from(new Set(cube.link.item.map(ds => ds.extension.geography.year)))
336+
.sort((a, b) => a - b)
337+
};
338+
339+
const summaryOutput = "./src/lib/data/json-stat-summary.json";
340+
writeFileSync(summaryOutput, JSON.stringify(summaryData));
341+
console.log(`Wrote ${summaryOutput}.`);

src/lib/api/data/filterCollection.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ export default async function filterCollection(params = {}) {
6666
}
6767

6868
// Create filters for standard dimensions
69-
if (params.geo !== "all")
70-
filters.areacd = makeGeoFilter(params.geo, params.geoExtent);
69+
if (params.geo !== "all" || params.geoCluster !== "all")
70+
filters.areacd = makeGeoFilter(params.geo, params.geoExtent, params.geoCluster);
7171
if (params.time !== "all") {
7272
if (
7373
[params.time]

src/lib/api/data/helpers/dataFilters.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { geoLevels } from "$lib/config/geo-levels.js";
33
import getChildAreas from "$lib/api/geo/getChildAreas.js";
44
import hasObservation from "./hasObservation.js";
55
import { isValidMonth, isValidYear } from "$lib/api/utils.js";
6+
import readData from "$lib/data";
7+
8+
const areasClusters = await readData("areas-clusters");
69

710
export function ascending(a, b) {
811
return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
@@ -13,12 +16,12 @@ export function makeFilter(param) {
1316
return d => set.has(d[0]);
1417
}
1518

16-
export function makeGeoFilter(geo, geoExtent) {
19+
export function makeGeoFilter(geo, geoExtent, geoCluster) {
1720
const codes = new Set();
1821
const types = new Set();
1922
for (const g of [geo].flat()) {
2023
// if (g.match(/^[EKNSW]\d{2}$/)) types.add(g);
21-
if (geoLevels[g]) {
24+
if (geoLevels[g] && geoCluster === "all") {
2225
if (geoExtent.match(/^[EKNSW]\d{8}$/)) {
2326
const children = getChildAreas({code: geoExtent, geoLevel: g, includeNames: false});
2427
for (const child of children) codes.add(child);
@@ -28,6 +31,13 @@ export function makeGeoFilter(geo, geoExtent) {
2831
}
2932
else if (g.match(/^[EKNSW]\d{8}$/) && !types.has(g.slice(0, 3))) codes.add(g);
3033
}
34+
if (geoCluster) {
35+
const [grouping, cluster] = geoCluster.split("_");
36+
const cds = areasClusters.clusters?.[grouping]?.[cluster];
37+
if (Array.isArray(cds)) {
38+
for (const cd of cds) codes.add(cd);
39+
}
40+
}
3141
return codes.size > 0 && types.size > 0 ? d => codes.has(d[0]) || types.has(d[0].slice(0, 3)) :
3242
types.size > 0 ? d => types.has(d[0].slice(0, 3)) :
3343
codes.size > 0 ? d => codes.has(d[0]) :
@@ -56,11 +66,13 @@ export function getTime(values, params = {}) {
5666

5767
const periods = values.map(v => ({value: v, period: periodToDateRange(v[0])}));
5868
const nearest = params.nearest || "none";
59-
const isRange = periods[0].period.length > 1;
60-
const date = isRange || params.time.length === 10 ? toPlainDate(params.time, true) : [toPlainDate(params.time, false), toPlainDate(params.time, true)];
69+
const periodIsRange = periods[0].period.length > 1; // Time periods have a duration component
70+
const dateIsExact = params.time.length === 10; // Requested date is to nearest day
71+
const date = periodIsRange || dateIsExact ? toPlainDate(params.time, true) : [toPlainDate(params.time, false), toPlainDate(params.time, true)];
6172

6273
let match;
63-
if (isRange) match = periods.findLast(p => Temporal.PlainDate.compare(date, p.period[0]) !== -1 && Temporal.PlainDate.compare(date, p.period[1]) !== 1);
74+
if (periodIsRange) match = periods.findLast(p => Temporal.PlainDate.compare(date, p.period[0]) !== -1 && Temporal.PlainDate.compare(date, p.period[1]) !== 1);
75+
else if (dateIsExact) match = periods.findLast(p => Temporal.PlainDate.compare(date, p.period[0]) === 0);
6476
else match = periods.findLast(p => Temporal.PlainDate.compare(p.period[0], date[0]) !== -1 && Temporal.PlainDate.compare(p.period[0], date[1]) !== 1);
6577
if (match) return [match.value];
6678

src/lib/api/metadata/getIndicators.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,27 @@ function formatMetadata(ds, minimalMetadata = false, fullDims = false) {
1515
description: ds.extension.subtitle,
1616
};
1717

18-
const metadata = { label: ds.label, ...ds.extension, updated: ds.updated, caveats: ds.note };
19-
metadata.dimensions = Object.fromEntries(ds.id.map((key, i) => [key, {...formatDimension(ds, key, fullDims), order: i}]));
18+
const metadata = {
19+
label: ds.label,
20+
...ds.extension,
21+
updated: ds.updated,
22+
caveats: ds.note,
23+
};
24+
metadata.dimensions = Object.fromEntries(
25+
ds.id.map((key, i) => [
26+
key,
27+
{ ...formatDimension(ds, key, fullDims), order: i },
28+
])
29+
);
2030
return metadata;
2131
}
2232

33+
function arrayToLookup(metadata) {
34+
const lookup = {};
35+
for (const ds of metadata) lookup[ds.slug] = ds;
36+
return lookup;
37+
}
38+
2339
export default function getIndicators(params = {}) {
2440
const filter = makeDatasetFilter(
2541
params.indicator,
@@ -32,9 +48,11 @@ export default function getIndicators(params = {}) {
3248

3349
const metadata = rawMetadata.link.item
3450
.filter(filter)
35-
.map((ds) =>
36-
formatMetadata(ds, params.minimalMetadata, params.fullDims)
37-
);
51+
.map((ds) => formatMetadata(ds, params.minimalMetadata, params.fullDims));
3852

39-
return params.singleIndicator ? metadata[0] : metadata;
53+
return params.singleIndicator
54+
? metadata[0]
55+
: params.asLookup
56+
? arrayToLookup(metadata)
57+
: metadata;
4058
}
Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import getIndicators from "./getIndicators.js";
2-
import { capitalise } from "$lib/utils.js";
2+
import summaryData from "$lib/data/json-stat-summary.json";
3+
import { capitalise } from "$lib/utils.ts";
34

45
function makeItem(slug, label = null, description = null) {
5-
const item = {label: label || capitalise(slug), slug};
6-
if (description) return {...item, description};
7-
return {...item, children: {}};
6+
const item = { label: label || capitalise(slug), slug };
7+
if (description) return { ...item, description };
8+
return { ...item, children: {} };
89
}
910

1011
function nestTaxonomy(taxonomy) {
@@ -18,21 +19,25 @@ function nestTaxonomy(taxonomy) {
1819
} else {
1920
if (!topicsIndex[ind.topic].children[ind.subTopic])
2021
topicsIndex[ind.topic].children[ind.subTopic] = makeItem(ind.subTopic);
21-
topicsIndex[ind.topic].children[ind.subTopic].children[ind.slug] = indicator;
22+
topicsIndex[ind.topic].children[ind.subTopic].children[ind.slug] =
23+
indicator;
2224
}
2325
}
2426

2527
const topics = Object.values(topicsIndex);
2628
for (const topic of topics) {
2729
topic.children = Object.values(topic.children);
2830
if (topic.children[0].children) {
29-
for (const subTopic of topic.children) subTopic.children = Object.values(subTopic.children);
31+
for (const subTopic of topic.children)
32+
subTopic.children = Object.values(subTopic.children);
3033
}
3134
}
3235
return topics;
3336
}
3437

3538
export default function getTaxonomy(params = {}) {
36-
const taxonomy = getIndicators({...params, minimalMetadata: true});
37-
return params.flat ? taxonomy : nestTaxonomy(taxonomy);
39+
const taxonomy = getIndicators({ ...params, minimalMetadata: true });
40+
const meta = { count: taxonomy.length, total: summaryData.count };
41+
const data = params.flat ? taxonomy : nestTaxonomy(taxonomy);
42+
return { meta, data };
3843
}

src/lib/components/modals/AreasModal.svelte

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { ONSpalette } from "$lib/config.js";
77
88
let pageState = getContext("pageState");
9+
let mode = $derived(page.params?.code?.match(/^[EKNSW]{1}\d{8}$/) ? "area" : "indicator");
910
1011
function addArea(area) {
1112
if (!pageState.selectedAreas.find(d => d.areacd === area.areacd)) pageState.selectedAreas.push(area);
@@ -16,15 +17,20 @@
1617
}
1718
</script>
1819
19-
<Modal title="Select areas" label="Change areas">
20-
<Dropdown id="geo-level-select" label="Geography type" options={page.data.geoLevels} bind:value={pageState.selectedGeoLevel}/>
20+
<Modal title="Select areas" label="Change areas" icon="pin">
21+
{#if mode === "indicator"}
22+
<Dropdown id="geo-level-select" label="Geography type" options={page.data.geoLevels} bind:value={pageState.selectedGeoLevel}/>
23+
{/if}
24+
{#if mode === "area"}
25+
<Dropdown id="geo-related-select" label="Geography group" options={page.data.geoGroups} bind:value={pageState.selectedGeoGroup}/>
26+
{/if}
2127
<div class="select-container">
22-
<Select id="area-select" label="Individual areas" placeholder="Choose one or more" options={page.data.areas} labelKey="areanm" on:change={(e) => addArea(e.detail)} autoClear/>
28+
<Select id="area-select" label={mode === "area" ? "Comparison areas" : "Individual areas"} placeholder="Choose one or more" options={page.data.areas} labelKey="areanm" on:change={(e) => addArea(e.detail)} autoClear/>
2329
</div>
2430
{#each pageState.selectedAreas as area, i}
2531
<Button
2632
icon="cross"
27-
color={ONSpalette[i] || "darkgrey"}
33+
color={(mode === "area" ? ONSpalette[i + 1] : ONSpalette[i]) || "darkgrey"}
2834
small
2935
on:click={() => removeArea(area)}>{area.areanm}</Button>
3036
{/each}
@@ -38,6 +44,7 @@
3844
margin: .5em .5em 0 0;
3945
}
4046
.select-container {
47+
margin-top: 1em;
4148
width: 22.5rem;
4249
max-width: 100%;
4350
}

src/lib/components/modals/Modal.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<script>
22
import { Button } from "@onsvisual/svelte-components";
33
4-
let { title, label, children } = $props();
4+
let { title, label, icon = null, children } = $props();
55
66
let id = $derived(title.toLowerCase().replaceAll(" ", "-"));
77
let dialog = $state();
88
</script>
99

10-
<Button variant="secondary" small on:click={() => dialog.showModal()}>{label}</Button>
10+
<Button variant="secondary" {icon} small on:click={() => dialog.showModal()}>{label}</Button>
1111

1212
<dialog aria-labelledby="{id}" bind:this={dialog}>
1313
<h1 id="{id}" tabindex="-1">{title}</h1>

src/lib/components/modals/OptionsModal.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
88
let pageState = getContext("pageState");
99
10-
let formatTick = $derived(pageState.formatPeriod());
10+
let formatTick = $derived(pageState?.formatPeriod?.() || ((d) => d));
1111
</script>
1212
13-
<Modal title="Chart options" label="Chart options">
13+
<Modal title="Chart options" label="Chart options" icon="cog">
1414
<RangeSlider
1515
label="Selected time range"
1616
options={page.data.periods}

0 commit comments

Comments
 (0)