Skip to main content

Gauge Chart

A gauge chart visualizes a metric against a threshold, to monitor progress or benchmark against a target.

  • Good for: showing a single metric against its maximum, monitoring progress toward a goal, at-a-glance KPI panels with a benchmark.
  • Not great for: comparing many categories at once (use a bar or bullet chart), trends over time, or part-to-whole composition.

Syntax

Two variants are available:

  • Simple Gauge Chart: value against a maximum.
  • Gauge Chart with Target: adds a target marker to benchmark against a goal.

Use one of the following AML definitions to add the Gauge Chart to your custom chart library.

Simple Gauge Chart

reporting-custom-chart/simple-gauge-chart
Legacy syntax
CustomChart {
fields {
field value {
type: "measure"
label: "Value"
}
field max_value {
type: "measure"
label: "Max Value"
}
}

options {
option background_color {
type: "color-picker"
label: "Background Color"
default_value: "#cbd1d6"
}
option value_color {
type: "color-picker"
label: "Value Color"
default_value: "black"
}
option fillColor {
type: "color-picker"
label: "Fill Color"
default_value: "#3b60d2"
}
option needle_color {
type: "color-picker"
label: "Needle Color"
default_value: "black"
}
option needleScale {
label: 'Needle Scaling Factor'
type: 'number-input'
default_value: 0.8
}
option showLabels {
label: 'Show Labels'
type: 'toggle'
default_value: true
}
option showTicks {
label: 'Show Ticks'
type: 'toggle'
default_value: true
}
option tickGaps {
label: 'Tick Gaps'
type: 'number-input'
default_value: 0.25
}
}
template: @vgl {
"config": {
"padding": {"left": 0, "top": 0, "right": 0, "bottom": 0},
"autosize": "fit",
"view": {
"stroke": "transparent"
},
"font": "Inter"
},
"params": [
{"name": "centerX", "expr": "width/2"},
{"name": "centerY", "expr": "height/2 + outerRadius/2"},
{"name": "outerRadius", "expr": "radiusRef"},
{"name": "radiusRef", "expr": "min(width/2, height/2)"},
{"name": "innerRadius", "expr": "outerRadius - outerRadius * 0.25"},
{"name": "fontFactor", "expr": "radiusRef/5"},
{"name": "backgroundColor", "value": @{options.background_color.value}},
{"name": "fillColor", "value": @{options.fillColor.value}},
{"name": "needleColor", "value": @{options.needle_color.value}},
{"name": "needleSize", "expr": "innerRadius * @{options.needleScale.value}"}
],
"data": {
"values": @{values}
},
"transform": [
{"calculate": "datum['@{fields.value.name}'] / datum['@{fields.max_value.name}']", "as": "percentage"},
{"calculate": "(datum.percentage) * (PI) + (-PI/2)", "as": "arcValue"}
],
"layer": [
// Base Gauge
{
"mark": {
"type": "arc",
"name": "gauge",
"theta": {"expr": "-PI/2"},
"theta2": {"expr": "PI/2"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"innerRadius": {"expr": "innerRadius"},
"outerRadius": {"expr": "outerRadius"},
"fill": {"expr": "backgroundColor"},
"stroke": {"expr": "backgroundColor"},
"strokeWidth": 1,
}
},
// Value Gauge
{
"mark": {
"type": "arc",
"name": "gauge",
"theta": {"expr": "-PI/2"},
"theta2": {"expr": "datum.arcValue"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"innerRadius": {"expr": "innerRadius"},
"outerRadius": {"expr": "outerRadius"},
"fill": {"expr": "fillColor"}
}
},
// Label
{
"transform": [
{"calculate": "[0, datum['@{fields.max_value.name}']]", "as": "label"},
{"flatten": ["label"]},
{"calculate": "datum.label === 0 ? 0 : 1", "as": "angle"}
],
"encoding": {
"text": {
"field": "label",
"format": @{fields.max_value.format},
"formatType": "holisticsFormat"
},
"theta": {
"field": "angle",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [{"expr": "-PI/2"}, {"expr": "PI/2"}]}
}
},
"mark": {
"type": "text",
"fontSize": {"expr": "fontFactor/2"},
"fontWeight": 600,
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"radius": {"expr": "outerRadius*1.05"},
"align": {"expr": "datum.angle === 0 ? 'right' : 'left'"},
"baseline": "alphabetic",
"opacity": {"expr": "@{options.showLabels.value} ? 1 : 0"}
}
},
// Main Value
{
"transform": [
{"calculate": "datum['@{fields.value.name}'] + ' (' + format(datum.percentage, '.0%') + ')'", "as": "mainValue"}
],
"encoding": {
"text": {
"field": "mainValue"
}
},
"mark": {
"type": "text",
"fontSize": {"expr": "fontFactor"},
"fontWeight": 600,
"x": {"expr": "centerX"},
"y": {"expr": "centerY + fontFactor*1.2"},
"baseline": "alphabetic",
"align": "center",
"color": @{options.value_color.value}
}
},
// Needle
{
"encoding": {
"angle": {
"field": "percentage",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [-90, 90]}
}
},
"mark": {
"type": "point",
"shape": {
"expr": "'M 5 -2 A 7 7 0 0 1 -10 -7 L 0 -' + toString(needleSize) + ' Z'"
},
"size": 4,
"opacity": 1,
"fill": {"expr": "needleColor"},
"stroke": {"expr": "needleColor"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"}
}
},
// Ticks
{
"data": {
"sequence": {
"as": "ticks",
"start": 0,
"stop": 1.01,
"step": @{options.tickGaps.value}
}
},
"encoding": {
"theta": {
"field": "ticks",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [{"expr": "-PI/2"}, {"expr": "PI/2"}]}
},
"theta2": {
"field": "ticks",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [{"expr": "-PI/2"}, {"expr": "PI/2"}]}
}
},
"mark": {
"type": "arc",
"outerRadius": {"expr": "outerRadius"},
"innerRadius": {"expr": "innerRadius"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"stroke": {"expr": "needleColor"},
"opacity": {"expr": "@{options.showTicks.value} ? 1 : 0"}
}
}
]
};;
}

