Blog Post

Power BI Community Blog
6 MIN READ

Building Dynamic Sales KPI Cards in Power BI Using SVG and DAX

mh2587's avatar
mh2587
Icon for Super User rankSuper User
8 months ago

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:

  • Create precise, pixel-perfect visuals
  • Combine multiple KPIs in a single card
  • Add gradients, mini bars, and highlights
  • Fully control layout, spacing, and colors
  • Keep visuals dynamic and filter-aware

    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:

    • Positive growth is shown in green
    • Negative growth is shown in red
    • Triangles indicate the direction of the trend

      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:

      1. Total Sales Header — a dark blue container for immediate focus
      2. KPI Detail Section — shows last month, quarter, and year with mini bars and trend indicators; highlights the best KPI
      3. Trend Summary Section — a compact strip showing MoM, QoQ, and YoY growth percentages

        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

        • SVG + DAX allows for fully custom, responsive visuals.
        • Automatic highlights and trend indicators improve dashboard storytelling.
        • Mini bars provide visual context for comparisons without adding clutter.
        • The card is compact, modern, and reusable across any KPI.

          With this approach, Power BI reports can go beyond standard visuals and deliver rich, executive-friendly insights in a single card.

Updated 8 months ago
Version 1.0
No CommentsBe the first to comment