Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions docs/src/components/LiveChart.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useEffect} from 'react';
import React, {useEffect, useRef} from 'react';
import BrowserOnly from '@docusaurus/BrowserOnly';
import useBaseUrl from '@docusaurus/useBaseUrl';

Expand All @@ -7,11 +7,27 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
// catalog) load from the prebaked demo bundle the docs build assembles under
// /<baseUrl>/demo/ — see `make demo` and .github/workflows/docs.yml. Build it
// locally first with: make demo DEMO_OUT=docs/static/demo

// The demo opens on Annapolis harbour at 1:6090 (a detailed harbour view). Widget
// mode is HERMETIC — it ignores localStorage and boots from DEFAULT_MARINER — so the
// non-default display state we want for the demo (Display Other + scale boundaries)
// is forced at ready, and the scale is pinned via the zoom attribute below.
const CENTER = [-76.48167, 38.975]; // Annapolis, MD — 38°58.5′N 076°28.9′W
const SCALE = 6090; // display scale denominator (1:6090)
// scale → MapLibre zoom (512-tile resolution, default 0.2645 mm CSS pixel — the
// widget's DEFAULT_PX_PITCH_MM), so scaleDenomPhysical reads ~1:6090 in the HUD.
const M_PER_PX_Z0 = 78271.516964020485;
const PX_PITCH_M = 0.0002645;
const ZOOM = Math.log2(
(M_PER_PX_Z0 * Math.cos((CENTER[1] * Math.PI) / 180)) / (PX_PITCH_M * SCALE),
);

