DAX SVG measure · Progress to target
Data bars with a target in Power BI (DAX SVG)
By Nawaf Sharaf · Founder & Reliability Engineer · Updated
Use it when
- Rows must be compared on one absolute scale (regions, products, stores).
- Each row has its own target and you want to see who is above it.
Skip it when
The row column isn't the only thing on the rows (a matrix with two row levels): the shared scale is computed over the column you name, so nested levels need their own measure.
Three looks
The data bar with target in three palettes; click one to edit it.
Data it needs
The builder asks for exactly these fields; type your own measure and column names there.
- Row column
- The column on the table's rows, e.g. 'Product'[Category].
- Actual measure
- The bar length.
- Target measure
- The tick position; blank targets hide the tick.
The full DAX
Swap 'Product'[Category] and [Sales] and [Sales Target] for your own names.
Data Bar =
-- VizForge SVG measure: data bar with target, scaled across every row in the visual.
-- Set Measure tools > Data category = Image URL. The row column must be the column on the table's rows.
-- Made with the free VizForge SVG measure builder: vizforge.ai/tools/svg-measure-builder
-- Need per-point tooltips, click-to-select inside the chart or Format-pane settings? Generate it as a real custom visual: vizforge.ai/visuals
VAR _Actual = [Sales]
VAR _Target = [Sales Target]
VAR _Rows = ADDCOLUMNS ( ALLSELECTED ( 'Product'[Category] ), "@a", [Sales], "@t", [Sales Target] )
VAR _Max = MAXX ( _Rows, MAX ( [@a], [@t] ) )
RETURN
IF (
ISBLANK ( _Actual ),
BLANK (),
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='150' height='18' viewBox='0 0 150 18'><rect x='0' y='3.5' width='"
& FORMAT ( DIVIDE ( MAX ( 0, _Actual ), _Max, 0 ) * 100, "0.0", "en-US" )
& "' height='11' fill='%23118DFF'/>"
& IF (
ISBLANK ( _Target ),
"",
"<rect x='"
& FORMAT ( 0 + DIVIDE ( _Target - 0, _Max - 0, 0.5 ) * 100, "0.0", "en-US" )
& "' y='1' width='2' height='16' fill='%23252423'/>"
)
& "<text x='150' y='9' dy='0.35em' text-anchor='end' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='12' fill='%23252423'>"
& FORMAT ( _Actual, "#,0.0,""K""", "en-US" )
& "</text></svg>"
)DAX user-defined function variant (VizForge.SvgDataBar)
One-line call: Data Bar = VizForge.SvgDataBar ( 'Product'[Category], [Sales], [Sales Target] )
-- 1) Paste into DAX query view and choose 'Update model with changes'.
-- Needs DAX user-defined functions (generally available since the June 2026 Power BI release).
DEFINE
/// Data bar with target as an SVG image URL (VizForge). Optional parameters default to the colours you picked.
FUNCTION VizForge.SvgDataBar = (
pCategory : ANYREF EXPR,
pActual : ANYREF EXPR,
pTarget : ANYREF EXPR,
pBarColor : STRING = "#118DFF",
pTargetColor : STRING = "#252423",
pLabelColor : STRING = "#252423"
) =>
VAR _Actual = pActual
VAR _Target = pTarget
VAR _Rows = ADDCOLUMNS ( ALLSELECTED ( pCategory ), "@a", CALCULATE ( pActual ), "@t", CALCULATE ( pTarget ) )
VAR _Max = MAXX ( _Rows, MAX ( [@a], [@t] ) )
RETURN
IF (
ISBLANK ( _Actual ),
BLANK (),
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='150' height='18' viewBox='0 0 150 18'><rect x='0' y='3.5' width='"
& FORMAT ( DIVIDE ( MAX ( 0, _Actual ), _Max, 0 ) * 100, "0.0", "en-US" )
& "' height='11' fill='"
& SUBSTITUTE ( pBarColor, "#", "%23" )
& "'/>"
& IF (
ISBLANK ( _Target ),
"",
"<rect x='"
& FORMAT ( 0 + DIVIDE ( _Target - 0, _Max - 0, 0.5 ) * 100, "0.0", "en-US" )
& "' y='1' width='2' height='16' fill='"
& SUBSTITUTE ( pTargetColor, "#", "%23" )
& "'/>"
)
& "<text x='150' y='9' dy='0.35em' text-anchor='end' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='12' fill='"
& SUBSTITUTE ( pLabelColor, "#", "%23" )
& "'>"
& FORMAT ( _Actual, "#,0.0,""K""", "en-US" )
& "</text></svg>"
)
EVALUATE { VizForge.SvgDataBar ( 'Product'[Category], [Sales], [Sales Target] ) }
-- 2) Then create the measure and set Measure tools > Data category = Image URL:
-- Data Bar = VizForge.SvgDataBar ( 'Product'[Category], [Sales], [Sales Target] )
-- Colours and thresholds are optional parameters: pass a value after the fields to override one.
-- Pass colours as plain "#RRGGBB"; the function encodes the # for the data URI.Placing it in a report
Needs 'Product'[Category] on the rows of the same visual, plus [Sales] and [Sales Target]. Image size 18 × 150. Remove other columns from rows, or the shared maximum is computed over categories the reader can't see. Remember the Image URL data category. All setup steps.
Gotchas
- The scale is the largest actual or target among visible rows; a single huge row compresses the rest.
- Totals rows evaluate the measure at the grand total, which is usually larger than every row: hide the image on totals with ISINSCOPE if needed.
- Negative actuals are drawn as zero-width bars; use the variance bar for signed values.
- Length. 387 characters, but every row scans all visible rows for the shared scale.
- Context. Each row reads ALLSELECTED ( 'Product'[Category] ) to find the largest actual or target on screen, so every bar is on one scale. A slicer on category changes every row's bar length, which is the point.
- Tooltip. Hovering shows the SVG text; a blank report-page tooltip hides it.
When an SVG measure isn't enough
A bar chart visual can sort by gap to target and filter the page when a bar is clicked. See the Bullet chart custom visual for what an interactive version can do.
KPI card designs using it
Finished designs built on this pattern, from the KPI card gallery.