# Gauge Chart > Gauge charts are effective in visualizing metrics in comparison to a threshold to monitor progress or benchmark against a target. 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 ```aml CustomChartDef gauge_chart { label: 'Gauge Chart' icon: 'hicon:chart-gauge-2' description: 'To show a single metric as a needle on a gauge against its maximum, making progress easy to read at a glance.' fields { field value { label: 'Value' type: 'measure' sort { apply_order: 1 direction: 'desc' } } field max_value { label: 'Max value' type: 'measure' sort { apply_order: 2 direction: 'desc' } } } options { 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 } option show_tooltip { label: 'Show tooltip' type: 'toggle' default_value: true } } template: @vg { "$schema": "https://vega.github.io/schema/vega/v5.json", "autosize": "none", "padding": 0, "holisticsConfig": { "crossFilterSignals": ["normalPointSelection"], "contextMenuSignals": ["hoverPointSelection"] }, "signals": [ { "name": "width", "init": "containerSize()[0]", "on": [{"events": "window:resize", "update": "containerSize()[0]"}] }, { "name": "height", "init": "containerSize()[1]", "on": [{"events": "window:resize", "update": "containerSize()[1]"}] }, {"name": "margin", "value": 4}, // Field format configs. They must be injected in a JSON position like this: interpolating // one inside an expression string coerces it to "[object Object]". {"name": "valueFormat", "value": @{fields.value.format}}, {"name": "maxFormat", "value": @{fields.max_value.format}}, // Layout budget. Text is reserved first and the arc takes what is left, so nothing can // be pushed outside the container. `radiusGuess` exists only to break the circular // dependency between the font sizes and the radius they are derived from. {"name": "radiusGuess", "update": "min((width - 2 * margin) / 2, (height - 2 * margin) * 0.62)"}, {"name": "labelFont", "update": "clamp(round(radiusGuess / 5), 9, 20)"}, {"name": "valueFont", "update": "clamp(round(radiusGuess / 3), 12, 40)"}, {"name": "labelsVisible", "update": "@{options.showLabels.value} && radiusGuess >= 60"}, {"name": "labelBand", "update": "labelsVisible ? labelFont * 1.6 : 0"}, {"name": "valueBand", "update": "valueFont * 1.4"}, {"name": "radius", "update": "max(10, min((width - 2 * margin) / 2, height - 2 * margin - labelBand - valueBand))"}, {"name": "innerRadius", "update": "radius * 0.75"}, {"name": "drawnHeight", "update": "radius + labelBand + valueBand"}, {"name": "cx", "update": "width / 2"}, {"name": "cy", "update": "(height - drawnHeight) / 2 + radius"}, {"name": "needleLength", "update": "innerRadius * @{options.needleScale.value}"}, {"name": "tickStep", "update": "max(@{options.tickGaps.value}, 0.05)"}, { "name": "normalPointSelection", "value": null, "on": [ {"events": "@valueArc:click", "update": "{'@{fields.value.name}': [datum.metricValue]}"}, {"events": "click[!event.item]", "update": "null"} ] }, { "name": "hoverPointSelection", "value": null, "on": [ {"events": "@valueArc:mouseover", "update": "{'@{fields.value.name}': [datum.metricValue]}"}, {"events": "@valueArc:mouseout", "update": "null"} ] } ], "data": [ { "name": "table", "values": @{values} }, { "name": "metric", "source": "table", "transform": [ {"type": "formula", "as": "metricValue", "expr": "datum['@{fields.value.name}']"}, {"type": "formula", "as": "metricMax", "expr": "datum['@{fields.max_value.name}']"}, {"type": "formula", "as": "ratio", "expr": "datum.metricMax > 0 ? clamp(datum.metricValue / datum.metricMax, 0, 1) : 0"} ] }, { "name": "tickMarks", "transform": [ {"type": "sequence", "start": 0, "stop": {"signal": "1 + tickStep / 2"}, "step": {"signal": "tickStep"}} ] } ], "marks": [ // Track { "type": "arc", "from": {"data": "metric"}, "encode": { "update": { "x": {"signal": "cx"}, "y": {"signal": "cy"}, "startAngle": {"signal": "-PI / 2"}, "endAngle": {"signal": "PI / 2"}, "innerRadius": {"signal": "innerRadius"}, "outerRadius": {"signal": "radius"}, "fillOpacity": {"value": 0.18} } } }, // Progress. No fill set, so it takes config.arc.fill — the workspace palette. { "type": "arc", "name": "valueArc", "from": {"data": "metric"}, "encode": { "update": { "x": {"signal": "cx"}, "y": {"signal": "cy"}, "startAngle": {"signal": "-PI / 2"}, "endAngle": {"signal": "-PI / 2 + datum.ratio * PI"}, "innerRadius": {"signal": "innerRadius"}, "outerRadius": {"signal": "radius"}, "tooltip": { "signal": "@{options.show_tooltip.value} ? {'@{fields.value.name}': holisticsFormat(datum.metricValue, valueFormat), '@{fields.max_value.name}': holisticsFormat(datum.metricMax, maxFormat), 'Progress': format(datum.ratio, '.0%')} : null" } } } }, // Ticks. Real rules rather than zero-width arcs, so they inherit config.rule.stroke. { "type": "rule", "from": {"data": "tickMarks"}, "encode": { "update": { "x": {"signal": "cx + innerRadius * sin(-PI / 2 + datum.data * PI)"}, "y": {"signal": "cy - innerRadius * cos(-PI / 2 + datum.data * PI)"}, "x2": {"signal": "cx + radius * sin(-PI / 2 + datum.data * PI)"}, "y2": {"signal": "cy - radius * cos(-PI / 2 + datum.data * PI)"}, "strokeWidth": {"value": 1}, "opacity": {"signal": "@{options.showTicks.value} ? 1 : 0"} } } }, // Needle { "type": "path", "from": {"data": "metric"}, "encode": { "update": { "x": {"signal": "cx"}, "y": {"signal": "cy"}, "angle": {"signal": "datum.ratio * 180 - 90"}, "strokeWidth": {"signal": "max(2, radius * 0.018)"}, "strokeCap": {"value": "round"}, "path": {"signal": "'M 0 0 L 0 ' + (-needleLength)"} } } }, // Needle hub { "type": "symbol", "from": {"data": "metric"}, "encode": { "update": { "x": {"signal": "cx"}, "y": {"signal": "cy"}, "shape": {"value": "circle"}, "size": {"signal": "PI * pow(radius * 0.08, 2)"} } } }, // Min and max labels, tucked under the arc ends so they can never overhang it { "type": "text", "style": "guide-label", "from": {"data": "metric"}, "encode": { "update": { "x": {"signal": "cx - radius"}, "y": {"signal": "cy + labelFont * 0.5"}, "text": {"signal": "holisticsFormat(0, maxFormat)"}, "align": {"value": "left"}, "baseline": {"value": "top"}, "fontSize": {"signal": "labelFont"}, "opacity": {"signal": "labelsVisible ? 1 : 0"} } } }, { "type": "text", "style": "guide-label", "from": {"data": "metric"}, "encode": { "update": { "x": {"signal": "cx + radius"}, "y": {"signal": "cy + labelFont * 0.5"}, "text": {"signal": "holisticsFormat(datum.metricMax, maxFormat)"}, "align": {"value": "right"}, "baseline": {"value": "top"}, "fontSize": {"signal": "labelFont"}, "opacity": {"signal": "labelsVisible ? 1 : 0"} } } }, // Value { "type": "text", "from": {"data": "metric"}, "encode": { "update": { "x": {"signal": "cx"}, "y": {"signal": "cy + labelBand + valueBand * 0.5"}, "text": {"signal": "holisticsFormat(datum.metricValue, valueFormat) + ' (' + format(datum.ratio, '.0%') + ')'"}, "align": {"value": "center"}, "baseline": {"value": "middle"}, "fontSize": {"signal": "valueFont"}, "fontWeight": {"value": 600} } } } ] };; } ``` View on GitHub
Legacy syntax ```aml 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 ```aml CustomChartDef gauge_chart_with_target { label: 'Gauge Chart with Target' description: 'To show a metric as a needle on a gauge against its maximum, with a target marker to benchmark progress toward a goal.' icon: 'hicon:chart-gauge-2' fields { field value { label: 'Value' type: 'measure' sort { apply_order: 1 direction: 'desc' } } field max_value { label: 'Max value' type: 'measure' sort { apply_order: 2 direction: 'desc' } } field target { label: 'Target' type: 'measure' sort { apply_order: 3 direction: 'desc' } } } options { option needleScale { label: 'Needle scaling factor' type: 'number-input' default_value: 0.8 } option tickGaps { label: 'Tick gaps' type: 'number-input' default_value: 0.25 } option showLabels { label: 'Show labels' type: 'toggle' default_value: true } option showTicks { label: 'Show ticks' type: 'toggle' default_value: true } option show_tooltip { label: 'Show tooltip' type: 'toggle' default_value: true } option targetColor { label: 'Target color' type: 'color-picker' default_value: '#5fc93c' } } template: @vgl { "padding": {"left": 0, "top": 0, "right": 0, "bottom": 0}, "autosize": "fit", "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": "needleSize", "expr": "innerRadius * @{options.needleScale.value}"}, {"name": "valueFormat", "value": @{fields.value.format}}, {"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"} ], "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"}, "fillOpacity": 0.18 } }, // Value Gauge { "encoding": { "tooltip": [ {"field": @{fields.value.name}, "title": "Value", "format": @{fields.value.format}, "formatType": "holisticsFormat"}, {"field": @{fields.target.name}, "title": "Target", "format": @{fields.target.format}, "formatType": "holisticsFormat"}, {"field": @{fields.max_value.name}, "title": "Max value", "format": @{fields.max_value.format}, "formatType": "holisticsFormat"} ] }, "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"}, "tooltip": @{options.show_tooltip.value} } }, // 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", "fontWeight": 600, "fontSize": {"expr": "fontFactor/2"}, "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": "holisticsFormat(datum['@{fields.value.name}'], valueFormat) + ' (' + 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", "fontWeight": 600, "fontSize": {"expr": "fontFactor"}, "x": {"expr": "centerX"}, "y": {"expr": "centerY + fontFactor*1.2"}, "baseline": "alphabetic", "align": "center" } }, // 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, "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"}, "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 } } ] } ;; } ``` View on GitHub
Legacy syntax ```aml 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. **Simple Gauge Chart:** | Option | Default | Effect | |---------------|---------|--------| | `needleScale` | `0.8` | Needle length as a fraction of the inner radius. | | `showLabels` | `true` | Toggles the 0 and max-value end labels. They also hide on their own when the gauge is too small to show them legibly. | | `showTicks` | `true` | Toggles the evenly spaced tick marks. | | `tickGaps` | `0.25` | Spacing between ticks as a fraction of the range (smaller means more ticks). | | `show_tooltip` | `true` | Turns 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:** | Option | Default | Effect | |----------------|-----------|--------| | `needleScale` | `0.8` | Needle length as a fraction of the inner radius. | | `tickGaps` | `0.25` | Spacing between ticks as a fraction of the range (smaller means more ticks). | | `showLabels` | `true` | Toggles the 0 and max-value end labels. | | `showTicks` | `true` | Toggles the evenly spaced tick marks. | | `show_tooltip` | `true` | Turns the hover tooltip on or off. | | `targetColor` | `#5fc93c` | Color 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.