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
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
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:
| Field | Label | Type | Role |
|---|---|---|---|
value | Value | measure | Current metric, drawn as the filled arc and needle. Sorted descending (apply_order: 1). |
max_value | Max Value | measure | Top of the gauge range (the 100% end). Sorted descending (apply_order: 2). |
Gauge Chart with Target:
| Field | Label | Type | Role |
|---|---|---|---|
value | Value | measure | Current metric, drawn as the filled arc and needle. Sorted descending (apply_order: 1). |
max_value | Max Value | measure | Top of the gauge range (the 100% end). Sorted descending (apply_order: 2). |
target | Target | measure | Goal 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:
| value | max_value |
|---|---|
| 720 | 1000 |
Gauge Chart with Target:
| value | max_value | target |
|---|---|---|
| 720 | 1000 | 850 |
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.
Both variants share these options:
| Option | Default | Effect |
|---|---|---|
background_color | #cbd1d6 | Color of the unfilled background arc. |
value_color | black | Color of the main value label text. |
fillColor | #3b60d2 | Color of the filled arc up to the current value. |
needle_color | black | Color of the needle (and tick marks). |
needleScale | 0.8 | Needle length as a fraction of the inner radius. |
showLabels | true | Toggles the 0 and max-value end labels. Defaults to false in the Target variant. |
showTicks | true | Toggles the evenly spaced tick marks. Defaults to false in the Target variant. |
tickGaps | 0.25 | Spacing between ticks as a fraction of the range (smaller means more ticks). |
The Gauge Chart with Target adds one more option:
| Option | Default | Effect |
|---|---|---|
targetColor | #5fc93c | Color of the target marker on the arc. |
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_valuemust 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_valueacross a 180-degree sweep. Custom minimums or full-circle gauges require editing the template.