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 )
