SVG-меры в таблицах Power BI используются для создания компактных, интуитивно понятных и динамически изменяющихся визуальных элементов, которые значительно улучшают читаемость, контекст и эстетику отчета. Они позволяют выйти далеко за рамки стандартных возможностей условного форматирования, добавляя в таблицу гибкие и выразительные визуальные элементы, такие как мини-гистограммы, индикаторы прогресса или иконки состояния.
SVG-изображения вычисляются динамически с помощью DAX: каждая строка таблицы получает собственную визуализацию на основе контекста фильтров. Это делает SVG-подход гораздо более вариативным, чем статические картинки, загруженные вручную.
Далее — немного теории, а затем практический пример с разбором кода.
Что такое SVG?
SVG (Scalable Vector Graphics) — это формат изображений, которые состоят не из пикселей, а из математических формул и кода. Это инструкция, в которой написано: "нарисуй прямоугольник с такой-то шириной и вот с такой высотой в этих координатах, залей его красным цветом...". Ширину, высоту и вообще много чего другого можно определить переменными в этой же инструкции.
SVG-мера обычно включает две основные части:
Структуру изображения — каркас, в котором размещаются фигуры, тексты и другие элементы.
Стиль (CSS) изображения — параметры оформления: цвета, шрифты, прозрачность, градиенты и т.д.
1. Структура
Мера может начинаться сразу с корневого тега <svg>, но Power BI иногда воспринимает такой результат как обычный текст. Чтобы визуализация корректно отображалась, рекомендуется использовать префикс — строку, указывающую, что это встроенное изображение в формате SVG:
"data:image/svg+xml;utf8," -- стандартный, понимает кириллицу "data:image/svg+xml," -- можно без указания кодировки, если SVG полностью состоит из латиницы
Корневой тег <svg>: Определяет рабочую область для дальнейшего рисования и начинается он с атрибута, указывающего пространство имён SVG. Без него Power BI может не понять, что это SVG.
<svg xmlns='http://www.w3.org/2000/svg'
Далее задаются ключевые атрибуты контейнера, в котором будет находиться рисунок:
Атрибут | Назначение | Пример |
width | ширина SVG-области в пикселях | width='300' |
height | высота области | height='50' |
viewBox | масштабирование и координаты (x, y, w, h) | viewBox='0 0 300 50' |
preserveAspectRatio | как масштабировать при изменении размера | preserveAspectRatio='none' |
style | CSS-стили контейнера | style='background:white' |
viewBox управляет масштабом, системой координат и видимой областью. Он состоит из четырёх параметров:
Параметр | Название | Значение |
x | Начало координат по горизонтали | Где начинается видимая область по оси X |
y | Начало координат по вертикали | Где начинается видимая область по оси Y |
width (w) | Ширина системы координат | Сколько единиц помещается по горизонтали |
height (h) | Высота системы координат | Сколько единиц помещается по вертикали |
если viewBox ≠ width/height — начинается масштабирование
Например, в такой записи:
<svg width="300" height="100" viewBox="0 0 150 50">
мы говорим, что у нас есть окно 300*100 единиц и мы в него помещаем содержимое размером 150*50 единиц. Каждая единица растянута в 2 пикселя. То есть картинка увеличится в 2 раза.
А для чего в этой записи 0 0?
0 0 означает, что мы начинаем отсчет от левого верхнего угла без смещений
viewBox="50 20 300 100"
означает, что рисунок будет сдвинут на 50 ед. вправо и 20 ед. вниз относительно верхнего левого угла.
2. Стиль
После объявления контейнера можно переходить к рисованию элементов:
SVG_example = "data:image/svg+xml;utf8," & "<svg xmlns='http://www.w3.org/2000/svg' width='300' height='100' viewBox='0 0 600 200'>" & -- сюда вставляется графика -- "</svg>"
SVG строится из базовых фигур - "строительных кирпичиков". Вот основные:
Элемент | Описание | Пример |
<rect> | прямоугольник | <rect x="10" y="20" width="100" height="50" fill="blue" /> |
<circle> | круг | <circle cx="50" cy="50" r="30" fill="red" /> |
<ellipse> | эллипс | <ellipse cx="80" cy="50" rx="40" ry="20" fill="green" /> |
<line> | линия | <line x1="0" y1="0" x2="200" y2="100" stroke="black" stroke-width="2" /> |
<polygon> | многоугольник | <polygon points="0,100 50,25 50,75 100,0" fill="orange" /> |
<polyline> | ломаная линия | <polyline points="0,40 40,20 80,40 120,20" fill="none" stroke="blue" /> |
<path> | произвольная форма (всё, что угодно) | <path d="M10 10 H 90 V 90 H 10 Z" fill="none" stroke="black" /> |
<text> | текст | <text x="150" y="50" font-size="20" fill="black">Привет!</text> |
<g> | группа (контейнер для нескольких элементов) | <g transform="translate(10,10)"... </g> |
SVG-элементы рисуются последовательно, слоями — элементы, размещённые позже, перекрывают предыдущие:
svg1 = "data:image/svg+xml;utf8," & "<svg xmlns='http://www.w3.org/2000/svg' width='200' height='100' viewBox='0 0 200 100'>" & "<rect width='200' height='200' fill='lightgray'/>" & "<circle cx='100' cy='50' r='30' fill='orange'/>" & "<text x='100' y='55' text-anchor='middle' font-size='20' fill='black'>SVG</text>" & "</svg>"
Результат:
серый прямоугольник — фон,
поверх него оранжевый круг,
поверх круга — текст “SVG”.

