Forum Discussion
Deneb Vega Lite Sub Columns?
Morning All,
Can i please find out whether are sub-columns possible in a Facet?
The best i can come up with is :
Tried this but not seems to be working.
Please advice.
Thanks in advance.
Jay
have you considered using the standard visual?
Deneb/Vega(-Lite) newb, but tried to tackle this as a good learning experience. Playing around with this, from what I can tell, if you want nested column facets, the only way to do it is with the facet operator (i.e. facet/spec).
General pattern is:
"facet": { "column": { ... } }, "spec": { "facet": { "column": { ... } }, "spec": { "mark": { ... }, "encoding": { ... } } }I tried to figure out how to get something close to your desired output. The closest I got was the following:
A note before I share the vega lite json (tldr: we need to construct and pass a measure along with your other columns in order to get the right scale throughout) :
- The only way I was able to get rid of the repeating Y axis to give a continuous temporal axis look, was to turn the y axis off completely.
- But since we still want one axis on the left, I ended up creating a dummy visual to stick in the front of an hconcat with the main visual. E.g. ... hconcat: [ {dummy visual to get axis}, {main nested facet visual} ]
- I then had the issue of how to ensure that the y-axis scale of the dummy and the actual matched up. Additionally, the y-axis scale of the actual visual was off anyway (I'm guessing that this had something to do how the underlying data at that level was not being filtered by the nested Location facet context).
- My solution, while a bit brute force-ish, was to just calculate the max [Value_01 + Value_02] by YearMonth | Location | Type, and pass that into Deneb to kind of hard-code in the correct scale
- (I feel like there is perhaps a more elegant approach to this by doing transforms within lower layers to massage the underlying data to just provide the right outputs and scales. I got this working for the dummy axis, but doing it with the facets was creating new complexities so I just gave up as the measure approach was already working at that point)
Measure to also pass into Deneb:
MaxY = VAR _iter = CALCULATETABLE( GENERATE( ALL( Sheet1[Location], Sheet1[Type] ), SUMMARIZE( GENERATE( VALUES(Sheet1[Date]), ROW( "YearMonth", FORMAT( Sheet1[Date], "YYYY-MMM" ) ) ), [YearMonth], "Val01+Val02", CALCULATE(SUM(Sheet1[Value_01])) + CALCULATE(SUM(Sheet1[Value_02])) ) ), REMOVEFILTERS(Sheet1) ) RETURN CONVERT( MAXX( _iter, [Val01+Val02] ) * 1.1, //tailor factor to control vertical buffer on max value INTEGER )So, Values well of the visual should look like:
And 'dataset' when viewing the Deneb's source data table will look like:
We will then, regardless of the current input data object (datum), grab MaxY value as needed with:
{"expr":"data('dataset')[0].MaxY"}With that setup, here is the Vega-Lite JSON spec to get the visual I shared at the top:
{ "data": {"name": "dataset"}, "transform": [{"fold": ["Value_01", "Value_02"], "as": ["MT", "MTvalue"]}], "spacing": 0, "bounds": "flush", "hconcat": [ { "bounds": "flush", "view": {"stroke": "transparent"}, "width": 1, "mark": {"type": "bar", "clip": true}, "encoding": { "y": { "field": "Value_01", "type": "quantitative", "aggregate": "sum", "scale": {"domainMax": {"expr": "data('dataset')[0].MaxY"}}, "axis": {"title": "Total Value"} }, "opacity": {"value": 0} } }, { "spacing": 0, "resolve": {"scale": {"x": "independent"}}, "facet": { "column": { "field": "Date", "timeUnit": "yearmonth", "type": "temporal", "title": "Year Month", "header": { "labelExpr": "[timeFormat(datum.value, '%Y'), timeFormat(datum.value, '%b')]" } } }, "spec": { "spacing": 0, "bounds": "flush", "resolve": {"scale": {"x": "independent"}}, "facet": { "column": { "field": "Location", "type": "ordinal", "title": "Location" } }, "spec": { "width": {"step": 25}, "mark": {"type": "bar", "width": {"band": 0.9}}, "encoding": { "x": { "field": "Type", "type": "ordinal", "axis": {"title": null, "labelAngle": 0} }, "y": { "aggregate": "sum", "field": "MTvalue", "type": "quantitative", "axis": { "domain": false, "labels": false, "ticks": false, "title": false, "grid": true }, "scale": {"domainMax": {"expr": "data('dataset')[0].MaxY"}} }, "color": { "field": "MT", "type": "nominal", "legend": {"title": "MT"} } } } } } ] }So, pretty close I think to what you wanted. Some limitations / stuff I couldn't figure out / thoughts:- There are a lot of limits to setting up responsive sizing when you get into "multi-view displays" (includes facets among some other things): https://vega.github.io/vega-lite/docs/size.html#width-and-height-of-multi-view-displays
I tried to, among other things, dynamically set the height/width of the innermost facet, but found that expressions just didn't work with height/width at that nesting level. E.g. expressions/signals that pulled in pbiContainer.height worked fine at top level of the JSON spec, but neither they nor any defined params could be used where I actually needed them (innermost facet) - I could not for the life of me figure out how to draw one line or border of any kind around the facet header labels
- I could transform the data to sum Value_01 and Value_02 by YearMonth, Location, and Type and get a somewhat similar bar chart (in terms of bars, not grouped labels that you get ootb with facets) into a single-view display (meaning we can make the visual a lot more responsive per first bullet), but figuring out 2-3 levels of label hierarchy (and grid groups) seemed more complicated that the facet approach
Edit: repasted in same spec JSON but formatted by the Vega-Lite editor rather than PBI Deneb Vega-Lite editor. The former actually introduces a little less white space but in a way that, I think, makes it slightly more readable.
8 Replies
- MarkLafSuper User
Deneb/Vega(-Lite) newb, but tried to tackle this as a good learning experience. Playing around with this, from what I can tell, if you want nested column facets, the only way to do it is with the facet operator (i.e. facet/spec).
General pattern is:
"facet": { "column": { ... } }, "spec": { "facet": { "column": { ... } }, "spec": { "mark": { ... }, "encoding": { ... } } }I tried to figure out how to get something close to your desired output. The closest I got was the following:
A note before I share the vega lite json (tldr: we need to construct and pass a measure along with your other columns in order to get the right scale throughout) :
- The only way I was able to get rid of the repeating Y axis to give a continuous temporal axis look, was to turn the y axis off completely.
- But since we still want one axis on the left, I ended up creating a dummy visual to stick in the front of an hconcat with the main visual. E.g. ... hconcat: [ {dummy visual to get axis}, {main nested facet visual} ]
- I then had the issue of how to ensure that the y-axis scale of the dummy and the actual matched up. Additionally, the y-axis scale of the actual visual was off anyway (I'm guessing that this had something to do how the underlying data at that level was not being filtered by the nested Location facet context).
- My solution, while a bit brute force-ish, was to just calculate the max [Value_01 + Value_02] by YearMonth | Location | Type, and pass that into Deneb to kind of hard-code in the correct scale
- (I feel like there is perhaps a more elegant approach to this by doing transforms within lower layers to massage the underlying data to just provide the right outputs and scales. I got this working for the dummy axis, but doing it with the facets was creating new complexities so I just gave up as the measure approach was already working at that point)
Measure to also pass into Deneb:
MaxY = VAR _iter = CALCULATETABLE( GENERATE( ALL( Sheet1[Location], Sheet1[Type] ), SUMMARIZE( GENERATE( VALUES(Sheet1[Date]), ROW( "YearMonth", FORMAT( Sheet1[Date], "YYYY-MMM" ) ) ), [YearMonth], "Val01+Val02", CALCULATE(SUM(Sheet1[Value_01])) + CALCULATE(SUM(Sheet1[Value_02])) ) ), REMOVEFILTERS(Sheet1) ) RETURN CONVERT( MAXX( _iter, [Val01+Val02] ) * 1.1, //tailor factor to control vertical buffer on max value INTEGER )So, Values well of the visual should look like:
And 'dataset' when viewing the Deneb's source data table will look like:
We will then, regardless of the current input data object (datum), grab MaxY value as needed with:
{"expr":"data('dataset')[0].MaxY"}With that setup, here is the Vega-Lite JSON spec to get the visual I shared at the top:
{ "data": {"name": "dataset"}, "transform": [{"fold": ["Value_01", "Value_02"], "as": ["MT", "MTvalue"]}], "spacing": 0, "bounds": "flush", "hconcat": [ { "bounds": "flush", "view": {"stroke": "transparent"}, "width": 1, "mark": {"type": "bar", "clip": true}, "encoding": { "y": { "field": "Value_01", "type": "quantitative", "aggregate": "sum", "scale": {"domainMax": {"expr": "data('dataset')[0].MaxY"}}, "axis": {"title": "Total Value"} }, "opacity": {"value": 0} } }, { "spacing": 0, "resolve": {"scale": {"x": "independent"}}, "facet": { "column": { "field": "Date", "timeUnit": "yearmonth", "type": "temporal", "title": "Year Month", "header": { "labelExpr": "[timeFormat(datum.value, '%Y'), timeFormat(datum.value, '%b')]" } } }, "spec": { "spacing": 0, "bounds": "flush", "resolve": {"scale": {"x": "independent"}}, "facet": { "column": { "field": "Location", "type": "ordinal", "title": "Location" } }, "spec": { "width": {"step": 25}, "mark": {"type": "bar", "width": {"band": 0.9}}, "encoding": { "x": { "field": "Type", "type": "ordinal", "axis": {"title": null, "labelAngle": 0} }, "y": { "aggregate": "sum", "field": "MTvalue", "type": "quantitative", "axis": { "domain": false, "labels": false, "ticks": false, "title": false, "grid": true }, "scale": {"domainMax": {"expr": "data('dataset')[0].MaxY"}} }, "color": { "field": "MT", "type": "nominal", "legend": {"title": "MT"} } } } } } ] }So, pretty close I think to what you wanted. Some limitations / stuff I couldn't figure out / thoughts:- There are a lot of limits to setting up responsive sizing when you get into "multi-view displays" (includes facets among some other things): https://vega.github.io/vega-lite/docs/size.html#width-and-height-of-multi-view-displays
I tried to, among other things, dynamically set the height/width of the innermost facet, but found that expressions just didn't work with height/width at that nesting level. E.g. expressions/signals that pulled in pbiContainer.height worked fine at top level of the JSON spec, but neither they nor any defined params could be used where I actually needed them (innermost facet) - I could not for the life of me figure out how to draw one line or border of any kind around the facet header labels
- I could transform the data to sum Value_01 and Value_02 by YearMonth, Location, and Type and get a somewhat similar bar chart (in terms of bars, not grouped labels that you get ootb with facets) into a single-view display (meaning we can make the visual a lot more responsive per first bullet), but figuring out 2-3 levels of label hierarchy (and grid groups) seemed more complicated that the facet approach
Edit: repasted in same spec JSON but formatted by the Vega-Lite editor rather than PBI Deneb Vega-Lite editor. The former actually introduces a little less white space but in a way that, I think, makes it slightly more readable.
- JayWeeFrequent Visitor
Hi MarkLaf,
Thanks for the solution. Way more complicated than i can comprehend at the moment with my limited knowledge as i've just only started not long ago.
Going to take me some time to digest your code for sure.
Thanks
Jay
- MarkLafSuper User
Yeah, it's quite possible I overcomplicated things. In case helpful, here is the same spec stripped down to bare essentials. You can see from the snip, though, that it's quite far from desired look. All the extra stuff is to get the default small multiples look to instead have the continuous x-axis look.
{ "data": {"name": "dataset"}, "transform": [{"fold": ["Value_01", "Value_02"], "as": ["MT", "MTvalue"]}], "facet": { "column": {"field": "Date", "timeUnit": "yearmonth", "type": "temporal", "title": "Year Month"} }, "spec": { "facet": { "column": {"field": "Location", "type": "ordinal", "title": "Location"} }, "spec": { "mark": "bar", "encoding": { "x": {"field": "Type", "type": "ordinal"}, "y": {"aggregate": "sum", "field": "MTvalue", "type": "quantitative"}, "color": {"field": "MT", "type": "nominal", "legend": {"title": "MT"}} } } } }
- v-echaithraCommunity Support
Hi JayWee ,
Would it be possible for you to upload or share a sample .pbix file that replicates the issue? That would help us better understand the setup and provide a more accurate solution for the sub-columns in a Deneb Vega Lite facet.
Thanks and regards,
Chaithra