Главная » XSLT » Преобразование имеющейся заготовки SVG

0

Задача

Требуется графически отобразить данные, заполнив ими имеющийся SVG- шаблон.

Решение XSLT 1.0

Пусть требуется вывести данные в виде столбчатой диаграммы. Можно представить себе XSLT-преобразование, которое конструирует SVG-пред- ставление такой диаграммы с нуля (см. рецепт 11.2). Однако человеку, кото­рой не очень уверенно владеет графическими манипуляциями, эта задача мо­жет показаться пугающе сложной. Но иногда нужно лишь вывести данные в нескольких фиксированных точках. В таком случае проще заранее создать в SVG-редакторе изображение, которое будет служить шаблоном, в который мы подставим конкретные данные с помощью XSLT. При таком подходе задача существенно упрощается.

Рассмотрим следующий шаблон столбчатой диаграммы:

<svg width="65 0" height="500">

<g id="axis" transform="translate(0 500) scale(1 -1)"> <line id="axis-y" x1="30" y1="20" x2="30" y2="450"

style="fill:none;stroke:rgb(0,0,0);stroke-width:2"/> <line id="axis-x" x1="30" y1="20" x2="460" y2="20"

style="fill:none;stroke:rgb(0,0,0);stroke-width:2"/>

</g>

<g id="bars" transform="translate(30 479) scale(1 -430)"> <rect x="30" y="0" width="5 0" height="0.25"

style="fill:rgb(255,0,0);stroke:rgb(0,0,0);stroke-width:0"/> <rect x="100" y="0" width="50" height="0.5"

style="fill:rgb(0,255,0);stroke:rgb(0,0,0);stroke-width:0"/> <rect x="17 0" y="0" width="5 0" height="0.75"

style="fill:rgb(255,255,0);stroke:rgb(0,0,0);stroke-width:0"/> <rect x="240" y="0" width="50" height="0.9"

style="fill:rgb(0,255,255);stroke:rgb(0,0,0);stroke-width:0"/> <rect x="310" y="0" width="5 0" height="1"

style="fill:rgb(0,0,255);stroke:rgb(0,0,0);stroke-width:0"/>

</g>

<g id="scale" transform="translate(2 9 60)"> <text id="scale1" x="0px" y="320px"

style="text-anchor:end;fill:rgb(0,0,0);font-size:10;font-family: Arial">0.25</text> <text id="scale2" x="0px" y="215px"

style="text-anchor:end;fill:rgb(0,0,0);font-size:10;font-family: Arial">0.50</text> <text id="scale3" x="0px" y="107.5px"

style="text-anchor:end;fill:rgb(0,0,0);font-size:10;font-family: Arial">0.75</text> <text id="scale4" x="0px" y="0px" style="text-anchor:end;fill: rgb(0,0,0);font-size:10;font-family:Arial">1.00</text>

</g>

<g id="key">

<rect id="key1" x="430" y="80" width="25" height="15"

style="fill:rgb(255,0,0);stroke:rgb(0,0,0);stroke-width:1"/> <rect id="key2" x="430" y="100" width="25" height="15"

style="fill:rgb(0,255,0);stroke:rgb(0,0,0);stroke-width:1"/> <rect id="key3" x="430" y="120" width="25" height="15"

style="fill:rgb(255,255,0);stroke:rgb(0,0,0);stroke-width:1"/> <rect id="key5" x="430" y="140" width="25" height="15"

style="fill:rgb(0,255,255);stroke:rgb(0,0,0);stroke-width:1"/> <rect id="key4" x="430" y="160" width="25" height="15"

style="fill:rgb(0,0,255);stroke:rgb(0,0,0);stroke-width:1"/>

<text id="key1-text" x="465px" y="92px"

style="fill:rgb(0,0,0);font-size:18;font-family:Arial">key1</text> <text id="key2-text" x="465px" y="112px"

style="fill:rgb(0,0,0);font-size:18;font-family:Arial">key2</text> <text id="key3-text" x="465px" y="132px"

style="fill:rgb(0,0,0);font-size:18;font-family:Arial">key3</text> <text id="key4-text" x="465px" y="152px"

style="fill:rgb(0,0,0);font-size:18;font-family:Arial">key4</text> <text id="key5-text" x="465px" y="172px"

style="fill:rgb(0,0,0);font-size:18;font-family:Arial">key5</text>

</g>

<g id="title">

<text x="325px" y="20px" style="text- anchor:middle;fill:rgb(0,0,0);

font-size:24;font-family:Arial">Title</text>

</g> </svg>

На устройстве вывода он выглядит, как показано на рис. 11.1.

При создании этого шаблона мы руководствовались следующими соображениями.

Рис. 11.1. SVG-шаблон столбчатой диаграммы

