Deneb Example - Gauges 2

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

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)

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)

1 Like

marking as solved