I. Introduction
SVG visuals in Power BI unlock a level of precision and storytelling that standard charts can’t easily reach. Because SVG is just markup, every pixel – positions, shapes, labels, and interactions – can be driven by measures and dynamic business rules. The result: compact, brand-consistent visuals (custom gauges, bullet charts, waterfalls, timelines, scorecards, schematics) that stay responsive to slicers, drill-throughs, and row-level context without external add-ins.
In this article, we’ll walk step-by-step through building an SVG visual from scratch: choosing the coordinate system, binding measures to geometry, formatting text for legibility, layering annotations, and wiring interactivity. Along the way we’ll highlight best practices (scales, padding, alignment, accessibility, color tokens, performance hygiene) and call out common pitfalls (overdraw, label collisions, DAX bloat, and maintenance trade-offs). You’ll finish with a reusable pattern you can adapt to any KPI or layout.
We’ll also weigh the pros and cons. On the plus side: pixel-perfect control, small footprint, semantic-model reusability, and seamless theming. On the minus side: a learning curve, the need for disciplined naming and testing, and potential performance issues if markup or DAX isn’t optimized. Understanding both sides helps you decide when SVG is the right tool – and when a native visual is “good enough.”
Our team specializes in exactly this craft: transforming your favorite charts and KPIs into fully automated, production-ready Power BI reports powered by custom SVG. From discovery and design systems to DAX engineering and governance, we support clients end-to-end so their reports aren’t just beautiful—they’re maintainable, fast, and trusted.
II. What’s SVG is
SVG visualizations in Power BI are visuals that render graphics using Scalable Vector Graphics (SVG)—an XML-based format that stays crisp at any size. In Power BI, you typically generate SVG strings in DAX or Power Query and display them with a visual that supports HTML/SVG (e.g., HTML Content, Deneb, or custom visuals). This lets you draw fully custom shapes (icons, bars, bullets, Gantt bars, flags, sparklines) with dynamic colors, sizes, and labels driven by your data.
Why use SVG in Power BI?
-
- Pixel-perfect, resolution-independent graphics.
- Highly dynamic: every attribute (color, position, opacity, text) can respond to measures.
- Enables charts not available out-of-the-box (bullet charts, waterfall bridges, bespoke KPI tiles).
Trade-offs:
-
- More effort to design and maintain (you’re crafting SVG markup).
- Can be slower if you output very large SVGs per row.
- Accessibility and tooltips need deliberate handling.
III. Pros&cons of using SVG
Pros:
-
-
Pixel-perfect control
Exact positioning, sizing, and styling of every element (labels, ticks, shapes) beyond what native visuals allow.
-
Fully data-driven
Geometry and text can respond to slicers/filters via DAX, enabling slicer-aware KPIs, dynamic thresholds, and conditional annotations.
-
Unlimited visual types
Build bespoke charts (e.g., bullet gauges, custom waterfalls, timelines, schematics) not available natively.
-
Brand consistency
Centralize colors, typography, and spacing with measure-based tokens for consistent theming across reports.
-
Lightweight & portable
No external custom visual code; SVG is just text, easy to template, reuse, and version in source control.
-
-
-
Steep learning curve
Requires comfort with both DAX and SVG; debugging long string measures can be tedious.
-
Maintenance risk
Without strict naming and modularization, SVG/DAX quickly becomes hard to read, test, and extend.
-
Limited native interactivity
Cross-highlighting, built-in tooltips, and accessibility features are weaker vs. standard visuals.
-
Export & layout quirks
Email subscriptions, PDF export, and mobile view can render differently; careful testing is needed.
-
Performance pitfalls
Excessive markup, many elements, or heavy string concatenation (especially in DirectQuery) can slow visuals.
-
IV. Best practices of using SVG
There is five best practices for using SVG in your report:
- Design tokens first (colors, sizes, spacing)
- Centralize style as measures (e.g.,
__Color_Primary,__Font_S,__Pad_M), so themes change in one place. - Example:
VAR cMain = [__Color_Primary] VAR f12 = [__Font_S]
- Centralize style as measures (e.g.,
- Modularize the SVG (compose from fragments)
- Build small measures for primitives (
axis,bars,labels) and concatenate them. - Example pattern:
__svg_axis(), __svg_bars(), __svg_labels() → CONCATENATEX({[__axis],[__bars],[__labels]},[Value],"")
- Build small measures for primitives (
- Use a stable coordinate system
- Set a
viewBox="0 0 W H"and derive all positions from margins and plot width/height. - Example:
VAR W = 280 VAR mL = 12 VAR plotW = W - (mL + [mR]) positions = mL + scaleX(Value)
- Set a
- Be ruthless about element count
- Limit shapes; aggregate where possible; avoid per-point heavy decorations.
- Collapse repeated attributes with
<g>groups and shared class/style strings.
- Performance hygiene in DAX
- Cache with
VARand reuse; avoid repeatedCALCULATEfor the same sub-results. - Build markup via
CONCATENATEXover a smallVARtable instead of many&concatenations. - Guardrails: cap rows (
TOPN), avoid heavy per-row logic inDirectQuery.
- Cache with
V. SVG as a future of Power BI Advanced Reports – real example
The image above shows a tornado chart displaying the percentage distribution of the turnover rate by department and gender.
How to create a measure to show it? Dax code has more than 150 rows so I will go through step by step
Each SVG visualization demands to set key settings of height and width of selected visual.
Key settings:
Wbase = 280→ base canvas width (px/user units) for the whole SVG.mL, mR, mT, mB = 12→ left/right/top/bottom margins (padding) around the drawing area.rowH = 34→ height of one row/category lane.barH = ROUND(rowH*0.65,0)→ bar thickness set to ~65% of the row height (so some vertical padding remains).
Typical use:
Inner plot width = Wbase - mL - mRRow y-position = mT + rowIndex * rowHBar y = rowY + (rowH - barH)/2(centers the bar within the row)
There is a short description of used variables:
Color tokens(colF,colM,colDeptTxt) define reusable hex colors for female/male series and department labels — easy theming and consistency.DeptListbuilds the distinct list of departments in the current filter context (respects slicers/RLS).ByDeptadds two metrics per department: female (Fv) and male (Mv) turnover rates, usingCALCULATEwithKEEPFILTERSso gender is applied without wiping other filters.MaxVfinds the single highest turnover rate across both genders and all departments — used to scale bar lengths consistently in the SVG.MaxVnzis a safe divisor (falls back to 1 if everything is ≤0) to avoid divide-by-zero in width/position calculations.
There is a short description of used variables:
W/plotW/gap: total SVG width, drawable width after side margins (mL,mR), and spacing between label column and bars.MaxDeptLen: longest department name length (characters) fromDeptList.labelW_auto: auto label column width, approximated from text length (~8 px per char + 28 px padding).labelW: final label column width — capped at 48% ofWand at least 110 px, but no smaller than the auto width.halfW: width allocated to each of the two value areas (e.g., female vs male) = (plotW − labelW − gap) / 2, with a safeDIVIDE(..., 0)fallback.leftX: x-start of the first value area =mL + labelW + gap.midX: x-start of the second value area =leftX + halfW(the midpoint between the two bar regions).
There is a short description of used variables:
Ranked: extendsByDeptwith:Peak– the higher of female (Fv) vs male (Mv) turnover for each department (BLANKs → 0).Ord– dense rank of thatPeakacross departments (highest value = rank 1), used to control row order in the SVG.N: number of departments (rows) after ranking → drives how many bars/labels to draw.H: total SVG height = top margin + bottom margin +N × rowH→ ensures the canvas fits all ranked rows neatly.
There is a short description of used variables:
Loop & order:CONCATENATEX(Ranked, … , "", [Ord], ASC)iterates departments in ranked order and concatenates each row’s SVG.- Row geometry:
yTop,yBar,cyset the row’s top, bar’s y, and vertical text center.halfW/midXcame earlier — center split for left (F) and right (M) bars.
- Values & widths:
fv,mv= female/male rates (BLANK→ 0).wf,wm= bar widths scaled byMaxVnzwithin each half.
- X positions:
- Female bar grows left from center: start
fx = midX - wf; label atfcx = midX - wf/2. - Male bar grows right from center: start
mx = midX; label atmcx = midX + wm/2.
- Female bar grows left from center: start
- Drawing:
- If width > 0, draw a rounded
<rect>with fillcolF/colMand a centered<text>showingFORMAT(...,"0.0%").MAX(w,1)enforces a 1-px minimum so tiny values remain visible. - Text aligned via
dominant-baseline='middle',text-anchor='middle', withdyFixandfsValfor vertical tweak and font size.
- If width > 0, draw a rounded
There is a short description of used variables:
Iteration & order:CONCATENATEX(Ranked, …, "", [Ord], ASC)loops departments in ranked row order and concatenates label SVG.- Vertical position:
cycenters each label in its row:mT + (ord−1)*rowH + rowH/2. - Truncation width:
maxCharsestimates how many characters fit in the left label column based onlabelW(≈7 px/char, minus padding). - Ellipsis logic:
deptShorttrims long names and adds “…” so text doesn’t overflow the label column. - Text drawing: outputs a
<text>atx=mL,y=cy, left-aligned (text-anchor='start'), vertically centered (dominant-baseline='middle'), with small vertical tweakdyFix, sizefsDept, and colorcolDeptTxt.
There is a short description of used variables:
legendH/HFinal/lY: reserve space for a legend (legendH=26), set final SVG heightHFinal = H + legendH, and place the legend vertically atlY = H + legendH/2.lBoxW/lBoxH/lGapItem/cx: legend box size (18×12), spacing between legend items (70), and horizontal center of the canvas (cx = W/2).- LegendSVG:
- Wraps items in a
<g>group. - Female swatch: rounded
<rect>filled withcolF, positioned left of center atcx - lGapItem, with its label placed immediately to the right (+lBoxW + 6), vertically centered onlY. - Male swatch: same pattern to the right of center (offset
cx + 20), filled withcolM, with its label beside it. - Text uses
dominant-baseline='middle'+dyFixto fine-tune vertical alignment, andfsDept/colDeptTxtfor size/color.
- Wraps items in a
There is a final render returning a SVG used in HTML Content (Lite). It presents tornado chart.
VI Summary
SVG in Power BI unlocks precision, storytelling, and truly custom visuals – but it comes with a steeper learning curve and disciplined engineering to keep things fast and maintainable. If you want the benefits without the complexity, we can design, build, and support your SVG visuals end-to-end as part of fully automated reports. Prefer to keep it in-house? We also offer focused training so your team can confidently create and maintain SVG-based KPIs and charts. Either way, you get the control of SVG with the reliability your stakeholders expect.