Gauge Chart with Target

reporting-custom-chart/gauge-chart-with-target
Legacy syntax
CustomChart {
fields {
field value {
type: "measure"
label: "Value"
}
field max_value {
type: "measure"
label: "Max Value"
}
field target {
type: "measure"
label: "Target"
}
}

options {
option background_color {
type: "color-picker"
label: "Background Color"
default_value: "#cbd1d6"
}
option fillColor {
type: "color-picker"
label: "Fill Color"
default_value: "#3b60d2"
}
option value_color {
type: "color-picker"
label: "Value Color"
default_value: "black"
}
option needle_color {
type: "color-picker"
label: "Needle Color"
default_value: "black"
}
option needleScale {
label: 'Needle Scaling Factor'
type: 'number-input'
default_value: 0.8
}
option showLabels {
label: 'Show Labels'
type: 'toggle'
default_value: false
}
option showTicks {
label: 'Show Ticks'
type: 'toggle'
default_value: false
}
option tickGaps {
label: 'Tick Gaps'
type: 'number-input'
default_value: 0.25
}
option targetColor {
type: "color-picker"
label: "Target Color"
default_value: "#5fc93c"
}
}
template: @vgl {
"config": {
"padding": {"left": 0, "top": 0, "right": 0, "bottom": 0},
"autosize": "fit",
"view": {
"stroke": "transparent"
},
"font": "Inter"
},
"params": [
{"name": "centerX", "expr": "width/2"},
{"name": "centerY", "expr": "height/2 + outerRadius/2"},
{"name": "outerRadius", "expr": "radiusRef"},
{"name": "radiusRef", "expr": "min(width/2, height/2)"},
{"name": "innerRadius", "expr": "outerRadius - outerRadius * 0.25"},
{"name": "fontFactor", "expr": "radiusRef/5"},
{"name": "backgroundColor", "value": @{options.background_color.value}},
{"name": "fillColor", "value": @{options.fillColor.value}},
{"name": "needleColor", "value": @{options.needle_color.value}},
{"name": "needleSize", "expr": "innerRadius * @{options.needleScale.value}"},
{"name": "targetColor", "value": @{options.targetColor.value}}
],
"data": {
"values": @{values}
},
"transform": [
{"calculate": "datum['@{fields.value.name}'] / datum['@{fields.max_value.name}']", "as": "percentage"},
{"calculate": "(datum.percentage) * (PI) + (-PI/2)", "as": "arcValue"},
{"calculate": "datum['@{fields.target.name}'] / datum['@{fields.max_value.name}']", "as": "targetPercentage"}
],
"encoding": {
"tooltip": [
{"field": @{fields.value.name}, "format": @{fields.value.format}, "formatType": "holisticsFormat"},
{"field": @{fields.target.name}, "format": @{fields.target.format}, "formatType": "holisticsFormat"},
{"field": @{fields.max_value.name}, "format": @{fields.max_value.format}, "formatType": "holisticsFormat"}
]
},
"layer": [
// Base Gauge
{
"mark": {
"type": "arc",
"name": "gauge",
"theta": {"expr": "-PI/2"},
"theta2": {"expr": "PI/2"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"innerRadius": {"expr": "innerRadius"},
"outerRadius": {"expr": "outerRadius"},
"fill": {"expr": "backgroundColor"},
"stroke": {"expr": "backgroundColor"},
"strokeWidth": 1,
}
},
// Value Gauge
{
"mark": {
"type": "arc",
"name": "gauge",
"theta": {"expr": "-PI/2"},
"theta2": {"expr": "datum.arcValue"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"innerRadius": {"expr": "innerRadius"},
"outerRadius": {"expr": "outerRadius"},
"fill": {"expr": "fillColor"},
}
},
// Label
{
"transform": [
{"calculate": "[0, datum['@{fields.max_value.name}']]", "as": "label"},
{"flatten": ["label"]},
{"calculate": "datum.label === 0 ? 0 : 1", "as": "angle"}
],
"encoding": {
"text": {
"field": "label",
"format": @{fields.max_value.format},
"formatType": "holisticsFormat"
},
"theta": {
"field": "angle",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [{"expr": "-PI/2"}, {"expr": "PI/2"}]}
}
},
"mark": {
"type": "text",
"fontSize": {"expr": "fontFactor/2"},
"fontWeight": 600,
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"radius": {"expr": "outerRadius*1.05"},
"align": {"expr": "datum.angle === 0 ? 'right' : 'left'"},
"baseline": "alphabetic",
"opacity": {"expr": "@{options.showLabels.value} ? 1 : 0"}
}
},
// Main Value
{
"transform": [
{"calculate": "datum['@{fields.value.name}'] + ' (' + format(datum.percentage, '.0%') + ')'", "as": "mainValue"},
{
"calculate": "format(datum['@{fields.value.name}'] - datum['@{fields.target.name}'], ',.0f') + ' (' + format(datum['@{fields.value.name}']/datum['@{fields.target.name}'] - 1, '.0%') + ')'",
"as": "reference"
}
],
"encoding": {
"text": {
"field": "mainValue"
}
},
"mark": {
"type": "text",
"fontSize": {"expr": "fontFactor"},
"fontWeight": 600,
"x": {"expr": "centerX"},
"y": {"expr": "centerY + fontFactor*1.2"},
"baseline": "alphabetic",
"align": "center",
"color": @{options.value_color.value}
}
},
// Needle
{
"encoding": {
"angle": {
"field": "percentage",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [-90, 90]}
}
},
"mark": {
"type": "point",
"shape": {
"expr": "'M 5 -2 A 7 7 0 0 1 -10 -7 L 0 -' + toString(needleSize) + ' Z'"
},
"size": 4,
"opacity": 1,
"fill": {"expr": "needleColor"},
"stroke": {"expr": "needleColor"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"}
}
},
// Ticks
{
"data": {
"sequence": {
"as": "ticks",
"start": 0,
"stop": 1.01,
"step": @{options.tickGaps.value}
}
},
"encoding": {
"theta": {
"field": "ticks",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [{"expr": "-PI/2"}, {"expr": "PI/2"}]}
},
"theta2": {
"field": "ticks",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [{"expr": "-PI/2"}, {"expr": "PI/2"}]}
}
},
"mark": {
"type": "arc",
"outerRadius": {"expr": "outerRadius"},
"innerRadius": {"expr": "innerRadius"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"stroke": {"expr": "needleColor"},
"opacity": {"expr": "@{options.showTicks.value} ? 1 : 0"}
}
},
// Target Gauge
{
"encoding": {
"theta": {
"field": "targetPercentage",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [{"expr": "-PI/2"}, {"expr": "PI/2"}]}
},
"theta2": {
"field": "targetPercentage",
"type": "quantitative",
"scale": {"domain": [0, 1], "range": [{"expr": "-PI/2"}, {"expr": "PI/2"}]}
}
},
"mark": {
"type": "arc",
"outerRadius": {"expr": "outerRadius*1.05"},
"innerRadius": {"expr": "innerRadius*0.95"},
"x": {"expr": "centerX"},
"y": {"expr": "centerY"},
"stroke": {"expr": "targetColor"},
"strokeWidth": 2
}
}
]
};;
}

