Reorderable Matrix Example
Matrix diagrams visualize a network by treating nodes as rows and columns of a table; cells are colored in if an edge exists between two nodes. This example depicts character co-occurrences in Victor Hugo’s Les Misérables. The underlying data is an undirected graph, and so the matrix is symmetric around the diagonal. The matrix is also reorderable: grab a node label to rearrange rows and columns!
Vega JSON Specification <>
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A re-orderable adjacency matrix depicting character co-occurrence in the novel Les Misérables.",
"width": 770,
"height": 770,
"padding": 2,
"signals": [
{ "name": "cellSize", "value": 10 },
{ "name": "count", "update": "length(data('nodes'))" },
{ "name": "width", "update": "span(range('position'))" },
{ "name": "height", "update": "width" },
{
"name": "src", "value": {},
"on": [
{"events": "text:pointerdown", "update": "datum"},
{"events": "window:pointerup", "update": "{}"}
]
},
{
"name": "dest", "value": -1,
"on": [
{
"events": "[@columns:pointerdown, window:pointerup] > window:pointermove",
"update": "src.name && datum !== src ? (0.5 + count * clamp(x(), 0, width) / width) : dest"
},
{
"events": "[@rows:pointerdown, window:pointerup] > window:pointermove",
"update": "src.name && datum !== src ? (0.5 + count * clamp(y(), 0, height) / height) : dest"
},
{"events": "window:pointerup", "update": "-1"}
]
}
],
"data": [
{
"name": "nodes",
"url": "data/miserables.json",
"format": {"type": "json", "property": "nodes"},
"transform": [
{
"type": "formula", "as": "order",
"expr": "datum.group"
},
{
"type": "formula", "as": "score",
"expr": "dest >= 0 && datum === src ? dest : datum.order"
},
{
"type": "window", "sort": {"field": "score"},
"ops": ["row_number"], "as": ["order"]
}
]
},
{
"name": "edges",
"url": "data/miserables.json",
"format": {"type": "json", "property": "links"},
"transform": [
{
"type": "lookup", "from": "nodes", "key": "index",
"fields": ["source", "target"], "as": ["sourceNode", "targetNode"]
},
{
"type": "formula", "as": "group",
"expr": "datum.sourceNode.group === datum.targetNode.group ? datum.sourceNode.group : count"
}
]
},
{
"name": "cross",
"source": "nodes",
"transform": [
{ "type": "cross" }
]
}
],
"scales": [
{
"name": "position",
"type": "band",
"domain": {"data": "nodes", "field": "order", "sort": true},
"range": {"step": {"signal": "cellSize"}}
},
{
"name": "color",
"type": "ordinal",
"range": "category",
"domain": {
"fields": [
{"data": "nodes", "field": "group"},
{"signal": "count"}
],
"sort": true
}
}
],
"marks": [
{
"type": "rect",
"from": {"data": "cross"},
"encode": {
"update": {
"x": {"scale": "position", "field": "a.order"},
"y": {"scale": "position", "field": "b.order"},
"width": {"scale": "position", "band": 1, "offset": -1},
"height": {"scale": "position", "band": 1, "offset": -1},
"fill": [
{"test": "datum.a === src || datum.b === src", "value": "#ddd"},
{"value": "#f5f5f5"}
]
}
}
},
{
"type": "rect",
"from": {"data": "edges"},
"encode": {
"update": {
"x": {"scale": "position", "field": "sourceNode.order"},
"y": {"scale": "position", "field": "targetNode.order"},
"width": {"scale": "position", "band": 1, "offset": -1},
"height": {"scale": "position", "band": 1, "offset": -1},
"fill": {"scale": "color", "field": "group"}
}
}
},
{
"type": "rect",
"from": {"data": "edges"},
"encode": {
"update": {
"x": {"scale": "position", "field": "targetNode.order"},
"y": {"scale": "position", "field": "sourceNode.order"},
"width": {"scale": "position", "band": 1, "offset": -1},
"height": {"scale": "position", "band": 1, "offset": -1},
"fill": {"scale": "color", "field": "group"}
}
}
},
{
"type": "text",
"name": "columns",
"from": {"data": "nodes"},
"encode": {
"update": {
"x": {"scale": "position", "field": "order", "band": 0.5},
"y": {"offset": -2},
"text": {"field": "name"},
"fontSize": {"value": 10},
"angle": {"value": -90},
"align": {"value": "left"},
"baseline": {"value": "middle"},
"fill": [
{"test": "datum === src", "value": "steelblue"},
{"value": "black"}
]
}
}
},
{
"type": "text",
"name": "rows",
"from": {"data": "nodes"},
"encode": {
"update": {
"x": {"offset": -2},
"y": {"scale": "position", "field": "order", "band": 0.5},
"text": {"field": "name"},
"fontSize": {"value": 10},
"align": {"value": "right"},
"baseline": {"value": "middle"},
"fill": [
{"test": "datum === src", "value": "steelblue"},
{"value": "black"}
]
}
}
}
]
}