Fit against the box the chart actually has, and make the cross read as one

Raising the padding fixed seven layouts and left the eighth exactly as it was,
which was the clue: padding cannot rescue a fit aimed at a box of the wrong
shape. That layout is short and wide, and the fit ran against a stale
measurement, so it resolved to the width and clipped the cloud top and bottom.

The engine is now told to re-read its container before the fit, the fit runs on
the next frame so the flex layout can finish, and it runs once more when the map
settles for a container still resolving on that frame. None of it depends on a
timer.

The cross was drawing as a plus. The upper arm was two thirds of the lower, which
is a real difference and far too small to see at this size. Latin proportions
now: the lower arm is three times the upper and the bar is narrower than the
figure is tall, so the shape is legible as what it is at the size it is actually
drawn rather than only in the geometry.
This commit is contained in:
Doxa Maps 2026-09-28 21:00:15 +00:00
parent 932727be76
commit 5681f9db22
13 changed files with 71 additions and 52 deletions

View file

@ -102,19 +102,26 @@ onMounted(async () => {
console.warn('[gospel-internet] the centre cross could not be drawn:', e && e.message || e)
}
// ★ FIT THE WHOLE TARGET ON LOAD. The default zoom clipped the outer rings top and bottom —
// the reader met a drawing already cut off, which reads as a rendering fault rather than as
// something to zoom out of. Fitted from the projected extent, so it follows the chart's own
// size rather than a guessed zoom.
try {
const [w, sLat] = project(-SPREAD, -SPREAD)
const [e, nLat] = project(SPREAD, SPREAD)
// Padding is generous and symmetric because the fit must hold the LABELS too, not just the
// points: at 1280 the outermost band label sat on the canvas edge. And a short, wide canvas
// (the bounded chart in a narrow column is 796x249) fits to WIDTH and clips top and bottom
// unless the bounds are given room in the dimension that is scarce.
map.fitBounds([[w, sLat], [e, nLat]], { padding: 40, duration: 0 })
} catch (_) {}
// ★ FIT AFTER THE BOX HAS SETTLED, AND RESIZE FIRST. The previous version fitted
// immediately: on one narrow layout the canvas was still 796x249 from a stale measurement,
// so the fit was computed against the wrong aspect, resolved to the WIDTH, and clipped the
// cloud top and bottom. Raising the padding did nothing there, because padding cannot fix a
// fit that was aimed at a box of the wrong shape — which is why that one layout was
// unchanged while the others improved.
// map.resize() makes the engine re-read the container; fitting on the next frame lets the
// flex layout finish first. Both are cheap and neither depends on a timer.
const fitTarget = () => {
try {
map.resize()
const [w, sLat] = project(-SPREAD, -SPREAD)
const [e, nLat] = project(SPREAD, SPREAD)
map.fitBounds([[w, sLat], [e, nLat]], { padding: 40, duration: 0 })
} catch (_) { /* a fit that cannot be computed leaves the default view, which still draws */ }
}
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(fitTarget)
else fitTarget()
// and again once the engine settles, for a container that was still resolving on that frame
try { map.once('idle', fitTarget) } catch (_) {}
}
// ★ CALLED UNCONDITIONALLY, AND THE GUARD THAT USED TO BE HERE IS WHY THIS WAS BROKEN TWICE.
// createBoardMap only resolves after `await map.once('load')`, so by here the event HAS fired.

View file

