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
- Open your Power BI Desktop file and navigate to the DAX Query View (the fourth icon on the left sidebar).
- Paste the provided code into a new query tab.
- Click the „Update model: Add new function” button that appears above the code. The
KpiCardSvgfunction 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 =
""
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
- Select your newly created measure and change its Data Category to Image URL in the measure tools ribbon.
- Add the native Card (New) visual or a Table/Matrix to your canvas.
- Drop the measure into the visual. Adjust the image size to 'Fit’ or 'Normal’ to see your stunning custom KPI card in action!
