{
"$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"}
}
}
}
]
}
]
}