Forum Discussion
Dynamic y-Axis calculation with Deneb
Hi everyone, hi giammariam,
I have recently started exploring Deneb Visuals. In my particular case I want to display Average Cost per Month while offering to switch between currencies on my Dashboard. So I decided to try and adapt a Deneb template for a KPI Card with a line chart, that shows the KPI for the month the user hovers over: Here is what I (almost) want it to look like:
This is what I achieved with the Template and some help by Claude and Copilot.
However, although I usually advocate for the zero-based approach to show the actual dimensions, in this case the changes month over month are so small, that the customers of this dashboard want this chart to be zoomed in on the area around the max and min values to actually see changes.
I calculated max and min boundaries (global max + 20% of the global min value & global min value - 20% of global min value) but I can't get the axis to adapt to the borders I defined. To test the functionality I wanted to provide static values first to set my boundaries and after that apply my calculated values. But even with the static values I don't get to the desired result.
When I add the following section to my y-Encoding, my generated KPI card does not display any axis anymore.
My data range is from 228 to 232, so that should well be within the defined range.
"scale": {
"zero": false,
"domain": [150,250]
},If I use an even wider range like [80, 300] the Axis re-appears but will not use the entire range of the Line Chart anymore. Plus it also does not correctly scale the axis.
Do I have a wrong understanding of the domain attribute?
I am using Vega-Lite with a Specification- and a Config-File, I can provide both codes in full if needed.
Thanks for any help!
Hi distra,
The area mark is designed to have a zero baseline, so even if you cut the domain, it is trying to plot the remainder of the shape outside the domain (as the second y-value is zero). It would work with a line mark as there is only a single y-coordinate to worry about.
So, what is happening here is that your y-scale and axis are being correctly set to the desired range, but the size of your chart is condensing the vertical scale into the equivalent amount of space. You can see it "kind of" working if you make the visual container larger, e.g.:
(ignore the 'undefined'; that's a copy/paste issue with your sample data at my end)
One way to resolve this is to instruct Vega-Lite to clip the area mark. You should be able to make the following changes to your spec:
1. Update the scale as follows (removing "zero": false), e.g.:
{ ... "encoding": { "y": { "field": "Avg Rate dynamic Currency", "type": "quantitative", "scale": { "domain": [80, 235] // or whatever you need min/max to be }, ... }, ... } ... }2. Update the area mark definition to include a clip as follows:
{ ... "layer": [ { "description": "sparkline definition", "mark": { "type":"area", "clip": true }, ... }, ... } ... }This will now cut the baseline of the plotted area, e.g.:
Hopefully this should be all you need to resolve. Good luck!
(and thanks for using Deneb!)
Daniel
17 Replies
- v-menakakotaCommunity Support
Hi distra ,
Thanks for reaching out to the Microsoft fabric community forum.Make sure the values you're plotting actually fall within the domain [150, 250] in the layer where you're applying the scale.
In layered visuals like KPI cards, ensure the scale override is set on the correct layer, ideally the one rendering the line/chart.
Make sure that axis is not set to null or being turned off in your encoding.
If I misunderstand your needs or you still have problems on it, please feel free to let us know.
Best Regards,
Community Support Team- distraFrequent Visitor
Thank you v-menakakota,
all values are well within that range, these are my current values:
Maybe I did not understand the concept of layers well enough to define the axis correctly. THis is my code that defines the encoding:
"encoding": { "y": { "field": "Avg Rate dynamic Currency", "type": "quantitative", "scale": { "zero": false, "domain": [80,300] }, "axis": { "title": null, "labels": true, "ticks": true, "grid": true, "domain": true, "labelColor": "#605E5C", "labelFont": "Segoe UI Semibold", "labelFontSize": 10, "tickColor": "#E1DFDD", "gridColor": "#F3F2F1", "domainColor": "#E1DFDD" } }, "x": { "field": "Month", "type": "temporal" } },But actually, this is not defined inside the layer definitions, maybe that's the mistake?
- v-menakakotaCommunity Support
Hi distra ,
Please go through the below blog and solved issues which may help you in resolving the issues:DENEB and Power BI Enthusiasts - Microsoft Fabric Community
Solved: Deneb Vega Lite Sub Columns? - Microsoft Fabric Community
Solved: haRe: dynamic y-axis with dynamic y-axis label - Microsoft Fabric Community
If I misunderstand your needs or you still have problems on it, please feel free to let us know.
Best Regards,
Community Support Team
- dm-pSuper User
Hi distra,
The area mark is designed to have a zero baseline, so even if you cut the domain, it is trying to plot the remainder of the shape outside the domain (as the second y-value is zero). It would work with a line mark as there is only a single y-coordinate to worry about.
So, what is happening here is that your y-scale and axis are being correctly set to the desired range, but the size of your chart is condensing the vertical scale into the equivalent amount of space. You can see it "kind of" working if you make the visual container larger, e.g.:
(ignore the 'undefined'; that's a copy/paste issue with your sample data at my end)
One way to resolve this is to instruct Vega-Lite to clip the area mark. You should be able to make the following changes to your spec:
1. Update the scale as follows (removing "zero": false), e.g.:
{ ... "encoding": { "y": { "field": "Avg Rate dynamic Currency", "type": "quantitative", "scale": { "domain": [80, 235] // or whatever you need min/max to be }, ... }, ... } ... }2. Update the area mark definition to include a clip as follows:
{ ... "layer": [ { "description": "sparkline definition", "mark": { "type":"area", "clip": true }, ... }, ... } ... }This will now cut the baseline of the plotted area, e.g.:
Hopefully this should be all you need to resolve. Good luck!
(and thanks for using Deneb!)
Daniel
- distraFrequent Visitor
Hi Daniel,
thank you for reaching out, this works like a charm! I used it with the same boundaries I had provided for the example chart with native Power BI visuals and it looks great!
I was expecting it to be a simple task to replace the static values by my pre-calculated values, but it seems like Vega-Lite won't let me use the calculations in the domain directly.
I have made the following update to my transform section. Is there a way to directly reference my calculated upper and lower boundaries in the domain?"transform": [ { "joinaggregate": [ { "op": "max", "field": "Month", "as": "max_period" }, { "op": "max", "field": "Avg Rate dynamic Currency", "as": "max_avg_rate" }, { "op": "min", "field": "Avg Rate dynamic Currency", "as": "min_avg_rate" } ] }, { "calculate": "datum.max_avg_rate - datum.min_avg_rate", "as": "delta_max_min" }, { "calculate": "datum.max_avg_rate + datum.delta_max_min", "as": "upper_limit_yAxis" }, { "calculate": "datum.min_avg_rate - datum.delta_max_min", "as": "lower_limit_yAxis" } ],Thanks a lot!
Dirk
- dm-pSuper User
I'm a bit short on time as it's later here, but you can do this with the extent transform, which will let you hoist your min/max to a top-level signal (called params in Vega-Lite). These are easier to access in expressions, which you can use in the scale domain.
Here's a quick, minimal example that you can use as a reference. This clips the area to the extents of the values (in conjunction with the clip approach discussed previously):
{ "$schema": "https://vega.github.io/schema/vega-lite/v6.json", "data": { "url": "data/stocks.csv" }, "transform": [ { "filter": "datum.symbol==='GOOG'" }, { "extent": "price", "param": "y_extents" } ], "mark": { "type": "area", "clip": true }, "encoding": { "x": { "field": "date", "type": "temporal" }, "y": { "field": "price", "type": "quantitative", "scale": { "domain": [{"expr": "y_extents[0]"}, {"expr": "y_extents[1]"}] } } } }You can explore this in the Vega Editor here.
The pertinent parts are as follows:
{ ... "transform": [ ... { "extent": "price", "param": "y_extents" } ], ... }This is a calculation that will take the min/max of a specified field and store them as a variable. You can see this in the Signal pane (in either Deneb or the Vega Editor), e.g.:
This is an array that can then be referenced in the domain evaluation, e.g.:
{ "encoding": { ... "y": { ... "scale": { "domain": [ {"expr": "y_extents[0]"}, {"expr": "y_extents[1]"} ] } } } }This is an example of extraction of the direction values, but you could also use downstream params to manipulate these values (such as adding a padding factor). this is similar to a transform in a data stream.
Anyway, I hope this is clear and provides you with some ideas on how to apply it to your design. If you need me to implement your specification exactly, it would be helpful if you could supply an example .pbix file, and I can review it when I'm next back online.
Good luck!
Daniel
- distraFrequent Visitor
Vega Lite Specification
{ "description": "Area chart with text updated when hovering over a point. Author: Joanna Sekiewicz", "title": "Average Rate by Month", "data": {"name": "dataset"}, "transform": [ { "joinaggregate": [ { "op": "max", "field": "Month", "as": "max_period" } ] }, { "calculate": "datum.max_avg_rate + datum.min_avg_rate * 0.4", "as": "upper_limit_yAxis" }, { "calculate": "datum.min_avg_rate + datum.min_avg_rate * 0.4", "as": "lower_limit_yAxis" } ], "encoding": { "y": { "field": "Avg Rate dynamic Currency", "type": "quantitative", "scale": { "zero": false, "domain": [80,300] }, "axis": { "title": null, "labels": true, "ticks": true, "grid": true, "domain": true, "labelColor": "#605E5C", "labelFont": "Segoe UI Semibold", "labelFontSize": 10, "tickColor": "#E1DFDD", "gridColor": "#F3F2F1", "domainColor": "#E1DFDD" } }, "x": { "field": "Month", "type": "temporal" } }, "layer": [ { "description": "sparkline definition", "mark": "area", "encoding": { "y": {"field": "Avg Rate dynamic Currency"} } }, { "description": "points activated by hovering over", "params": [ { "name": "hoverPoint", "select": { "type": "point", "on": "pointermove", "encodings": ["x"], "clear": "pointerout", "nearest": true } } ], "encoding": { "y": {"field": "Avg Rate dynamic Currency"}, "opacity": {"value": 0} }, "mark": {"type": "point"} }, { "description": "details for hovered point (or the last one)", "transform": [ { "calculate": "isDefined(hoverPoint.Month) ? hoverPoint.Month[0] == datum.Month : datum.Month==datum.max_period", "as": "displayDetails" }, { "filter": "datum.displayDetails" } ], "layer": [ { "description": "point referring to displayed details", "mark": "point", "encoding": { "opacity": {"value": 1}, "y": {"field": "Avg Rate dynamic Currency"} } }, { "mark": "rule", "encoding": { "opacity": { "condition": { "param": "hoverPoint", "empty": false, "value": 1 }, "value": 0 } } }, { "mark": { "type": "text", "size": 28, "fontWeight": "normal", "color": "#2D7A89" }, "encoding": { "text": { "field": "Avg Rate formatted" }, "x": {"value": -10}, "y": {"value": -50} } }, { "mark": { "type": "text", "size": 13 }, "encoding": { "text": { "field": "Month", "format": "%b-%Y", "formatType": "time" }, "x": {"value": -10}, "y": {"value": -25} } } ] } ] }Config
{ "view": { "stroke": "transparent", "fill": "transparent" }, "font": "Segoe UI semibold", "area": { "line": true, "interpolate": "monotone", "color": { "gradient": "linear", "x1": 1, "y1": 1, "x2": 1, "y2": 0, "stops": [ {"offset": 0, "color": "#ffffff00"}, { "offset": 1, "color": "#2D7A89A0" } ] } }, "rule": { "strokeWidth": 0.3, "color": "grey" }, "line": { "interpolate": "monotone", "color": "#2D7A89", "strokeWidth": 3, "strokeCap": "round", "strokeJoin": "round" }, "point": { "filled": true, "size": 85, "color": "#2D7A89" }, "text": { "align": "left", "fill": "#605E5C" }, "axis": { "title": null, "labels": false, "ticks": false, "grid": false, "domain": false }, "title": { "anchor": "start", "dx": 0, "dy": -8, "fontSize": 18.5, "fontWeight": "bold", "color": "#252423" } }