@rcpch/imd-map 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.esm.js +104 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/umd/rcpch-imd-map.min.js +41 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -271,6 +271,10 @@ createImdMap({
|
|
|
271
271
|
nationLabel: 'Nation',
|
|
272
272
|
patientLabel: 'Patient',
|
|
273
273
|
leadCentreLabel: 'Lead centre',
|
|
274
|
+
areaTooltipText:
|
|
275
|
+
'<strong>{{areaName}}</strong><br/>' +
|
|
276
|
+
'<span>{{decileLabel}}: {{imdDecile}}</span><br/>' +
|
|
277
|
+
'<span>{{nationLabel}}: {{nation}}</span>',
|
|
274
278
|
patientTooltipText: '{{patientLabel}}',
|
|
275
279
|
leadCentreTooltipText: '{{leadCentreLabel}}: {{label}}',
|
|
276
280
|
},
|
|
@@ -280,7 +284,7 @@ createImdMap({
|
|
|
280
284
|
|
|
281
285
|
### Tooltip templates
|
|
282
286
|
|
|
283
|
-
`patientTooltipText
|
|
287
|
+
`areaTooltipText`, `patientTooltipText`, and `leadCentreTooltipText` support `{{token}}` interpolation.
|
|
284
288
|
|
|
285
289
|
If you are writing inline JavaScript inside a Django template, Django will try to
|
|
286
290
|
evaluate `{{...}}` first. Use one of these patterns so the map library still
|
|
@@ -321,6 +325,29 @@ patientTooltipText: 'Group: {{group}}'
|
|
|
321
325
|
| `{{leadCentreLabel}}` | The `leadCentreLabel` style option (default `"Lead centre"`) |
|
|
322
326
|
| `{{label}}` | The `label` field from `setLeadCentre({ label, lat, lon })` |
|
|
323
327
|
|
|
328
|
+
**Area tokens** (`areaTooltipText`):
|
|
329
|
+
|
|
330
|
+
| Token | Value |
|
|
331
|
+
|---|---|
|
|
332
|
+
| `{{areaCode}}` | Area code from tile `code` |
|
|
333
|
+
| `{{areaName}}` | Area name from tile `area_name` |
|
|
334
|
+
| `{{areaType}}` | Area type from tile `area_type` (fallback `LSOA`) |
|
|
335
|
+
| `{{nation}}` | Nation from tile `nation` |
|
|
336
|
+
| `{{imdDecile}}` | IMD decile from tile `imd_decile` |
|
|
337
|
+
| `{{imdYear}}` | IMD publication year from tile `imd_year` |
|
|
338
|
+
| `{{boundaryYear}}` | Boundary year from tile `year` |
|
|
339
|
+
| `{{laCode}}` | Local authority code from tile `la_code` |
|
|
340
|
+
| `{{laName}}` | Local authority name from tile `la_name` |
|
|
341
|
+
| `{{laYear}}` | Local authority year from tile `la_year` |
|
|
342
|
+
| `{{nhserCode}}` | NHS England region code from tile `nhser_code` |
|
|
343
|
+
| `{{nhserName}}` | NHS England region name from tile `nhser_name` |
|
|
344
|
+
| `{{icbCode}}` | ICB code from tile `icb_code` |
|
|
345
|
+
| `{{icbName}}` | ICB name from tile `icb_name` |
|
|
346
|
+
| `{{lhbCode}}` | Local health board code from tile `lhb_code` |
|
|
347
|
+
| `{{lhbName}}` | Local health board name from tile `lhb_name` |
|
|
348
|
+
| `{{decileLabel}}` | The `decileLabel` style option |
|
|
349
|
+
| `{{nationLabel}}` | The `nationLabel` style option |
|
|
350
|
+
|
|
324
351
|
Style can also be updated at runtime:
|
|
325
352
|
|
|
326
353
|
```js
|
|
@@ -353,11 +380,34 @@ map.setStyle({ tooltip: { areaLabel: 'Local area' } });
|
|
|
353
380
|
| `center` | `[lon, lat]` | UK center | Initial map center |
|
|
354
381
|
| `zoom` | `number` | `5` | Initial zoom level |
|
|
355
382
|
| `style` | `MapStyleOptions` | RCPCH defaults | Visual style overrides |
|
|
383
|
+
| `areaTooltipMode` | `'default' \| 'template' \| 'none'` | `'default'` | Built-in area tooltip, template tooltip, or no built-in area popup |
|
|
356
384
|
| `onViewChange` | `function` | — | Called when nation or era changes |
|
|
357
|
-
| `onAreaHover` | `function` | — | Called on choropleth feature hover |
|
|
385
|
+
| `onAreaHover` | `function` | — | Called on choropleth feature hover (includes pointer `lngLat`) |
|
|
358
386
|
| `onAreaClick` | `function` | — | Called on choropleth feature click |
|
|
359
387
|
| `onWarning` | `function` | — | Called for non-fatal issues |
|
|
360
388
|
|
|
389
|
+
### Area tooltip mode
|
|
390
|
+
|
|
391
|
+
```js
|
|
392
|
+
createImdMap({
|
|
393
|
+
container: 'map',
|
|
394
|
+
tilesBaseUrl: '...',
|
|
395
|
+
areaTooltipMode: 'template',
|
|
396
|
+
style: {
|
|
397
|
+
tooltip: {
|
|
398
|
+
areaTooltipText:
|
|
399
|
+
'<strong>{{areaName}}</strong><br/>' +
|
|
400
|
+
'<span>{{decileLabel}}: {{imdDecile}}</span><br/>' +
|
|
401
|
+
'<span>{{nationLabel}}: {{nation}}</span>',
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
- `default`: current built-in area tooltip rows.
|
|
408
|
+
- `template`: render `style.tooltip.areaTooltipText` with token interpolation.
|
|
409
|
+
- `none`: no built-in area popup; `onAreaHover`/`onAreaClick` still fire for fully external tooltips.
|
|
410
|
+
|
|
361
411
|
### Instance methods
|
|
362
412
|
|
|
363
413
|
| Method | Description |
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,11 @@ interface TooltipStyleOptions {
|
|
|
58
58
|
patientLabel?: string;
|
|
59
59
|
/** Label used in lead-centre hover tooltip. Default: "Lead centre". */
|
|
60
60
|
leadCentreLabel?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Area tooltip content template.
|
|
63
|
+
* Supports token interpolation, e.g. "{{areaName}}", "{{imdDecile}}", "{{nation}}".
|
|
64
|
+
*/
|
|
65
|
+
areaTooltipText?: string;
|
|
61
66
|
/**
|
|
62
67
|
* Patient tooltip content template.
|
|
63
68
|
* Supports token interpolation, e.g. "{{patientLabel}}" or "{{id}}".
|
|
@@ -105,10 +110,28 @@ interface ImdMapState {
|
|
|
105
110
|
};
|
|
106
111
|
}
|
|
107
112
|
interface AreaHoverPayload {
|
|
113
|
+
areaCode: string | undefined;
|
|
114
|
+
areaName: string | undefined;
|
|
115
|
+
areaType: string | undefined;
|
|
116
|
+
nation: string | undefined;
|
|
117
|
+
imdDecile: number | undefined;
|
|
118
|
+
imdYear: number | undefined;
|
|
119
|
+
boundaryYear: number | undefined;
|
|
120
|
+
laCode: string | undefined;
|
|
121
|
+
laName: string | undefined;
|
|
122
|
+
laYear: number | undefined;
|
|
123
|
+
nhserCode: string | undefined;
|
|
124
|
+
nhserName: string | undefined;
|
|
125
|
+
icbCode: string | undefined;
|
|
126
|
+
icbName: string | undefined;
|
|
127
|
+
lhbCode: string | undefined;
|
|
128
|
+
lhbName: string | undefined;
|
|
129
|
+
lngLat: {
|
|
130
|
+
lng: number;
|
|
131
|
+
lat: number;
|
|
132
|
+
};
|
|
108
133
|
lsoaCode: string | undefined;
|
|
109
134
|
lsoaName: string | undefined;
|
|
110
|
-
imdDecile: number | undefined;
|
|
111
|
-
nation: string | undefined;
|
|
112
135
|
rawProperties: Record<string, unknown>;
|
|
113
136
|
}
|
|
114
137
|
type AreaClickPayload = AreaHoverPayload;
|
|
@@ -179,6 +202,13 @@ interface CreateImdMapOptions {
|
|
|
179
202
|
center?: [number, number];
|
|
180
203
|
zoom?: number;
|
|
181
204
|
style?: MapStyleOptions;
|
|
205
|
+
/**
|
|
206
|
+
* Area tooltip rendering mode.
|
|
207
|
+
* - default: built-in tooltip content
|
|
208
|
+
* - template: uses style.tooltip.areaTooltipText token interpolation
|
|
209
|
+
* - none: disables built-in area popup (callbacks still fire)
|
|
210
|
+
*/
|
|
211
|
+
areaTooltipMode?: 'default' | 'template' | 'none';
|
|
182
212
|
onViewChange?: (view: {
|
|
183
213
|
nation: Nation;
|
|
184
214
|
era: Era;
|
package/dist/index.esm.js
CHANGED
|
@@ -23,7 +23,8 @@ function willEraBeOverridden(nation, requestedEra) {
|
|
|
23
23
|
var ZOOM_TIERS = [
|
|
24
24
|
{ tier: "z0_4", minzoom: 0, maxzoom: 5 },
|
|
25
25
|
{ tier: "z5_7", minzoom: 5, maxzoom: 8 },
|
|
26
|
-
{ tier: "z8_10", minzoom: 8, maxzoom:
|
|
26
|
+
{ tier: "z8_10", minzoom: 8, maxzoom: 11 },
|
|
27
|
+
{ tier: "z11_14", minzoom: 11, maxzoom: 24 }
|
|
27
28
|
];
|
|
28
29
|
function resolveFullTableName(effectiveEra, tier) {
|
|
29
30
|
return `public.uk_master_${effectiveEra}_${tier}`;
|
|
@@ -272,6 +273,7 @@ var DEFAULT_STYLE = {
|
|
|
272
273
|
nationLabel: "Nation",
|
|
273
274
|
patientLabel: "Patient",
|
|
274
275
|
leadCentreLabel: "Lead centre",
|
|
276
|
+
areaTooltipText: '<strong style="display:block;margin-bottom:2px;">{{areaName}}</strong><span>LSOA year: {{boundaryYear}}</span><br/><span>{{decileLabel}}: <strong>{{imdDecile}}</strong></span><br/><span>IMD year: {{imdYear}}</span><br/><span>{{nationLabel}}: {{nation}}</span>',
|
|
275
277
|
patientTooltipText: "{{patientLabel}}",
|
|
276
278
|
leadCentreTooltipText: "{{leadCentreLabel}}: {{label}}"
|
|
277
279
|
},
|
|
@@ -564,6 +566,61 @@ function buildChoroplethTooltipHtml(properties, style) {
|
|
|
564
566
|
<span>${t.nationLabel ?? "Nation"}: ${String(nation)}</span>
|
|
565
567
|
</div>`;
|
|
566
568
|
}
|
|
569
|
+
function buildChoroplethTooltipHtmlFromTemplate(properties, style) {
|
|
570
|
+
const t = style.tooltip;
|
|
571
|
+
const bg = t.backgroundColor ?? "#0d0d58";
|
|
572
|
+
const color = t.textColor ?? "#ffffff";
|
|
573
|
+
const radius = t.borderRadius ?? 4;
|
|
574
|
+
const areaCode = getFeatureProperty(properties, "code");
|
|
575
|
+
const areaName = getFeatureProperty(properties, "area_name") ?? "Unknown area";
|
|
576
|
+
const areaType = getFeatureProperty(properties, "area_type") ?? "LSOA";
|
|
577
|
+
const nation = getFeatureProperty(properties, "nation") ?? "\u2013";
|
|
578
|
+
const imdDecile = getFeatureProperty(properties, "imd_decile") ?? "\u2013";
|
|
579
|
+
const imdYear = getFeatureProperty(properties, "imd_year") ?? "\u2013";
|
|
580
|
+
const boundaryYear = getFeatureProperty(properties, "year") ?? "\u2013";
|
|
581
|
+
const laCode = getFeatureProperty(properties, "la_code") ?? "";
|
|
582
|
+
const laName = getFeatureProperty(properties, "la_name") ?? "";
|
|
583
|
+
const laYear = getFeatureProperty(properties, "la_year") ?? "";
|
|
584
|
+
const nhserCode = getFeatureProperty(properties, "nhser_code") ?? "";
|
|
585
|
+
const nhserName = getFeatureProperty(properties, "nhser_name") ?? "";
|
|
586
|
+
const icbCode = getFeatureProperty(properties, "icb_code") ?? "";
|
|
587
|
+
const icbName = getFeatureProperty(properties, "icb_name") ?? "";
|
|
588
|
+
const lhbCode = getFeatureProperty(properties, "lhb_code") ?? "";
|
|
589
|
+
const lhbName = getFeatureProperty(properties, "lhb_name") ?? "";
|
|
590
|
+
const template = t.areaTooltipText ?? "<strong>{{areaName}}</strong><br/><span>{{decileLabel}}: {{imdDecile}}</span>";
|
|
591
|
+
const text = interpolateTemplate(template, {
|
|
592
|
+
...properties ?? {},
|
|
593
|
+
areaCode,
|
|
594
|
+
areaName,
|
|
595
|
+
areaType,
|
|
596
|
+
nation,
|
|
597
|
+
imdDecile,
|
|
598
|
+
imdYear,
|
|
599
|
+
boundaryYear,
|
|
600
|
+
laCode,
|
|
601
|
+
laName,
|
|
602
|
+
laYear,
|
|
603
|
+
nhserCode,
|
|
604
|
+
nhserName,
|
|
605
|
+
icbCode,
|
|
606
|
+
icbName,
|
|
607
|
+
lhbCode,
|
|
608
|
+
lhbName,
|
|
609
|
+
decileLabel: t.decileLabel ?? "IMD decile",
|
|
610
|
+
nationLabel: t.nationLabel ?? "Nation"
|
|
611
|
+
});
|
|
612
|
+
return `<div style="background:${bg};color:${color};padding:8px 12px;border-radius:${radius}px;font-size:13px;line-height:1.6;font-family:sans-serif;">
|
|
613
|
+
<span>${text}</span>
|
|
614
|
+
</div>`;
|
|
615
|
+
}
|
|
616
|
+
function toOptionalNumber(value) {
|
|
617
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
618
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
619
|
+
const parsed = Number(value);
|
|
620
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
621
|
+
}
|
|
622
|
+
return void 0;
|
|
623
|
+
}
|
|
567
624
|
function buildPatientTooltipHtml(properties, style) {
|
|
568
625
|
const t = style.tooltip;
|
|
569
626
|
const id = getFeatureProperty(properties, "id") ?? "";
|
|
@@ -602,14 +659,44 @@ function buildLeadCentreTooltipHtml(properties, style) {
|
|
|
602
659
|
<span>${text}</span>
|
|
603
660
|
</div>`;
|
|
604
661
|
}
|
|
605
|
-
function featureToPayload(feature) {
|
|
662
|
+
function featureToPayload(feature, lngLat) {
|
|
606
663
|
const props = feature.properties ?? {};
|
|
607
|
-
const
|
|
664
|
+
const areaCode = getFeatureProperty(props, "code");
|
|
665
|
+
const areaName = getFeatureProperty(props, "area_name");
|
|
666
|
+
const areaType = getFeatureProperty(props, "area_type");
|
|
667
|
+
const nation = getFeatureProperty(props, "nation");
|
|
668
|
+
const imdDecile = toOptionalNumber(getFeatureProperty(props, "imd_decile"));
|
|
669
|
+
const imdYear = toOptionalNumber(getFeatureProperty(props, "imd_year"));
|
|
670
|
+
const boundaryYear = toOptionalNumber(getFeatureProperty(props, "year"));
|
|
671
|
+
const laCode = getFeatureProperty(props, "la_code");
|
|
672
|
+
const laName = getFeatureProperty(props, "la_name");
|
|
673
|
+
const laYear = toOptionalNumber(getFeatureProperty(props, "la_year"));
|
|
674
|
+
const nhserCode = getFeatureProperty(props, "nhser_code");
|
|
675
|
+
const nhserName = getFeatureProperty(props, "nhser_name");
|
|
676
|
+
const icbCode = getFeatureProperty(props, "icb_code");
|
|
677
|
+
const icbName = getFeatureProperty(props, "icb_name");
|
|
678
|
+
const lhbCode = getFeatureProperty(props, "lhb_code");
|
|
679
|
+
const lhbName = getFeatureProperty(props, "lhb_name");
|
|
608
680
|
return {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
nation:
|
|
681
|
+
areaCode: areaCode === void 0 || areaCode === null ? void 0 : String(areaCode),
|
|
682
|
+
areaName: areaName === void 0 || areaName === null ? void 0 : String(areaName),
|
|
683
|
+
areaType: areaType === void 0 || areaType === null ? void 0 : String(areaType),
|
|
684
|
+
nation: nation === void 0 || nation === null ? void 0 : String(nation),
|
|
685
|
+
imdDecile,
|
|
686
|
+
imdYear,
|
|
687
|
+
boundaryYear,
|
|
688
|
+
laCode: laCode === void 0 || laCode === null ? void 0 : String(laCode),
|
|
689
|
+
laName: laName === void 0 || laName === null ? void 0 : String(laName),
|
|
690
|
+
laYear,
|
|
691
|
+
nhserCode: nhserCode === void 0 || nhserCode === null ? void 0 : String(nhserCode),
|
|
692
|
+
nhserName: nhserName === void 0 || nhserName === null ? void 0 : String(nhserName),
|
|
693
|
+
icbCode: icbCode === void 0 || icbCode === null ? void 0 : String(icbCode),
|
|
694
|
+
icbName: icbName === void 0 || icbName === null ? void 0 : String(icbName),
|
|
695
|
+
lhbCode: lhbCode === void 0 || lhbCode === null ? void 0 : String(lhbCode),
|
|
696
|
+
lhbName: lhbName === void 0 || lhbName === null ? void 0 : String(lhbName),
|
|
697
|
+
lngLat,
|
|
698
|
+
lsoaCode: areaCode === void 0 || areaCode === null ? void 0 : String(areaCode),
|
|
699
|
+
lsoaName: areaName === void 0 || areaName === null ? void 0 : String(areaName),
|
|
613
700
|
rawProperties: props
|
|
614
701
|
};
|
|
615
702
|
}
|
|
@@ -621,8 +708,15 @@ function attachChoroplethInteraction(map, popup, style, options) {
|
|
|
621
708
|
if (!features.length) return;
|
|
622
709
|
map.getCanvas().style.cursor = "pointer";
|
|
623
710
|
const feature = features[0];
|
|
624
|
-
|
|
625
|
-
|
|
711
|
+
const areaTooltipMode = options.areaTooltipMode ?? "default";
|
|
712
|
+
if (areaTooltipMode !== "none") {
|
|
713
|
+
const html = areaTooltipMode === "template" ? buildChoroplethTooltipHtmlFromTemplate(
|
|
714
|
+
feature.properties,
|
|
715
|
+
style
|
|
716
|
+
) : buildChoroplethTooltipHtml(feature.properties, style);
|
|
717
|
+
popup.setLngLat(e.lngLat).setHTML(html).addTo(map);
|
|
718
|
+
}
|
|
719
|
+
options.onAreaHover?.(featureToPayload(feature, { lng: e.lngLat.lng, lat: e.lngLat.lat }));
|
|
626
720
|
});
|
|
627
721
|
map.on("mouseleave", layerId, () => {
|
|
628
722
|
map.getCanvas().style.cursor = "";
|
|
@@ -631,7 +725,7 @@ function attachChoroplethInteraction(map, popup, style, options) {
|
|
|
631
725
|
map.on("click", layerId, (e) => {
|
|
632
726
|
const features = map.queryRenderedFeatures(e.point, { layers: [layerId] });
|
|
633
727
|
if (!features.length) return;
|
|
634
|
-
options.onAreaClick?.(featureToPayload(features[0]));
|
|
728
|
+
options.onAreaClick?.(featureToPayload(features[0], { lng: e.lngLat.lng, lat: e.lngLat.lat }));
|
|
635
729
|
});
|
|
636
730
|
}
|
|
637
731
|
}
|