Deneb/Vega-Lite can be used to create an almost endless variety of custom gauge visuals. This example shows 4 linear gauges with a variety of styles.
I previously presented 4 options (2 linear gauges and 2 circular gauges) in a post last year.
These examples illustrate a number of Deneb/Vega-Lite features, including:
1 - Segmented:
- a “transform/calculate” transformation to set a hard-coded value of 100
- a “params” block with a “bar height” parameter
- a “layer” block to overlay 4 visuals:
- background:
- a “bar” mark using the “100” width and bar height values previously set (light grey)
- foreground:
- a “bar” mark using the percentage from the Power BI numeric slicer and the bar height value previously set (dark blue)
- segments:
- a hard-coded 4-row inline dataset with 20, 40, 60, and 80 values
- a “rule” mark with no Y encoding (to span the height) and X encoding at the inline dataset values (same colour as visual background [white])
- label:
- a “text” mark to display the value selected in the Power BI numeric slicer
- background:
2 - Unit:
- multiple datasets:
- a 100-row “sequence” dataset internally-generated in Vega-Lite with integer values from 1 to 101
- the “percentage” value from the Power BI numeric slicer
- a “transform” block with a “lookup” transform to add the “percentage” to the “sequence” dataset
- a “bar” mark with:
- rounded corners
- ranges using X and X2 encoding ( no Y encoding, thus columns)
- an axis set to render only 20, 40, 60, and 80 values
- fill colour set by conditions (if at or below percentage then dark green if 20, 40, 60 or 80 then dark grey else light grey)
3 - Thermometer-style scale:
- a “transform/calculate” transformation to set a hard-coded value of 100
- a “layer” block to overlay 3 visuals:
- background:
- a “bar” mark using the “100” width value previously set (light grey and 70% opacity)
- a custom X axis with:
- orientation on the top
- conditional tick size (if x%10=0 then 21 else if x%5=0 then 14 else 7)
- conditional tick and grid colour (if x%10=0 then dark grey else medium grey)
- conditional label font size (if x%10=0 then 16 else if x%5=0 then 14 else 0 [i.e., don’t show])
- conditional label font weight (if x%10=0 then bold else normal)
- foreground:
- a “bar” mark using the percentage from the Power BI numeric slicer (dark red and 90% opacity)
- a custom X axis with:
- orientation on the bottom
- conditional tick size (if x%10=0 then 21 else if x%5=0 then 14 else 7)
- conditional tick and grid colour (if x%10=0 then dark grey else medium grey)
- conditional label font size (if x%10=0 then 16 else if x%5=0 then 14 else 0 [i.e., don’t show])
- conditional label font weight (if x%10=0 then bold else normal)
- label:
- a “text” mark to display the percentage value selected in the Power BI numeric slicer with:
- conditional alignment (if x>=5 then right else left)
- conditional colour (if x>=5 then white else black)
- conditional X offset (if x>=5 then -4 else +4)
- a “text” mark to display the percentage value selected in the Power BI numeric slicer with:
- background:
4 - Rating:
- multiple datasets:
- a 5-row “sequence” dataset internally-generated in Vega-Lite with integer values from 1 to 5
- the “rating” value from the Power BI numeric slicer
- a “transform” block with:
- a “lookup” transform to add the “rating” to the “sequence” dataset
- 2x “calculate” transforms to determine the integer and remainder portions of the rating
- a “params” block with:
- 5x objects for empty, 1-quarter, half, 3-quarters, and full linear gradient fill colours (1/4, 1/2, and 3/4 objects use double stop points to allow the gradient to render as a “gold-to-grey” step change)
- the SVG code for a star image
- a “point/shape” mark to render the “star” with:
- hard-coded X-axis “scale/domain” of 1 to 5
- hard-coded border [stroke] colour (dark grey)
- conditional fill colour using the integer and remainder value to assign the correct fill object
Deneb/Vega-Lite JSON Code - Segmented:
{
"width": 690,
"data": {
"name": "dataset"
},
"transform": [
{
"calculate": "100 * 1",
"as": "_100"
}
],
"params": [
{
"name": "_bar_height",
"value": 40
}
],
"layer": [
{
"name": "1-SEGMENTED_LINEAR_GAUGE",
"title": {
"text": "1-Linear Gauge with Segments",
"anchor": "start",
"align": "left",
"fontSize": 16
},
"layer": [
{
"name": "BACKGROUND_PERCENT_BAR",
"mark": {
"type": "bar",
"height": {
"expr": "_bar_height"
},
"color": "#E3E3E3"
},
"encoding": {
"y": {
"field": "y",
"type": "nominal",
"axis": null
},
"x": {
"field": "_100",
"type": "quantitative",
"axis": null
}
}
},
{
"name": "FOREGROUND_PERCENT_BAR",
"mark": {
"type": "bar",
"height": {
"expr": "_bar_height"
},
"color": "#0F4C81"
},
"encoding": {
"y": {
"field": "y",
"type": "nominal",
"axis": null
},
"x": {
"field": "Percentage",
"type": "quantitative",
"axis": null
}
}
},
{
"name": "SEGMENT_OVERLAY_BARS",
// data for overlay markers
"data": {
"values": [
{
"_id": 1,
"_overlay": 20
},
{
"_id": 2,
"_overlay": 40
},
{
"_id": 3,
"_overlay": 60
},
{
"_id": 4,
"_overlay": 80
}
]
},
"mark": {
"type": "rule",
"strokeWidth": 4,
"color": "white"
},
"encoding": {
"x": {
"field": "_overlay",
"type": "quantitative",
"axis": null
}
}
},
{
"name": "PERCENT_TEXT",
"mark": {
"type": "text",
"align": "left",
"color": "black",
"xOffset": 20,
"fontSize": 14,
"fontWeight": "bold"
},
"encoding": {
"text": {
"field": "Percentage",
"type": "quantitative"
},
"x": {
"datum": 100
}
}
}
]
}
]
}
Deneb/Vega-Lite JSON Code - Unit:
{
"width": 690,
"data": {
"sequence": {
"start": 1,
"stop": 101,
"step": 1,
"as": "_x2"
},
"name": "dataset"
},
"transform": [
{
"lookup": "_threshold",
"from": {
"data": {
"name": "dataset"
},
"key": "_x2",
"fields": [
"Percentage"
]
}
},
{
"calculate": "datum['_x2'] - 1",
"as": "_x1"
}
],
"layer": [
{
"name": "2-UNIT_LINEAR_GAUGE",
"title": {
"text": "2-Linear Gauge with Units",
"anchor": "start",
"align": "left",
"fontSize": 16
},
"mark": {
"type": "bar",
"height": 40,
"cornerRadius": 10,
"stroke": "white"
},
"encoding": {
"x": {
"field": "_x1",
"type": "quantitative",
"axis": {
"title": null,
"values": [
20,
40,
60,
80
],
"labelFontSize": 16,
"tickOffset": -4
}
},
"x2": {
"field": "_x2"
},
"fill": {
"condition": [
{
"test": "datum['_x2'] <= datum['Percentage']",
"value": "#0E5358"
},
{
"test": "datum['_x2'] % 20 == 0",
"value": "#C9C9C9"
}
],
"value": "#E3E3E3"
}
}
}
]
}
Deneb/Vega-Lite JSON Code - Thermometer-style Scale:
{
"width": 690,
"data": {
"name": "dataset"
},
"transform": [
{
"calculate": "100 * 1",
"as": "_100"
}
],
"layer": [
{
"name": "3-THERMOMETER_STYLE_SCALE_LINEAR_GAUGE",
"title": {
"text": "3-Linear Gauge with Thermometer-style Scale",
"anchor": "start",
"align": "left",
"fontSize": 16,
"offset": 20
},
"layer": [
{
"name": "BACKGROUND_PERCENT_BAR",
"mark": {
"type": "bar",
"height": 50,
"color": "#E3E3E3",
"opacity": 0.7
},
"encoding": {
"y": {
"field": "y",
"type": "nominal",
"axis": null
},
"x": {
"field": "_100",
"type": "quantitative",
"axis": {
"orient": "top",
"ticks": true,
"tickCount": 101,
"tickSize": {
"expr": "datum.value % 10 == 0 ? 21 : datum.value % 5 == 0 ? 14 : 7"
},
"tickColor": {
"expr": "datum.value % 10 == 0 ? '#969696' : '#C9C9C9'"
},
"gridColor": {
"expr": "datum.value % 10 == 0 ? '#969696' : '#C9C9C9'"
},
"labelFontSize": {
"expr": "datum.value % 10 == 0 ? 16 : datum.value % 5 == 0 ? 10 : 0"
},
"labelFontWeight": {
"expr": "datum.value % 10 == 0 ? 'bold' : datum.value % 5 == 0 ? 'normal' : null"
},
"title": null
}
}
}
},
{
"name": "FOREGROUND_PERCENT_BAR",
"mark": {
"type": "bar",
"height": 40,
"color": "#880C18",
"opacity": 0.9
},
"encoding": {
"y": {
"field": "y",
"type": "nominal"
},
"x": {
"field": "Percentage",
"type": "quantitative",
"axis": {
"orient": "bottom",
"ticks": true,
"tickCount": 101,
"tickSize": {
"expr": "datum.value % 10 == 0 ? 21 : datum.value % 5 == 0 ? 14 : 7"
},
"tickColor": {
"expr": "datum.value % 10 == 0 ? '#969696' : '#C9C9C9'"
},
"gridColor": {
"expr": "datum.value % 10 == 0 ? '#969696' : '#C9C9C9'"
},
"labelFontSize": {
"expr": "datum.value % 10 == 0 ? 16 : datum.value % 5 == 0 ? 10 : 0"
},
"labelFontWeight": {
"expr": "datum.value % 10 == 0 ? 'bold' : datum.value % 5 == 0 ? 'normal' : null"
},
"title": null
}
}
}
},
{
"name": "PERCENT_TEXT",
"mark": {
"type": "text",
"align": {
"expr": "datum['Percentage'] >= 5 ? 'right' : 'left'"
},
"color": {
"expr": "datum['Percentage'] >= 5 ? 'white' : 'black'"
},
"xOffset": {
"expr": "datum['Percentage'] >= 5 ? -4 : 4"
},
"fontSize": 24,
"fontWeight": "bold"
},
"encoding": {
"text": {
"field": "Percentage"
},
"x": {
"field": "Percentage",
"type": "quantitative",
"axis": null
}
}
}
]
}
]
}
Deneb/Vega-Lite JSON Code - Rating:
{
"width": 690,
"data": {
"sequence": {
"start": 1,
"stop": 6,
"step": 1,
"as": "_x"
},
"name": "dataset"
},
"transform": [
{
"lookup": "_threshold",
"from": {
"data": {
"name": "dataset"
},
"key": "_x",
"fields": [
"Rating"
]
}
},
{
"calculate": "floor( datum['Rating'] )",
"as": "_integer"
},
{
"calculate": "datum['Rating'] - datum['_integer']",
"as": "_remainder"
}
],
"params": [
{
"name": "_empty",
"value": {
"gradient": "linear",
"stops": [
{
"offset": 0.0,
"color": "#E3E3E3"
},
{
"offset": 1.0,
"color": "#E3E3E3"
}
]
}
},
{
"name": "_1_quarter",
"value": {
"gradient": "linear",
"stops": [
{
"offset": 0.0,
"color": "gold"
},
{
"offset": 0.25,
"color": "gold"
},
{
"offset": 0.25,
"color": "#E3E3E3"
},
{
"offset": 1.0,
"color": "#E3E3E3"
}
]
}
},
{
"name": "_half",
"value": {
"gradient": "linear",
"stops": [
{
"offset": 0.0,
"color": "gold"
},
{
"offset": 0.5,
"color": "gold"
},
{
"offset": 0.5,
"color": "#E3E3E3"
},
{
"offset": 1.0,
"color": "#E3E3E3"
}
]
}
},
{
"name": "_3_quarters",
"value": {
"gradient": "linear",
"stops": [
{
"offset": 0.0,
"color": "gold"
},
{
"offset": 0.75,
"color": "gold"
},
{
"offset": 0.75,
"color": "#E3E3E3"
},
{
"offset": 1.0,
"color": "#E3E3E3"
}
]
}
},
{
"name": "_full",
"value": {
"gradient": "linear",
"stops": [
{
"offset": 0.0,
"color": "gold"
},
{
"offset": 1.0,
"color": "gold"
}
]
}
},
{
"name": "_star_svg",
"value": "M0,.5L.6,.8L.5,.1L1,-.3L.3,-.4L0,-1L-.3,-.4L-1,-.3L-.5,.1L-.6,.8L0,.5Z"
}
],
"layer": [
{
"name": "4-RATING_LINEAR_GAUGE",
"title": {
"text": "4-Linear Gauge with Ratings",
"anchor": "start",
"align": "left",
"fontSize": 16
},
"layer": [
{
"name": "STAR_SVGS",
"mark": {
"type": "point",
"shape": {
"expr": "_star_svg"
},
"size": 5000,
"stroke": "#969696"
},
"encoding": {
"x": {
"field": "_x",
"type": "quantitative",
"axis": null,
"scale": {
"domain": [
1,
5
]
}
},
"fill": {
"condition": [
{
"test": "datum['_x'] <= datum['_integer']",
"value": {
"expr": "_full"
}
},
{
"test": "datum['_x'] == ( datum['_integer'] + 1 ) && datum['_remainder'] == 0.25",
"value": {
"expr": "_1_quarter"
}
},
{
"test": "datum['_x'] == ( datum['_integer'] + 1 ) && datum['_remainder'] == 0.5",
"value": {
"expr": "_half"
}
},
{
"test": "datum['_x'] == ( datum['_integer'] + 1 ) && datum['_remainder'] == 0.75",
"value": {
"expr": "_3_quarters"
}
}
],
"value": {
"expr": "_empty"
}
}
}
}
]
}
]
}
The intent of this example is not to provide finished visuals, but rather to serve as a starting point for further custom visual development.
Also included is the sample PBIX using simple numeric Power BI slicers.
This example is provided as-is for information purposes only, and its use is solely at the discretion of the end user; no responsibility is assumed by the author.
Greg
Deneb Examples - Gauges 2 - V5.pbix (1.4 MB)