diff --git a/plugin-designs/doxa-research-map/research-map.vue b/plugin-designs/doxa-research-map/research-map.vue index 95a107aa1..1532dcac8 100644 --- a/plugin-designs/doxa-research-map/research-map.vue +++ b/plugin-designs/doxa-research-map/research-map.vue @@ -1111,6 +1111,26 @@ function _buildFlatLegendMatchExpr(node, legendType) { const REGION_IDLE_FILL = ['case', ['has', 'iso_3166_1_alpha_3'], 0.7, 0.7] const REGION_IDLE_COLOR = ['case', ['has', 'iso_3166_1_alpha_3'], 'rgba(60,60,80,0.35)', 'rgba(60,60,80,0.35)'] const REGION_IDLE_WIDTH = ['case', ['has', 'iso_3166_1_alpha_3'], 0.6, 0.6] +// Make the region paints data-driven WITHOUT changing what they show: each property is read back and +// re-written wrapped in a case whose two branches are the value it already had. A property the layer +// does not define is skipped rather than invented, and one already wrapped is left alone. +function prewarmRegionPaint(m) { + if (!m || !m.getLayer('regions-fill')) return + const wrap = (v) => ['case', ['has', 'iso_3166_1_alpha_3'], v, v] + const warm = (layer, prop) => { + if (!m.getLayer(layer)) return + const cur = m.getPaintProperty(layer, prop) + if (cur === undefined || cur === null) return + if (Array.isArray(cur) && cur[0] === 'case') return + m.setPaintProperty(layer, prop, wrap(cur)) + } + try { + warm('regions-fill', 'fill-opacity') + warm('regions-border', 'line-color') + warm('regions-border', 'line-width') + } catch (_) { /* style still loading - the first real paint carries it */ } +} + function paintRegionScope(m, isoCodes) { if (!m || !m.getLayer('regions-fill')) return // ★ CANONICAL FIRST, OR THE COMPARE BELOW IS A LIE (rega, L43: still 9 writes per click, not 3). @@ -1618,9 +1638,20 @@ async function ensureRegionsLoaded() { // ★ PRE-WARM HERE, which is the only moment that helps. These layers are added on first visit // to the Regions tab, and rega measured the first ROW click paying 5.5 s (MapLibre) / 11.1 s // (Mapbox) of shader compile because that click was when their paint first became data-driven. - // Painting the idle state through paintRegionScope makes it data-driven from the first draw, so - // the variant compiles here — where a 6.4 MB GeoJSON is already loading — instead of mid-gesture. - paintRegionScope(map.value, []) + // Making the paint data-driven at layer-add moves that compile here, where a 6.4 MB GeoJSON is + // already loading, instead of into the middle of a gesture. + // + // WARNING IT WRAPS WHAT THE LAYER ALREADY PAINTS, and does NOT paint the cleared state. The + // first version called paintRegionScope(map.value, []) — the CLEARED look, flat grey borders at + // width 0.6. addRegionsLayer does not start there: it sets line-color to the region-coloured + // match expression and line-width 1 (useMapLayers). So pre-warming with the cleared values + // silently repainted every border grey and thinner the moment the layer appeared — a visible + // change nobody asked for, and invisible to both things being measured at the time, a timing + // check and a write count. + // Reading each property back and re-writing it wrapped in an identical-branch case keeps the + // appearance exactly as the layer defined it while still making the property data-driven, which + // is all the shader variant depends on. + prewarmRegionPaint(map.value) // Hide regions on non-Doxa-Regions tabs so they don't show through. if (map.value.getLayer('regions-fill') && activeTab.value?.id !== 'doxa-regions') { map.value.setLayoutProperty('regions-fill', 'visibility', 'none')