Во-первых, заранее известно, что предстоит вывести ровно пять значений, поэтому мы и создали пять столбцов. Столбцы помещены внутрь группы SVG (элемент g) с атрибутом id="bars". Далее делается нечто важное – преобразо­вание системы координат так, чтобы высота столбца представляла выводимое значение. Конкретно, преобразование transform="translate(3 0 479) scale(1 -430)". Translate совмещает начало координат с началом столб­цов. Transform меняет направление оси y и масштабирует ее так, что высота height="1" соответствует столбцу максимальной высоты. Отрицательное зна­чение означает отражение (смену направления), а 430 определяет коэффициент масштабирования (430 – длина оси у). Конструкция scale(1,-1) внутри ат­рибута transform – это просто «заполнитель», который работает для фиктив­ных данных в SVG-графике. При обработке реальных данных таблица стилей подставит вместо него правильное значение.

Во-вторых, мы создали фиктивный элемент g с атрибутом id="key". Требуется, чтобы порядок элементов внутри этой группы соответствовал порядку столбцов. Только в этом случае после преобразования график будет нарисован правильно.

В третьих, в группе id="scale" мы создали четыре текстовых элемента с подходящими промежутками для вывода делений шкалы по оси у. Каждому тексто­вому узлу сопоставлен атрибут id, а число, которым заканчивается его значение, рав­но количеству четвертей. Например, для текстового элемента 0,50 id="scale2", потому что это деление представляет 2/4 (1/2) всей шкалы. В таблице стилей мы используем это число для отображения шкалы на значения реальных данных.

В-четвертых, мы создали фиктивный текстовый элемент внутри группы title. Представляемый им текст позиционируется в центре рисунка. И останет­ся там после замены на реальный заголовок.

Тот факт, что все основные компоненты SVG-диаграммы находятся внутри групп, упрощает таблицу стилей, которая будет загружать данные для графика.

Мы собираемся представить в виде столбчатой диаграммы данные о продажах пяти самых продаваемых продуктов компании:

<product-sales description="Top 5 Product Sales (in $1000)"> <product name="Widget">

<sales multiple="1000" currency="USD">70</sales> </product> <product name="Foo Bar">

<sales multiple="1000" currency="USD">8 8 0</sales> </product>

<product name="Grunt Master 9000">

<sales multiple="1000" currency="USD">1000</sales> </product>

<product name="Spam Slicer">

<sales multiple="1000" currency="USD">532</sales> </product>

<product name="Wonder Bar">

<sales multiple="1000" currency="USD">100</sales>

</product> </product-sales>

Показанная ниже таблица стилей объединяет SVG-шаблон с файлом данных, в результате чего создается график, на котором показаны реальные данные:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://www.exslt.org/math" exclude-result-prefixes="math">

<!— По умолчанию SVG-шаблон копируется в выходной документ —> <xsl:import href="../util/copy.xslt"/>

<!– Необходимо найти максимальное значение.–> <!– Для масштабирования применяем шаблон max. –> <xsl:include href="../math/math.max.xslt"/>

<!– Имя файла данных передается в виде параметра. –> <xsl:param name="data-file"/>

<!— Выходной файл будет иметь тип SVG и соответствующую ему DTD-схему. —> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD SVG 1.0/EN"

doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010 904/DTD/svg10.dtd"/>

<!– Загружаем данные в переменную-набор узлов, чтобы к ним было –> <!– проще обращаться — >

<xsl:variable name="bar-values" select="document($data-file)/*/*/sales"/>

<!— Загружаем имена столбцов в переменную-набор узлов, чтобы к ним было –> <!– проще обращаться — >

<xsl:variable name="bar-names" select="document($data-file)/*/*/@name"/>

<!– Находим максимальное значение –> <xsl:variable name="max-data">

<xsl:call-template name="math:max">

<xsl:with-param name="nodes" select="$bar-values"/> </xsl:call-template> </xsl:variable>

<!– Из эстетических соображений масштабируем график так, чтобы –> <!– максимально представимое значение было на 10% больше реального –> <!– максимума — >

<xsl:variable name="max-bar" select="$max-data + $max-data div 10"/> <!– Поскольку каждому компоненту графика соответствует именованная

группа, таблицу стилей легко структурировать так, чтобы производилось сопоставление с каждой группой и выполнялось подходящее преобразование. —>

<!– Копируем группу делений и заменяем текстовые значения соответствующими диапазону имеющихся данных. Для вычисления нужного кратного 0.25 пользуемся числовой частью значения атрибута. –>

<xsl:template match="g[@id=’scale’]"> <xsl:copy>

<xsl:copy-of select="@*"/> <xsl:for-each select="text"> <xsl:copy>

<xsl:copy-of select="@*"/> <xsl:variable name="factor"

select="substring-after(@id,’scale’) * 0.25"/> <xsl:value-of select="$factor * $max-bar"/> </xsl:copy> </xsl:for-each> </xsl:copy> </xsl:template>

<!— В каждом компоненте key подменяем текстовые значения. —> <xsl:template match="g[@id=’key’]"> <xsl:copy>

<xsl:copy-of select="@*"/> <xsl:apply-templates select="rect"/> <xsl:for-each select="text">

