Skip to content

Commit d10d841

Browse files
committed
Removed hard coding and added incomplete paint function
1 parent e24f68c commit d10d841

File tree

3 files changed

+146
-25
lines changed

3 files changed

+146
-25
lines changed

src/lib/components/data-vis/map/Map.svelte

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
filterGeo,
2525
jenksBreaks,
2626
quantileBreaks,
27+
createPaintObjectFromMetric,
28+
extractVectorMetricValues,
2729
} from "./mapUtils.js";
2830
import NonStandardControls from "./NonStandardControls.svelte";
2931
import { replaceState } from "$app/navigation";
@@ -84,6 +86,11 @@
8486
geoSource = "file",
8587
tileSource = "http://localhost:8080/{z}/{x}/{y}.pbf",
8688
paintObject,
89+
geojsonPromoteId = "areanm",
90+
vectorMetricProperty = "Index of Multiple Deprivation (IMD) Rank",
91+
vectorLayerName = "LSOA",
92+
borderColor = "#003300",
93+
labelSourceLayer = "place",
8794
}: {
8895
data: object[];
8996
paintObject?: object;
@@ -130,8 +137,18 @@
130137
onidle?: (e: maplibregl.MapLibreEvent) => void;
131138
geoSource: "file" | "tiles" | "none";
132139
tileSource?: string;
140+
geojsonPromoteId?: string;
141+
vectorMetricProperty?: string;
142+
vectorLayerName?: string;
143+
borderColor?: string;
144+
labelSourceLayer?: string;
145+
usingExternalData: false;
133146
} = $props();
134147
148+
let usingExternalData = false;
149+
const tileSourceId = "lsoas";
150+
const promoteProperty = "LSOA21NM";
151+
135152
let styleLookup = {
136153
"Carto-light":
137154
"https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
@@ -163,6 +180,7 @@
163180
);
164181
165182
let filteredGeoJsonData = $derived(filterGeo(geojsonData, year));
183+
$inspect(breakCount);
166184
167185
let fillColors: string[] = $derived(
168186
setCustomPalette == true
@@ -172,14 +190,54 @@
172190
173191
let tooFewColors = $derived(fillColors.length < breakCount);
174192
193+
let paint = $derived(() => {
194+
if (!map || !loaded) return;
195+
196+
const values = extractVectorMetricValues(
197+
map,
198+
vectorLayerName,
199+
vectorMetricProperty,
200+
);
201+
202+
if (!values || values.length === 0) return;
203+
204+
let breaks: number[];
205+
206+
if (breaksType === "quantile") {
207+
breaks = quantileBreaks(values, breakCount);
208+
} else if (breaksType === "jenks") {
209+
breaks = jenksBreaks(values, breakCount);
210+
} else {
211+
const min = Math.min(...values);
212+
const max = Math.max(...values);
213+
const step = (max - min) / breakCount;
214+
breaks = Array.from(
215+
{ length: breakCount },
216+
(_, i) => min + step * (i + 1),
217+
);
218+
}
219+
220+
const paint = createPaintObjectFromMetric(
221+
vectorMetricProperty,
222+
breaks,
223+
fillColors,
224+
fillOpacity,
225+
);
226+
227+
map.setPaintProperty(vectorLayerName, "fill-color", paint["fill-color"]);
228+
map.setPaintProperty(
229+
vectorLayerName,
230+
"fill-opacity",
231+
paint["fill-opacity"],
232+
);
233+
});
234+
175235
$effect(() => {
176236
if (tooFewColors) {
177237
console.warn("Too few colours for the number of breaks");
178238
}
179239
});
180240
181-
let borderColor = "#003300";
182-
183241
let map: maplibregl.Map | undefined = $state();
184242
185243
let loaded = $state(false);
@@ -265,6 +323,16 @@
265323
? quantileBreaks(vals, breakCount)
266324
: customBreaks,
267325
);
326+
let vectorPaintObject = $derived(
327+
usingExternalData
328+
? createPaintObjectFromMetric(metric, breaks, fillColors, fillOpacity)
329+
: createPaintObjectFromMetric(
330+
vectorMetricProperty,
331+
breaks,
332+
fillColors,
333+
fillOpacity,
334+
),
335+
);
268336
269337
let dataWithColor = $derived(
270338
filteredMapData.map((d) => {
@@ -385,7 +453,7 @@
385453
<ScaleControl position={scaleControlPosition} unit={scaleControlUnit} />
386454
{/if}
387455
{#if geoSource == "file"}
388-
<GeoJSON id="areas" data={merged} promoteId="areanm">
456+
<GeoJSON id="areas" data={merged} promoteId={geojsonPromoteId}>
389457
<FillLayer
390458
paint={{
391459
"fill-color": ["coalesce", ["get", "color"], "lightgrey"],
@@ -423,27 +491,22 @@
423491
</GeoJSON>
424492
{:else if geoSource == "tiles"}
425493
<VectorTileSource
426-
id={"lsoas"}
427-
promoteId={"LSOA21NM"}
494+
id={tileSourceId}
495+
promoteId={promoteProperty}
428496
tiles={[tileSource]}
429497
>
430498
<FillLayer
431-
paint={paintObject}
432-
sourceLayer={"LSOA"}
433-
onclick={interactive
434-
? (e) => {
435-
console.log(e);
436-
return zoomToArea(e);
437-
}
438-
: undefined}
499+
paint={vectorPaintObject}
500+
sourceLayer={vectorLayerName}
501+
onclick={interactive ? zoomToArea : undefined}
439502
onmousemove={interactive
440503
? (e) => {
441-
hoveredArea = e.features[0].id;
442-
hoveredAreaData =
443-
e.features[0].properties[
444-
"Index of Multiple Deprivation (IMD) Rank"
445-
];
446-
currentMousePosition = e.event.point;
504+
if (e.features?.[0]) {
505+
hoveredArea = e.features[0].id;
506+
hoveredAreaData =
507+
e.features[0].properties[vectorMetricProperty];
508+
currentMousePosition = e.event.point;
509+
}
447510
}
448511
: undefined}
449512
onmouseleave={interactive
@@ -452,7 +515,7 @@
452515
hoveredAreaData = null;
453516
}
454517
: undefined}
455-
></FillLayer>
518+
/>
456519
{#if showBorder}
457520
<LineLayer
458521
layout={{ "line-cap": "round", "line-join": "round" }}
@@ -466,7 +529,7 @@
466529
),
467530
}}
468531
beforeLayerType="symbol"
469-
sourceLayer={"LSOA"}
532+
sourceLayer={vectorLayerName}
470533
/>
471534
{/if}
472535
</VectorTileSource>

src/lib/components/data-vis/map/mapUtils.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,72 @@ export function quantileBreaks(data: number[], numBreaks: number): number[] {
105105

106106
let len = data.length;
107107

108-
let breaks: number[] = [
109-
];
108+
let breaks: number[] = [];
110109
for (let i = 0; i < numBreaks; i++) {
111110
breaks.push(data[Math.floor(len * (i * (1 / numBreaks)))]);
112111
}
113112
breaks.push(data[len - 1]);
114113

115114
return breaks;
116115
}
116+
export function createPaintObjectFromMetric(
117+
metricProperty: string,
118+
breaks: (string | number)[],
119+
fillColors: string[],
120+
fillOpacity: number = 0.4,
121+
): object {
122+
const usableLength = Math.min(breaks.length, fillColors.length);
123+
124+
function parseNumberWithCommas(value: string | number): number {
125+
if (typeof value === "number") return value;
126+
const cleaned = value.toString().replace(/,/g, "").trim();
127+
const parsed = Number(cleaned);
128+
return isNaN(parsed) ? 0 : parsed;
129+
}
130+
131+
const matchExpression: [string, any, ...any[]] = [
132+
"step",
133+
["to-number", ["get", metricProperty]],
134+
"rgba(0,0,0,0)", // Default color
135+
];
136+
137+
for (let i = 0; i < usableLength; i++) {
138+
const breakValue = parseNumberWithCommas(breaks[i]);
139+
matchExpression.push(breakValue, fillColors[i]);
140+
}
141+
142+
return {
143+
"fill-color": matchExpression,
144+
"fill-opacity": fillOpacity,
145+
};
146+
}
117147

118-
148+
export function extractVectorMetricValues(
149+
map: maplibregl.Map,
150+
layerId: string,
151+
metricProperty: string,
152+
): number[] {
153+
if (!map || !map.isStyleLoaded()) return [];
154+
155+
const features = map.queryRenderedFeatures({ layers: [layerId] });
156+
const seen = new Set();
157+
158+
return features
159+
.filter((f) => {
160+
const id = f.id;
161+
if (!id || seen.has(id)) return false;
162+
seen.add(id);
163+
return true;
164+
})
165+
.map((f) => {
166+
const raw = f.properties?.[metricProperty];
167+
if (typeof raw === "string") {
168+
const cleaned = raw.replace(/,/g, "");
169+
return parseFloat(cleaned);
170+
} else if (typeof raw === "number") {
171+
return raw;
172+
}
173+
return NaN;
174+
})
175+
.filter((v) => !isNaN(v));
176+
}

src/wrappers/components/data-vis/map/MapWrapper.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@
486486
"fill-opacity": 0.4,
487487
},
488488
},
489-
{ name: "addData", isProp: true, category: "Data", value: true },
489+
{ name: "addedDataURL", isProp: true, category: "Data", value: "" },
490490
{
491491
name: "year",
492492
isProp: true,

0 commit comments

Comments
 (0)