rizzma 1.13.7

A Rust reimplementation of the good parts of matplotlib/pyplot, with first-class wasm support.
Documentation
<style>
/* Layout for the crate-root live demo matrix (see lib.rs docs). Cells and
   their canvases shrink responsively; pointer math stays exact because the
   session maps events through the live bounding rect. */
.rizzma-live-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
  align-items: start;
}
.rizzma-live-grid .rizzma-live {
  margin: 0;
}
.rizzma-live canvas {
  border-radius: 3px;
}
.rizzma-live-expand {
  display: inline-block;
  margin: 2px 0 0;
  padding: 0;
  background: none;
  border: none;
  font: inherit;
  font-size: 12px;
  color: inherit;
  opacity: 0.65;
  cursor: pointer;
}
.rizzma-live-expand:hover {
  opacity: 1;
  text-decoration: underline;
}
.rizzma-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10000;
}
.rizzma-modal-panel {
  position: relative;
  background: #fff;
  border-radius: 8px;
  padding: 26px 14px 12px;
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.45);
  max-width: 96vw;
  max-height: 94vh;
  overflow: auto;
}
.rizzma-modal-close {
  position: absolute;
  top: 2px;
  right: 8px;
  padding: 2px 6px;
  background: none;
  border: none;
  font: inherit;
  font-size: 18px;
  line-height: 1;
  color: #444;
  cursor: pointer;
}
.rizzma-modal-close:hover {
  color: #000;
}
</style>
<script type="module">
// rizzma live docs: upgrade `.rizzma-live` placeholders in the rendered
// rustdoc into interactive wasm canvases (wheel zoom, drag pan, double-click
// reset), loaded lazily from the gh-pages demo bundle. The demos themselves
// live in `www/rizzma-demos.js`, published beside the pkg and shared with the
// demo site, so the two never drift. The static gallery
// image inside each placeholder stays as the fallback: offline docs, a
// blocked fetch, or any error simply leave it visible. Animated demos share
// one ticker and pause while scrolled out of view. Each live cell offers a
// "full size" pop-up: the same demo rebuilt larger in an inline modal
// (also reachable via a `#rizzma-live=<name>` URL hash).
(() => {
  const PKG =
    window.RIZZMA_DEMO_PKG ||
    "https://orbitalcommons.github.io/rizzma/demo/pkg/rizzma.js";

  const DEMOS = new URL("../rizzma-demos.js", new URL(PKG, location.href)).href;

  // Sessions must stay referenced or the GC detaches their interaction.
  const sessions = [];
  // Filled in by `boot` from the shared demos module.
  let demos = {};
  let unanimate = () => {};
  let loading = null;
  const boot = () =>
    (loading ??= Promise.all([import(PKG), import(DEMOS)]).then(
      async ([mod, shared]) => {
        await mod.default();
        ({ demos, unanimate } = shared);
        return mod;
      },
    ));

  const newCanvas = () => {
    const canvas = document.createElement("canvas");
    canvas.id = "rizzma-live-" + Math.random().toString(36).slice(2);
    canvas.style.touchAction = "none";
    canvas.style.display = "block";
    return canvas;
  };

  // Full-size pop-up: rebuild `name` at a viewport-fitted scale in a modal.
  // Esc, the close button, or a backdrop click tears it down and frees the
  // session.
  let openOverlay = null;
  const closeModal = () => {
    if (!openOverlay) return;
    const { overlay, panel, session, onKey } = openOverlay;
    openOverlay = null;
    unanimate(panel);
    try {
      session.free();
    } catch (_) {
      /* already freed */
    }
    document.removeEventListener("keydown", onKey);
    overlay.remove();
    document.body.style.overflow = "";
  };
  const openModal = (mod, name) => {
    closeModal();
    const build = demos[name];
    if (!build) return;
    const k = Math.max(
      1,
      Math.min(
        2.4,
        (Math.min(window.innerWidth, 1200) - 90) / 360,
        (window.innerHeight - 130) / 270,
      ),
    );
    const overlay = document.createElement("div");
    overlay.className = "rizzma-modal-overlay";
    const panel = document.createElement("div");
    panel.className = "rizzma-modal-panel";
    const close = document.createElement("button");
    close.className = "rizzma-modal-close";
    close.textContent = "×";
    close.setAttribute("aria-label", "close");
    close.addEventListener("click", closeModal);
    const canvas = newCanvas();
    panel.append(close, canvas);
    overlay.append(panel);
    overlay.addEventListener("pointerdown", (e) => {
      if (e.target === overlay) closeModal();
    });
    const onKey = (e) => {
      if (e.key === "Escape") closeModal();
    };
    document.addEventListener("keydown", onKey);
    document.body.append(overlay);
    document.body.style.overflow = "hidden";
    try {
      const session = build(mod, canvas, panel, k);
      openOverlay = { overlay, panel, session, onKey };
    } catch (e) {
      overlay.remove();
      document.body.style.overflow = "";
      document.removeEventListener("keydown", onKey);
      console.warn("rizzma full-size demo unavailable:", e);
    }
  };

  const upgrade = async (el) => {
    if (el.dataset.rizzmaMounted) return;
    el.dataset.rizzmaMounted = "1";
    try {
      const mod = await boot();
      const build = demos[el.dataset.demo];
      if (!build) return;
      const canvas = newCanvas();
      el.prepend(canvas);
      sessions.push(build(mod, canvas, el));
      const expand = document.createElement("button");
      expand.className = "rizzma-live-expand";
      expand.textContent = "⤢ full size";
      expand.addEventListener("click", () => openModal(mod, el.dataset.demo));
      canvas.after(expand);
      for (const img of el.querySelectorAll("img")) {
        img.style.display = "none";
      }
    } catch (e) {
      // Leave the static fallback image in place.
      console.warn("rizzma live demo unavailable:", e);
    }
  };

  const start = () => {
    const hosts = document.querySelectorAll(".rizzma-live[data-demo]");
    if (!hosts.length) return;
    const io = new IntersectionObserver(
      (entries) => {
        for (const en of entries) {
          if (en.isIntersecting) {
            io.unobserve(en.target);
            upgrade(en.target);
          }
        }
      },
      { rootMargin: "200px" },
    );
    hosts.forEach((h) => io.observe(h));
    // Deep link: #rizzma-live=<demo> opens the full-size modal directly.
    const m = /^#rizzma-live=([a-z0-9_]+)$/.exec(location.hash);
    if (m) {
      boot()
        .then((mod) => openModal(mod, m[1]))
        .catch(() => {});
    }
  };
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", start);
  } else {
    start();
  }
})();
</script>