Saturday, 30 August 2025

Box code v4

{

  "$schema": "https://vega.github.io/schema/vega/v5.json",

  "description": "Zoomable, collapsable tree by David Bacci: fixed for cycle error",

  "width": {"signal": "1240"},

  "height": {"signal": "600"},

  "background": "#f5f5f5",

  "autosize": "pad",

  "padding": 5,

  "signals": [

    {

      "name": "slicerSelection",

      "value": null,

      "update": "data('dataset') && length(data('dataset')) > 0 ? data('dataset')[0]['selected_path'] : null"

    },

    {

      "name": "slicerHighlightPath",

      "value": [],

      "update": "slicerSelection ? pluck(treeAncestors('treeCalcs', slicerSelection), 'id') : []"

    },

    {

      "name": "slicerParentPath",

      "value": [],

      "update": "slicerSelection ? slice(slicerHighlightPath, 0, -1) : []"

    },

    {

      "name": "slicerDescendantsPath",

      "value": [],

      "update": "slicerSelection ? pluck(data('slicerDescendantsData'), 'id') : []"

    },

    {"name": "nodeWidth", "value": 190},

    {"name": "nodeHeight", "value": 45},

    {

      "name": "verticalNodeGap",

      "value": 10

    },

    {

      "name": "horizontalNodeGap",

      "value": 140

    },

    {

      "name": "startingDepth",

      "value": 8,

      "on": [

        {

          "events": {

            "type": "timer",

            "throttle": 0

          },

          "update": "-1"

        }

      ]

    },

    {

      "name": "node",

      "value": 0,

      "on": [

        {

          "events": {

            "type": "click",

            "markname": "node"

          },

          "update": "datum.id"

        },

        {

          "events": {

            "type": "timer",

            "throttle": 10

          },

          "update": "0"

        }

      ]

    },

    {

      "name": "nodeHighlight",

      "value": "[0]",

      "on": [

        {

          "events": {

            "type": "mouseover",

            "markname": "node"

          },

          "update": "pluck(treeAncestors('treeCalcs', datum.id), 'id')"

        },

        {

          "events": {

            "type": "mouseout"

          },

          "update": "[0]"

        }

      ]

    },

    {

      "name": "isExpanded",

      "value": 0,

      "on": [

        {

          "events": {

            "type": "click",

            "markname": "node"

          },

          "update": "datum.children > 0 && indata('treeClickStorePerm', 'id', datum.childrenIds[0])?true:false"

        }

      ]

    },

    {

      "name": "xrange",

      "update": "[0, width]"

    },

    {

      "name": "yrange",

      "update": "[0, height]"

    },

    {

      "name": "down",

      "value": null,

      "on": [

        {

          "events": "touchend",

          "update": "null"

        },

        {

          "events": "mousedown, touchstart",

          "update": "xy()"

        }

      ]

    },

    {

      "name": "xcur",

      "value": null,

      "on": [

        {

          "events": "mousedown, touchstart, touchend",

          "update": "slice(xdom)"

        }

      ]

    },

    {

      "name": "ycur",

      "value": null,

      "on": [

        {

          "events": "mousedown, touchstart, touchend",

          "update": "slice(ydom)"

        }

      ]

    },

    {

      "name": "delta",

      "value": [0, 0],

      "on": [

        {

          "events": [

            {

              "source": "window",

              "type": "mousemove",

              "consume": true,

              "between": [

                {"type": "mousedown"},

                {

                  "source": "window",

                  "type": "mouseup"

                }

              ]

            },

            {

              "type": "touchmove",

              "consume": true,

              "filter": "event.touches.length === 1"

            }

          ],

          "update": "down ? [down[0]-x(), down[1]-y()] : [0,0]"

        }

      ]

    },

    {

      "name": "anchor",

      "value": [0, 0],

      "on": [

        {

          "events": "wheel",

          "update": "[invert('xscale', x()), invert('yscale', y())]"

        },

        {

          "events": {

            "type": "touchstart",

            "filter": "event.touches.length===2"

          },

          "update": "[(xdom[0] + xdom[1]) / 2, (ydom[0] + ydom[1]) / 2]"

        }

      ]

    },

    {

      "name": "zoom",

      "value": 1,

      "on": [

        {

          "events": "wheel!",

          "force": true,

          "update": "pow(1.001, event.deltaY * pow(16, event.deltaMode))"

        },

        {

          "events": {"signal": "dist2"},

          "force": true,

          "update": "dist1 / dist2"

        }

      ]

    },

    {

      "name": "dist1",

      "value": 0,

      "on": [

        {

          "events": {

            "type": "touchstart",

            "filter": "event.touches.length===2"

          },

          "update": "pinchDistance(event)"

        },

        {

          "events": {"signal": "dist2"},

          "update": "dist2"

        }

      ]

    },

    {

      "name": "dist2",

      "value": 0,

      "on": [

        {

          "events": {

            "type": "touchmove",

            "consume": true,

            "filter": "event.touches.length===2"

          },

          "update": "pinchDistance(event)"

        }

      ]

    },

    {

      "name": "initialXDom",

      "update": "data('treeExtents') && length(data('treeExtents')) > 0 ? [data('treeExtents')[0].paddedMinX, data('treeExtents')[0].paddedMaxX] : [0, width]"

    },

    {

      "name": "initialYDom",

      "update": "data('treeExtents') && length(data('treeExtents')) > 0 ? [data('treeExtents')[0].paddedMinY, data('treeExtents')[0].paddedMaxY] : [0, height]"

    },

    {

      "name": "xdom",

      "update": "initialXDom",

      "on": [

        {

          "events": {"signal": "delta"},

          "update": "[xcur[0] + span(xcur) * delta[0] / width, xcur[1] + span(xcur) * delta[0] / width]"

        },

        {

          "events": {"signal": "zoom"},

          "update": "[anchor[0] + (xdom[0] - anchor[0]) * zoom, anchor[0] + (xdom[1] - anchor[0]) * zoom]"

        },

        {

          "events": "dblclick",

          "update": "initialXDom"

        }

      ]

    },

    {

      "name": "ydom",

      "update": "initialYDom",

      "on": [

        {

          "events": {"signal": "delta"},

          "update": "[ycur[0] + span(ycur) * delta[1] / height, ycur[1] + span(ycur) * delta[1] / height]"

        },

        {

          "events": {"signal": "zoom"},

          "update": "[anchor[1] + (ydom[0] - anchor[1]) * zoom, anchor[1] + (ydom[1] - anchor[1]) * zoom]"

        },

        {

          "events": "dblclick",

          "update": "initialYDom"

        }

      ]

    },

    {

      "name": "scaledNodeWidth",

      "update": "(nodeWidth/ span(xdom))*width"

    },

    {

      "name": "scaledNodeHeight",

      "update": "abs(nodeHeight/ span(ydom))*height"

    },

    {

      "name": "scaledFont13",

      "update": "(13/ span(xdom))*width"

    },

    {

      "name": "scaledFont12",

      "update": "(12/ span(xdom))*width"

    },

    {

      "name": "scaledFont11",

      "update": "(11/ span(xdom))*width"

    },

    {

      "name": "scaledKPIHeight",

      "update": "(5/ span(xdom))*width"

    },

    {

      "name": "scaledLimit",

      "update": "(20/ span(xdom))*width"

    }

  ],

  "data": [

    {"name": "dataset"},

    {"name": "source", "source": "dataset"},

    {

      "name": "wideToTall",

      "source": "source",

      "transform": [

        {

          "type": "formula",

          "expr": "{key: datum.level1,parent: null, person:datum.person, kpi:datum.kpi}",

          "as": "l1"

        },

        {

          "type": "formula",

          "expr": "{key: datum.level1+ '/'+datum.level2,parent: datum.level1, person:datum.person, kpi:datum.kpi}",

          "as": "l2"

        },

        {

          "type": "formula",

          "expr": "{key:datum.level1 + '/'+datum.level2+ '/'+datum.level3,parent: datum.level1+ '/'+datum.level2, person:datum.person, kpi:datum.kpi}",

          "as": "l3"

        },

        {

          "type": "formula",

          "expr": "{key:datum.level1 + '/'+datum.level2+ '/'+datum.level3+ '/'+ datum.level4,parent: datum.level1 + '/'+datum.level2+ '/'+datum.level3, person:datum.person, kpi:datum.kpi}",

          "as": "l4"

        },

        {

          "type": "formula",

          "expr": "{key:datum.level1 + '/'+datum.level2+ '/'+datum.level3+ '/'+ datum.level4+ '/'+ datum.level5,parent: datum.level1 + '/'+datum.level2+ '/'+datum.level3+ '/'+ datum.level4, person:datum.person, kpi:datum.kpi}",

          "as": "l5"

        },

        {

          "type": "formula",

          "expr": "{key:datum.level1 + '/'+datum.level2+ '/'+datum.level3+ '/'+ datum.level4+ '/'+ datum.level5 + '/'+ datum.level6,parent: datum.level1 + '/'+datum.level2+ '/'+datum.level3+ '/'+ datum.level4 + '/'+ datum.level5, person:datum.person, kpi:datum.kpi}",

          "as": "l6"

        },

        {

          "type": "formula",

          "expr": "{key:datum.level1 + '/'+datum.level2+ '/'+datum.level3+ '/'+ datum.level4+ '/'+ datum.level5 + '/'+ datum.level6+ '/'+ datum.level7,parent: datum.level1 + '/'+datum.level2+ '/'+datum.level3+ '/'+ datum.level4 + '/'+ datum.level5+ '/'+ datum.level6, person:datum.person, kpi:datum.kpi}",

          "as": "l7"

        },

        {

          "type": "fold",

          "fields": ["l1", "l2", "l3", "l4", "l5", "l6", "l7"]

        },

        {

          "type": "project",

          "fields": ["key", "value"]

        },

        {

          "type": "formula",

          "expr": "datum.value.key",

          "as": "id"

        },

        {

          "type": "formula",

          "expr": "reverse(split(datum.value.key,'/'))[0]",

          "as": "title"

        },

        {

          "type": "formula",

          "expr": "datum.value.parent",

          "as": "parent"

        },

        {

          "type": "filter",

          "expr": "datum.title && trim(datum.title) != '' && datum.title != 'null' && datum.title != 'undefined'"

        },

        {

          "type": "aggregate",

          "groupby": ["id", "parent", "title", "value"]

        },

        {

          "type": "formula",

          "expr": "datum.value.person",

          "as": "person"

        },

        {

          "type": "formula",

          "expr": "datum.value.kpi",

          "as": "kpi"

        }

      ]

    },

    {

      "name": "treeCalcs",

      "source": "wideToTall",

      "transform": [

        {

          "type": "stratify",

          "key": "id",

          "parentKey": "parent"

        },

        {

          "type": "tree",

          "method": {"signal": "'tidy'"},

          "separation": {"signal": "false"},

          "as": ["y", "x", "depth", "children"]

        },

        {

          "as": "parent",

          "type": "formula",

          "expr": "datum.parent"

        }

      ]

    },

    {

      "name": "slicerDescendantsData",

      "source": "treeCalcs",

      "transform": [

        {

          "type": "filter",

          "expr": "slicerSelection && indexof(pluck(treeAncestors('treeCalcs', datum.id), 'id'), slicerSelection) > -1 && datum.id !== slicerSelection"

        }

      ]

    },

    {

      "name": "treeChildren",

      "source": "treeCalcs",

      "transform": [

        {

          "type": "aggregate",

          "groupby": ["parent"],

          "fields": ["parent"],

          "ops": ["values"],

          "as": ["childrenObjects"]

        },

        {

          "type": "formula",

          "expr": "pluck(datum.childrenObjects,'id')",

          "as": "childrenIds"

        }

      ]

    },

    {

      "name": "treeAncestors",

      "source": "treeCalcs",

      "transform": [

        {

          "type": "formula",

          "as": "treeAncestors",

          "expr": "treeAncestors('treeCalcs', datum.id, 'root')"

        },

        {

          "type": "flatten",

          "fields": ["treeAncestors"]

        },

        {

          "type": "formula",

          "expr": "datum.treeAncestors.parent",

          "as": "allParents"

        }

      ]

    },

    {

      "name": "treeChildrenAll",

      "source": "treeAncestors",

      "transform": [

        {

          "type": "project",

          "fields": ["allParents","id","name","parent","x","y","depth","children"]

        },

        {

          "type": "aggregate",

          "fields": ["parent","parent","id"],

          "ops": ["values","count","min"],

          "groupby": ["allParents"],

          "as": ["allChildrenObjects","allChildrenCount","id"]

        },

        {

          "type": "formula",

          "expr": "pluck(datum.allChildrenObjects,'id')",

          "as": "allChildrenIds"

        }

      ]

    },

    {

      "name": "treeClickStoreTemp",

      "source": "treeAncestors",

      "transform": [

        {

          "type": "filter",

          "expr": "startingDepth != -1 ? datum.depth <= startingDepth : node != 0 ? datum.parent == node : false"

        },

        {

          "type": "project",

          "fields": ["id","name","parent","x","y","depth","children"]

        }

      ]

    },

    {

      "name": "treeClickStorePerm",

      "source": "treeClickStoreTemp"

    },

    {

      "name": "treeLayout",

      "source": "wideToTall",

      "transform": [

        {

          "type": "filter",

          "expr": "indata('treeClickStorePerm', 'id', datum.id)"

        },

        {

          "type": "stratify",

          "key": "id",

          "parentKey": "parent"

        },

        {

          "type": "tree",

          "method": {"signal": "'tidy'"},

          "nodeSize": [{"signal": "nodeHeight+verticalNodeGap"}, {"signal": "nodeWidth+horizontalNodeGap"}],

          "separation": {"signal": "false"},

          "as": ["y", "x", "depth", "children"]

        },

        {

          "type": "formula",

          "expr": "datum.y+(height/2)",

          "as": "y"

        },

        {

          "type": "formula",

          "expr": "scale('xscale',datum.x)",

          "as": "xscaled"

        },

        {

          "as": "parent",

          "type": "formula",

          "expr": "datum.parent"

        }

      ]

    },

    {

      "name": "treeExtents",

      "source": "treeLayout",

      "transform": [

        {

          "type": "aggregate",

          "fields": ["x", "x", "y", "y"],

          "ops": ["min", "max", "min", "max"],

          "as": ["minX", "maxX", "minY", "maxY"]

        },

        {

          "type": "formula",

          "expr": "datum.minX - nodeWidth",

          "as": "paddedMinX"

        },

        {

          "type": "formula",

          "expr": "datum.maxX + nodeWidth * 2",

          "as": "paddedMaxX"

        },

        {

          "type": "formula",

          "expr": "datum.minY - nodeHeight",

          "as": "paddedMinY"

        },

        {

          "type": "formula",

          "expr": "datum.maxY + nodeHeight",

          "as": "paddedMaxY"

        }

      ]

    },

    {

      "name": "fullTreeLayout",

      "source": "treeLayout",

      "transform": [

        {

          "type": "lookup",

          "from": "treeChildren",

          "key": "parent",

          "fields": ["id"],

          "values": ["childrenObjects", "childrenIds"]

        },

        {

          "type": "lookup",

          "from": "treeChildrenAll",

          "key": "allParents",

          "fields": ["id"],

          "values": ["allChildrenIds", "allChildrenObjects"]

        },

        {

          "type": "lookup",

          "from": "treeCalcs",

          "key": "id",

          "fields": ["id"],

          "values": ["children"]

        },

        {

          "type": "formula",

          "expr": "reverse(pluck(treeAncestors('treeCalcs', datum.id), 'id'))[1]",

          "as": "treeParent"

        }

      ]

    },

    {

      "name": "visibleNodes",

      "source": "fullTreeLayout",

      "transform": [

        {

          "type": "filter",

          "expr": "indata('treeClickStorePerm', 'id', datum.id)"

        }

      ]

    },

    {

      "name": "maxWidthAndHeight",

      "source": "visibleNodes",

      "transform": [

        {

          "type": "aggregate",

          "groupby": ["depth"],

          "fields": ["depth", "x", "y"],

          "ops": ["count", "max", "max"],

          "as": ["count", "x", "y"]

        },

        {

          "type": "aggregate",

          "fields": ["depth", "count", "x", "y"],

          "ops": ["max", "max", "max", "max"],

          "as": ["maxDepth", "maxNodes", "maxX", "maxY"]

        }

      ]

    },

    {

      "name": "links",

      "source": "treeLayout",

      "transform": [

        {"type": "treelinks"},

        {

          "type": "linkpath",

          "orient": "horizontal",

          "shape": "diagonal",

          "sourceY": {"expr": "scale('yscale', datum.source.y)"},

          "sourceX": {"expr": "scale('xscale', datum.source.x+nodeWidth)"},

          "targetY": {"expr": "scale('yscale', datum.target.y)"},

          "targetX": {"expr": "scale('xscale', datum.target.x)"}

        },

        {

          "type": "filter",

          "expr": "indata('treeClickStorePerm', 'id', datum.target.id)"

        }

      ]

    }

  ],

  "scales": [

    {

      "name": "xscale",

      "zero": false,

      "domain": {"signal": "xdom"},

      "range": {"signal": "xrange"}

    },

    {

      "name": "yscale",

      "zero": false,

      "domain": {"signal": "ydom"},

      "range": {"signal": "yrange"}

    },

    {

      "name": "kpiscale",

      "zero": false,

      "domain": [0, 100],

      "range": {"signal": "[0,scaledNodeWidth]"}

    },

    {

      "name": "colour",

      "type": "ordinal",

      "range": [

        "#6f6f6f",

        "#4472C4",

        "#3A8E50",

        "#ED7D31",

        "#a63939",

        "#6338a6",

        "#3843a6",

        "#38a695"

      ],

      "domain": {"data": "visibleNodes", "field": "treeParent"}

    }

  ],

  "marks": [

    {

      "type": "path",

      "interactive": false,

      "from": {"data": "links"},

      "encode": {

        "update": {

          "path": {"field": "path"},

          "strokeWidth": {

            "signal": "slicerSelection && slicerSelection !== '' ? (indexof(slicerHighlightPath, datum.target.id) > -1 ? 2 : 1) : 1"

          },

          "stroke": {

            "scale": "colour",

            "signal": "reverse(pluck(treeAncestors('treeCalcs', datum.target.id), 'id'))[1]"

          }

        }

      }

    },

    {

      "name": "node",

      "description": "The parent node",

      "type": "group",

      "clip": false,

      "from": {"data": "visibleNodes"},

      "encode": {

        "update": {

          "x": {"field": "x", "scale": "xscale"},

          "width": {"signal": "scaledNodeWidth"},

          "yc": {"field": "y", "scale": "yscale"},

          "height": {"signal": "scaledNodeHeight"},

          "fill": {

            "signal": "slicerSelection && slicerSelection !== '' ? (datum.id === slicerSelection ? '#d4edda' : indexof(slicerParentPath, datum.id) > -1 ? '#f8d7da' : indexof(slicerDescendantsPath, datum.id) > -1 ? '#fff3cd' : '#ffffff') : '#eee'"

          },

          "stroke": {

            "signal": "slicerSelection && slicerSelection !== '' ? (datum.id === slicerSelection ? '#155724' : indexof(slicerParentPath, datum.id) > -1 ? '#721c24' : indexof(slicerDescendantsPath, datum.id) > -1 ? '#856404' : '#ccc') : '#999'"

          },

          "cornerRadius": {"value": 2},

          "cursor": {"signal": "datum.children>0?'pointer':''"},

          "tooltip": {"signal": "datum.title"},

          "zindex": {"value": 1}

        }

      },

      "marks": [

        {

          "name": "title",

          "type": "text",

          "interactive": false,

          "encode": {

            "update": {

              "x": {"signal": "(10/ span(xdom))*width"},

              "y": {"signal": "(6/ span(xdom))*width"},

              "fontWeight": {"value": "600"},

              "baseline": {"value": "top"},

              "fill": {"scale": "colour", "signal": "datum.treeParent"},

              "text": {"signal": "datum.title"},

              "fontSize": {"signal": "scaledFont13"},

              "limit": {"signal": "scaledNodeWidth-scaledLimit"},

              "font": {"value": "Calibri"}

            }

          }

        },

        {

          "name": "name",

          "type": "text",

          "interactive": false,

          "encode": {

            "update": {

              "x": {"signal": "(10/ span(xdom))*width"},

              "y": {"signal": "(22/ span(xdom))*width"},

              "align": {"value": "left"},

              "baseline": {"value": "top"},

              "fill": {"value": "#4D4B44"},

              "text": {"signal": "datum.depth == 0 ? '' : datum.person"},

              "fontSize": {"signal": "scaledFont11"},

              "limit": {"signal": "scaledNodeWidth-scaledLimit"},

              "font": {"value": "Calibri"}

            }

          }

        },

        {

          "name": "node children",

          "type": "text",

          "interactive": false,

          "encode": {

            "update": {

              "x": {"signal": "item.mark.group.width - (9/ span(xdom))*width"},

              "y": {"signal": "item.mark.group.height/2"},

              "align": {"value": "right"},

              "baseline": {"value": "middle"},

              "fill": {"scale": "colour", "signal": "datum.treeParent"},

              "text": {"signal": "datum.children>0?datum.children:''"},

              "fontSize": {"signal": "scaledFont12"},

              "font": {"value": "Calibri"}

            }

          }

        }

      ]

    }

  ]

}

!!

No comments:

Post a Comment