📈 Dynamic SVG KPI Card with Trendlines & Themes (DAX UDF)

Tired of standard, rigid KPI cards? Elevate your Power BI dashboards with this highly customizable, purely DAX-driven SVG KPI Card. Built as a User-Defined Function (UDF), this single piece of code allows you to generate beautiful, responsive cards across your entire model.

It automatically adapts to your data: showing green for positive YoY growth and red for negative trends. Whether you prefer a sleek line chart for daily trends or consolidated monthly bars, this template has you covered.

✨ Key Features:

  • 🔌 Plug & Play UDF: Add the function to your model once, and reuse it for as many measures as you need.
  • 📊 Dynamic Charting: Switch between a daily trendline (pChart = "line") or aggregated monthly bars (pChart = "bar").
  • 🚦 Smart Coloring: The primary accent color (green/red) automatically adjusts based on whether your YoY variance is positive or negative.
  • 🌗 Built-in Themes: Choose from stunning light and dark themes (e.g., emerald, dark-violet, dark-blue) with elegant neon glows.

🛠️ How to Implement This in Power BI

⚙️ Step 1: Add the Function to Your Model

  1. Open your Power BI Desktop file and navigate to the DAX Query View (the fourth icon on the left sidebar).
  2. Paste the provided code into a new query tab.
  3. Click the „Update model: Add new function” button that appears above the code. The KpiCardSvg function is now saved in your model.
// ============================================================
//  UDF: KpiCardSvg  (v4)
//   - pTheme zmienia TYLKO tlo (poswiaty + jasny/ciemny tryb)
//   - linia, slupki, kapsulka, ikona: kolory pierwotne (zielony/czerwony wg znaku)
//   - pChart = "bar" -> slupki SKONSOLIDOWANE PO MIESIACACH
//  MOTYWY JASNE: emerald | violet | blue | amber | rose | slate
//  MOTYWY CIEMNE: dark | dark-emerald | dark-violet | dark-blue | dark-amber | dark-rose | dark-slate
//  Wklej calosc do DAX query view -> uruchom -> "Update model: Add new function".
// ============================================================