function Chart() {
// useBaseUrl prefixes the site baseUrl, e.g. "/chartplotter/demo/". The widget
// resolves ALL of its assets (incl. vendor/maplibre-gl.js and charts-index.json)
// relative to this, so the whole demo is self-contained in that one directory.
const base = useBaseUrl('/demo/');
const ref = useRef(null);
useEffect(() => {
const id = 'chartplotter-widget-module';
if (document.getElementById(id)) return; // define <chart-plotter> once
Expand All @@ -21,11 +37,36 @@ function Chart() {
s.src = `${base}src/chartplotter.mjs`;
document.head.appendChild(s);
}, [base]);

// Once the map is ready, force the demo's display state (widget mode is hermetic, so
// these aren't persisted): Display category Other on, chart scale boundaries on.
useEffect(() => {
let tries = 0;
const iv = setInterval(() => {
const el = ref.current;
if (el && el.map) {
clearInterval(iv);
if (typeof el.applyMariner === 'function') {
try { el.applyMariner({displayOther: true, showScaleBoundaries: true}); } catch (e) { /* best-effort */ }
}
} else if (++tries > 60) {
clearInterval(iv);
}
}, 200);
return () => clearInterval(iv);
}, []);

return (
<>
<div className="liveChart">
{/* widget = read-only viewer; assets points every fetch at the demo bundle */}
<chart-plotter widget="" assets={base} center="-76.482,38.978" zoom="13" />
<chart-plotter
ref={ref}
widget=""
assets={base}
center={CENTER.join(',')}
zoom={ZOOM.toFixed(3)}
/>
</div>
{/* Plain <a> (not a router Link) → full-page nav to the static bundle. */}
<p className="liveChart__caption">
Expand Down
36 changes: 25 additions & 11 deletions web/src/chartplotter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,15 @@ export class ChartPlotter extends HTMLElement {
this._pxPitch = loadJSON(LS_PX_PITCH, undefined); // calibrated CSS-pixel pitch (mm); undefined → util default (CSS reference)
// Feature-inspect + tile-debugger state now live in DevTools (Advanced tab).
this._hasArchive = false; // is a chart archive currently loaded?
this._mariner = { ...DEFAULT_MARINER, ...loadJSON(LS_MARINER, {}) };
// Widget (embed) mode is HERMETIC for display settings: it must not read or write
// the shared localStorage scheme/basemap/mariner. Several embeds share one origin
// (the docs intro demo + the Chart 1 reference page), so persisting would let one
// clobber another — e.g. the reference page forcing dataQuality:true would leak
// onto the intro demo on its next load. Embeds boot from DEFAULT_MARINER and set
// their own state via applyMariner/applyScheme at ready. (boot() also sets
// this._widget the same way, before any apply* runs.)
const embed = this.hasAttribute("widget") || new URLSearchParams(location.search).has("widget");
this._mariner = { ...DEFAULT_MARINER, ...(embed ? {} : loadJSON(LS_MARINER, {})) };
// Migrate the old single-value display category (base|standard|other) to
// the multi-select Base/Standard/Other booleans (now client-side filters).
if (this._mariner.displayCategory) {
Expand All @@ -250,8 +258,8 @@ export class ChartPlotter extends HTMLElement {
// S-52 §10.2: Display Base is the minimum safe-navigation set and can never
// be deselected. Force it on regardless of any (stale) persisted value.
this._mariner.displayBase = true;
this._scheme = localStorage.getItem(LS_SCHEME) || "day";
if (!SCHEMES.includes(this._scheme)) this._scheme = "day"; // fall back if the persisted scheme isn't a known one
this._scheme = (embed ? this.getAttribute("scheme") : localStorage.getItem(LS_SCHEME)) || "day";
if (!SCHEMES.includes(this._scheme)) this._scheme = "day"; // fall back if the persisted/attr scheme isn't a known one
// The provision job is a SERVER task: `_task` mirrors GET /api/tasks (polled,
// never invented), `_taskMeta` holds the client-only label hints (which region,
// which verb) the server doesn't know. `_poll` is the polling interval handle.
Expand Down Expand Up @@ -393,14 +401,17 @@ export class ChartPlotter extends HTMLElement {
}

const plotter = document.createElement("chart-canvas");
const view = shareView || loadJSON(LS_VIEW, null); // resume the last view → load in-region
// Embeds are hermetic (see constructor): never resume a persisted view — always
// boot from the `center`/`zoom` attributes (the docs intro demo pins Annapolis),
// so the Chart 1 reference page's view can't leak onto the intro demo.
const view = shareView || (this._widget ? null : loadJSON(LS_VIEW, null)); // resume the last view → load in-region
plotter.setAttribute("center", view ? view.center.join(",") : (this.getAttribute("center") || "-76.4875,38.975"));
plotter.setAttribute("zoom", String(view ? view.zoom : (this.getAttribute("zoom") || 11)));
if (this.hasAttribute("cell-url")) plotter.setAttribute("cell-url", this.getAttribute("cell-url"));
plotter.setAttribute("assets", this._assets);
this._osmVecUrl = this._cfg("osm-pmtiles"); // hosted OSM vector basemap archive (enables the "Vector" option)
if (this._osmVecUrl) plotter.setAttribute("osm-pmtiles", this._osmVecUrl);
this._basemap = this._serverBasemap || localStorage.getItem(LS_BASEMAP) || this.getAttribute("basemap") || "coastline";
this._basemap = this._serverBasemap || (this._widget ? null : localStorage.getItem(LS_BASEMAP)) || this.getAttribute("basemap") || "coastline";
if (!["coastline", "osm", "osmvec", "none"].includes(this._basemap)) this._basemap = "coastline";
if (this._basemap === "osmvec" && !this._osmVecUrl) this._basemap = "coastline"; // vector not configured
plotter.setAttribute("basemap", this._basemap);
Expand Down Expand Up @@ -769,7 +780,9 @@ export class ChartPlotter extends HTMLElement {
loaded = true;
const frames = [...(regions || [])];
if (this._userBake && this._userBake.bounds && !this._isWorldBounds(this._userBake.bounds)) frames.push({ bounds: this._userBake.bounds });
if (frames.length && !loadJSON(LS_VIEW, null)) this._frameRegionArchives(frames);
// Embeds keep their pinned center/zoom (Annapolis) — don't auto-frame the
// loaded region and don't consult the persisted view (hermetic; see constructor).
if (frames.length && !this._widget && !loadJSON(LS_VIEW, null)) this._frameRegionArchives(frames);
}
}
if (loaded) { this.updateEmptyState(); return; }
Expand All @@ -784,7 +797,7 @@ export class ChartPlotter extends HTMLElement {
// answers "is the centre covered at all", which is all we need here.)
_frameInitial() {
// (the cell-picker "charts mode" was removed — this is just the no-saved-view guard)
if (loadJSON(LS_VIEW, null) || !this._districts.length) return;
if (this._widget || loadJSON(LS_VIEW, null) || !this._districts.length) return; // embeds keep their pinned view
const c = this._map.getCenter();
const covered = (d) => d.bounds && c.lng >= d.bounds[0] && c.lng <= d.bounds[2] && c.lat >= d.bounds[1] && c.lat <= d.bounds[3];
if (this._districts.some(covered)) return;
Expand Down Expand Up @@ -862,7 +875,7 @@ export class ChartPlotter extends HTMLElement {
try {
const arc = add ? await this._plotter.addArchive(url, "all") : await this._plotter.loadArchiveUrl(url);
const b = (entry && entry.bounds) || (arc && arc.bounds);
if (b && !loadJSON(LS_VIEW, null)) this._map.fitBounds([[b[0], b[1]], [b[2], b[3]]], { padding: 40, duration: 0 });
if (b && !this._widget && !loadJSON(LS_VIEW, null)) this._map.fitBounds([[b[0], b[1]], [b[2], b[3]]], { padding: 40, duration: 0 }); // embeds keep their pinned view
this._markArchive(entry ? { type: "url", file: entry.file } : null);
return true;
} catch (e) { console.warn("[archive] load", url, e); return false; }
Expand Down Expand Up @@ -1102,6 +1115,7 @@ export class ChartPlotter extends HTMLElement {
}

saveView() {
if (this._widget) return; // embeds are hermetic — never persist the view (see constructor)
// The cell-picker "charts mode" (whose zoomed-out framing we used to skip
// persisting) was removed; the live view is always the one to save.
const c = this._map.getCenter();
Expand Down Expand Up @@ -1801,7 +1815,7 @@ export class ChartPlotter extends HTMLElement {
this._scheme = name;
this._plotter.setScheme(name);
this.setAttribute("data-scheme", name);
localStorage.setItem(LS_SCHEME, name);
if (!this._widget) localStorage.setItem(LS_SCHEME, name); // embeds are hermetic (see constructor)
this._persistSettings();
this._syncSchemeUI();
}
Expand All @@ -1811,7 +1825,7 @@ export class ChartPlotter extends HTMLElement {
applyBasemap(mode) {
this._basemap = (mode === "osm" || mode === "osmvec" || mode === "none") ? mode : "coastline";
if (this._plotter) this._plotter.setBasemap(this._basemap);
localStorage.setItem(LS_BASEMAP, this._basemap);
if (!this._widget) localStorage.setItem(LS_BASEMAP, this._basemap); // embeds are hermetic (see constructor)
this._persistSettings();
}

Expand Down Expand Up @@ -1847,7 +1861,7 @@ export class ChartPlotter extends HTMLElement {
// re-bake), so just apply the changed key(s) and persist.
try { this._plotter.setMariner(patch); }
catch (e) { console.warn(e); }
localStorage.setItem(LS_MARINER, JSON.stringify(this._mariner));
if (!this._widget) localStorage.setItem(LS_MARINER, JSON.stringify(this._mariner)); // embeds are hermetic (see constructor)
this._persistSettings();
// Switching units relabels + reconverts the depth fields (still in metres
// under the hood), so redraw the settings panel.
Expand Down