Forum Discussion
Deneb / Vega - Variable Width Cummulative Bar Chart
I have been working on this problem myself and this that I have found a reasonable solution to creating a variable width bar/column chart where the area of the bar/column is meaningful. The Vega-Lite JSON with a dummy dataset is below.
{
"data": {
"values": [
{"vWidth": 10, "vHeight": 1, "vType": "A"},
{"vWidth": 20, "vHeight": 2, "vType": "B"},
{"vWidth": 1, "vHeight": 3, "vType": "C"},
{"vWidth": 5, "vHeight": 4, "vType": "D"}
]
},
"transform": [
{
"calculate": "datum.vWidth*datum.vHeight", "as": "cArea"
},
{
"window": [{"op": "sum", "field": "vWidth", "as": "cum_total"}],
"sort": [{"field": "cArea", "order": "descending"}],
"frame": [null, 0]
},
{
"window": [{"op": "first_value", "field": "cum_total", "as": "shift"}],
"sort": [{"field": "cArea", "order": "descending"}],
"frame": [-1, 0]
},
{
"calculate": "datum.cum_total === datum.shift ? 0 : datum.shift", "as": "leftedge"
}
],
"mark": {"type": "rect"},
"encoding": {
"y": {
"field": "vHeight",
"type": "quantitative"
},
"x": {
"field": "cum_total",
"type": "quantitative"
},
"x2": {
"field": "leftedge"
},
"color": {
"field": "vType"
}
}
}
The data is presented as a width and height value with a label.
I made use of 4 transforms.
- Calculate the area (width x height) so I could use if for sorting. I don't think you need this, but could be helpful as a reference.
- Create a cumulative sum (with required sorting) to determine the right edge of each bar ("cum_total").
- Create a window to look at the immediately preceeding value (same sorting) to determine the left edge of each bar ("shift"). This is essentially a rightward shift of the data. However, the first value will be filled in with a duplicate of its own value.
- Since the first value has a zero-width bar, the left edge needs to be moved to the zero of the chart. The last calculation just replaces that left edge with a 0 based on the shifted value and cumulative sum being the same. My method will cause problems if any of your width values are supposed to be zero.
The resultant chart from the above is below.
Hope that helps! I'm just getting into Vega-Lite myself and this is a chart type that I've been trying to create for a while.
- lbendlin2 years ago
Super User
Anonymous very nice! Just in time for a capacity metrics report visual I am working on. Thank you!
- Anonymous2 years agoNot applicable
Thank you for the kudos. Glad it could help you out! After refining a bit more, I found that I could come closer to the OP's code and simplify the process by finding the left edge by subtracting the width from the cumulative total. I have also added data labels and tooltips, but that is beyond what might be needed.
{ "data": { "values": [ {"vWidth": 10, "vHeight": 1, "vType": "A"}, {"vWidth": 20, "vHeight": 2, "vType": "B"}, {"vWidth": 1, "vHeight": 3, "vType": "C"}, {"vWidth": 5, "vHeight": 4, "vType": "D"} ] }, "transform": [ { "calculate": "datum.vWidth*datum.vHeight", "as": "cArea" }, { "window": [{"op": "sum", "field": "vWidth", "as": "cum_total"}], "sort": [{"field": "cArea", "order": "descending"}], "frame": [null, 0] }, { "calculate": "datum.cum_total - datum.vWidth", "as": "leftedge" } ], "encoding": { "y": { "field": "vHeight", "type": "quantitative" }, "x": { "field": "cum_total", "type": "quantitative" }, "x2": { "field": "leftedge" }, "color": { "field": "vType" } }, "layer": [ { "mark": { "type": "rect", "tooltip": true } }, { "mark": { "type": "text", "align": "right", "baseline": "bottom", "dx": -5, "dy": -5 }, "encoding": { "text": { "field": "cArea" } } } ] }- lbendlin2 years ago
Super User
Thank you for the simplification. Already implemented in my capacity monitoring report
{ "data": {"name": "dataset"}, "transform": [ { "calculate": "datum.ItemName + pbiFormat(datum['CU (s)'],' (#.00 -')+pbiFormat(datum['% Cap'],' #.0%)')", "as": "CUs" }, { "calculate": "now()+timezoneoffset(now())*60000", "as": "now_UTC" }, { "window": [{"op": "sum", "field": "CU (s)", "as": "cum_total"}], "sort": [{"field": "CU (s)", "order": "descending"}], "frame": [null, 0] }, { "calculate": "datum.cum_total - datum['CU (s)'] + 2", "as": "bottomedge" } ], "layer": [ { "mark": {"type": "bar"}, "encoding": { "x": { "field": "OperationStartTime", "type": "temporal" }, "x2": { "field": "OperationEndTime" }, "y": { "field": "cum_total", "type": "quantitative" }, "y2": { "field": "bottomedge" }, "color": {"field": "User"} } },{ "mark": {"type": "bar"}, "encoding": { "x": { "field": "Smoothing start", "type": "temporal" }, "x2": { "field": "Smoothing end" }, "y": { "field": "cum_total", "type": "quantitative" }, "y2": { "field": "bottomedge" }, "color": {"field": "User", "legend": null } } }, { "mark": { "type": "text", "align": "left", "dx": 3, "dy": 10 }, "encoding": { "text": { "field": "CUs", "type": "ordinal" }, "x": { "field": "Smoothing start", "type": "temporal" }, "y": { "field": "cum_total", "type": "quantitative" } } }, { "mark": { "type": "text", "align": "right", "dx": -3, "dy": 10 }, "encoding": { "text": { "field": "User", "type": "ordinal" }, "x": { "field": "Smoothing end", "type": "temporal" }, "y": { "field": "cum_total", "type": "quantitative" } } }, { "mark": { "type": "text", "align": "right", "dx": -3, "dy": 10 }, "encoding": { "text": { "field": "Duration", "type": "quantitative" }, "x": { "field": "OperationEndTime", "type": "temporal" }, "y": { "field": "cum_total", "type": "quantitative", "title":"Main consumers", "axis": {"labels": false} } } }, { "mark": "rule", "encoding": { "x": { "type": "temporal", "field": "now_UTC" }, "color": {"value": "pink"}, "size": {"value": 1} } } ] }