UNPKG

mapboxgl-legend

Version:

Mapbox-GL plugin that automatically draws a legend from layer styles

253 lines (252 loc) 10.4 kB
const j = (e) => Array.isArray(e) ? e : [e], A = (e, t, n, s = 0, o = 100) => (e - t) * (o - s) / (n - t) + s, w = (e, t) => Array.from( { length: Math.ceil(e.length / t) }, (n, s) => e.slice(s * t, s * t + t) ), F = (...e) => e[0].map((t, n) => e.slice(1).reduce((s, o) => [...s, o[n]], [t])), T = (e) => e.length === 2 ? e : [null, ...e], q = (e) => e.reduce((t, [n, s], o) => (o === e.length - 1 ? t.push([[n, null], s]) : t.push([[n, e[o + 1][0]], s]), t), []), g = (e, t = {}) => { const { classes: n, styles: s, attributes: o, events: r, content: i, appendTo: l } = t, c = document.createElement(e); return n && j(n).forEach((a) => c.classList.add(a)), s && Object.entries(s).forEach((a) => c.style.setProperty(...a)), o && Object.entries(o).forEach(([a, h]) => { h || h === 0 ? c.setAttribute(a, `${h}`) : c.removeAttribute(a); }), r && Object.entries(r).forEach(([a, h]) => c.addEventListener(a, h)), i && c.append(...j(i).filter(Boolean)), l && l.appendChild(c), c; }, I = (e, t, n) => { const s = Math.max(t, n), o = g("canvas", { attributes: { width: s, height: s } }), r = o.getContext("2d"), i = new ImageData(Uint8ClampedArray.from(e), t, n); return r == null || r.putImageData(i, (s - t) / 2, (s - n) / 2), o; }, b = (e, t) => { const { labels: n = {}, unit: s = "" } = t || {}; return Array.isArray(e) ? n[`${e}`] ?? (e[0] === null ? `< ${n[`${e[1]}`] || `${e[1]}${s}`}` : e[1] === null ? `> ${n[`${e[0]}`] || `${e[0]}${s}`}` : e.map((o) => n[`${o}`] || `${o}${s}`).join(" - ")) : e !== null ? n[`${e}`] ?? `${e}${s}` : n.other ?? "other"; }, x = {}, _ = (e, t, n) => { const { getter: s } = e; t.id in x || (x[t.id] = t.filter ?? null); const o = (i, l) => { const { delta: c = 0 } = l || {}; if (s) if (i == null) n.setFilter(t.id, x[t.id]); else if (Array.isArray(i)) { const [a, h] = i, p = a ? [">=", s, a] : !0, d = h ? ["<", s, h] : !0; n.setFilter(t.id, ["all", p, d]); } else { const a = typeof i == "number" ? ["all", [">=", s, i - c], ["<=", s, i + c]] : ["==", s, i]; n.setFilter(t.id, a); } }; return { highlight: o, events: (i) => ({ mouseenter: () => o(i), mouseleave: () => o(void 0) }) }; }, M = { x: 0 }, R = (e, t, n, s) => { const { inputs: o, stops: r, min: i, max: l } = e, { highlight: c } = _(e, t, n), a = (l - i) / 100, h = { mouseleave: () => c(void 0), mousemove: (d) => { const { offsetX: f, target: m } = d; M.x = f; const u = m, v = A(f, 0, u.offsetWidth, i, l); c(v, { delta: a }), u.style.setProperty("--x", `${f}px`); } }, p = r.map(([d, f]) => `${f} ${A(d, i, l)}%`); return g("div", { classes: ["gradient", `gradient--${s.highlight ? "highlight" : ""}`], content: [ g("p", { classes: "labels", content: o.map((d) => { const f = b(d, t.metadata); return f && g("span", { styles: { left: `${A(d, i, l)}%` }, content: f }); }) }), g("div", { classes: "bar", styles: { "background-image": `linear-gradient(90deg, ${p})`, "--x": `${M.x || 0}px` }, events: s.highlight ? h : {} }) ] }); }, U = (e, t, n, s) => { const { stops: o } = e, { events: r } = _(e, t, n); return g("ul", { classes: ["list", "list--color", `list--${s.highlight ? "highlight" : ""}`], content: o.map(([i, l]) => { const c = b(i, t.metadata); return c && g("li", { styles: { "--color": l }, events: s.highlight ? r(i) : {}, content: c }); }) }); }, N = (e, t, n, s) => { switch (e.name) { case "interpolate": return R(e, t, n, s); case "match": case "step": case "literal": return U(e, t, n, s); default: return; } }, V = (e, t, n, s) => { const { stops: o } = e, { events: r } = _(e, t, n); return g("ul", { classes: ["bubbles", `bubbles--${s.highlight ? "highlight" : ""}`], content: o.sort((i, l) => l[1] - i[1]).map(([i, l]) => { const c = b(i, t.metadata); return c && g("li", { styles: { "--radius": `${l}px` }, events: s.highlight ? r(i) : {}, content: g("span", { content: c }) }); }) }); }, O = (e, t, n, s) => { const { stops: o } = e, { events: r } = _(e, t, n); return g("ul", { classes: ["list", "list--icons", `list--${s.highlight ? "highlight" : ""}`], content: o.map(([i, l]) => { var f; const c = b(i, t.metadata); if (!c) return; const { height: a, width: h, data: p } = ((f = n.style.getImage(l)) == null ? void 0 : f.data) || {}; if (!a || !h || !p) return; const d = I(p, h, a); return g("li", { events: s.highlight ? r(i) : {}, content: [ g("img", { classes: ["icon"], attributes: { src: d.toDataURL() } }), c ] }); }) }); }, W = { color: N, radius: V, image: O, pattern: O }, E = (e, t) => w(e.slice(t), 2), L = { interpolate: (e) => E(e, 2), match: (e) => E(e, 1).map(T), step: (e) => q([[null, e[1]], ...E(e, 2)]), literal: (e) => [[...e, ...e]] }, P = (e) => Array.isArray(e) && !!e.length && typeof e[0] == "string", z = (e) => { var a; const [t, ...n] = P(e) ? e : ["literal", e]; if (t === "case") return n.slice(1).flatMap(z); const s = (a = L[t]) == null ? void 0 : a.call(L, n); if (!s) return []; const o = t === "literal" ? void 0 : ["match", "step"].includes(t) ? n[0] : n[1], [r, i] = F(...s), l = Math.min(...r.flat(2)), c = Math.max(...r.flat(2)); return [{ name: t, getter: o, stops: s, inputs: r, outputs: i, min: l, max: c }]; }, X = { isExpression: P, parse: z }, G = { collapsed: !1, toggler: !1, highlight: !1 }; class J { constructor(t) { const { layers: n, ...s } = t || {}; this._options = { ...G, layers: void 0, ...s }, n && this.addLayers(n), this._panes = g("div", { classes: "panes", styles: { display: (t == null ? void 0 : t.minimized) ?? !1 ? "none" : "block" } }), this._minimizer = (t == null ? void 0 : t.minimized) !== void 0 ? g("button", { classes: "minimizer", events: { click: () => { const { display: o } = this._panes.style; this._panes.style.display = o === "none" ? "block" : "none"; } } }) : void 0, this._container = g("div", { classes: ["mapboxgl-ctrl", "maplibregl-ctrl", "mapboxgl-ctrl-legend"], content: [this._minimizer, this._panes] }), this.refresh = this.refresh.bind(this); } onAdd(t) { return this._map = t, this._map.on("styledata", this.refresh), this._container; } onRemove() { var t, n; (t = this._container.parentNode) == null || t.removeChild(this._container), (n = this._map) == null || n.off("styledata", this.refresh); } addLayers(t) { var s, o; const n = (r, i) => { var d; const { collapsed: l = this._options.collapsed, toggler: c = this._options.toggler, highlight: a = this._options.highlight, onToggle: h = this._options.onToggle, attributes: p } = i || {}; (d = this._options.layers) == null || d.set(r, { collapsed: l, toggler: c, highlight: a, onToggle: h, attributes: p }); }; (s = this._options).layers ?? (s.layers = /* @__PURE__ */ new Map()), Array.isArray(t) ? t.forEach((r) => n(r)) : Object.entries(t).forEach(([r, i]) => { typeof i == "boolean" ? n(r) : Array.isArray(i) ? n(r, { attributes: i }) : n(r, i); }), (o = this._map) != null && o.isStyleLoaded() && this.refresh(); } removeLayers(t) { t.forEach((n) => { var o; (o = this._options.layers) == null || o.delete(n); const s = this._panes.querySelector(`.mapboxgl-ctrl-legend-pane--${n}`); s && this._panes.removeChild(s); }); } _getBlocks(t, n, s, o) { var a; const [r] = s.split("-").slice(-1), i = W[r]; if (!i) return; const l = X.parse(o), c = ((a = this._options.layers) == null ? void 0 : a.get(t)) || this._options; return l.map((h) => i(h, n, this._map, c)).filter(Boolean); } _toggleButton(t, n) { var i, l; const { onToggle: s = this._options.onToggle } = ((i = this._options.layers) == null ? void 0 : i.get(n)) || {}, o = ((l = this._map) == null ? void 0 : l.getLayoutProperty(t[0], "visibility")) || "visible", r = g("div", { classes: ["toggler", `toggler--${o}`] }); return r.addEventListener("click", (c) => { c.preventDefault(); const a = o === "none" ? "visible" : "none"; t.forEach((h) => { var p; (p = this._map) == null || p.setLayoutProperty(h, "visibility", a), s == null || s(h, a === "visible"); }); }), r; } refresh() { var n; const t = this._options.layers ? [...this._options.layers.keys()] : void 0; (n = this._map.getStyle()) == null || n.layers.filter((s) => { const o = "source" in s && s.source !== "composite", r = !t || [...t].some((i) => typeof i == "string" ? s.id === i : s.id.match(i)); return o && r; }).reverse().forEach((s) => { var C; const { id: o, layout: r, paint: i, metadata: l } = s, c = (t == null ? void 0 : t.find((y) => o.match(y))) || o, { collapsed: a, toggler: h, attributes: p } = ((C = this._options.layers) == null ? void 0 : C.get(c)) || this._options, d = Object.entries({ ...r, ...i }).reduce((y, [B, D]) => { if (!((p == null ? void 0 : p.includes(B)) ?? !0)) return y; const $ = this._getBlocks(c, s, B, D); return $ == null || $.forEach((S) => y.push(S)), y; }, []); if (!d.length) return; const f = `mapboxgl-ctrl-legend-pane--${o}`, m = this._panes.querySelector(`.${f}`), u = h ? typeof h == "boolean" ? [o] : h : void 0, v = (l == null ? void 0 : l.extraLegendClasses) ?? [], k = g("details", { classes: ["mapboxgl-ctrl-legend-pane", f, ...v], attributes: { open: m ? m.getAttribute("open") !== null : !a }, content: [ g("summary", { content: [ (l == null ? void 0 : l.name) || o, u && this._toggleButton(u, c) ] }), ...d ] }); m ? this._panes.replaceChild(k, m) : this._panes.appendChild(k); }); } } export { J as default };