Vega-lite 中的桑基图(冲积图)

Chr*_*ian 5 json visualization vega sankey-diagram vega-lite

有人知道如何创建像 Vega-lite 中那样的 Sankey 图吗?

https://observablehq.com/@d3/sankey-diagram 在此输入图像描述

输入将是这样的数据

From | To | Amount

dav*_*cci 5

使用 Yuri Astrakhan 的博客文章,我在 Vega 中创建了以下内容。

\n

在此输入图像描述

\n
{\n  "$schema": "https://vega.github.io/schema/vega/v5.2.json",\n  "height": 300,\n  "width": 600,\n  "data": [\n    {\n      "name": "rawData",\n      "values": [\n        {"key": {"stk1": "aa", "stk2": "cc"}, "doc_count": 7},\n        {"key": {"stk1": "aa", "stk2": "bb"}, "doc_count": 4},\n        {"key": {"stk1": "bb", "stk2": "aa"}, "doc_count": 8},\n        {"key": {"stk1": "bb", "stk2": "bb"}, "doc_count": 6},\n        {"key": {"stk1": "bb", "stk2": "cc"}, "doc_count": 3},\n        {"key": {"stk1": "cc", "stk2": "aa"}, "doc_count": 9}\n      ],\n      "transform": [\n        {"type": "formula", "expr": "datum.key.stk1", "as": "stk1"},\n        {"type": "formula", "expr": "datum.key.stk2", "as": "stk2"},\n        {"type": "formula", "expr": "datum.doc_count", "as": "size"}\n      ]\n    },\n    {\n      "name": "nodes",\n      "source": "rawData",\n      "transform": [\n        {\n          "type": "filter",\n          "expr": "!groupSelector || groupSelector.stk1 == datum.stk1 || groupSelector.stk2 == datum.stk2"\n        },\n        {"type": "formula", "expr": "datum.stk1+datum.stk2", "as": "key"},\n        {"type": "fold", "fields": ["stk1", "stk2"], "as": ["stack", "grpId"]},\n        {\n          "type": "formula",\n          "expr": "datum.stack == \'stk1\' ? datum.stk1+\' \'+datum.stk2 : datum.stk2+\' \'+datum.stk1",\n          "as": "sortField"\n        },\n        {\n          "type": "stack",\n          "groupby": ["stack"],\n          "sort": {"field": "sortField", "order": "descending"},\n          "field": "size"\n        },\n        {"type": "formula", "expr": "(datum.y0+datum.y1)/2", "as": "yc"}\n      ]\n    },\n    {\n      "name": "groups",\n      "source": "nodes",\n      "transform": [\n        {\n          "type": "aggregate",\n          "groupby": ["stack", "grpId"],\n          "fields": ["size"],\n          "ops": ["sum"],\n          "as": ["total"]\n        },\n        {\n          "type": "stack",\n          "groupby": ["stack"],\n          "sort": {"field": "grpId", "order": "descending"},\n          "field": "total"\n        },\n        {"type": "formula", "expr": "scale(\'y\', datum.y0)", "as": "scaledY0"},\n        {"type": "formula", "expr": "scale(\'y\', datum.y1)", "as": "scaledY1"},\n        {\n          "type": "formula",\n          "expr": "datum.stack == \'stk1\'",\n          "as": "rightLabel"\n        },\n        {\n          "type": "formula",\n          "expr": "datum.total/domain(\'y\')[1]",\n          "as": "percentage"\n        }\n      ]\n    },\n    {\n      "name": "destinationNodes",\n      "source": "nodes",\n      "transform": [{"type": "filter", "expr": "datum.stack == \'stk2\'"}]\n    },\n    {\n      "name": "edges",\n      "source": "nodes",\n      "transform": [\n        {"type": "filter", "expr": "datum.stack == \'stk1\'"},\n        {\n          "type": "lookup",\n          "from": "destinationNodes",\n          "key": "key",\n          "fields": ["key"],\n          "as": ["target"]\n        },\n        {\n          "type": "linkpath",\n          "orient": "horizontal",\n          "shape": "diagonal",\n          "sourceY": {"expr": "scale(\'y\', datum.yc)"},\n          "sourceX": {"expr": "scale(\'x\', \'stk1\') + bandwidth(\'x\')"},\n          "targetY": {"expr": "scale(\'y\', datum.target.yc)"},\n          "targetX": {"expr": "scale(\'x\', \'stk2\')"}\n        },\n        {\n          "type": "formula",\n          "expr": "range(\'y\')[0]-scale(\'y\', datum.size)",\n          "as": "strokeWidth"\n        },\n        {\n          "type": "formula",\n          "expr": "datum.size/domain(\'y\')[1]",\n          "as": "percentage"\n        }\n      ]\n    }\n  ],\n  "scales": [\n    {\n      "name": "x",\n      "type": "band",\n      "range": "width",\n      "domain": ["stk1", "stk2"],\n      "paddingOuter": 0.05,\n      "paddingInner": 0.95\n    },\n    {\n      "name": "y",\n      "type": "linear",\n      "range": "height",\n      "domain": {"data": "nodes", "field": "y1"}\n    },\n    {\n      "name": "color",\n      "type": "ordinal",\n      "range": "category",\n      "domain": {"data": "rawData", "field": "stk1"}\n    },\n    {\n      "name": "stackNames",\n      "type": "ordinal",\n      "range": ["Source", "Destination"],\n      "domain": ["stk1", "stk2"]\n    }\n  ],\n  "axes": [\n    {\n      "orient": "bottom",\n      "scale": "x",\n      "encode": {\n        "labels": {\n          "update": {"text": {"scale": "stackNames", "field": "value"}}\n        }\n      }\n    },\n    {"orient": "left", "scale": "y"}\n  ],\n  "marks": [\n    {\n      "type": "path",\n      "name": "edgeMark",\n      "from": {"data": "edges"},\n      "clip": true,\n      "encode": {\n        "update": {\n          "stroke": [\n            {\n              "test": "groupSelector && groupSelector.stack==\'stk1\'",\n              "scale": "color",\n              "field": "stk2"\n            },\n            {"scale": "color", "field": "stk1"}\n          ],\n          "strokeWidth": {"field": "strokeWidth"},\n          "path": {"field": "path"},\n          "strokeOpacity": {\n            "signal": "!groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 0.9 : 0.3"\n          },\n          "zindex": {\n            "signal": "!groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 1 : 0"\n          },\n          "tooltip": {\n            "signal": "datum.stk1 + \' \xe2\x86\x92 \' + datum.stk2 + \'    \' + format(datum.size, \',.0f\') + \'   (\' + format(datum.percentage, \'.1%\') + \')\'"\n          }\n        },\n        "hover": {"strokeOpacity": {"value": 1}}\n      }\n    },\n    {\n      "type": "rect",\n      "name": "groupMark",\n      "from": {"data": "groups"},\n      "encode": {\n        "enter": {\n          "fill": {"scale": "color", "field": "grpId"},\n          "width": {"scale": "x", "band": 1}\n        },\n        "update": {\n          "x": {"scale": "x", "field": "stack"},\n          "y": {"field": "scaledY0"},\n          "y2": {"field": "scaledY1"},\n          "fillOpacity": {"value": 0.6},\n          "tooltip": {\n            "signal": "datum.grpId + \'   \' + format(datum.total, \',.0f\') + \'   (\' + format(datum.percentage, \'.1%\') + \')\'"\n          }\n        },\n        "hover": {"fillOpacity": {"value": 1}}\n      }\n    },\n    {\n      "type": "text",\n      "from": {"data": "groups"},\n      "interactive": false,\n      "encode": {\n        "update": {\n          "x": {\n            "signal": "scale(\'x\', datum.stack) + (datum.rightLabel ? bandwidth(\'x\') + 8 : -8)"\n          },\n          "yc": {"signal": "(datum.scaledY0 + datum.scaledY1)/2"},\n          "align": {"signal": "datum.rightLabel ? \'left\' : \'right\'"},\n          "baseline": {"value": "middle"},\n          "fontWeight": {"value": "bold"},\n          "text": {\n            "signal": "abs(datum.scaledY0-datum.scaledY1) > 13 ? datum.grpId : \'\'"\n          }\n        }\n      }\n    },\n    {\n      "type": "group",\n      "data": [\n        {\n          "name": "dataForShowAll",\n          "values": [{}],\n          "transform": [{"type": "filter", "expr": "groupSelector"}]\n        }\n      ],\n      "encode": {\n        "enter": {\n          "xc": {"signal": "width/2"},\n          "y": {"value": 30},\n          "width": {"value": 80},\n          "height": {"value": 30}\n        }\n      },\n      "marks": [\n        {\n          "type": "group",\n          "name": "groupReset",\n          "from": {"data": "dataForShowAll"},\n          "encode": {\n            "enter": {\n              "cornerRadius": {"value": 6},\n              "fill": {"value": "#f5f5f5"},\n              "stroke": {"value": "#c1c1c1"},\n              "strokeWidth": {"value": 2},\n              "height": {"field": {"group": "height"}},\n              "width": {"field": {"group": "width"}}\n            },\n            "update": {"opacity": {"value": 1}},\n            "hover": {"opacity": {"value": 0.7}}\n          },\n          "marks": [\n            {\n              "type": "text",\n              "interactive": false,\n              "encode": {\n                "enter": {\n                  "xc": {"field": {"group": "width"}, "mult": 0.5},\n                  "yc": {\n                    "field": {"group": "height"},\n                    "mult": 0.5,\n                    "offset": 2\n                  },\n                  "align": {"value": "center"},\n                  "baseline": {"value": "middle"},\n                  "fontWeight": {"value": "bold"},\n                  "text": {"value": "Show All"}\n                }\n              }\n            }\n          ]\n        }\n      ]\n    },\n    {\n      "type": "rect",\n      "from": {"data": "nodes"},\n      "encode": {\n        "enter": {\n          "stroke": {"value": "#000"},\n          "strokeWidth": {"value": 2},\n          "width": {"scale": "x", "band": 1},\n          "x": {"scale": "x", "field": "stack"},\n          "y": {"field": "y0", "scale": "y"},\n          "y2": {"field": "y1", "scale": "y"}\n        }\n      }\n    }\n  ],\n  "signals": [\n    {\n      "name": "groupHover",\n      "value": {},\n      "on": [\n        {\n          "events": "@groupMark:mouseover",\n          "update": "{stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}"\n        },\n        {"events": "mouseout", "update": "{}"}\n      ]\n    },\n    {\n      "name": "groupSelector",\n      "value": false,\n      "on": [\n        {\n          "events": "@groupMark:click!",\n          "update": "{stack:datum.stack, stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}"\n        },\n        {\n          "events": [\n            {"type": "click", "markname": "groupReset"},\n            {"type": "dblclick"}\n          ],\n          "update": "false"\n        }\n      ]\n    }\n  ]\n}\n
Run Code Online (Sandbox Code Playgroud)\n