Skip to content

Add double-click 'disable' to Sankey view #277

Description

@pope7

I find it useful to 'cross off' items I've completed, visually. This tampermonkey script enables this, allowing the user to double-click nodes to 'disable' them visually. Sankey lines connecting two disabled nodes also get the disabled styling.

Obviously this would need to be tuned to your actual code, but I find it incredibly useful.

// ==UserScript==
// @name         KirkMcDonald Calc - Disable Nodes
// @namespace    https://github.com/KirkMcDonald
// @version      0.1.0
// @description  Double-click a node in the sankey to visually disable it. A link is disabled only when both of its endpoints are disabled.
// @match        https://kirkmcdonald.github.io/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
    "use strict";

    const STORAGE_KEY = "kmcd_disabled_nodes_v1";
    const disabled = new Set(
        JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]")
    );

    function save() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify([...disabled]));
    }

    const style = document.createElement("style");
    style.textContent = `
        svg#graph g.node.kmcdDisabled { opacity: 0.15; }
        svg#graph g.link.kmcdDisabled { opacity: 0.08; }
    `;
    document.head.appendChild(style);

    function datumOf(el) {
        if (typeof d3 === "undefined") return null;
        try { return d3.select(el).datum(); } catch (_) { return null; }
    }

    function recipeNameFromNode(node) {
        return node && node.recipe ? node.recipe.name : null;
    }

    function applyDisabled() {
        const svg = document.querySelector("svg#graph");
        if (!svg) return;

        svg.querySelectorAll("g.nodes > g.node").forEach((g) => {
            const node = datumOf(g);
            const name = recipeNameFromNode(node);
            g.classList.toggle("kmcdDisabled", !!name && disabled.has(name));
        });

        svg.querySelectorAll("g.links > g.link").forEach((g) => {
            const link = datumOf(g);
            if (!link || !link.source || !link.target) {
                g.classList.remove("kmcdDisabled");
                return;
            }
            const a = recipeNameFromNode(link.source);
            const b = recipeNameFromNode(link.target);
            const both = a && b && disabled.has(a) && disabled.has(b);
            g.classList.toggle("kmcdDisabled", !!both);
        });
    }

    function attachHandlers() {
        const svg = document.querySelector("svg#graph");
        if (!svg) return;
        svg.querySelectorAll("g.overlay > rect").forEach((rect) => {
            if (rect.__kmcdHooked) return;
            rect.__kmcdHooked = true;
            rect.addEventListener("dblclick", (event) => {
                event.preventDefault();
                const d = datumOf(rect);
                const name = recipeNameFromNode(d && d.node);
                if (!name) return;
                if (disabled.has(name)) disabled.delete(name);
                else disabled.add(name);
                save();
                applyDisabled();
            });
        });
    }

    let pending = false;
    function refresh() {
        if (pending) return;
        pending = true;
        requestAnimationFrame(() => {
            pending = false;
            attachHandlers();
            applyDisabled();
        });
    }

    function waitForGraph() {
        const svg = document.querySelector("svg#graph");
        if (!svg) {
            setTimeout(waitForGraph, 200);
            return;
        }
        new MutationObserver(refresh).observe(svg, {
            childList: true,
            subtree: true,
        });
        refresh();
    }

    waitForGraph();
})();

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions