Join us at FabCon Atlanta from March 16 - 20, 2026, for the ultimate Fabric, Power BI, AI and SQL community-led event. Save $200 with code FABCOMM.
Register now!Get Fabric certified for FREE! Don't miss your chance! Learn more
In this article, I’ll walk through how I built a Sales KPI Card that displays total sales, monthly, quarterly, and yearly comparisons, along with key trend indicators — all in a single polished visual.
Why Use SVG in Power BI?
SVG allows you to:
The best part? You don’t need custom visuals or external tools — everything can be done using a DAX measure.
Step 1: Core Sales Calculations
The first step is to calculate the main sales figures:
These values form the basis for the entire visual.
Step 2: Calculating Trends
Next, we calculate the month-over-month (MoM), quarter-over-quarter (QoQ), and year-over-year (YoY) growth. To avoid misleading numbers, we cap the percentages at ±100%:
This ensures the percentages remain clear and visually accurate.
Step 3: Visual Encoding
We use simple visual indicators for trends:
This gives users an instant understanding of performance.
Step 4: Highlighting the Best KPI
To make the card even more informative, we automatically highlight the best-performing KPI:
The card dynamically adjusts as data changes, ensuring that the most significant trend stands out.
Step 5: Mini Bars for Context
To provide visual scale, we include small bars for monthly, quarterly, and yearly sales. These bars are proportional to the largest value:
Step 6: Structured Layout with SVG
The card is divided into three logical sections:
This layout makes the card modern, clean, and easy to interpret at a glance. and it will look something like the following:
Step 7: Complete Measure Code
Sales KPI Card Polished SVG Fixed =
VAR TotalSales = CALCULATE(SUM(fct_Orders[Sales]))
VAR LastMonthSales = CALCULATE(SUM(fct_Orders[Sales]), DATEADD('dim-Date'[Date], -1, MONTH))
VAR LastQtrSales = CALCULATE(SUM(fct_Orders[Sales]), DATEADD('dim-Date'[Date], -1, QUARTER))
VAR LastYearSales = CALCULATE(SUM(fct_Orders[Sales]), DATEADD('dim-Date'[Date], -1, YEAR))
- Trends
VAR MoM = DIVIDE(TotalSales - LastMonthSales, LastMonthSales, 0)
VAR QoQ = DIVIDE(TotalSales - LastQtrSales, LastQtrSales, 0)
VAR YoY = DIVIDE(TotalSales - LastYearSales, LastYearSales, 0)
- Cap percentages ±100%
VAR MoMDisplay = MAX(-1, MIN(1, MoM))
VAR QoQDisplay = MAX(-1, MIN(1, QoQ))
VAR YoYDisplay = MAX(-1, MIN(1, YoY))
- Triangles
VAR MoMTriangle = IF(MoM >= 0,"▲","▼")
VAR QoQTriangle = IF(QoQ >= 0,"▲","▼")
VAR YoYTriangle = IF(YoY >= 0,"▲","▼")
- Colors
VAR MoMColor = IF(MoM >= 0,"#22C55E","#EF4444")
VAR QoQColor = IF(QoQ >= 0,"#22C55E","#EF4444")
VAR YoYColor = IF(YoY >= 0,"#22C55E","#EF4444")
- Best KPI highlight
VAR MaxTrend = MAXX({MoM, QoQ, YoY}, [Value])
VAR LMHighlight = IF(MoM = MaxTrend, "#DBEAFE", "#F3F4F6")
VAR LQHighlight = IF(QoQ = MaxTrend, "#DBEAFE", "#F3F4F6")
VAR LYHighlight = IF(YoY = MaxTrend, "#DBEAFE", "#F3F4F6")
- Fonts
VAR FontSizeMain = 22
VAR FontSizeSub = 16
- SVG dimensions
VAR W = 360
VAR H = 260
- Mini bars
VAR MaxVal = MAXX({LastMonthSales, LastQtrSales, LastYearSales}, [Value])
VAR LMBar = DIVIDE(LastMonthSales, MaxVal, 0) * 100
VAR LQBar = DIVIDE(LastQtrSales, MaxVal, 0) * 100
VAR LYBar = DIVIDE(LastYearSales, MaxVal, 0) * 100
VAR CenterX = 180
RETURN
"
<svg xmlns='http://www.w3.org/2000/svg' width='" & W & "' height='" & H & "'>
<! - TOTAL SALES →
<rect x='0' y='5' width='350' height='40' rx='6' fill='#1E3A8A'/>
<text x='15' y='32' font-size='22' font-weight='bold' fill='white'>
💰 Total Sales: " & FORMAT(TotalSales,"$#,##0") & "
</text>
<! - LM / LQ / LY CONTAINER →
<rect x='0' y='50' width='350' height='145' rx='6' fill='#F3F4F6'/>
<! - LM →
<rect x='5' y='55' width='340' height='35' rx='4' fill='" & LMHighlight & "'/>
<text x='15' y='75' font-size='16'>📅 LM: " & FORMAT(LastMonthSales,"$#,##0") & "</text>
<text x='" & CenterX & "' y='75' font-size='16' fill='" & MoMColor & "'>" & MoMTriangle & " " & FORMAT(MoMDisplay,"+0.0%;-0.0%") & "</text>
<rect x='15' y='80' width='" & LMBar & "' height='8' rx='4' fill='url(#LMGradient)'/>
<! - LQ →
<rect x='5' y='95' width='340' height='35' rx='4' fill='" & LQHighlight & "'/>
<text x='15' y='115' font-size='16'>📊 LQ: " & FORMAT(LastQtrSales,"$#,##0") & "</text>
<text x='" & CenterX & "' y='115' font-size='16' fill='" & QoQColor & "'>" & QoQTriangle & " " & FORMAT(QoQDisplay,"+0.0%;-0.0%") & "</text>
<rect x='15' y='120' width='" & LQBar & "' height='8' rx='4' fill='url(#LQGradient)'/>
<! - LY →
<rect x='5' y='135' width='340' height='35' rx='4' fill='" & LYHighlight & "'/>
<text x='15' y='155' font-size='16'>📈 LY: " & FORMAT(LastYearSales,"$#,##0") & "</text>
<text x='" & CenterX & "' y='155' font-size='16' fill='" & YoYColor & "'>" & YoYTriangle & " " & FORMAT(YoYDisplay,"+0.0%;-0.0%") & "</text>
<rect x='15' y='160' width='" & LYBar & "' height='8' rx='4' fill='url(#LYGradient)'/>
<! - TREND CONTAINER →
<rect x='0' y='200' width='350' height='55' rx='6' fill='#1F2A44'/>
<text x='35' y='220' font-size='14' fill='#FFFFFF'>MoM</text>
<text x='35' y='242' font-size='18' font-weight='bold' fill='" & MoMColor & "'>
" & MoMTriangle & " " & FORMAT(MoMDisplay,"+0%;-0%") & " |
</text>
<text x='140' y='220' font-size='14' fill='#FFFFFF'>QoQ</text>
<text x='140' y='242' font-size='18' font-weight='bold' fill='" & QoQColor & "'>
" & QoQTriangle & " " & FORMAT(QoQDisplay,"+0%;-0%") & " |
</text>
<text x='245' y='220' font-size='14' fill='#FFFFFF'>YoY</text>
<text x='245' y='242' font-size='18' font-weight='bold' fill='" & YoYColor & "'>
" & YoYTriangle & " " & FORMAT(YoYDisplay,"+0%;-0%") & "
</text>
<! - GRADIENTS →
<defs>
<linearGradient id='LMGradient' x1='0' y1='0' x2='1' y2='0'>
<stop offset='0%' stop-color='" & IF(MoM>=0,"#22C55E","#EF4444") & "'/>
<stop offset='100%' stop-color='" & IF(MoM>=0,"#A7F3D0","#FCA5A5") & "'/>
</linearGradient>
<linearGradient id='LQGradient' x1='0' y1='0' x2='1' y2='0'>
<stop offset='0%' stop-color='" & IF(QoQ>=0,"#22C55E","#EF4444") & "'/>
<stop offset='100%' stop-color='" & IF(QoQ>=0,"#A7F3D0","#FCA5A5") & "'/>
</linearGradient>
<linearGradient id='LYGradient' x1='0' y1='0' x2='1' y2='0'>
<stop offset='0%' stop-color='" & IF(YoY>=0,"#22C55E","#EF4444") & "'/>
<stop offset='100%' stop-color='" & IF(YoY>=0,"#A7F3D0","#FCA5A5") & "'/>
</linearGradient>
</defs>
</svg>
"
Note: Don't forget to put this measure in HTML Content import visual.
Key Takeaways
With this approach, Power BI reports can go beyond standard visuals and deliver rich, executive-friendly insights in a single card.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.