Ещё один пример с разными атрибутами svg-меры:
SVG_example = "data:image/svg+xml;utf8," & "<svg xmlns='http://www.w3.org/2000/svg' width='300' height='100' viewBox='0 0 600 200'>" & "<rect x='0' y='0' width='300' height='100' fill='#118DFF'/>" & "<circle cx='400' cy='100' r='50' fill='orange'/>" & "<text x='400' y='110' text-anchor='middle' font-size='40' fill='#333'>🚗</text>" & "</svg>"

Практический пример
Создадим датасет, в котором будет содержаться информация о клиентах и их покупках в разных категориях.
Sales = DATATABLE( "Customer", STRING, "Category", STRING, "Revenue", INTEGER, { {"LeBron James", "Shoes", 12000}, {"LeBron James", "Apparel", 8000}, {"LeBron James", "Accessories", 4000}, {"Cristiano Ronaldo", "Shoes", 15000}, {"Cristiano Ronaldo", "Apparel", 6000}, {"Roger Federer", "Shoes", 9000}, {"Serena Williams", "Shoes", 7000}, {"Serena Williams", "Apparel", 5000}, {"Serena Williams", "Accessories", 3000}, {"Lionel Messi", "Shoes", 11000}, {"Lionel Messi", "Apparel", 9000}, {"Michael Jordan", "Shoes", 20000}, {"Usain Bolt", "Shoes", 8000}, {"Usain Bolt", "Accessories", 2000}, {"Conor McGregor", "Shoes", 6000}, {"Conor McGregor", "Apparel", 4000}, {"Conor McGregor", "Accessories", 1000}, {"Novak Djokovic", "Shoes", 9500}, {"Novak Djokovic", "Apparel", 3500}, {"Tiger Woods", "Accessories", 2500} })
Например, мы хотим добавить в наш дашборд нормированную линейчатую диаграмму, но не отдельным виджетом, который занимает много места, а компактным столбцом в имеющейся таблице.

