DAX SVG measure · KPI card

KPI card with sparkline in one DAX SVG measure

The new Card visual can show an image from data, which means one measure can draw the entire card: caption, value, change against a comparison and a trend line, in your theme's colours. It is also handy in a matrix where each row becomes a mini card.

By · Founder & Reliability Engineer · Updated

KPI card (value, delta, sparkline) rendered from the DAX below with sample data
Rendered from the measure below with sample data at 2× (220×104 px in Power BI). 923 characters.

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 (measure)
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] )

DAX query view script
-- 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

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.