DAX SVG measure · KPI card
KPI card with sparkline in one DAX SVG measure
By Nawaf Sharaf · Founder & Reliability Engineer · Updated
Use it when
- You want a KPI tile with a trend that looks the same in every report, independent of card formatting.
- A matrix of mini cards per region or product.
Skip it when
Users need to hover the trend for a month's value or click one point to filter: the picture itself isn't interactive. Use the native card's callout with a delta badge image, or a KPI card custom visual.
Three looks
The kpi card (value, delta, sparkline) 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.
- Value measure
- The headline number, formatted by the format you pick.
- Comparison measure
- Prior year or target for the delta.
- Trend axis column
- A date or numeric column; the trend covers the selected periods.
The full DAX
Swap [Revenue] and [Revenue PY] and 'Date'[Month Start] for your own names.
KPI Card =
-- VizForge SVG measure: KPI card (caption, value, delta, sparkline).
-- Set Measure tools > Data category = Image URL.
-- New Card visual: Format > Visual > Image > Image source: Select from data, then pick this measure; set Image fit to Fit.
-- 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 _Value = [Revenue]
VAR _Compare = [Revenue PY]
VAR _Delta = DIVIDE ( _Value - _Compare, ABS ( _Compare ), BLANK () )
VAR _DeltaColour = IF ( ABS ( _Delta ) < 0.005, "%23605E5C", IF ( _Delta > 0, "%231AAB40", "%23D64554" ) )
VAR _Base = FILTER (
ADDCOLUMNS (
ALLSELECTED ( 'Date'[Month Start] ),
"@x", CONVERT ( 'Date'[Month Start], DOUBLE ),
"@v", [Revenue]
),
NOT ISBLANK ( [@v] )
)
VAR _XMin = MINX ( _Base, [@x] )
VAR _XMax = MAXX ( _Base, [@x] )
VAR _VMin = MINX ( _Base, [@v] )
VAR _VMax = MAXX ( _Base, [@v] )
VAR _Points = CONCATENATEX (
_Base,
FORMAT ( 10 + DIVIDE ( [@x] - _XMin, _XMax - _XMin, 0.5 ) * 200, "0.0", "en-US" )
& ","
& FORMAT ( 54 + ( 1 - DIVIDE ( [@v] - _VMin, _VMax - _VMin, 0.5 ) ) * 40, "0.0", "en-US" ),
" ",
[@x], ASC
)
RETURN
IF (
ISBLANK ( _Value ),
BLANK (),
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='104' viewBox='0 0 220 104'><rect x='0.5' y='0.5' width='219' height='103' rx='8' fill='%23FFFFFF' stroke='%23E6E6E6'/><text x='10' y='19.6' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='12' fill='%23605E5C'>Revenue</text><text x='10' y='46' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='27' font-weight='700' fill='%23252423'>"
& FORMAT ( _Value, "#,0.0,,""M""", "en-US" )
& "</text><text x='210' y='19.6' text-anchor='end' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='11' font-weight='600' fill='"
& _DeltaColour
& "'>"
& SUBSTITUTE ( FORMAT ( _Delta, "+0.0%;-0.0%;0.0%", "en-US" ), "%", "%25" )
& "</text>"
& IF (
COUNTROWS ( _Base ) < 2,
"",
"<polyline fill='none' stroke='%23118DFF' stroke-width='1.75' stroke-linejoin='round' stroke-linecap='round' points='"
& _Points
& "'/><circle cx='"
& MAXX ( TOPN ( 1, _Base, [@x], DESC ), FORMAT ( 10 + DIVIDE ( [@x] - _XMin, _XMax - _XMin, 0.5 ) * 200, "0.0", "en-US" ) )
& "' cy='"
& MAXX ( TOPN ( 1, _Base, [@x], DESC ), FORMAT ( 54 + ( 1 - DIVIDE ( [@v] - _VMin, _VMax - _VMin, 0.5 ) ) * 40, "0.0", "en-US" ) )
& "' r='2.75' fill='%23118DFF'/>"
)
& "</svg>"
)DAX user-defined function variant (VizForge.SvgKpiCard)
One-line call: KPI Card = VizForge.SvgKpiCard ( [Revenue], [Revenue PY], 'Date'[Month Start] )
-- 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
/// KPI card (value, delta, sparkline) as an SVG image URL (VizForge). Optional parameters default to the colours you picked.
FUNCTION VizForge.SvgKpiCard = (
pValue : ANYREF EXPR,
pCompare : NUMERIC,
pAxis : ANYREF EXPR,
pLabelColor : STRING = "#605E5C",
pGoodColor : STRING = "#1AAB40",
pBadColor : STRING = "#D64554",
pSurfaceColor : STRING = "#FFFFFF",
pBorderColor : STRING = "#E6E6E6",
pTextColor : STRING = "#252423",
pLineColor : STRING = "#118DFF"
) =>
VAR _Value = pValue
VAR _Compare = pCompare
VAR _Delta = DIVIDE ( _Value - _Compare, ABS ( _Compare ), BLANK () )
VAR _DeltaColour = IF (
ABS ( _Delta ) < 0.005,
SUBSTITUTE ( pLabelColor, "#", "%23" ),
IF (
_Delta > 0,
SUBSTITUTE ( pGoodColor, "#", "%23" ),
SUBSTITUTE ( pBadColor, "#", "%23" )
)
)
VAR _Base = FILTER (
ADDCOLUMNS (
ALLSELECTED ( pAxis ),
"@x", CONVERT ( pAxis, DOUBLE ),
"@v", CALCULATE ( pValue )
),
NOT ISBLANK ( [@v] )
)
VAR _XMin = MINX ( _Base, [@x] )
VAR _XMax = MAXX ( _Base, [@x] )
VAR _VMin = MINX ( _Base, [@v] )
VAR _VMax = MAXX ( _Base, [@v] )
VAR _Points = CONCATENATEX (
_Base,
FORMAT ( 10 + DIVIDE ( [@x] - _XMin, _XMax - _XMin, 0.5 ) * 200, "0.0", "en-US" )
& ","
& FORMAT ( 54 + ( 1 - DIVIDE ( [@v] - _VMin, _VMax - _VMin, 0.5 ) ) * 40, "0.0", "en-US" ),
" ",
[@x], ASC
)
RETURN
IF (
ISBLANK ( _Value ),
BLANK (),
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='104' viewBox='0 0 220 104'><rect x='0.5' y='0.5' width='219' height='103' rx='8' fill='"
& SUBSTITUTE ( pSurfaceColor, "#", "%23" )
& "' stroke='"
& SUBSTITUTE ( pBorderColor, "#", "%23" )
& "'/><text x='10' y='19.6' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='12' fill='"
& SUBSTITUTE ( pLabelColor, "#", "%23" )
& "'>Revenue</text><text x='10' y='46' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='27' font-weight='700' fill='"
& SUBSTITUTE ( pTextColor, "#", "%23" )
& "'>"
& FORMAT ( _Value, "#,0.0,,""M""", "en-US" )
& "</text><text x='210' y='19.6' text-anchor='end' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='11' font-weight='600' fill='"
& _DeltaColour
& "'>"
& SUBSTITUTE ( FORMAT ( _Delta, "+0.0%;-0.0%;0.0%", "en-US" ), "%", "%25" )
& "</text>"
& IF (
COUNTROWS ( _Base ) < 2,
"",
"<polyline fill='none' stroke='"
& SUBSTITUTE ( pLineColor, "#", "%23" )
& "' stroke-width='1.75' stroke-linejoin='round' stroke-linecap='round' points='"
& _Points
& "'/><circle cx='"
& MAXX ( TOPN ( 1, _Base, [@x], DESC ), FORMAT ( 10 + DIVIDE ( [@x] - _XMin, _XMax - _XMin, 0.5 ) * 200, "0.0", "en-US" ) )
& "' cy='"
& MAXX ( TOPN ( 1, _Base, [@x], DESC ), FORMAT ( 54 + ( 1 - DIVIDE ( [@v] - _VMin, _VMax - _VMin, 0.5 ) ) * 40, "0.0", "en-US" ) )
& "' r='2.75' fill='"
& SUBSTITUTE ( pLineColor, "#", "%23" )
& "'/>"
)
& "</svg>"
)
EVALUATE { VizForge.SvgKpiCard ( [Revenue], [Revenue PY], 'Date'[Month Start] ) }
-- 2) Then create the measure and set Measure tools > Data category = Image URL:
-- KPI Card = VizForge.SvgKpiCard ( [Revenue], [Revenue PY], 'Date'[Month Start] )
-- 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
Designed for the new Card visual: Format > Visual > Image, Image source: Select from data, Image fit: Fit, at 220 × 104. In a matrix it turns each region into a mini card. Remember the Image URL data category. All setup steps.
Gotchas
- The card draws its own background and border; set the Card visual's background to transparent or match it.
- The value and delta are drawn as SVG text in Segoe UI, so they don't follow the report theme's fonts.
- The trend uses ALLSELECTED over the axis, so a date slicer controls how many periods the line covers.
- A change smaller than ±0.5%, or a missing comparison, is drawn in the caption colour rather than red.
- Length. 923 characters at 12 periods, 1,176 at 36, once per row.
- Context. The value and delta follow the card's filters, while the trend iterates ALLSELECTED months, so a date slicer shortens the line and also changes the value, which then totals the selected months.
- Tooltip. Hovering shows the SVG text; a blank report-page tooltip hides it.
When an SVG measure isn't enough
A KPI card visual adds point tooltips on the trend, click-a-point filtering by period and format-pane colours. See the KPI card 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.