mapboxgl-legend
Version:
Mapbox-GL plugin that automatically draws a legend from layer styles
253 lines (252 loc) • 10.4 kB
JavaScript
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
};