Required fields

Both variants take a single row of measures. The Simple Gauge Chart expects two fields; the Gauge Chart with Target expects three. The template draws the value as a needle and an arc filling toward the maximum.

Simple Gauge Chart:

FieldLabelTypeRole
valueValuemeasureCurrent metric, drawn as the filled arc and needle. Sorted descending (apply_order: 1).
max_valueMax ValuemeasureTop of the gauge range (the 100% end). Sorted descending (apply_order: 2).

Gauge Chart with Target:

FieldLabelTypeRole
valueValuemeasureCurrent metric, drawn as the filled arc and needle. Sorted descending (apply_order: 1).
max_valueMax ValuemeasureTop of the gauge range (the 100% end). Sorted descending (apply_order: 2).
targetTargetmeasureGoal value, drawn as a colored marker on the arc. Sorted descending (apply_order: 3).

Data requirements: Provide a single row. The template reads one record and computes value / max_value as the fill percentage, so pre-aggregate to one value per measure. Keep max_value greater than zero, since it is the divisor for the gauge percentage.

Sample data:

Simple Gauge Chart:

valuemax_value
7201000

Gauge Chart with Target:

valuemax_valuetarget
7201000850

Options

Set these options to adjust the chart without editing the Vega template. The CustomChartDef block above declares each option's type and allowed values.

