DAX SVG measure · Composition
Mini donut chart with a DAX SVG measure
By Nawaf Sharaf · Founder & Reliability Engineer · Updated
Use it when
- Two or three parts that make a whole: new vs returning customers, channel mix.
- A square image slot in a Card visual or a matrix of segments.
Skip it when
More than three parts, or parts that don't add up to a meaningful whole: use a bar or a 100% stacked bar.
Three looks
The mini donut in three palettes; click one to edit it.
Data it needs
The builder asks for exactly these fields; type your own measure and column names there.
- Part measures
- Two or three non-negative measures; the total is their sum.
The full DAX
Swap [New Customers] and [Returning Customers] and [Lapsed Customers] for your own names.
Mini Donut =
-- VizForge SVG measure: mini donut (parts of a whole).
-- Set Measure tools > Data category = Image URL. Negative parts are not meaningful in a donut.
-- Made with the free VizForge SVG measure builder: vizforge.ai/tools/svg-measure-builder
-- Need per-point tooltips, click-to-select inside the chart or Format-pane settings? Generate it as a real custom visual: vizforge.ai/visuals
VAR _Total = [New Customers] + [Returning Customers]
VAR _S1 = DIVIDE ( [New Customers], _Total, 0 )
VAR _S2 = DIVIDE ( [Returning Customers], _Total, 0 )
RETURN
IF (
_Total < 0,
BLANK (),
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44'><circle cx='22' cy='22' r='18.5' fill='none' stroke='%23118DFF' stroke-width='7' transform='rotate(-90 22 22)' stroke-dasharray='"
& FORMAT ( _S1 * 116.24, "0.0", "en-US" )
& " 116.24' stroke-dashoffset='"
& FORMAT ( -0, "0.0", "en-US" )
& "'/><circle cx='22' cy='22' r='18.5' fill='none' stroke='%23D9B300' stroke-width='7' transform='rotate(-90 22 22)' stroke-dasharray='"
& FORMAT ( _S2 * 116.24, "0.0", "en-US" )
& " 116.24' stroke-dashoffset='"
& FORMAT ( -( _S1 * 116.24 ), "0.0", "en-US" )
& "'/><text x='22' y='22' dy='0.35em' text-anchor='middle' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='11' font-weight='600' fill='%23252423'>"
& SUBSTITUTE ( FORMAT ( _S1, "0%", "en-US" ), "%", "%25" )
& "</text></svg>"
)DAX user-defined function variant (VizForge.SvgMiniDonut)
One-line call: Mini Donut = VizForge.SvgMiniDonut ( [New Customers], [Returning Customers] )
-- 1) Paste into DAX query view and choose 'Update model with changes'.
-- Needs DAX user-defined functions (generally available since the June 2026 Power BI release).
DEFINE
/// Mini donut as an SVG image URL (VizForge). Optional parameters default to the colours you picked.
FUNCTION VizForge.SvgMiniDonut = (
pPart1 : NUMERIC,
pPart2 : NUMERIC,
pColor1 : STRING = "#118DFF",
pColor2 : STRING = "#D9B300",
pLabelColor : STRING = "#252423"
) =>
VAR _Total = pPart1 + pPart2
VAR _S1 = DIVIDE ( pPart1, _Total, 0 )
VAR _S2 = DIVIDE ( pPart2, _Total, 0 )
RETURN
IF (
_Total < 0,
BLANK (),
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44'><circle cx='22' cy='22' r='18.5' fill='none' stroke='"
& SUBSTITUTE ( pColor1, "#", "%23" )
& "' stroke-width='7' transform='rotate(-90 22 22)' stroke-dasharray='"
& FORMAT ( _S1 * 116.24, "0.0", "en-US" )
& " 116.24' stroke-dashoffset='"
& FORMAT ( -0, "0.0", "en-US" )
& "'/><circle cx='22' cy='22' r='18.5' fill='none' stroke='"
& SUBSTITUTE ( pColor2, "#", "%23" )
& "' stroke-width='7' transform='rotate(-90 22 22)' stroke-dasharray='"
& FORMAT ( _S2 * 116.24, "0.0", "en-US" )
& " 116.24' stroke-dashoffset='"
& FORMAT ( -( _S1 * 116.24 ), "0.0", "en-US" )
& "'/><text x='22' y='22' dy='0.35em' text-anchor='middle' font-family='Segoe UI, Helvetica, Arial, sans-serif' font-size='11' font-weight='600' fill='"
& SUBSTITUTE ( pLabelColor, "#", "%23" )
& "'>"
& SUBSTITUTE ( FORMAT ( _S1, "0%", "en-US" ), "%", "%25" )
& "</text></svg>"
)
EVALUATE { VizForge.SvgMiniDonut ( [New Customers], [Returning Customers] ) }
-- 2) Then create the measure and set Measure tools > Data category = Image URL:
-- Mini Donut = VizForge.SvgMiniDonut ( [New Customers], [Returning Customers] )
-- Colours and thresholds are optional parameters: pass a value after the fields to override one.
-- Pass colours as plain "#RRGGBB"; the function encodes the # for the data URI.Placing it in a report
44 × 44 square: a Card image or a matrix cell per segment. Two or three part measures go in the builder; the first part's share is printed in the centre. Remember the Image URL data category. All setup steps.
Gotchas
- Negative parts make the shares meaningless: clamp them to zero in the part measures.
- If all parts are zero the measure returns BLANK().
- Put the most important part first: its share is the centre label.
- Length. 617 characters whatever the data; no iteration.
- Context. Shares are computed from the parts in the row's context, so each row's donut always closes at 100%. Parts that overlap (a customer counted as both new and returning) inflate the total.
- Tooltip. Hovering shows the SVG text; a blank report-page tooltip hides it.
When an SVG measure isn't enough
A donut visual can add a legend, slice tooltips and click-to-filter on a slice. See the Sunburst custom visual for what an interactive version can do.
KPI card designs using it
Finished designs built on this pattern, from the KPI card gallery.