@ -117,19 +117,26 @@ onMounted(async () => {
console.warn('[gospel-internet] the centre cross could not be drawn:', e && e.message || e)
}
// ★ FIT THE WHOLE TARGET ON LOAD. The default zoom clipped the outer rings top and bottom —
// the reader met a drawing already cut off, which reads as a rendering fault rather than as
// something to zoom out of. Fitted from the projected extent, so it follows the chart's own
// size rather than a guessed zoom.
try {
const [w, sLat] = project(-EXTENT, -EXTENT)
const [e, nLat] = project(EXTENT, EXTENT)
// Padding is generous and symmetric because the fit must hold the LABELS too, not just the
// points: at 1280 the outermost band label sat on the canvas edge. And a short, wide canvas
// (the bounded chart in a narrow column is 796x249) fits to WIDTH and clips top and bottom
// unless the bounds are given room in the dimension that is scarce.
map.fitBounds([[w, sLat], [e, nLat]], { padding: 40, duration: 0 })
} catch (_) {}
// ★ FIT AFTER THE BOX HAS SETTLED, AND RESIZE FIRST. The previous version fitted
// immediately: on one narrow layout the canvas was still 796x249 from a stale measurement,
// so the fit was computed against the wrong aspect, resolved to the WIDTH, and clipped the
// cloud top and bottom. Raising the padding did nothing there, because padding cannot fix a
// fit that was aimed at a box of the wrong shape — which is why that one layout was
// unchanged while the others improved.
// map.resize() makes the engine re-read the container; fitting on the next frame lets the
// flex layout finish first. Both are cheap and neither depends on a timer.
const fitTarget = () => {
try {
map.resize()
const [w, sLat] = project(-EXTENT, -EXTENT)
const [e, nLat] = project(EXTENT, EXTENT)
map.fitBounds([[w, sLat], [e, nLat]], { padding: 40, duration: 0 })
} catch (_) { /* a fit that cannot be computed leaves the default view, which still draws */ }
}
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(fitTarget)
else fitTarget()
// and again once the engine settles, for a container that was still resolving on that frame
try { map.once('idle', fitTarget) } catch (_) {}
ready.value = true
}
// ★ CALLED UNCONDITIONALLY, AND THE GUARD THAT USED TO BE HERE IS WHY THIS WAS BROKEN TWICE.

View file

@ -157,8 +157,13 @@ export function degreesToPixelRadius(degrees, { minZoom = 0, maxZoom = 22 } = {}
* else here.
*/
export function crossFeature(project, { arm = 13, cx = 0, cy = 0 } = {}) {
const vert = [[cx, cy - arm], [cx, cy + arm]].map(([u, v]) => project(u, v))
const horiz = [[cx - arm * 0.7, cy - arm * 0.35], [cx + arm * 0.7, cy - arm * 0.35]].map(([u, v]) => project(u, v))
// Latin proportions, not a plus. Reported as reading "+ with equal arms": the upper arm was
// 0.65 of the lower, which is a real difference and too small to see at this size. The upper
// arm is now a third of the lower and the bar is narrower than the height, so the shape is
// legible as a cross at the size it is actually drawn.
const upper = arm * 0.55, lower = arm * 1.65, bar = arm * 0.62
const vert = [[cx, cy - upper], [cx, cy + lower]].map(([u, v]) => project(u, v))
const horiz = [[cx - bar, cy - upper * 0.05], [cx + bar, cy - upper * 0.05]].map(([u, v]) => project(u, v))
return {
type: 'FeatureCollection',
features: [

View file

@ -615,7 +615,7 @@
"bundle": "gospel-internet",
"scriptUrl": "./gospel-internet/gospel-internet.js",
"output": "dx-maps/gospel-internet/gospel-internet.js",
"integrity": "sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr",
"integrity": "sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn",
"elements": [
"gospel-internet-board"
],
@ -635,7 +635,7 @@
"scriptUrl": "./gospel-internet/gospel-internet.js",
"embed": {
"iframe": "<iframe src=\"./gospel-internet/board/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
},
"instances": [
{

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../gospel-internet.js" integrity="sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr" crossorigin="anonymous"></script>
<script src="../gospel-internet.js" integrity="sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -615,7 +615,7 @@
"bundle": "gospel-internet",
"scriptUrl": "./gospel-internet/gospel-internet.js",
"output": "dx-maps/gospel-internet/gospel-internet.js",
"integrity": "sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr",
"integrity": "sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn",
"elements": [
"gospel-internet-board"
],
@ -635,7 +635,7 @@
"scriptUrl": "./gospel-internet/gospel-internet.js",
"embed": {
"iframe": "<iframe src=\"./gospel-internet/board/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-ms4BzXmzlKrMiucFY6RR1kcxpNI1KEllXaqE6K4JtxWe0I6VkpkFcf7GulVybnrr\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
},
"instances": [
{