data:image/svg+xml;utf8,...) через поле с типом данных Image URL. DAX-мера возвращает строку с этим URI, и в ячейке таблицы появляется отрисованный SVG. Этим способом делаются sparklines, progress bar, custom KPI-карточки, mini-donut, status-иконки. Главное правило — escape кавычек и # в HEX-цветах. Дальше по статье — готовые сниппеты, которые можно копировать в свой проект.
Как Power BI понимает SVG
Power BI Desktop умеет показывать картинки в ячейках таблиц и матриц — через тип данных Image URL. Обычно туда кладут реальные ссылки (https://...), но Power BI поддерживает и data-URI — inline-кодированную картинку прямо в строке. Для SVG это означает: можно вернуть из меры строку вида data:image/svg+xml;utf8,<svg>...</svg> — и Power BI отрисует её как картинку.
Для Card-визуала прямого SVG-рендера нет, поэтому там используется обходной приём: помещаем поле с Image URL в Multi-row card или в Table с одной строкой и настраиваем как «карточку».
Прочему это круто:
- Никаких custom visuals. Стандартная Table / Matrix умеет рендерить SVG из коробки.
- Динамика. SVG строится из значений мер, поэтому реагирует на фильтры мгновенно.
- Полная свобода. Любая визуализация, которую можно описать SVG-разметкой, — ваша. Sparkline, donut, mini-bar, progress, иконка статуса, custom KPI — всё.
Минус один — DAX становится длиннее обычного. Но это один раз пишете, потом копируете.
Базовый шаблон SVG-меры
Любая SVG-мера состоит из трёх частей: префикс data-URI, само SVG-тело, конкатенация значений из других мер. Минимальный шаблон:
SVG Demo =
VAR _w = 100
VAR _h = 30
VAR _color = "#3B52D5"
VAR _value = [Sales Amount]
VAR _ratio = DIVIDE(_value, [Sales Target])
VAR _svg =
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' " &
"viewBox='0 0 " & _w & " " & _h & "' width='" & _w & "' height='" & _h & "'>" &
"<rect x='0' y='5' width='" & _w & "' height='20' fill='%23E5E7EB' rx='3'/>" &
"<rect x='0' y='5' width='" & ROUND(_w * _ratio, 0) & "' height='20' fill='" & SUBSTITUTE(_color, "#", "%23") & "' rx='3'/>" &
"</svg>"
RETURN _svg
Что тут важно:
- Префикс
data:image/svg+xml;utf8,. Без него Power BI считает строку обычным текстом. - Атрибут
xmlns='http://www.w3.org/2000/svg'. Обязателен — иначе SVG не валиден и Power BI не нарисует. - Одинарные кавычки внутри SVG. Двойные ломают конкатенацию DAX — приходится их экранировать через
UNICHAR(34), что резко удлиняет код. - Escape
#в цветах. Хеш в data-URI — это якорь URL. Заменяем на%23, иначе цвет потеряется. Делается черезSUBSTITUTE(_color, "#", "%23")или сразу пишем%23в литерале. - Round чисел до 0–1 знаков. Длинные дроби в координатах раздувают строку и ломают точность отрисовки.
После создания меры:
- Откройте свойства поля → Data category → выберите Image URL.
- Положите меру в Table или Matrix.
- В настройках столбца увеличьте Image height до разумного (обычно 20–40 px).
Готовые сниппеты — 6 кейсов из production
1. Status icon — иконка по значению
Status SVG =
VAR _ratio = DIVIDE([Sales Amount], [Sales Target])
VAR _color =
SWITCH(TRUE(),
_ratio >= 1, "%2310B981", // зелёный
_ratio >= 0.8, "%23F7A81B", // жёлтый
"%23C73E5A" // красный
)
RETURN
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' " &
"viewBox='0 0 20 20' width='20' height='20'>" &
"<circle cx='10' cy='10' r='8' fill='" & _color & "'/>" &
"</svg>"
Простой пример, но показывает базовый паттерн. Дальше всё — расширения этой темы.
2. Mini progress bar
Progress Bar =
VAR _ratio = MIN(DIVIDE([Sales Amount], [Sales Target]), 1)
VAR _w = 200
VAR _filled = ROUND(_w * _ratio, 0)
VAR _pct = ROUND(_ratio * 100, 0)
RETURN
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' " &
"viewBox='0 0 " & _w & " 20' width='" & _w & "' height='20'>" &
"<rect x='0' y='6' width='" & _w & "' height='8' fill='%23E5E7EB' rx='3'/>" &
"<rect x='0' y='6' width='" & _filled & "' height='8' fill='%233B52D5' rx='3'/>" &
"<text x='" & (_w - 28) & "' y='14' font-size='10' " &
"font-family='Segoe UI' font-weight='600' fill='%231F2025'>" & _pct & "%25</text>" &
"</svg>"
Заметьте: знак % в тексте — это %25 в data-URI. Иначе Power BI прочитает следующие два символа как hex-код.
3. Sparkline — мини-линия динамики
Sparkline =
VAR _series =
ADDCOLUMNS(
VALUES(dimDate[YearMonth]),
"@val", [Sales Amount]
)
VAR _xMax = 12
VAR _yMin = MINX(_series, [@val])
VAR _yMax = MAXX(_series, [@val])
VAR _yRange = _yMax - _yMin
// Превращаем точки в строку SVG-path
VAR _path =
CONCATENATEX(
ADDCOLUMNS(_series, "@idx", RANK.EQ(dimDate[YearMonth], dimDate[YearMonth], ASC)),
VAR _x = ROUND(([@idx] - 1) * 180 / (_xMax - 1), 1)
VAR _y = ROUND(28 - ([@val] - _yMin) / _yRange * 24, 1)
RETURN _x & " " & _y,
" L ", [@idx], ASC
)
VAR _color =
IF([Sales Amount] >= CALCULATE([Sales Amount], DATEADD(dimDate[Date], -1, YEAR)),
"%233B52D5", "%23C73E5A")
RETURN
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' " &
"viewBox='0 0 180 32' width='180' height='32'>" &
"<path d='M " & _path & "' stroke='" & _color & "' " &
"stroke-width='1.5' fill='none'/>" &
"</svg>"
Это самый длинный сниппет в статье. Главная сложность — превратить серию точек в строку SVG path. Делается через CONCATENATEX с вычислением координат для каждой точки. Префикс "M " в начале добавляется отдельно (move-to первой точки), остальные через разделитель " L " (line-to).
Параметр RANK.EQ — чтобы пронумеровать месяцы. Без него CONCATENATEX может выдать значения в произвольном порядке.
4. Mini donut — одна доля
stroke-dasharray: одна окружность фон, поверх неё вторая с частичной обводкой.Mini Donut =
VAR _ratio = MIN(DIVIDE([Sales Amount], [Sales Target]), 1)
VAR _circumference = 2 * PI() * 14 // радиус 14
VAR _filled = ROUND(_circumference * _ratio, 1)
VAR _empty = ROUND(_circumference - _filled, 1)
VAR _pct = ROUND(_ratio * 100, 0)
RETURN
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' " &
"viewBox='0 0 40 40' width='40' height='40'>" &
"<circle cx='20' cy='20' r='14' fill='none' stroke='%23E5E7EB' stroke-width='6'/>" &
"<circle cx='20' cy='20' r='14' fill='none' stroke='%233B52D5' stroke-width='6' " &
"stroke-dasharray='" & _filled & " " & _empty & "' " &
"transform='rotate(-90 20 20)'/>" &
"<text x='20' y='24' text-anchor='middle' font-size='9' " &
"font-family='Segoe UI' font-weight='600' fill='%231F2025'>" & _pct & "%25</text>" &
"</svg>"
Поворот rotate(-90 20 20) начинает рисовать дугу с двенадцати часов, а не с трёх (как по умолчанию). Без этого донут смотрится «повёрнутым на бок».
5. Variance bar — отклонение от нуля
Variance Bar =
VAR _delta = DIVIDE([Sales Amount] - [Sales Target], [Sales Target])
VAR _maxAbsScale = 0.30 // -30% .. +30% — фиксированная шкала
VAR _wHalf = 100
VAR _ratio = MIN(MAX(_delta / _maxAbsScale, -1), 1)
VAR _barW = ROUND(ABS(_ratio) * _wHalf, 0)
VAR _barX = IF(_delta < 0, _wHalf - _barW, _wHalf)
VAR _color = IF(_delta < 0, "%23C73E5A", "%2310B981")
VAR _label =
IF(_delta < 0, "", "+") & FORMAT(_delta, "0%")
VAR _labelX = IF(_delta < 0, _wHalf - _barW - 4, _wHalf + _barW + 4)
VAR _labelAnchor = IF(_delta < 0, "end", "start")
RETURN
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' " &
"viewBox='0 0 200 24' width='200' height='24'>" &
"<line x1='100' y1='2' x2='100' y2='22' stroke='%231F2025' stroke-width='1'/>" &
"<rect x='" & _barX & "' y='8' width='" & _barW & "' height='8' " &
"fill='" & _color & "' rx='2'/>" &
"<text x='" & _labelX & "' y='15' text-anchor='" & _labelAnchor & "' " &
"font-size='10' font-family='Segoe UI' font-weight='600' fill='" & _color & "'>" &
SUBSTITUTE(_label, "%", "%25") & "</text>" &
"</svg>"
Главный приём — фиксированная шкала _maxAbsScale. Без неё каждая строка таблицы рисуется со своим относительным масштабом, и визуально нельзя сравнить «−5%» одной строки с «−10%» другой — оба покажут максимум. Жёсткая шкала ±30% решает.
6. Star rating — оценка по 5 шкале
Star Rating =
VAR _rating = MIN(MAX([Avg Rating], 0), 5)
VAR _full = INT(_rating)
VAR _starPath = "10,2 12,7 17,7 13,11 15,17 10,14 5,17 7,11 3,7 8,7"
VAR _stars =
CONCATENATEX(
GENERATESERIES(1, 5),
VAR _i = [Value]
VAR _x = (_i - 1) * 20
VAR _color = IF(_i <= _full, "%23F7A81B", "%23E5E7EB")
RETURN
"<polygon points='" &
CONCATENATEX(
GENERATESERIES(1, 10),
VAR _pt = [Value]
VAR _coords = SWITCH(_pt,
1, "10,2", 2, "12,7", 3, "17,7", 4, "13,11", 5, "15,17",
6, "10,14", 7, "5,17", 8, "7,11", 9, "3,7", 10, "8,7"
)
RETURN _coords,
" "
) & "' transform='translate(" & _x & " 0)' fill='" & _color & "'/>",
""
)
RETURN
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' " &
"viewBox='0 0 100 20' width='100' height='20'>" & _stars & "</svg>"
Этот сниппет показывает важный приём — генерация повторяющихся фигур через GENERATESERIES. Тот же подход работает для иконок, мини-баров, точек. Звёздная форма — просто 10 точек polygon, повторённая 5 раз с разными translate-X и цветом по индексу.
Подводные камни
<text>-тегах надо аккуратно: data-URI с ;utf8 поддерживает Unicode напрямую, но на старых версиях Power BI бывают сюрпризы. Альтернатива — ;base64 с дополнительной кодировкой строки, но это уже сложнее.
Альтернатива — HTML Content
Когда нужен SVG большего размера (полноценный mini-dashboard, marimekko, sankey) — ячейки таблицы становятся тесными. Для таких случаев есть HTML Content — certified visual от Daniel Marsh-Patrick, который рендерит произвольный HTML/SVG из DAX-меры на всю площадь визуала.
Это один из немногих case'ов, когда мы в DEEONE рекомендуем custom visual — потому что он certified, активно поддерживается, и используется как общий «движок» для любых ваших SVG-композиций. Поставили один раз — дальше любые кастомные графики делаются через DAX.
Главное правило: HTML Content не значит «полная свобода». Используйте его для того, что стандартные визуалы не умеют (marimekko, sankey, custom-композиции), а не «потому что красивее». Стандартный bar+conditional formatting почти всегда лучше custom-альтернативы по скорости и поддержке.
Когда НЕ использовать SVG-меры
- Большие визуализации. SVG в ячейке — только маленькие глифы (до 200×40). Большие — через HTML Content или стандартные визуалы.
- Когда стандартное справляется. Если native sparkline в Power BI делает то же самое — используйте его, не кастомное.
- Команда без DAX-эксперта. Поддержка SVG-мер — это DAX уровня middle+. Если следующий разработчик увидит и не поймёт — через год отчёт сломается, и никто не сможет его починить.
- Слабая модель. SVG-меры тяжёлые. На flat-таблице 5М строк без агрегатов — забудьте.
Связанные материалы
- Нестандартные диаграммы стандартными визуалами — парная статья про то, что делается без SVG-мер.
- Гид по визуализациям — 9 семейств и что выбирать под задачу.
- Как сделать дашборд по ТЗ — полный pillar для BI-разработчика.