Simple Gauge Chart:

OptionDefaultEffect
needleScale0.8Needle length as a fraction of the inner radius.
showLabelstrueToggles the 0 and max-value end labels. They also hide on their own when the gauge is too small to show them legibly.
showTickstrueToggles the evenly spaced tick marks.
tickGaps0.25Spacing between ticks as a fraction of the range (smaller means more ticks).
show_tooltiptrueTurns the hover tooltip on or off.

This variant takes no color options. The filled arc uses your workspace color palette, and the track, needle, ticks and text follow the app theme, so the gauge matches the rest of your dashboard in both light and dark mode.

Gauge Chart with Target:

OptionDefaultEffect
needleScale0.8Needle length as a fraction of the inner radius.
tickGaps0.25Spacing between ticks as a fraction of the range (smaller means more ticks).
showLabelstrueToggles the 0 and max-value end labels.
showTickstrueToggles the evenly spaced tick marks.
show_tooltiptrueTurns the hover tooltip on or off.
targetColor#5fc93cColor of the target marker on the arc.

Aside from the target marker, this variant takes no color options either: the filled arc uses your workspace palette, and the needle, ticks, and text follow the app theme, matching the Simple Gauge Chart.

Known limitations

  • Single value only. Each gauge renders one record. To compare categories, use a separate gauge per value or switch to a bar or bullet chart.

  • max_value must be greater than zero. It is the divisor for the fill percentage, so a zero or missing maximum produces an invalid gauge.

  • Half-circle range fixed at 0 to max. The arc always runs from 0 to max_value across a 180-degree sweep. Custom minimums or full-circle gauges require editing the template.


Open Markdown
Let us know what you think about this document :)