<xsl:variable name="pos" select="position( )"/> <xsl:copy>

<xsl:copy-of select="@*"/>

<xsl:value-of select="$bar-names[$pos]"/> </xsl:copy> </xsl:for-each> </xsl:copy> </xsl:template>

<!– Вместо заголовка подставляем извлеченное из данных описание. Можно было бы вместо этого передавать заголовок в качестве параметра. –> <xsl:template match="g[@id=’title’]"> <xsl:copy>

<xsl:copy-of select="@*"/> <xsl:for-each select="text"> <xsl:copy>

<xsl:copy-of select="@*"/>

<xsl:value-of select="document($data-file)/*/@description"/> </xsl:copy> </xsl:for-each> </xsl:copy> </xsl:template>

<!– Для создания столбцов: –>

<!— 1) в атрибут transform подставляем преобразование масштабирования, —>

<!– вычисляемое по значению $max-bar –>

<!– 2) Подменяем значение высоты столбца –>

<xsl:template match="g[@id=’bars’]">

<xsl:copy>

<xsl:copy-of select="@id"/> <xsl:attribute name="transform">

<xsl:value-of select="concat(‘translate(60 479) scale(1 ‘,

-430 div $max-bar,’)’)"/> </xsl:attribute> <xsl:for-each select="rect">

<xsl:variable name="pos" select="position()"/> <xsl:copy>

<xsl:copy-of select="@*"/> <xsl:attribute name="height">

<xsl:value-of select="$bar-values[$pos]"/> </xsl:attribute> </xsl:copy> </xsl:for-each> </xsl:copy> </xsl:template>

</xsl:stylesheet>

После применения этой таблицы стилей к SVG-шаблону получится диаграм­ма, показанная на рис. 1.2.

XSLT 2.0

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

Вместо шаблона max, написанного в главе 3, можно воспользоваться встроен­ной в XPath 2.0 функцией max.

<xsl:variable name="bar-values" select="document($data-file)/*/*/sales"

as="xs:double*"/> <!– Находим максимальное значение –>

<xsl:variable name="max-data" select="max($bar-values)"/>

Рис. 11.2. Столбчатая диаграмма, сгенерированная с помощью XSLT

В XSLT 2.0 к типам надо относиться внимательно. Для преобразования стро­ки в число и обратно необходимо пользоваться специальными функциями:

<xsl:template match="g[@id=’scale’]"> <xsl:copy>

<xsl:copy-of select="@*"/> <xsl:for-each select="text"> <xsl:copy>

<xsl:copy-of select="@*"/>

<xsl:variable name="factor"

select="number(substring-after(@id,’scale’)) * 0.25"/>

<xsl:value-of select="$factor * $max-bar"/> </xsl:copy> </xsl:for-each> </xsl:copy> </xsl:template>

Можно использовать более краткую форму записи xsl:attribute. Обрати­те также внимание на обязательное явное преобразование числа в строку:

<xsl:attribute name="transform" select="concat(‘translate(60 479) scale(1 string(-430 div $max-bar),’)’)"/>

Обсуждение

Создание SVG-документа непосредственно из данных требует большого объе­ма математических вычислений. Это чревато ошибками и разочарованиями, осо­бенно если работа с графикой – не ваш конек. В этом рецепте мы предложили обходной путь – создавать заготовку SVG в визуальном редакторе, а не программ­но. Кроме того, чтобы упростить преобразование данных в графическую форму, мы задействовали встроенный в SVG механизм трансформаций.

Конечно, у этого рецепта есть очевидные ограничения.

Во-первых, нужно заранее знать количество точек на графике. Более общий построитель столбчатых диаграмм мог бы вычислить количество столбцов и рас­пределить их автоматически, анализируя данные на этапе выполнения. Эту про­блему можно частично разрешить, создав SVG-шаблон, содержащий, скажем, де­сять столбцов, и удалить лишние во время выполнения. Но при таком подходе представление будет некрасивым; ведь если подставить в шаблон, рассчитанный на десять элементов, всего два, то останутся «дырки».

Во-вторых, хотя и можно создавать графики, более сложные, чем в этом про­стеньком примере, мы ограничены линейным отображением величин на их визу­альные образы. Так, не вполне ясно, как можно было бы применить ту же технику к построению секторных диаграмм; для изменения площади сектора недостаточ­но подставить значение единственного атрибута.

Третье ограничение проистекает из того факта, что источник данных, SVG- шаблон и таблица стилей оказываются тесно связанными между собой. Поэтому всякий раз, как вы захотите воспользоваться этой техникой, придется создавать новый шаблон и новую таблицу стилей. В конечном итоге вы потратите на это больше времени, чем на поиск более общего решения с самого начала.

Несмотря на все ограничения, этот пример все же представляет ценность в качестве отправной точки для тех, кто только приступает к изучению SVG-гра- фики. Точнее, он позволяет задействовать возможности SVG-редактора для пред­варительной подготовки изображений: выравнивания, равномерного распределе­ния и пропорционального масштабирования.

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии