The ultimate Fabric, Power BI, SQL, and AI community-led learning event. Save €200 with code FABCOMM.
Get registeredCompete to become Power BI Data Viz World Champion! First round ends August 18th. Get started.
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!
Solved! Go to Solution.
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
Proud to be a Super User!
On how to ask a technical question, if you really want an answer (courtesy of SQLBI)
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
Proud to be a Super User!
On how to ask a technical question, if you really want an answer (courtesy of SQLBI)
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
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
Proud to be a Super User!
On how to ask a technical question, if you really want an answer (courtesy of SQLBI)
Daniel, you're my hero!
By replacing my existing min and max calculations with the proposed y_extents of my field
Glad it's working for you! It occurred to me (after some sleep) that you might be able to set the y2 encoding channel for the area mark to your derived minimum value from the join aggregate, which would minimize the number of moving parts. You would still need to set the y-scale's zero to false, but it works just as well without needing to apply the extents and expressions to the domain. Here's a quick revision using the above example:
{
"$schema": "https://vega.github.io/schema/vega-lite/v6.json",
"data": {
"url": "data/stocks.csv"
},
"transform": [
{
"filter": "datum.symbol==='GOOG'"
},
{
"joinaggregate": [
{
"op": "min",
"field": "price",
"as": "min_price"
}
]
}
],
"mark": {
"type": "area"
},
"encoding": {
"x": {
"field": "date",
"type": "temporal"
},
"y": {
"field": "price",
"type": "quantitative",
"scale": {
"zero": false
}
},
"y2": {
"field": "min_price"
}
}
}
And here it is in the Vega Editor.
Cheers,
Daniel
Proud to be a Super User!
On how to ask a technical question, if you really want an answer (courtesy of SQLBI)
Thank you for further enhancing my understanding of the logic.
Since this was my first encounter with Deneb and Vega-Lite I yet have to make some big effort to fully understand the structure of it. But your comments and the link to the Vega-Editor to play around help a lot.
Thanks!
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
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?
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
Hello @v-menakakota,
thank you for your reply. I checked the group and the two solved issues but unfortunately none of the displayed visuals contained what I need to achieve.
Maybe to make it clearer, all I need to achieve with this visual is to define a range for the y-axis which I want to see. Let me give you an example:
In my case I want the y axis to begin at the lower and at min_avg_rate - ( 40% of min_avg_rate). And the top end of the axis should be max_avg_rate + ( 40% of min_avg_rate).
And I tried to first scale the y-axis with static values, but even that gets me weird results. The boundaries are not respected and the y-Axis is not displayed properly anymore.
I am wondering if I defined the scale of the axis with the domain in the wrong place or in the wrong way.
Hi @distra ,
Please provide sample data that covers your issue or question completely, in a usable format (not as a screenshot).
Do not include sensitive information. Do not include anything that is unrelated to the issue or question.
Please show the expected outcome based on the sample data you provided.
How to provide sample data in the Power BI Forum - Microsoft Fabric Community
Best Regards,
Community Support Team
Hi @v-menakakota,
please find the sample data in a usable format. Sorry for not providing it in this format earlier, I thought it was only needed to verify my defined y-Axis range is not too small for my actual range of values.
__row__ | Month | MonthName | Avg Rate dynamic Currency | Avg Rate formatted |
0 | 2025-Jan | Jan | 232,9179259 | $ 232.92 |
1 | 2025-Feb | Feb | 232,1264879 | $ 232.13 |
2 | 2025-Mar | Mar | 232,1775412 | $ 232.18 |
3 | 2025-Apr | Apr | 232,0739998 | $ 232.07 |
4 | 2025-May | May | 230,4090975 | $ 230.41 |
5 | 2025-Jun | Jun | 229,1148892 | $ 229.11 |
6 | 2025-Jul | Jul | 230,0921796 | $ 230.09 |
7 | 2025-Aug | Aug | 229,7630773 | $ 229.76 |
8 | 2025-Sep | Sep | 229,6737823 | $ 229.67 |
9 | 2025-Oct | Oct | 229,8118923 | $ 229.81 |
10 | 2025-Nov | Nov | 229,8118923 | $ 229.81 |
11 | 2025-Dec | Dec | 229,8935053 | $ 229.89 |
Here is a quick summary on what I have vs. what I want to achieve:
I am very satisfied with the visual created, everything works well, besides the Y-Axis Range. By default it spans from 0 to a little above my maximum Value. As you can see in the table, my values for Avg Rate dynamic Currency are in a very small range, so the actual changes are hardly visible. So instead of the default range that should be about [0, 250], I want something like this:
This was created using a standard Line chart in Power BI. But I would prefer to use my custom Deneb visual over this one, because when I hover I immediately see the current month's value in the proper format, without needing a tooltip.
I hope this is clearer, all that's missing is the ability to set the axis on my Deneb chart to a smaller range. For the reference chart with the standard line chart I used the exact same data with a y-Axis range of [225, 235].
Hi @distra ,
Apologies for the delayed response. Could you please review the following configuration and confirm if it meets the requirement.
"scale": {
"zero": false,
"domain": [225, 235]
}
If I misunderstand your needs or you still have problems on it, please feel free to let us know.
Best Regards,
Community Support Team
Hello @v-menakakota,
no problem at all, take all the time you need please.
It was my understanding that the exact code you put there would scale my y-Axis to the samE range that I had shared in my example. However, when I add it, the visual output is the following:
The y-Axis and area shading completely disappear, although the range is absolutely correct.
For your reference, I added it here:
"encoding": {
"y": {
"field": "Avg Rate dynamic Currency",
"type": "quantitative",
"scale": {
"zero": false,
"domain": [225, 235]
},
"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"
}
},
This is the encoding block after the fransforms and before the layers (please reference my full configuration in my other comment if more context is needed). If I defined it in the wrong way or position, please let me know. I am quite confused why this does not work, because I had exactly the same approach.
Thank you for your continous support!
Hi @distra ,
Thank you for sharing the sample data.
We are currently trying to reproduce the issue on our side for further analysis. To help with that, could you please also share the corresponding pbix file? This will ensure we are aligned with your visual configurations.
Thank you.
Hi @v-menakakota,
unfortunately the pbix contains sensitive information which I cannot share.
Since Deneb is a standalone visual, which can be configured completely decoupled from the rest of my file, what can I provide besides the sample dataset that could help you?
I will try to provide whatever is required to solve this.
Thank you!
{
"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}
}
}
]
}
]
}
{
"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"
}
}