DEFINE
    /// Karta KPI jako SVG: tytul, duza wartosc, pigulka YoY, linia MAX i wykres w czasie.
    /// @param {STRING} pTitle - Tytul karty, np. "Revenue"
    /// @param {SCALAR} pMeasure - Miara na wykresie i jako duza wartosc, np. [Total Revenue] (tryb expr)
    /// @param {DOUBLE} pYoY - Zmiana r/r jako ulamek, np. [Revenue YoY %]
    /// @param {STRING} pFmt - Format liczb walutowych, domyslnie "$#,0"
    /// @param {INT64} pW - Szerokosc karty w px, domyslnie 700
    /// @param {INT64} pH - Wysokosc karty w px, domyslnie 500
    /// @param {STRING} pChart - "line" (domyslnie) lub "bar" (slupki miesieczne)
    /// @param {STRING} pTheme - Motyw TLA, jasny lub "dark-..." (ciemny)
    /// @returns Lancuch data:image/svg+xml gotowy dla kategorii Image URL
    FUNCTION KpiCardSvg =
    (
        pTitle   : STRING,
        pMeasure : SCALAR EXPR,
        pYoY     : DOUBLE,
        pFmt     : STRING = "$#,0",
        pW       : INT64  = 700,
        pH       : INT64  = 500,
        pChart   : STRING = "line",
        pTheme   : STRING = "emerald"
    ) =>

    -- =========================
    -- 1. WYMIARY KARTY
    -- =========================
    VAR CardW = pW
    VAR CardH = pH
    VAR Pad = 18
    VAR RectW = CardW - (Pad * 2)
    VAR RectH = CardH - (Pad * 2)
    VAR PillW = 240
    VAR PillX = CardW - PillW - 40
    VAR ShapeX = PillX + 8
    VAR VsTextX = PillX + 55

    VAR YoYValue = pYoY
    VAR IsPositive = YoYValue >= 0
    VAR _isBar = LOWER ( pChart ) = "bar"

    -- ---- MOTYW: jasny/ciemny + odcien poswiaty ----
    VAR _theme = LOWER ( pTheme )
    VAR _isDark = LEFT ( _theme, 4 ) = "dark"
    -- odcien bazowy (po zdjeciu prefiksu "dark-"); samo "dark" => emerald
    VAR _hue =
        SWITCH ( TRUE (),
            _theme = "dark", "emerald",
            _isDark, MID ( _theme, 6, 50 ),
            _theme
        )
    -- kolory poswiat (A = lewa, B = prawa)
    VAR _glowA =
        SWITCH ( _hue,
            "emerald", "#10B981",
            "violet",  "#8B5CF6",
            "blue",    "#3B82F6",
            "amber",   "#F59E0B",
            "rose",    "#F43F5E",
            "slate",   "#64748B",
            "#10B981"
        )
    VAR _glowB =
        SWITCH ( _hue,
            "emerald", "#F43F5E",
            "violet",  "#EC4899",
            "blue",    "#06B6D4",
            "amber",   "#EF4444",
            "rose",    "#FB7185",
            "slate",   "#94A3B8",
            "#F43F5E"
        )

    -- ---- TOKENY KOLOROW zalezne od trybu jasny/ciemny ----
    VAR _bg0     = IF ( _isDark, "#111827", "#ffffff" )
    VAR _bg1     = IF ( _isDark, "#0F172A", "#f8fafc" )
    VAR _bg2     = IF ( _isDark, "#020617", "#f1f5f9" )
    VAR _cTitle  = IF ( _isDark, "#CBD5E1", "#334155" )
    VAR _cBig    = IF ( _isDark, "#F8FAFC", "#0F172A" )
    VAR _cSub    = IF ( _isDark, "#94A3B8", "#475569" )
    VAR _cBorder = IF ( _isDark, "#1E293B", "#E2E8F0" )
    VAR _cGrid   = IF ( _isDark, "#1E293B", "#E2E8F0" )
    VAR _cAxis   = IF ( _isDark, "#475569", "#94A3B8" )
    VAR _cTick   = IF ( _isDark, "#94A3B8", "#64748B" )
    VAR _cPill   = IF ( _isDark, "#1E293B", "white"   )
    VAR _glowO1  = IF ( _isDark, "0.22", "0.12" )   -- poswiata lewa, mocniejsza na ciemnym
    VAR _glowO2  = IF ( _isDark, "0.06", "0.03" )
    VAR _glowO3  = IF ( _isDark, "0.20", "0.10" )   -- poswiata prawa
    VAR _fadeTo  = IF ( _isDark, "#020617", "#ffffff" ) -- kolor wygaszenia poswiat

    -- ---- AKCENT: pierwotny, wg znaku (NIE zalezy od motywu) ----
    VAR MainColor = IF ( IsPositive, "#10B981", "#F43F5E" )

    VAR Title = pTitle
    VAR BigValue = FORMAT ( pMeasure, pFmt )
    VAR VsLabel = "vs PY: " & FORMAT(YoYValue, "+0.00%;-0.00%;0%")

    /* ========= MAX wartosc i data ========= */
    VAR _MaxDailyRevenue = MAXX(VALUES('Date'[Date]), CALCULATE ( pMeasure ))
    VAR _MaxDate =
        CALCULATE(
            MIN('Date'[Date]),
            FILTER( VALUES('Date'[Date]), CALCULATE ( pMeasure ) = _MaxDailyRevenue )
        )
    VAR Line2 = "MAX: " & FORMAT(_MaxDailyRevenue, pFmt) & " | " & FORMAT(_MaxDate, "dd MMM yyyy")

    -- =========================
    -- 2. LAYOUT WYKRESU
    -- =========================
    VAR AxisX = 130
    VAR X0    = AxisX + 25
    VAR X1    = CardW - 65
    VAR YTop  = CardH - 220
    VAR YBot  = CardH - 110
    VAR AxisY = CardH - 90

    VAR _shape =
        IF(IsPositive,
            "",
            "!"
        )

    -- =========================
    -- 3a. SERIA DZIENNA (linia)
    -- =========================
    VAR DatesInScope =
        CALCULATETABLE ( VALUES ( 'Date'[Date] ), ALLSELECTED ( 'Date'[Date] ) )
    VAR Series0 = ADDCOLUMNS ( DatesInScope, "__Sales", CALCULATE ( pMeasure ) )
    VAR SeriesNB = FILTER ( Series0, NOT ISBLANK ( [__Sales] ) )
    VAR N = COUNTROWS ( SeriesNB )
    VAR MaxSales = MAXX ( SeriesNB, [__Sales] )
    VAR RangeSales = IF ( MaxSales = 0, 1, MaxSales )

    VAR Series1 =
        ADDCOLUMNS ( SeriesNB, "__Idx", RANKX ( SeriesNB, 'Date'[Date], , ASC, Dense ) )
    VAR Series2 =
        ADDCOLUMNS (
            Series1,
            "__X", X0 + DIVIDE ( ([__Idx] - 1) * (X1 - X0), MAX ( N - 1, 1 ) ),
            "__Y", YBot - DIVIDE ( [__Sales] * (YBot - YTop), RangeSales )
        )
    VAR PathD =
        IF (
            N >= 2,
            "M " & CONCATENATEX ( Series2, FORMAT ( [__X], "0" ) & " " & FORMAT ( [__Y], "0" ), " L ", 'Date'[Date], ASC )
        )

    -- =========================
    -- 3b. SERIA MIESIECZNA (slupki) - suma wartosci dziennych w miesiacu
    -- =========================
    VAR _Series2M =
        ADDCOLUMNS ( Series2, "__MStart", DATE ( YEAR ( 'Date'[Date] ), MONTH ( 'Date'[Date] ), 1 ) )
    VAR _MonthAgg =
        GROUPBY ( _Series2M, [__MStart], "@MV", SUMX ( CURRENTGROUP (), [__Sales] ) )
    VAR _MonthN = COUNTROWS ( _MonthAgg )
    VAR _MonthMax = MAXX ( _MonthAgg, [@MV] )
    VAR _MonthScaleMax = IF ( _MonthMax <= 0, 1, _MonthMax )
    VAR _MonthRanked =
        ADDCOLUMNS ( _MonthAgg, "__MIdx", RANKX ( _MonthAgg, [__MStart], , ASC, Dense ) )
    VAR _MonthPlot =
        ADDCOLUMNS (
            _MonthRanked,
            "__MX", X0 + DIVIDE ( ([__MIdx] - 1) * (X1 - X0), MAX ( _MonthN - 1, 1 ) ),
            "__MY", YBot - DIVIDE ( [@MV] * (YBot - YTop), _MonthScaleMax )
        )
    VAR _MonthStep = DIVIDE ( X1 - X0, MAX ( _MonthN - 1, 1 ) )

    VAR _ScaleMax = IF ( _isBar, _MonthScaleMax, RangeSales )

    -- =========================
    -- 4. WYKRESY
    -- =========================
    VAR LineSvg =
        "" &
            "" &
            "" &
        ""

    VAR BarsSvg =
        "" &
        CONCATENATEX (
            _MonthPlot,
            VAR _bw  = MAX ( MIN ( _MonthStep * 0.55, 34 ), 4 )
            VAR _bx  = [__MX] - _bw / 2
            VAR _by  = [__MY]
            VAR _bh  = MAX ( YBot - [__MY], 1 )
            VAR _brx = MIN ( _bw / 2, 8 )
            RETURN
                "",
            "", [__MStart], ASC
        ) &
        ""

    VAR _noData =
        "No trend data"

    VAR ChartSvg =
        IF ( _isBar,
            IF ( _MonthN >= 1, BarsSvg, _noData ),
            IF ( N >= 2, LineSvg, _noData )
        )

    -- =========================
    -- 5. OS Y
    -- =========================
    VAR YTickCount = 4
    VAR YIdx = GENERATESERIES ( 0, YTickCount - 1, 1 )
    VAR YTicks =
        ADDCOLUMNS (
            YIdx,
            "__y", YBot - DIVIDE ( [Value], YTickCount - 1 ) * (YBot - YTop),
            "__label", FORMAT ( DIVIDE ( [Value], YTickCount - 1 ) * _ScaleMax, pFmt )
        )
    VAR YAxisSVG =
        CONCATENATEX (
            YTicks,
            "" &
            "" &
            "" &
            [__label] & "",
            "", 1, ASC
        )

    -- =========================
    -- 6. OS X
    -- =========================
    VAR MidI = INT ( DIVIDE ( N + 1, 2 ) )
    VAR XTickI = UNION ( ROW ( "i", 1 ), ROW ( "i", MidI ), ROW ( "i", N ) )
    VAR XTicks =
        ADDCOLUMNS (
            XTickI,
            "__x", MAXX ( FILTER ( Series2, [__Idx] = [i] ), [__X] ),
            "__label", FORMAT ( MAXX ( FILTER ( Series2, [__Idx] = [i] ), 'Date'[Date] ), "MMM yy" )
        )
    VAR XAxisLine =
        CONCATENATEX (
            XTicks,
            "" &
            "" &
            [__label] & "",
            "", [i], ASC
        )
    VAR XAxisBar =
        CONCATENATEX (
            _MonthPlot,
            "" &
            "" &
            FORMAT ( [__MStart], "MMM" ) & "",
            "", [__MStart], ASC
        )

    VAR _hasAxis = IF ( _isBar, _MonthN >= 1, N >= 2 )
    VAR YAxisFinal = IF ( _hasAxis, YAxisSVG, "" )
    VAR XAxisFinal = IF ( _isBar, IF ( _MonthN >= 1, XAxisBar, "" ), IF ( N >= 2, XAxisLine, "" ) )

    -- =========================
    -- 7. RENDER SVG
    -- =========================
    VAR SVGraw =
    "" &
    "" &

    -- Tlo bazowe (jasne lub ciemne)
    "" &
    "" &
    "" &
    "" &
    "" &

    -- Poswiata lewa = kolor motywu A
    "" &
    "" &
    "" &
    "" &
    "" &

    -- Poswiata prawa = kolor motywu B
    "" &
    "" &
    "" &
    "" &
    "" &

    -- Gradient slupkow = kolor pierwotny (MainColor wg znaku)
    "" &
    "" &
    "" &
    "" &

    "" &
    "" &
    "" &

    "" &
    "" &
    "" &
    "" &
    "" &

    "" &

    "" & Title & "" &
    "" & BigValue & "" &

    -- Kapsulka (tlo wg trybu, obrys/tekst pierwotne wg znaku)
    "" &
    _shape &
    "" & VsLabel & "" &
    "" & Line2 & "" &

    "" &
    "" &
    "" &

    YAxisFinal &
    XAxisFinal &
    ChartSvg &

    ""

    RETURN
    "data:image/svg+xml;utf8," & SVGraw


