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)

Высота системы координат

Сколько единиц помещается по вертикали

если viewBoxwidth/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”.

мера svg1 в Таблице
мера svg1 в Таблице

Ещё один пример с разными атрибутами 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>"
мера SVG_example в Таблице
мера SVG_example в Таблице

Практический пример

Создадим датасет, в котором будет содержаться информация о клиентах и их покупках в разных категориях.

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 )
Добавляем меру с градиентом
Добавляем меру с градиентом

Комментарии (0)