Теперь добавим меру, которая построит компактную линейчатую диаграмму прямо внутри таблицы.
SVG_Revenue_Bar_1 = VAR Width = 300 VAR Height = 30 VAR CurrCustomer = SELECTEDVALUE( Sales[Customer] ) VAR Base = SUMMARIZE( FILTER( Sales, Sales[Customer] = CurrCustomer ), Sales[Category], "Revenue", SUM( Sales[Revenue] ) ) VAR TotalRevenue = SUMX( Base, [Revenue] ) VAR tShare = ADDCOLUMNS( Base, "Share", IF( TotalRevenue = 0, 0, DIVIDE( [Revenue], TotalRevenue ) ) ) VAR tWidth = ADDCOLUMNS( tShare, "WidthPx", ROUND( [Share] * Width, 0 ) ) VAR tOrdered = ADDCOLUMNS( tWidth, "Order", RANKX( tWidth, [Share], , DESC, DENSE ), "Color", SWITCH( TRUE(), Sales[Category] = "Accessories" , "#0B3D78", Sales[Category] = "Apparel" , "#297BBA", Sales[Category] = "Shoes" , "#A0D1FF", "#CCCCCC" ) ) VAR TotalWidthPx = SUMX( tOrdered, [WidthPx] ) VAR MaxOrder = MAXX( tOrdered, [Order] ) VAR tWithX = ADDCOLUMNS( tOrdered, "X", VAR CurrOrder = [Order] RETURN SUMX( FILTER( tOrdered, [Order] < CurrOrder ), [WidthPx] ) ) VAR tWithFinal = ADDCOLUMNS( tWithX, "FinalWidth", IF( [Order] = MaxOrder, [WidthPx] + ( Width - TotalWidthPx ), [WidthPx] ) ) VAR tRects = ADDCOLUMNS( tWithFinal, "Rect", "<rect x='" & [X] & "' y='0' width='" & [FinalWidth] & "' height='" & Height & "' fill='" & [Color] & "' />" ) VAR Bars = CONCATENATEX( tRects, [Rect], "", [Order], ASC ) VAR svg = "data:image/svg+xml;utf8," & "<svg xmlns='http://www.w3.org/2000/svg' width='" & Width & "' height='" & Height & "'>" & Bars & "</svg>" RETURN IF( ISBLANK( CurrCustomer ) || TotalRevenue = 0, BLANK(), svg )
Устанавливаем категорию меры: URL-адрес изображения

и добавляем её в таблицу

В нашей мере заданы следующие размеры контейнера: ширина – 300, высота – 30. Однако по умолчанию в настройках таблицы ограничены размеры изображений. Для того, чтобы таблица разрешила нашему контейнеру принять свой размер, необходимо увеличить размеры изображений в настройках форматирования таблицы

Добавляем спецэффекты: градиент
SVG_Revenue_Bar_Gradient = VAR Width = 300 VAR Height = 30 VAR CurrCustomer = SELECTEDVALUE( Sales[Customer] ) VAR Base = SUMMARIZE( FILTER( Sales, Sales[Customer] = CurrCustomer ), Sales[Category], "Revenue", SUM( Sales[Revenue] ) ) VAR TotalRevenue = SUMX( Base, [Revenue] ) VAR tShare = ADDCOLUMNS( Base, "Share", IF( TotalRevenue = 0, 0, DIVIDE( [Revenue], TotalRevenue ) ) ) VAR tWidth = ADDCOLUMNS( tShare, "WidthPx", ROUND( [Share] * Width, 0 ) ) VAR tOrdered = ADDCOLUMNS( tWidth, "Order", RANKX( tWidth, [Share], , DESC, DENSE ), "Color", SWITCH( TRUE(), Sales[Category] = "Accessories" , "#0B3D78", Sales[Category] = "Apparel" , "#297BBA", Sales[Category] = "Shoes" , "#A0D1FF", "#CCCCCC" ) ) VAR TotalWidthPx = SUMX( tOrdered, [WidthPx] ) VAR MaxOrder = MAXX( tOrdered, [Order] ) VAR tWithX = ADDCOLUMNS( tOrdered, "X", VAR CurrOrder = [Order] RETURN SUMX( FILTER( tOrdered, [Order] < CurrOrder ), [WidthPx] ) ) VAR tWithFinal = ADDCOLUMNS( tWithX, "FinalWidth", IF( [Order] = MaxOrder, [WidthPx] + ( Width - TotalWidthPx ), [WidthPx] ) ) -- создаём градиенты VAR Gradients = CONCATENATEX( tWithFinal, "<linearGradient id='grad" & [Order] & "' x1='0%' y1='0%' x2='100%' y2='0%'> <stop offset='0%' stop-color='" & [Color] & "' stop-opacity='0.9'/> <stop offset='100%' stop-color='" & [Color] & "' stop-opacity='0.5'/> </linearGradient>", "" ) VAR Rects = CONCATENATEX( tWithFinal, "<rect x='" & [X] & "' y='0' width='" & [FinalWidth] & "' height='" & Height & "' fill='url(#grad" & [Order] & ")' stroke='white' stroke-width='0.5' />", "", [Order], ASC ) VAR svg = "data:image/svg+xml;utf8," & "<svg xmlns='http://www.w3.org/2000/svg' width='" & Width & "' height='" & Height & "'>" & "<defs>" & Gradients & "</defs>" & Rects & "</svg>" RETURN IF( ISBLANK( CurrCustomer ) || TotalRevenue = 0, BLANK(), svg )

