Add lazy initialization for petition map Update documentation with new positioning guidance Note full-width section rendering behavior
4.1 KiB
petition-map plugin
Page plugin that renders an interactive Leaflet map of petition signers across Norway's 15 fylker.
Enable
In a page's metadata.ini (must also have petition-form loaded first):
plugins = "petition-form, petition-map"
Embed in a content file (e.g. 90-form.php, between the form and the signatures list):
<?= $petition_map ?? '' ?>
The plugin renders a <section class="petition-map-section escape"> which breaks out of the content grid to fill the full page width.
Architecture
petition-form CSV
│
▼
petition-map.php (PHP, page plugin)
├── Reads CSV, extracts: first name, anonymous flag, region, confirmed status
├── Writes private cache: custom/data/petition-map-cache.json (60s TTL)
└── Writes public JSON: custom/assets/petition-map-data.json (served at /petition-map-data.json)
│
▼
petition-map.js (client-side)
├── Fetches /petition-map-data.json + /norway-fylker.geojson
├── Renders Leaflet map with CartoDB Positron tiles (muted, no labels)
├── Draws 15 fylke outlines from GeoJSON
├── Places name labels (L.tooltip permanent) randomly within each fylke polygon
└── Polls every 60s for new signers
Files
| File | Location | Purpose |
|---|---|---|
petition-map.php |
custom/plugins/page/ |
PHP plugin: cache logic, data builder, HTML widget renderer, TEMPLATE_VARS hook |
petition-map.js |
custom/assets/ |
Client JS: Leaflet map, dot placement, point-in-polygon, polling |
petition-map.css |
custom/assets/ |
Map and label styles |
norway-fylker.geojson |
custom/assets/ |
Simplified fylke boundaries (15 current fylker, from OpenStreetMap Overpass, RDP simplified to ~69KB) |
petition-map-data.json |
custom/assets/ |
Auto-generated public signer data (written by PHP, served as static asset) |
petition-map-cache.json |
custom/data/ |
Private server-side cache (same data, controls rebuild frequency) |
anon.svg |
custom/assets/ |
Mask icon for anonymous signers |
generate-mock-petitions.php |
custom/tools/ |
Dev-only CLI to generate mock CSV data |
Public JSON format
{
"generated": 1772052913,
"total": 500,
"regions": {
"oslo": {
"count": 57,
"signers": [
{"n": "Kari", "a": false},
{"n": null, "a": true}
]
}
}
}
n= first name (null if anonymous or empty)a= anonymous boolean- No emails, surnames, tokens, or IP hashes ever leave the server
Privacy rules (from CSV display column)
fullorsemi: first name shown on mapanonymous: shown as mask icon, no name- Only
confirmedstatus rows are included
Region keys
15 keys matching the petition-form select options and GeoJSON name property:
agder, akershus, buskerud, finnmark, innlandet, more_og_romsdal, nordland, oslo, rogaland, telemark, troms, trondelag, vestfold, vestland, ostfold
JS internals
- Point placement: rejection sampling within fylke polygon bounding box, ray-casting point-in-polygon test, 4% edge inset
- Label rendering:
L.tooltip({ permanent: true })bound to invisible zero-sizeL.markerin a customdotspane (z-index 450) - Lazy init: map initialises only when
#petition-mapenters the viewport (IntersectionObserver, 10% threshold); falls back to immediate init on older browsers - Initial animation: all labels shuffled (Fisher-Yates) and staggered over 6s
- Polling: fetches data JSON every 60s with cache-bust timestamp; new signers added immediately
- Fylke interaction: hover highlights polygon, click shows popup with fylke name + count
Cache invalidation
Delete custom/data/petition-map-cache.json to force a rebuild on next page load. The mock generator does this automatically. The public JSON is rewritten whenever the cache rebuilds.
Dev tools
Generate mock data (run inside the container):
podman exec stopplidelsen.no php /var/www/custom/tools/generate-mock-petitions.php
Interactive: asks for count and regional variance. Writes CSV and invalidates cache files.