DAX SVG measure · Composition

Mini donut chart with a DAX SVG measure

A mini donut works for exactly one job: showing that one part dominates or doesn't. With two or three parts, the arcs are readable and the centre label gives the share that matters. Each segment is a dashed circle stroke, so there is no arc maths in DAX.

By · Founder & Reliability Engineer · Updated

Mini donut rendered from the DAX below with sample data
Rendered from the measure below with sample data at 2× (44×44 px in Power BI). 617 characters.

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 (measure)
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] )

DAX query view script
-- 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

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.