Does anyone know if it is possible to convert this vega spec from sanddance into a vega-lite spec? I really like the idea of every data point being a separate box so you can hover to get the information.
In my case I want to create a new chart where my main groups are resources and each work orders will be colored by status. So for the current month you can see which work orders are overdue, postponed, completed, cancelled etc. I will demo this later.
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"data": [
{
"name": "input",
"url": "https://microsoft.github.io/SandDance/sample-data/titanicmaster.tsv",
"format": {"parse": "auto", "type": "tsv", "delimiter": "\t"}
},
{"name": "data_source", "source": "input", "transform": []},
{
"name": "data_topcolorlookup",
"source": "data_source",
"transform": [
{"type": "aggregate", "groupby": ["Class"]},
{"type": "window", "ops": ["count"], "as": ["__SandDance__TopIndex"]},
{"type": "filter", "expr": "datum[\"__SandDance__TopIndex\"] <= 19"}
]
},
{
"name": "data_legend",
"source": "data_source",
"transform": [
{
"type": "lookup",
"from": "data_topcolorlookup",
"key": "Class",
"fields": ["Class"],
"values": ["Class"],
"as": ["__SandDance__TopColor"]
},
{
"type": "formula",
"expr": "datum[\"__SandDance__TopColor\"] == null ? '__Other' : datum[\"__SandDance__TopColor\"]",
"as": "__SandDance__TopColor"
},
{
"type": "joinaggregate",
"groupby": ["Department"],
"ops": ["count"],
"as": ["agg_1_aggregate_value"]
},
{
"type": "extent",
"field": "agg_1_aggregate_value",
"signal": "agg_1_count_extent"
},
{
"type": "stack",
"groupby": ["Department"],
"as": ["square_2_stack0", "square_2_stack1"],
"sort": {"field": "Class", "order": "ascending"}
}
]
},
{
"name": "band_0_accumulative",
"source": "data_legend",
"transform": [
{"type": "aggregate", "groupby": ["Department"], "ops": ["count"]}
]
}
],
"marks": [
{
"type": "group",
"encode": {
"update": {
"x": {"signal": "PlotOffsetLeft - RoleFacet_AxesAdjustSignalX"},
"y": {"signal": "PlotOffsetTop"},
"height": {"signal": "PlotHeightOut - RoleFacet_AxesAdjustSignalY"},
"width": {"signal": "PlotWidthOut + RoleFacet_AxesAdjustSignalX"}
}
},
"marks": [
{
"name": "square_2",
"type": "rect",
"from": {"data": "output"},
"encode": {
"update": {
"height": {
"signal": "(((band_0_bandwidth) / ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled))))) - min(0.1 * ((band_0_bandwidth) / (ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled)))) - 1)), 1))"
},
"width": [
{"test": "datum.__SandDance__Collapsed", "value": 0},
{"test": "datum.__SandDance__Collapsed", "value": 0},
{
"signal": "(((aggMaxExtentScaled) / ceil(aggMaxExtent / ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled)))))) - min(0.1 * ((band_0_bandwidth) / (ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled)))) - 1)), 1))"
}
],
"z": {"value": 0},
"depth": [
{"test": "datum.__SandDance__Collapsed", "value": 0},
{"value": 0}
],
"x": [
{"test": "datum.__SandDance__Collapsed", "signal": "0"},
{"test": "datum.__SandDance__Collapsed", "signal": "0"},
{"field": "__SandDance__X"}
],
"y": {"field": "__SandDance__Y"},
"fill": {
"scale": "scale_color",
"field": "__SandDance__TopColor"
},
"opacity": {"signal": "Mark_OpacitySignal"}
}
}
}
],
"axes": [
{
"scale": "scale_band_0_y",
"orient": "left",
"domain": true,
"ticks": true,
"domainColor": "#0b0b0b",
"tickColor": "#0b0b0b",
"tickSize": 10,
"title": "Department",
"titleAlign": "right",
"titleAngle": {"signal": "Text_AngleYSignal"},
"titleColor": "#0b0b0b",
"titleFontSize": {"signal": "Text_TitleSizeSignal"},
"titleLimit": 100,
"titlePadding": 60,
"labels": true,
"labelAlign": "right",
"labelBaseline": "middle",
"labelAngle": {"signal": "Text_AngleYSignal"},
"labelColor": "#0b0b0b",
"labelFontSize": {"signal": "Text_SizeSignal"},
"labelLimit": 100
},
{
"scale": "scale_agg_1",
"orient": "bottom",
"domain": true,
"ticks": true,
"domainColor": "#0b0b0b",
"tickColor": "#0b0b0b",
"tickSize": 10,
"title": "Count",
"titleAlign": "left",
"titleAngle": {"signal": "Text_AngleXSignal"},
"titleColor": "#0b0b0b",
"titleFontSize": {"signal": "Text_TitleSizeSignal"},
"titleLimit": 100,
"titlePadding": 30,
"labels": true,
"labelAlign": "left",
"labelBaseline": "top",
"labelAngle": {"signal": "Text_AngleXSignal"},
"labelColor": "#0b0b0b",
"labelFontSize": {"signal": "Text_SizeSignal"},
"labelLimit": 100
}
],
"data": [
{
"name": "output",
"source": "data_legend",
"transform": [
{
"type": "formula",
"expr": "0 + floor((datum[\"square_2_stack0\"]) / ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled))))) * ((((aggMaxExtentScaled) / ceil(aggMaxExtent / ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled)))))) - min(0.1 * ((band_0_bandwidth) / (ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled)))) - 1)), 1)) + min(0.1 * ((band_0_bandwidth) / (ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled)))) - 1)), 1))",
"as": "__SandDance__X"
},
{
"type": "formula",
"expr": "0 + scale(\"scale_band_0_y\", datum[\"Department\"]) + (band_0_bandwidth) / ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled)))) * ((datum[\"square_2_stack0\"]) % ceil(sqrt(aggMaxExtent * ((band_0_bandwidth) / (aggMaxExtentScaled)))))",
"as": "__SandDance__Y"
}
]
}
]
}
],
"signals": [
{
"name": "RoleZ_ProportionSignal",
"value": 0.6,
"bind": {
"name": "Z scale proportion to Y",
"debounce": 250,
"input": "range",
"min": 0.1,
"max": 2,
"step": 0.1
}
},
{
"name": "RoleZ_HeightSignal",
"update": "ViewportHeight * RoleZ_ProportionSignal"
},
{
"name": "Text_ScaleSignal",
"value": 1.2,
"bind": {
"name": "Text scale",
"debounce": 250,
"input": "range",
"min": 0.5,
"max": 2,
"step": 0.1
}
},
{"name": "Text_SizeSignal", "update": "Text_ScaleSignal * 10"},
{"name": "Text_TitleSizeSignal", "update": "Text_ScaleSignal * 15"},
{"name": "Text_AngleXSignal", "value": 30},
{"name": "Text_AngleYSignal", "value": 0},
{"name": "Mark_OpacitySignal", "value": 1},
{"name": "MinCellWidth", "update": "140"},
{
"name": "MinCellHeight",
"update": "max((180), (length(data(\"band_0_accumulative\")) * 15))"
},
{"name": "ViewportHeight", "update": "max(MinCellHeight, 1253)"},
{"name": "ViewportWidth", "update": "max(MinCellWidth, 2252)"},
{"name": "PlotOffsetLeft", "update": "120"},
{"name": "PlotOffsetTop", "update": "0"},
{"name": "PlotOffsetBottom", "update": "120"},
{"name": "PlotOffsetRight", "update": "0"},
{"name": "RoleFacet_AxesAdjustSignalX", "update": "0"},
{"name": "RoleFacet_AxesAdjustSignalY", "update": "0"},
{
"name": "PlotHeightIn",
"update": "ViewportHeight - PlotOffsetBottom + RoleFacet_AxesAdjustSignalY"
},
{
"name": "PlotWidthIn",
"update": "ViewportWidth - PlotOffsetLeft - PlotOffsetRight"
},
{"name": "PlotHeightOut", "update": "PlotHeightIn"},
{"name": "PlotWidthOut", "update": "PlotWidthIn"},
{
"name": "height",
"update": "PlotOffsetTop + PlotHeightOut + PlotOffsetBottom - RoleFacet_AxesAdjustSignalY"
},
{
"name": "width",
"update": "PlotWidthOut + PlotOffsetLeft + PlotOffsetRight"
},
{
"name": "RoleColor_BinCountSignal",
"value": 7,
"bind": {
"name": "Color bin count",
"debounce": 250,
"input": "range",
"min": 1,
"max": 20,
"step": 1
}
},
{
"name": "RoleColor_ReverseSignal",
"value": false,
"bind": {"name": "Color reverse", "input": "checkbox"}
},
{"name": "band_0_bandwidth", "update": "bandwidth(\"scale_band_0_y\")"},
{"name": "aggMaxExtent", "update": "agg_1_count_extent[1]"},
{
"name": "aggMaxExtentScaled",
"update": "scale(\"scale_agg_1\", aggMaxExtent)"
}
],
"legends": [
{
"orient": "none",
"title": "Class",
"fill": "scale_color",
"encode": {"symbols": {"update": {"shape": {"value": "square"}}}}
}
],
"scales": [
{
"name": "scale_color",
"type": "ordinal",
"domain": {
"data": "data_legend",
"field": "__SandDance__TopColor",
"sort": true
},
"range": {"scheme": "category10"},
"reverse": {"signal": "RoleColor_ReverseSignal"}
},
{
"type": "band",
"name": "scale_band_0_y",
"range": [0, {"signal": "PlotHeightIn"}],
"padding": 0.1,
"domain": {"data": "data_legend", "field": "Department", "sort": true},
"reverse": true
},
{
"type": "linear",
"name": "scale_agg_1",
"domain": [0, {"signal": "aggMaxExtent"}],
"range": [0, {"signal": "PlotWidthIn"}],
"nice": true,
"zero": true,
"reverse": false
},
{
"name": "scale_square_2_z",
"type": "linear",
"range": [0, {"signal": "(PlotHeightIn) * RoleZ_ProportionSignal"}],
"round": true,
"reverse": false,
"domain": {"data": "data_legend", "field": "TicketCost"},
"zero": true,
"nice": true
}
],
"config": {}
}
I managed. Here is the code if anyone wants to contribute and improve. I think it is very interesting to see the unique boxes to illustrate actual records. When you hover you get a detailed tool-tip about that specific record for example.
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {"url": "data/cars.json"},
"transform": [
{
"joinaggregate": [{"op": "count", "as": "Count"}],
"groupby": ["Cylinders", "Origin"]
},
{
"joinaggregate": [{"op": "sum", "field": "Count", "as": "OriginCount"}],
"groupby": ["Origin"]
}
],
"facet": {
"row": {
"field": "Origin",
"sort": {"field": "OriginCount", "order": "descending"},
"header": {
"title": "",
"labelAngle": 0,
"labelPadding": 15,
"labelAlign": "left",
"labelFontSize": 12
}
}
},
"spacing": 5,
"spec": {
"width": 300,
"height": 100,
"layer": [
{
"transform": [
{
"window": [{"op": "row_number", "as": "id"}],
"sort": [{"field": "Cylinders", "order": "ascending"}],
"groupby": ["Origin"]
},
{"calculate": "ceil(datum.id / 10)", "as": "row"},
{"calculate": "datum.id - datum.row * 10", "as": "col"}
],
"mark": {
"type": "rect",
"stroke": "#fff",
"strokeWidth": 1,
"filled": true,
"tooltip": true
},
"encoding": {
"y": {"field": "col", "type": "ordinal", "axis": null},
"x": {"field": "row", "type": "ordinal", "axis": null},
"color": {
"field": "Cylinders",
"type": "nominal",
"scale": {"scheme": "set1"}
},
"tooltip": [
{"field": "Origin", "type": "nominal"},
{"field": "Name", "type": "nominal"},
{"field": "Cylinders", "type": "nominal"},
{"field": "Horsepower", "type": "nominal"},
{"field": "id", "type": "nominal"}
]
}
},
{
"transform": [
{
"window": [{"op": "row_number", "as": "id"}],
"sort": [{"field": "Cylinders", "order": "ascending"}],
"groupby": ["Origin"]
},
{"calculate": "ceil(datum.id / 10)", "as": "row"},
{"calculate": "datum.id - datum.row * 10", "as": "col"},
{
"joinaggregate": [{"op": "max", "field": "id", "as": "maxId"}],
"groupby": ["Origin"]
},
{"filter": "datum.id === datum.maxId"}
],
"mark": {
"type": "text",
"align": "left",
"baseline": "middle",
"dx": 15,
"color": "black"
},
"encoding": {
"text": {"field": "id", "type": "nominal"},
"x": {"field": "row", "type": "ordinal", "axis": null},
"tooltip": [
{"field": "Origin", "type": "nominal"},
{"field": "Name", "type": "nominal"},
{"field": "Cylinders", "type": "nominal"},
{"field": "id", "type": "nominal"}
]
}
}
]
},
"config": {"view": {"stroke": "transparent"}}
}
We can also do interesting things like color based on horsepower.
Or by Miles per gallon. Here we see Japan has the highest percentage of efficient vehicles.
We can even add additional marks to represent other things. Below I have added some marks for priority and criticality.
Lots of possibilities here...
Happy Charting!
Adam