Table of Contents
- Key Highlights:
- Introduction
- Why choose SVG for Power BI KPIs?
- Preparing the SVG canvas in Boxy SVG
- Editing the SVG for DAX use: quotes, encoding and data URIs
- Layering the ring: background + dynamic stroke
- Adding dynamic center text: typography and anchors
- Building the Power BI model: parameter, measures and formatting
- Practical examples and real-world use cases
- Troubleshooting and common pitfalls
- Performance considerations and best practices
- Extending the design: multiple rings, labels and small multiples
- Security and sanitization considerations
- Exporting, printing and cross-platform behavior
- Alternatives and related approaches
- Step-by-step checklist for implementation
- Troubleshooting scenarios and fixes
- Design and usability tips
- FAQ
Key Highlights:
- Create a crisp, scalable progress ring by layering two SVG circles (background + dynamic stroke) and render it in Power BI as an Image URL generated by a DAX measure.
- Drive the ring and the centered percentage label from model values: a numeric parameter (target) plus a measure (actual), with DAX calculating circumference, stroke-dashoffset and conditional color.
- Use Boxy SVG to construct the visual, convert the SVG for DAX by replacing quote characters, and assemble the final data URI string inside a single measure for easy placement in tables or specialized image visuals.
Introduction
An attention-grabbing KPI needs to be compact, accurate and sharp at any scale. SVG is the right format for that: vector fidelity, tiny file size and full control over shape, stroke and text. This guide walks through building a dynamic SVG progress ring that updates from Power BI data using only Boxy SVG (or any SVG editor), a numeric parameter and a handful of DAX lines. The solution avoids external custom visuals, relies on Power BI’s ability to render image URLs, and produces a reusable component you can drop into dashboards for targets, progress tracking and single-number KPIs.
The method covers the entire workflow: building the canvas, preparing SVG code for use in DAX, computing the geometry that drives stroke-dashoffset, adding a dynamic text label, assembling the DAX string, and practical tips for formatting, troubleshooting and production deployment.
Why choose SVG for Power BI KPIs?
SVG (Scalable Vector Graphics) offers benefits that bitmap images and many built-in visuals cannot match.
- Resolution independence. Vector shapes remain crisp across screen sizes and exports to PDF or PNG.
- Small payloads. A concise SVG for a ring and a text label is orders of magnitude smaller than a PNG of equal visual fidelity.
- Full styling control. Stroke, stroke-linecap, transforms and text anchors are all controllable via the SVG markup.
- Dynamic binding. Because SVG is plain XML text, you can assemble it into a data URI inside a DAX string and change colors, stroke offsets and text content at runtime.
- No custom visual installation. Deliver the functionality without requiring consumers to install marketplace visuals—Power BI renders the generated image.
Limitations to keep in mind:
- Power BI sanitizes or strips some elements when rendering SVG, especially scripts or embedded external references. Keep the SVG markup self-contained and free of scripting.
- Not all report placements will render image URLs identically. Tables and matrix visuals render image URLs natively; other placements may require a supporting visual (image viewer) from the marketplace.
- Browser and mobile differences can alter small text metrics and alignment; test on your target platforms.
Understanding these trade-offs allows you to take full advantage of SVG while designing predictable report experiences.
Preparing the SVG canvas in Boxy SVG
Start by creating the visual layout in a vector editor. Boxy SVG is used here, but any editor that exports plain SVG will do.
- Create a small canvas:
- Set Width: 100 and Height: 100, with X and Y at 0. This keeps coordinates predictable and easy for math.
- Draw the background circle:
- Select the Ellipse tool and place a circle centered within the canvas.
- Set Geometry (for example): cx=50, cy=50, rx=40, ry=40. This produces a ring area inside the 100x100 viewport and leaves room for a 10px stroke.
- Fill: make it transparent (no fill).
- Stroke: hex #EAEAEA (light gray) and stroke-width: 10.
- Stroke caps aren’t important for the full circle; you want a smooth background ring.
- Save the file:
- Use File > Save. Boxy requires a free account to export; export the SVG to your local machine.
Why those sizes? The 100x100 viewport makes the center coordinates and radius simple integers. A 40-unit radius with a 10-unit stroke gives a visible ring without clipping, and the math for circumference stays straightforward.
Editing the SVG for DAX use: quotes, encoding and data URIs
Power BI expects an image URL or a data URI to render an inline image from a text field. To embed SVG directly inside a DAX measure you’ll form a data URI that contains the SVG XML.
Key preparatory steps:
- Text wrapping: Edit the exported SVG in a plain text editor such as Notepad.
- Quotes: Replace the double-quote characters (") used in XML attributes with single quotes ('). This reduces escaping complexity inside the DAX string. In many workflows, developers replace " with ' before embedding the SVG string into a DAX measure.
- Remove XML declaration: Replace "
<?xml version='1.0' encoding='utf-8'?>" with the data URI header "data:image/svg+xml;utf8,". Power BI does not need the XML prolog when the SVG is embedded in a data URI. - Keep markup minimal and self-contained. Remove comments and any extraneous metadata. Avoid external font references or scripts.
Example of the minimalized result:
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><ellipse cx='50' cy='50' rx='40' ry='40' fill='none' stroke='#EAEAEA' stroke-width='10'/></svg>"
That string is what Power BI will see; when placed in a measure and marked as Image URL it renders as an inline SVG image.
Note on URL encoding: In many straightforward cases, Power BI accepts the raw data URI with hash characters (#) used by hex color codes. If you encounter rendering issues in particular hosting environments, you can URL-encode the SVG payload. However, URL encoding will change the string length and may be harder to inspect in debugging, so try the raw approach first.
Layering the ring: background + dynamic stroke
The progress ring comprises two concentric ellipses that share the same center, radius and stroke width:
- Background ellipse: a full, static ring colored light gray as the visual baseline.
- Top ellipse (the dynamic ring): an identical circle that serves as the progress arc. Use stroke-dasharray and stroke-dashoffset to reveal a portion of the stroke corresponding to the progress percentage.
How stroke-dasharray and stroke-dashoffset work:
- stroke-dasharray defines a repeating pattern of dash and gap lengths along the stroke. When you set stroke-dasharray to the total circumference, you effectively create a single dash equal to the full circumference, which is useful for progress trimming.
- stroke-dashoffset specifies how far into that dash the stroke is offset. By increasing stroke-dashoffset from 0 up to the circumference, you reduce the visible stroke from 100% down toward 0%.
Mathematics:
- Radius (r): the circle's radius (e.g., 40 units).
- Circumference (C): 2 * PI() * r.
- To draw Pct percent of the ring, set:
- stroke-dasharray = C
- stroke-dashoffset = C * (1 - Pct), where Pct ranges 0..1.
Start position control:
- By default, SVG circles start drawing at the 3 o’clock position.
- To make progress start at the top (12 o’clock), rotate the circle by -90 degrees about the center: transform='rotate(-90 50 50)'. For consistent results across viewers, also set style='transform-box:fill-box;transform-origin:50% 50%;'.
Stroke endings:
- Set stroke-linecap='round' on the dynamic ellipse for a smooth rounded end where the ring breaks.
Example SVG for the pair:
<svg ...>
<ellipse cx='50' cy='50' rx='40' ry='40' fill='none' stroke='#EAEAEA' stroke-width='10'/>
<ellipse cx='50' cy='50' rx='40' ry='40' fill='none' stroke='#06D6A0' stroke-width='10' stroke-dasharray='251.33' stroke-dashoffset='62.83' stroke-linecap='round' transform='rotate(-90 50 50)' style='transform-box:fill-box;transform-origin:50% 50%;'/>
</svg>
(That sample uses a circumference of approximately 2 * PI * 40 ≈ 251.33 and a dashoffset that shows 75% progress.)
Adding dynamic center text: typography and anchors
A numeric label in the center communicates the percentage explicitly. Add a text element with attributes that keep it centered and legible.
Recommended text element:
-
<text x='50' y='55' text-anchor='middle' font-family='Arial' font-size='18' fill='#222'>75%</text>
Why y='55'? Visual centering often requires a slight vertical offset because font metrics align differently; 55 positions the baseline so the glyphs appear visually centered. You can tweak this value to match the font and browser rendering.
To make the text dynamic, you will substitute the static "75%" with a DAX-calculated PctText and concatenate it into the SVG string.
Accessibility note:
- Inline SVG doesn't provide native alt text when rendered as an image URI in Power BI. To make visuals accessible for screen readers, include a separate numeric text field or a tooltip that exposes the numeric value.
Building the Power BI model: parameter, measures and formatting
The interactivity stems from a numeric parameter and two DAX measures: an Actual measure (Total Sales) and the SVG-generating measure that combines values and SVG markup.
-
Create the parameter:
- Modeling > New Parameter.
- Type: Numeric range.
- Name it (e.g., Target Goal ($)).
- Data type: Whole number.
- Min: e.g., 20000000 (for $20M); Max: 30000000 (for $30M); step size and default as needed.
- Add the generated parameter slicer to the report page so report viewers can change the target.
-
Create the Actual measure: Total Sales = SUM(financials[Sales])
This returns the value you want to compare to the target.
- Create the SVG measure Below is a production-ready DAX measure that assembles the SVG data URI, computes circumference and offset, chooses ring color by thresholds, and injects the percentage text into the center.
SVG Goal Ring =
VAR Target = 'Target Goal ($)'[Target Goal ($) Value]
VAR Actual = [Total Sales]
VAR Pct = MIN( DIVIDE( Actual, Target, 0 ), 1 )
VAR PctText = FORMAT( DIVIDE( Actual, Target, 0 ), "0.0%" )
VAR Radius = 40
VAR Circumference = 2 * PI() * Radius
VAR DashArray = Circumference
VAR DashOffset = Circumference * (1 - Pct)
VAR Ringcolor =
SWITCH(
TRUE(),
Pct >= 1.0, "#06D6A0",
Pct >= 0.75, "#FFD166",
"#FF6B6B"
)
VAR SVGHeader = "data:image/svg+xml;utf8,"
VAR SVGBody =
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>" &
"<ellipse cx='50' cy='50' rx='40' ry='40' fill='none' stroke='#EAEAEA' stroke-width='10'/>" &
"<ellipse cx='50' cy='50' rx='40' ry='40' fill='none' stroke='" & Ringcolor & "' stroke-width='10' stroke-dasharray='" & FORMAT( DashArray, "0.00" ) & "' stroke-dashoffset='" & FORMAT( DashOffset, "0.00" ) & "' stroke-linecap='round' style='transform-box:fill-box;transform-origin:50% 50%;' transform='rotate(-90 50 50)'/>" &
"<text x='50' y='55' text-anchor='middle' font-family='Arial' font-size='18' fill='#222'>" & PctText & "</text>" &
"</svg>"
RETURN
SVGHeader & SVGBody
Notes on the code:
- PI() is used for numerical precision.
- FORMAT is applied to DashArray and DashOffset to ensure the SVG attributes receive a textual representation suitable for concatenation.
- Ringcolor uses thresholds: green at or above 100%, yellow at or above 75%, red otherwise. Adjust thresholds and colors to fit your design language.
- Mark the measure as Image URL
- Select the SVG Goal Ring measure, then in the Modeling ribbon set Data Category to Image URL. This instructs Power BI to treat the string as a renderable image rather than plain text.
- Place the measure on the report
- Insert a Table visual and drop the SVG Goal Ring measure into the values. The image should appear in the table cell.
- For single-tile use, consider using an image visual from the marketplace that supports image URLs or a small one-column table styled to remove gridlines and headers to mimic a KPI card.
- Formatting the target parameter slicer
- Select the slicer and, in the Format pane, set value font size, numeric format (Currency, $ with 0 decimals), and add background, border or shadow to fit the dashboard style. Provide padding so the control is usable on touch devices.
- Enable data bars for numeric columns
- If you present Total Sales as a numeric column in a table, turn Data bars on: Visual > Cell elements > Select Total Sales > Data bars. This provides a complementary, textual and visual cue to the ring.
Practical examples and real-world use cases
These progress rings fit many scenarios across business reporting:
- Executive sales dashboard: A target selector adjusts target quota and the ring shows department-level, product-line or territory progress.
- Fundraising campaign: Donors can see progress toward goal; colors shift to signal urgency.
- Hiring pipeline: Track hires against a target; rings for each team on a single report page provide quick comparison.
- Project completion: Visualize percent complete for tasks, sprints or milestone achievement.
- Customer success adoption: Show percent adoption of a new feature vs. goal across customer cohorts.
Examples of multi-use deployment:
- KPI grid: Use a small table with multiple measures, each measure generating a separate SVG string. This enables a compact grid of progress rings for comparison. Be mindful of performance if rendering dozens of SVGs on one page.
- Drill-through: Place the ring in a report page that receives context from a report-level filter or drill-through so the ring updates for a selected team, product or time frame.
Troubleshooting and common pitfalls
When the image does not render or behaves unexpectedly, check the following:
- Data category not set
- The most common issue: the SVG string is not marked as Image URL. Set Data Category of the measure to Image URL.
- Double quotes inside strings
- If you left double quotes in SVG attributes while wrapping the entire DAX string in double quotes, the DAX expression will break. Replace attribute double quotes with single quotes before assembling or escape properly.
- Unexpected characters or line breaks
- Long SVG strings with line breaks can sometimes be problematic. Keep the final data URI in one continuous string or ensure your concatenation produces a well-formed single-line header + body string.
- Percent text shows as blank or wrong
- Verify PctText variable uses FORMAT on the DIVIDE result. If Target is zero or blank, DIVIDE with alternative result (like 0) avoids divide-by-zero errors.
- Incorrect dash lengths
- If stroke-dasharray or stroke-dashoffset values are too small or large, verify the Radius value and the Circumference calculation (2 * PI() * Radius). Use FORMAT to produce readable decimal numbers.
- Visual doesn’t render on Power BI Service
- Test in Power BI Desktop and in the Service. Some HTML/SVG features behave slightly differently across environments. If it shows in Desktop but not Service, check for security filtering or unsupported markup.
- Multiple images in a single visual
- Rendering dozens of SVG data URIs in a table can increase memory use and slow rendering. If you plan to show many rings, consider paging or alternative aggregation visuals.
Performance considerations and best practices
Keep these recommendations in mind for production dashboards:
- Keep the SVG minimal. Avoid gradients, filters, masks or embedded fonts unless strictly necessary.
- Reuse variables and compute expensive values once. The DAX measure above computes Circumference once and reuses it.
- Limit the number of dynamically generated SVG visuals on a single page. Rendering many images can spike memory and increase report load times.
- Cache heavy computations at the model level, if possible. If Total Sales is costly to compute, push some logic into pre-aggregated measures or stored columns.
- Test in the Power BI Service and on mobile clients. Tiny font sizes or subtle transforms may render slightly differently.
- Consider multiple visual resolutions. If you embed the ring in a large visual area, you may want a larger viewBox and radius for consistent stroke thickness across sizes.
Extending the design: multiple rings, labels and small multiples
You can expand the concept with a few extensions:
- Concentric multiple rings: Add additional ellipses with different radii to show several metrics at once (e.g., revenue, margin, churn). Keep stroke widths narrow or increase the canvas size.
- Center labels with multiple lines: Use
<tspan>elements within the<text>element to include a numeric value on top and a label below. - Small multiples: Create a table with a grouping (e.g., by region) and generate an SVG per group. Be cautious with performance.
- Threshold tick marks: Add small line segments around the ring (short strokes) to show target thresholds. These are static lines and contribute minimal overhead.
Example of a two-line center label:
"<text x='50' y='50' text-anchor='middle' font-family='Arial' font-size='14' fill='#222'><tspan x='50' dy='-2'>" & PctText & "</tspan><tspan x='50' dy='16'>of target</tspan></text>"
Adjust dy values to position the lines relative to each other.
Security and sanitization considerations
Power BI applies sanitization when rendering images and custom HTML. Keep the SVG content safe:
- Avoid scripts, external images, foreignObject elements or other constructs that could be treated as risky.
- Do not try to inject JavaScript, CSS with external references, or embedded fonts that require remote loading.
- Keep data URIs internal and self-contained.
If you need richer interactions (hover animations or clickable elements inside the SVG), consider using a custom visual that supports HTML or JavaScript, but be aware that many organizations restrict marketplace visuals for governance reasons.
Exporting, printing and cross-platform behavior
SVG rendered inside Power BI can be exported through the Power BI export to PDF or printed. Check these caveats:
- Export fidelity: Because the exported PDF might rasterize the vector content, very thin strokes or tiny fonts can appear thinner or grainier when printed. Increase font size slightly if print quality matters.
- Mobile: Text metrics and anti-aliasing vary across mobile browsers; validate the appearance with typical smartphone and tablet targets.
- Scaling: If you need a larger ring on an export or downloadable report, consider generating two SVG versions (small for screen, large for printed output) or use scalable container settings that preserve stroke thickness (vector stroke width scales with vector units).
Alternatives and related approaches
If inline SVG measures are not suitable for your scenario, consider these options:
- Built-in visuals: Use KPI or Gauge visuals for simple percent displays. They lack the design flexibility of SVG but are robust and supported.
- Custom visuals from AppSource: Use visuals like Bullet charts, Circular Gauge, or custom KPI visuals. These can offer animation and native interactivity.
- HTML Content custom visual: Some marketplace visuals allow HTML and SVG rendering; use them to embed richer markup with more control, but test governance rules.
- Image hosting: Host SVG files on a web server and reference them by URL. This centralizes assets but introduces dependencies and potential CORS issues.
Each approach has trade-offs between control, governance, performance and deployment complexity.
Step-by-step checklist for implementation
Follow this checklist to reduce friction when implementing the progress ring in a report:
- Design the ring in Boxy SVG or another editor using a 100x100 viewBox and radius 40.
- Make two concentric ellipses: background (light grey) and top (dynamic).
- Add a centered text element; anchor it in the middle.
- Save and open the SVG in a text editor.
- Replace double quotes with single quotes throughout the SVG.
- Remove the XML prolog and prepend "data:image/svg+xml;utf8,".
- Create a numeric parameter in Power BI for the target.
- Build Total Sales measure and test its values.
- Write the SVG Goal Ring measure with variables for circumference, offsets and color.
- Set the SVG measure’s data category to Image URL.
- Place the measure into a table or image visual to validate rendering.
- Test interactions: change parameter slicer, apply filters, and drill-through context to confirm dynamic updates.
- Test rendering in Power BI Service and mobile apps.
- Optimize: reduce string length, remove unnecessary elements, and limit number of SVGs on a single page.
Troubleshooting scenarios and fixes
- Problem: Image doesn’t appear in the table.
- Fix: Confirm the measure is categorized as Image URL and that the table column is not being summarized or transformed in a way that changes the string.
- Problem: The stroke starts at 3 o’clock instead of 12 o’clock.
- Fix: Ensure the dynamic ellipse has transform='rotate(-90 50 50)' and transform-box plus transform-origin are set.
- Problem: The ring is always full or always empty.
- Fix: Check Pct calculation, ensure DIVIDE(Actual,Target,0) returns the expected numeric and that Target is non-zero. Use MIN(DIVIDE(...),1) to cap at 100%.
- Problem: Color thresholds not matching expectations.
- Fix: Confirm Pct is between 0 and 1 and that the SWITCH logic uses descending thresholds or TRUE() as a selector for chained comparisons.
Design and usability tips
- Maintain contrast between background and ring colors. Avoid light pastel ring hues on light backgrounds.
- Use rounded caps and a subtle shadow on the ring if you want dimensionality, keeping performance in mind.
- Provide a tooltip or a text field adjacent to the ring that shows raw numbers behind the percentage (Actual and Target) for users who need exact values.
- Group rings with labels below them if you use multiple rings in a small-multiples arrangement.
- Keep the target selection control accessible and clearly labeled to avoid confusion about what the ring represents.
FAQ
Q: Why replace double quotes with single quotes in the SVG? A: Embedding an SVG string directly in DAX becomes simpler when attributes use single quotes. DAX strings are typically wrapped in double quotes, and using single quotes inside avoids escaping and keeps the measure readable.
Q: How does stroke-dashoffset translate to percentage? A: Calculate the circumference C = 2 * PI() * radius. To show Pct (where Pct is 0..1), set stroke-dasharray=C and stroke-dashoffset=C*(1-Pct). When Pct=1 you see the whole stroke (dashoffset=0); when Pct=0 the stroke is fully offset (dashoffset=C), appearing hidden.
Q: How do I start the ring at the top (12 o’clock) rather than 3 o’clock? A: Rotate the dynamic ellipse by -90 degrees about the center: transform='rotate(-90 50 50)'. Include style='transform-box:fill-box;transform-origin:50% 50%;' to ensure the transform uses the element's own box as reference.
Q: Can the SVG include animations? A: Power BI strips or sanitizes many scripting and animation elements for security reasons. Basic SMIL animations are not reliably supported. For animated effects, consider using a custom visual or simulate transitions by replacing the SVG with successive states—though this increases complexity and may affect performance.
Q: Will the ring render in Power BI Service and mobile apps? A: Generally yes, but rendering quirks can vary across platforms. Test the report in the Service and on mobile devices. Adjust font sizes, stroke widths and viewBox settings if alignment shifts on different clients.
Q: How do I display multiple progress rings side-by-side? A: Create a measure for each ring and use a table, matrix or small-multiples layout. Limit the number of rings per page to avoid performance degradation.
Q: Why is my SVG not rendering after publishing? A: Check that the measure's Data Category is Image URL. If the measure contains characters that cause sanitization (scripts, external references), rework the SVG to be self-contained and minimal.
Q: Can I use different shapes instead of rings? A: Yes. The same approach applies to any SVG shape—progress can be represented by rectangles (width proportional to Pct), arcs, or custom silhouettes. The core technique remains embedding computed numeric values into an SVG string created by DAX.
Q: Is it safe to use color hex codes (with #) inside the data URI? A: Yes. In most cases Power BI accepts raw hex codes inside the data URI. If you encounter issues in some environments, encode the SVG payload. But try raw strings first.
Q: Does this approach require installing marketplace visuals? A: No. The technique leverages Power BI's Image URL data category and built-in visuals. You only need a marketplace visual if you want additional interactivity or layout that the built-in visuals cannot provide.
Q: How do I make the number in the center display raw values instead of percent? A: Replace PctText with any formatted string from DAX. For example: VAR PctText = FORMAT(Actual, "Currency") Concatenate that formatted number into the SVG text element.
Q: How to add a label describing the metric?
A: Use <tspan> elements inside <text> to render two lines (value and label), or place a separate Power BI text box or card near the ring for accessibility and clarity.
Q: Will very long DAX string measures affect performance? A: Constructing long strings consumes memory and has impact if repeated many times (e.g., many rows of a table). Use wisely and avoid rendering hundreds of SVGs on a single page concurrently.
Q: Can I generate the SVG outside Power BI and fetch it as an external URL? A: Yes, you can host SVGs and reference them by URL. That centralizes design but introduces external dependencies and possible CORS or governance constraints.
Q: How can I test the SVG before embedding it in DAX? A: Open the minimal SVG file in a browser or an SVG editor preview. Then test the final data URI by pasting it into the browser address bar (data:image/svg+xml;utf8,<svg ...>) to validate rendering before inserting into DAX.
Implementing dynamic SVGs gives you pixel-perfect, scalable KPI visuals driven by your model and controlled entirely by DAX. That combination of precision and flexibility makes SVG rings a practical tool in modern Power BI reporting. Adjust the geometry, colors and text to match your dashboard style and keep performance in mind when scaling to many visuals.