// ============================================================
//  TEST w DAX query view
// ============================================================
EVALUATE
{ KpiCardSvg ( "Revenue", [Total Revenue], [Revenue YoY %], , , , "bar", "dark-violet" ) }


// ============================================================
//  PRZYKLADY (Data category = Image URL)
// ============================================================
//  KPI Revenue Card = KpiCardSvg ( "Revenue", [Total Revenue], [Revenue YoY %] )                            -- linia, tlo jasne emerald
//  KPI Qty Dark     = KpiCardSvg ( "Quantity", [Total Quantity], [Qty YoY %], , , , "bar", "dark-emerald" ) -- slupki miesieczne, tlo ciemne
//  KPI Orders Dark  = KpiCardSvg ( "Orders", [Total Orders], [Orders YoY %], "#,0", 800, 520, "line", "dark-blue" )

📐 Step 2: Create Your KPI Measure

Create a new measure using the function you just added. Pass your specific data to the parameters. Here are a few examples:

💡 Example 1: Standard Line Chart (Light Theme)

KPI Revenue Card = 
KpiCardSvg ( 
    "Revenue", 
    [Total Revenue], 
    [Revenue YoY %] 
)

💡 Example 2: Monthly Bar Chart (Dark Theme)

KPI Orders Dark = 
KpiCardSvg ( 
    "Orders", 
    [Total Orders], 
    [Orders YoY %], 
    "#,0", 
    700, 
    500, 
    "bar", 
    "dark-blue" 
)

🚀 Step 3: Display the Visual

  1. Select your newly created measure and change its Data Category to Image URL in the measure tools ribbon.
  2. Add the native Card (New) visual or a Table/Matrix to your canvas.
  3. Drop the measure into the visual. Adjust the image size to 'Fit’ or 'Normal’ to see your stunning custom KPI card in action!