Point the cross the right way up, and let the fit follow its container
The cross drew inverted. The projection computes Y = 0.5 - v*K, so a larger v is a HIGHER latitude, and the parts were named on the screen convention where +y goes down — which put the long arm at the top. That is a different symbol, not a cosmetic slip. The parts are named by compass now, and the test asserts in latitude after projection rather than in layout units, which is the only space where the question means anything. The fit was bit-identical through three attempts — more padding, a resize, a refit on idle — and that was the evidence. None of them was wrong; none of them was the moment the box changed size. These panels are v-show, so a map can be built while its container is display:none and therefore zero-sized, and the engine keeps the size it measured then. A fit computed against that lands at the wrong zoom, which is why one layout opened at 0.64 while an almost identically shaped one opened at 0.31. A ResizeObserver is the only thing that knows when the box became real, so the fit follows the container instead of guessing at a moment, and skips while the box is still hidden. The failure is reported rather than swallowed, for the same reason as the cross layer before it.
This commit is contained in:
parent
5681f9db22
commit
fc589cef1c
13 changed files with 76 additions and 43 deletions
|
|
@ -35,6 +35,7 @@ const board = useBoardState()
|
|||
const el = ref(null)
|
||||
const mapRef = shallowRef(null)
|
||||
const handle = shallowRef(null)
|
||||
let fitRo = null
|
||||
const failed = ref('')
|
||||
|
||||
const R = 120, SPREAD = 260
|
||||
|
|
@ -110,18 +111,31 @@ onMounted(async () => {
|
|||
// 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.
|
||||
// ★ REFIT WHENEVER THE BOX CHANGES SIZE, because a one-shot fit cannot know when that is.
|
||||
// These panels are v-show, so a map can be created while its container is display:none and
|
||||
// therefore zero-sized; the engine keeps whatever size it measured then, and a fit computed
|
||||
// against it lands at the wrong zoom. That is why one layout opened at 0.64 while an almost
|
||||
// identically-shaped one opened at 0.31 — and why raising padding, resizing once and
|
||||
// refitting on idle all left it BIT-IDENTICAL: none of them was the moment the box changed.
|
||||
// A ResizeObserver is the only thing that knows, so the fit follows the container instead of
|
||||
// guessing at a moment.
|
||||
const fitTarget = () => {
|
||||
try {
|
||||
const box = el.value
|
||||
if (!box || !box.clientWidth || !box.clientHeight) return // still hidden: wait to be shown
|
||||
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 */ }
|
||||
} catch (e) {
|
||||
console.warn('[gospel-internet] could not fit the chart to its box:', e && e.message || e)
|
||||
}
|
||||
}
|
||||
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 (_) {}
|
||||
fitTarget()
|
||||
try {
|
||||
fitRo = new ResizeObserver(fitTarget)
|
||||
if (el.value) fitRo.observe(el.value)
|
||||
} catch (_) { /* no ResizeObserver: the initial fit stands */ }
|
||||
}
|
||||
// ★ 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.
|
||||
|
|
@ -138,7 +152,7 @@ onMounted(async () => {
|
|||
})
|
||||
|
||||
watch(data, (d) => { try { mapRef.value?.getSource('gbsm')?.setData(d) } catch (_) {} })
|
||||
onBeforeUnmount(() => { try { handle.value?.destroy?.() } catch (_) {} ; try { mapRef.value?.remove?.() } catch (_) {} })
|
||||
onBeforeUnmount(() => { try { fitRo?.disconnect() } catch (_) {} ; try { handle.value?.destroy?.() } catch (_) {} ; try { mapRef.value?.remove?.() } catch (_) {} })
|
||||
|
||||
useShadowStyles(
|
||||
'.gbsm{position:absolute;inset:0;min-height:0;min-width:0}' +
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const board = useBoardState()
|
|||
const el = ref(null)
|
||||
const mapRef = shallowRef(null)
|
||||
const handle = shallowRef(null)
|
||||
let fitRo = null
|
||||
const ready = ref(false)
|
||||
const failed = ref('')
|
||||
|
||||
|
|
@ -125,18 +126,31 @@ onMounted(async () => {
|
|||
// 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.
|
||||
// ★ REFIT WHENEVER THE BOX CHANGES SIZE, because a one-shot fit cannot know when that is.
|
||||
// These panels are v-show, so a map can be created while its container is display:none and
|
||||
// therefore zero-sized; the engine keeps whatever size it measured then, and a fit computed
|
||||
// against it lands at the wrong zoom. That is why one layout opened at 0.64 while an almost
|
||||
// identically-shaped one opened at 0.31 — and why raising padding, resizing once and
|
||||
// refitting on idle all left it BIT-IDENTICAL: none of them was the moment the box changed.
|
||||
// A ResizeObserver is the only thing that knows, so the fit follows the container instead of
|
||||
// guessing at a moment.
|
||||
const fitTarget = () => {
|
||||
try {
|
||||
const box = el.value
|
||||
if (!box || !box.clientWidth || !box.clientHeight) return // still hidden: wait to be shown
|
||||
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 */ }
|
||||
} catch (e) {
|
||||
console.warn('[gospel-internet] could not fit the chart to its box:', e && e.message || e)
|
||||
}
|
||||
}
|
||||
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 (_) {}
|
||||
fitTarget()
|
||||
try {
|
||||
fitRo = new ResizeObserver(fitTarget)
|
||||
if (el.value) fitRo.observe(el.value)
|
||||
} catch (_) { /* no ResizeObserver: the initial fit stands */ }
|
||||
ready.value = true
|
||||
}
|
||||
// ★ CALLED UNCONDITIONALLY, AND THE GUARD THAT USED TO BE HERE IS WHY THIS WAS BROKEN TWICE.
|
||||
|
|
@ -166,7 +180,7 @@ watch(opacityExpr, (o) => {
|
|||
try { mapRef.value?.setPaintProperty(id, 'circle-opacity', o) } catch (_) {}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => { try { handle.value?.destroy?.() } catch (_) {} ; try { mapRef.value?.remove?.() } catch (_) {} })
|
||||
onBeforeUnmount(() => { try { fitRo?.disconnect() } catch (_) {} ; try { handle.value?.destroy?.() } catch (_) {} ; try { mapRef.value?.remove?.() } catch (_) {} })
|
||||
|
||||
useShadowStyles(
|
||||
'.gcsm{position:absolute;inset:0;min-height:0;min-width:0}' +
|
||||
|
|
|
|||
|
|
@ -157,13 +157,18 @@ export function degreesToPixelRadius(degrees, { minZoom = 0, maxZoom = 22 } = {}
|
|||
* else here.
|
||||
*/
|
||||
export function crossFeature(project, { arm = 13, cx = 0, cy = 0 } = {}) {
|
||||
// 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))
|
||||
// ★ +v IS NORTH, NOT DOWN. The projection computes Y = 0.5 - v*K, so a LARGER v is a HIGHER
|
||||
// latitude. An earlier version named its parts "upper" and "lower" on the screen convention
|
||||
// where +y goes down, and therefore drew the LONG arm upward — an inverted cross, which is a
|
||||
// different symbol rather than a cosmetic slip. Named by compass here so the mistake cannot be
|
||||
// made silently again: `north` is toward the top of the drawing.
|
||||
const H = arm * 2.2
|
||||
const north = cy + H * 0.35 // the short arm, above the bar
|
||||
const south = cy - H * 0.65 // the long arm, below it
|
||||
const barV = cy + H * 0.18 // the bar sits high, nearer the top than the middle
|
||||
const barHalf = arm * 0.62
|
||||
const vert = [[cx, north], [cx, south]].map(([u, v]) => project(u, v))
|
||||
const horiz = [[cx - barHalf, barV], [cx + barHalf, barV]].map(([u, v]) => project(u, v))
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
|
|
|
|||
|
|
@ -615,7 +615,7 @@
|
|||
"bundle": "gospel-internet",
|
||||
"scriptUrl": "./gospel-internet/gospel-internet.js",
|
||||
"output": "dx-maps/gospel-internet/gospel-internet.js",
|
||||
"integrity": "sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn",
|
||||
"integrity": "sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj",
|
||||
"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-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>"
|
||||
"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-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj\" 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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
|
||||
<script src="../../gospel-internet.js" integrity="sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-internet.js" integrity="sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
|
||||
<script src="../../gospel-internet.js" integrity="sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
|
||||
<script src="../../gospel-internet.js" integrity="sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
|
||||
<script src="../../gospel-internet.js" integrity="sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
|
||||
<script src="../../gospel-internet.js" integrity="sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn" crossorigin="anonymous"></script>
|
||||
<script src="../../gospel-internet.js" integrity="sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -615,7 +615,7 @@
|
|||
"bundle": "gospel-internet",
|
||||
"scriptUrl": "./gospel-internet/gospel-internet.js",
|
||||
"output": "dx-maps/gospel-internet/gospel-internet.js",
|
||||
"integrity": "sha384-6ypksiuOg1UYnG8SJOD9nFoDxMeisCv7Z8ZxvQtGI8E0Yonjw1IkXPHOI2hoHVTn",
|
||||
"integrity": "sha384-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj",
|
||||
"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-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>"
|
||||
"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-zx0Es4trCFtqdzhsZHh78nh9D+Cpyp8lw7oZiucX7huOJzQlOzmIAcqJfhh5Pesj\" 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": [
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue