Главная » XSLT » Создание повторно используемых утилит генерации SVG для графиков и диаграмм

0

Задача

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

Решение XSLT 1.0

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

Генерация осей

В этом примере создается набор общих шаблонов для генерации размеченных осей x и y.

<!– Нарисовать ось X с делениями –>

<xsl:template name="svgu:xAxis"> <xsl:param name="min"

select="0"/> <!– Минимальная абсцисса –> <xsl:param name="max"

select="10 0"/> <!– Макимальная абсцисса –> <xsl:param name="offsetX"

select="0"/> <!– Смещение от левой границы области –> <xsl:param name="offsetY"

select="0"/> <!— Смещение от нижней границы области —> <xsl:param name="width"

select="5 00"/> <!– Ширина физической области рисования –> <xsl:param name="height"

select="5 00"/> <!– Высота физической области рисования —> <xsl:param name="majorTicks"

select="10"/> <!– Количество крупных делений по оси –> <xsl:param name="majorBottomExtent"

select="4"/> <!— Длина части крупного деления под осью —> <xsl:param name="majorTopExtent"

select="$majorBottomExtent"/> <!– Длина части крупного

деления над осью –>

<xsl:param name="labelMajor"

select="true( )"/> <!– Если true, подписывать крупные

деления –>

<xsl:param name="minorTicks"

select="4"/> <!– Количество мелких делений внутри крупного –> <xsl:param name="minorBottomExtent"

select="2"/> <!– Длина части мелкого деления под осью –> <xsl:param name="minorTopExtent"

select="$minorBottomExtent"/> <!– Длина части мелкого

деления над осью –> <xsl:param name="context"/> <!– Определенный пользователем

индикатор контекста для вызовов шаблона форматирования –>

<!— Вычислить диапазон и коэффициенты масштабирования —> <xsl:variable name="range" select="$max – $min"/> <xsl:variable name="scale" select="$width div $range"/>

<!– Определить декартову систему, задав смещение начала координат и –> <!– масштабирование –>

<svg:g transform="translate({$offsetX},{$offsetY+$height})

scale({$scale},-1) translate({$min},0)"> <!– Провести прямую, представляющую ось –> <svg:line x1="{$min}" y1="0" x2="{$max}" y2="0"> <xsl:attribute name="style"> <!— Вызвать шаблон, который можно переопределить для —> <!– задания другого стиля рисования оси –> <xsl:call-template name="xAxisStyle">

<xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:attribute> </svg:line>

<!– Нарисовать деления и подписи –> <xsl:call-template name="svgu:ticks">

<xsl:with-param name="xMajor1" select="$min"/> <xsl:with-param name="yMajor1" select="$majorTopExtent"/> <xsl:with-param name="xMajor2" select="$min"/>

<xsl:with-param name="yMajor2" select="-$majorBottomExtent"/> <xsl:with-param name="labelMajor" select="$labelMajor"/> <xsl:with-param name="freq" select="$minorTicks"/> <xsl:with-param name="xMinor1" select="$min"/> <xsl:with-param name="yMinor1" select="$minorTopExtent"/> <xsl:with-param name="xMinor2" select="$min"/>

<xsl:with-param name="yMinor2" select="-$minorBottomExtent"/> <xsl:with-param name="nTicks"

select="$majorTicks * $minorTicks + 1"/> <xsl:with-param name="xIncr"

select="($max – $min) div ($majorTicks * $minorTicks)"/> <xsl:with-param name="scale" select="1 div $scale"/> </xsl:call-template> </svg:g>

</xsl:template>

<xsl:template name="svgu:yAxis">

<xsl:param name="min"

select="0"/> <!– Минимальная ордината –> <xsl:param name="max"

select="100"/> <!– Максимальная ордината –> <xsl:param name="offsetX"

select="0"/> <!– Смещение от левой границы области –> <xsl:param name="offsetY"

select="0"/> <!– Смещение от правой границы области –> <xsl:param name="width"

select="5 00"/> <!– Ширина физической области рисования —> <xsl:param name="height"

select="5 00"/> <!– Высота физической области рисования —> <xsl:param name="majorTicks"

select="10"/> <!– Количество крупных делений по оси –> <xsl:param name="majorLeftExtent"

select="4"/> <!– Длина части крупного деления слева от оси — > <xsl:param name="majorRightExtent"

select="$majorBottomExtent"/> <!– Длина части крупного

деления справа от оси –>

<xsl:param name="labelMajor"

select="true( )"/> <!– Если true, подписывать крупные

деления –>

<xsl:param name="minorTicks"

select="4"/>          <!– Количество мелких делений внутри

крупного –>

<xsl:param name="minorLeftExtent"

select="2"/>          <!– Длина части мелкого деления слева

от оси — > <xsl:param name="minorRightExtent"

select="$minorBottomExtent"/> <!– Длина части мелкого

деления справа от оси –> <xsl:param name="context"/> <!– Определенный пользователем

индикатор контекста для вызовов шаблона форматирования —>

<xsl:param name="majorLeftExtent"

select="4"/> <xsl:param name="majorRightExtent"

select="$majorLeftExtent"/> <xsl:param name="minorLeftExtent"

select="2"/> <xsl:param name="minorRightExtent"

select="$minorLeftExtent"/>

<!– Вычислить диапазон и коэффициенты масштабирования –> <xsl:variable name="range" select="$max – $min"/> <xsl:variable name="scale" select="$height div $range"/>

<!– Определить декартову систему, задав смещение начала координат и –> <!– масштабирование –>

<svg:g transform="translate({$offsetX},{$offsetY+$height}) scale(1,{-$scale}) translate(0,{-$min})"> <svg:line x1="0" y1="{$min}" x2="0" y2="{$max}"> <xsl:attribute name="style">

<xsl:call-template name="yAxisStyle">

<xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:attribute> </svg:line>

<xsl:call-template name="svgu:ticks">

<xsl:with-param name="xMajor1" select="-$majorLeftExtent"/> <xsl:with-param name="yMajor1" select="$min"/> <xsl:with-param name="xMajor2" select="$majorRightExtent"/> <xsl:with-param name="yMajor2" select="$min"/> <xsl:with-param name="labelMajor" select="$labelMajor"/> <xsl:with-param name="freq" select="$minorTicks"/> <xsl:with-param name="xMinor1" select="-$minorLeftExtent"/> <xsl:with-param name="yMinor1" select="$min"/> <xsl:with-param name="xMinor2" select="$minorRightExtent"/> <xsl:with-param name="yMinor2" select="$min"/> <xsl:with-param name="nTicks"

select="$majorTicks * $minorTicks + 1"/> <xsl:with-param name="yIncr"

select="($max – $min) div ($majorTicks * $minorTicks)"/> <xsl:with-param name="scale" select="1 div $scale"/> </xsl:call-template> </svg:g>

</xsl:template>

<!– Рекурсивный шаблон для рисования делений и подписей –>

<xsl:template name="svgu:ticks"> <xsl:param name="xMajor1" /> <xsl:param name="yMajor1" /> <xsl:param name="xMajor2" /> <xsl:param name="yMajor2" /> <xsl:param name="labelMajor"/>

<xsl:param name="freq" /> <xsl:param name="xMinor1" /> <xsl:param name="yMinor1" /> <xsl:param name="xMinor2" /> <xsl:param name="yMinor2" /> <xsl:param name="nTicks" select="0"/> <xsl:param name="xIncr" select="0"/> <xsl:param name="yIncr" select="0"/> <xsl:param name="i" select="0"/> <xsl:param name="scale"/> <xsl:param name="context"/>

<xsl:if test="$i &lt; $nTicks"> <xsl:choose>

<!– Рисуем крупное деление –> <xsl:when test="$i mod $freq = 0">

<svg:line x1="{$xMajor1}" y1="{$yMajor1}"

x2="{$xMajor2}" y2="{$yMajor2}"> </svg:line>

<xsl:if test="$labelMajor"> <xsl:choose>

Далее рисуются деления по осям x и у. В этом примере форматная строка «за­шита» в код, чтобы не передавать еще один параметр, но можно было бы передать ее параметром в этот или в отдельный шаблон для форматирования:

<!– Деления по оси x –> <xsl:when test="$xIncr > 0">

<!– При выводе подписи нужно компенсировать искажение системы координат –>

<svg:text x="{$xMajor1}" y="{$yMajor2}"

transform="translate({$xMajor1},{$yMajor2}) scale({$scale},-1)

translate({-$xMajor1},{-$yMajor2})"> <xsl:attribute name="style"> <xsl:call-template name="xAxisLabelStyle"> <xsl:with-param name="context"

select="$context"/> </xsl:call-template> </xsl:attribute>

<!– Может быть, формат подписи следовало сделать параметром –> <xsl:value-of select="format-number($xMajor1,’#0.0′)"/> </svg:text> </xsl:when> <!– Деления по оси y –>

<xsl:otherwise>

<svg:text x="{$xMajor1}" y="{$yMajor1}"

transform="translate({$xMajor1},{$yMajor1}) scale(1,{-$scale}) translate({-$xMajor1},{-$yMajor1})"> <xsl:attribute name="style">

<xsl:call-template name="yAxisLabelStyle">

<xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:attribute>

<xsl:value-of select="format-number($yMajor1,’#0.0′)"/> </svg:text> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:when>

<!– Рисуем мелкие деления –> <xsl:otherwise>

<svg:line x1="{$xMinor1}" y1="{$yMinor1}" x2="{$xMinor2}" y2="{$yMinor2}"> </svg:line> </xsl:otherwise> </xsl:choose>

<!– Рекypсивный вызов для pисoвaния следующего деления –> <xsl:call-template name="svgu:ticks">

<xsl:with-param name="xMajor1" select="$xMajor1 + $xIncr"/> <xsl:with-param name="yMajor1" select="$yMajor1 + $yIncr"/> <xsl:with-param name="xMajor2" select="$xMajor2 + $xIncr"/> <xsl:with-param name="yMajor2" select="$yMajor2 + $yIncr"/> <xsl:with-param name="labelMajor" select="$labelMajor"/> <xsl:with-param name="freq" select="$freq"/>

<xsl:with-param name="xMinor1" select="$xMinor1 + $xIncr"/> <xsl:with-param name="yMinor1" select="$yMinor1 + $yIncr"/> <xsl:with-param name="xMinor2" select="$xMinor2 + $xIncr"/> <xsl:with-param name="yMinor2" select="$yMinor2 + $yIncr"/> <xsl:with-param name="nTicks" select="$nTicks"/> <xsl:with-param name="xIncr" select="$xIncr"/> <xsl:with-param name="yIncr" select="$yIncr"/> <xsl:with-param name="i" select="$i + 1"/> <xsl:with-param name="scale" select="$scale"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:if>

</xsl:template>

<!– Переопределить этот шаблон, если нужно изменить стиль оси x –> <xsl:template name="xAxisStyle"> <xsl:param name="context"/> <xsl:text>stroke-width:0.5;stroke:black</xsl:text> </xsl:template>

<!– Переопределить этот шаблон, если нужно изменить стиль оси y –> <xsl:template name="yAxisStyle"> <xsl:param name="context"/> <xsl:text>stroke-width:0.5;stroke:black</xsl:text> </xsl:template>

<!– Переопределить этот шаблон, если нужно изменить стиль подписей под осью x — >

<xsl:template name="xAxisLabelStyle"> <xsl:param name="context"/> <xsl:text>text-anchor:middle; font-size:8;

baseline-shift:-110%</xsl:text> </xsl:template>

<!– Переопределить этот шаблон, если нужно изменить стиль подписей слева от оси y –>

<xsl:template name="yAxisLabelStyle"> <xsl:param name="context"/>

<xsl:text>text-anchor:end;font-size:8;baseline-shift:-50%</xsl:text> </xsl:template>

Следующая таблица стилей порождает оси x и у с крупными и мелкими деле­ниями и соответствующими им подписями:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg"

xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu test">

<xsl:import href="svg-utils.xslt"/>

<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-20 010 904/DTD/ svg10.dtd"/>

<xsl:variable name="width" select="3 0 0"/> <xsl:variable name="height" select="3 0 0"/> <xsl:variable name="pwidth" select="$width * 0.8"/> <xsl:variable name="pheight" select="$height * 0.8"/> <xsl:variable name="offsetX" select="($width – $pwidth) div 2"/> <xsl:variable name="offsetY" select="($height – $pheight) div 2"/>

<xsl:template match="/">

<svg:svg width="{$width}" height="{$height}"> <xsl:call-template name="svgu:xAxis">

<xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="10"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> </xsl:call-template>

<xsl:call-template name="svgu:yAxis">

<xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="10"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> </xsl:call-template>

</svg:svg>

</xsl:template>

</xsl:stylesheet>

В результате получаем оси, изображенные на рис. 11.3.

Если задать длины частей крупных делений выступающих над и справа от оси равными соответственно полной высоте и ширине области рисования, то полу­чится сетка, изображенная на рис. 11.4.

<xsl:call-template name="svgu:xAxis">

<xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="10"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="majorTopExtent" select="$pheightM/> </xsl:call-template>

Рис. 11.3. Результат применения повторно используемой таблицы стилей

для рисования осей

<xsl:call-template name="svgu:yAxis"> <xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="10"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="majorRightExtent" select="$pwidth"/> </xsl:call-template>

Если также поступить и с длинами мелких делений, то получится более мел­кая сетка, изображенная на рис. 11.5.

<xsl:call-template name="svgu:xAxis">

<xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="10"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="majorTopExtent" select="$pheight"/> <xsl:with-param name="minorTopExtent" select="$pheight"/> </xsl:call-template>

<xsl:call-template name="svgu:yAxis">

Рис. 11.4. Координатная сетка

Рис. 11.5. Мелкая координатная сетка

<xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="10"/>

<xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="majorRightExtent" select="$pwidth"/> <xsl:with-param name="minorRightExtent" select="$pwidth"/> </xsl:call-template>

Если сдвинуть оси и задать длины частей делений по обе стороны осей, то по­лучатся четыре квадранта, как показано на рис. 11.6:

<xsl:call-template name="svgu:xAxis">

<xsl:with-param name="min" select="-5"/> <xsl:with-param name="max" select="5"/> <xsl:with-param name="offsetX" select="0"/> <xsl:with-param name="offsetY" select="-$pheight div 2"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="majorTopExtent" select="$pwidth div 2"/> <xsl:with-param name="majorBottomExtent" select="$pwidth div 2"/> </xsl:call-template>

<xsl:call-template name="svgu:yAxis">

<xsl:with-param name="min" select="-5"/> <xsl:with-param name="max" select="5"/>

Рис. 11.6. Четыре квадранта и координатная сетка

<xsl:with-param name="offsetX" select="-$pwidth div 2"/> <xsl:with-param name="offsetY" select="0"/>

<xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/>

<xsl:with-param name="majorRightExtent" select="$pwidth div 2"/> <xsl:with-param name="majorLeftExtent" select="$pwidth div 2"/>

</xsl:call-template>

По умолчанию подписи располагаются снизу и слева от области рисования. Но можно придвинуть их к осям, переопределив два шаблона. Тогда получится результат, показанный на рис. 11.7.

<xsl:template name="xAxisLabelYOffset">

<xsl:value-of select="-$pheight div 2"/> </xsl:template>

<xsl:template name="yAxisLabelXOffset">

<xsl:value-of select="$pwidth div 2"/> </xsl:template>

Рис. 11.7. Четыре квадранта с координатной сеткой и подписями на осях

Генерация столбцов

Данные часто представляются в виде столбчатых диаграмм, на которых мож­но провести наглядное сравнение. Напишем утилиту, которая будет порождать один столбец по переданному значению. Цвет и ширину столбца легко настроить.

В примере ниже столбцы можно также ориентировать в разных направлениях за счет поворота системы координат. Но при этом не следует забывать о компенса­ции поворота при выводе текста и о видимом порядке следования значений:

<xsl:template name="svgu:bars">

<xsl:param name="data" select="/.."/> <!– данные для диaгpaммы –> <xsl:param name="width" select="5 0 0"/> <xsl:param name="height" select="5 0 0"/> <xsl:param name="orientation" select="0"/> <xsl:param name="barWidth" select="5"/> <xsl:param name="offsetX" select="0"/> <xsl:param name="offsetY" select="0"/> <xsl:param name="boundingBox" select="false( )"/> <xsl:param name="barLabel" select="false( )"/> <xsl:param name="max"> <xsl:call-template name="emath:max">

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

<xsl:param name="context"/>

<xsl:variable name="numBars" select="count($data)"/> <xsl:variable name="spacing" select="$width div ($numBars + 1)"/>

<xsl:if test="$boundingBox"> <svg:g transform="translate({$offsetX},{$offsetY})

translate({$width div 2},{$height div 2}) rotate({$orientation – 180})

translate({-$width div 2},{-$height div 2})"> <svg:rect x="0" y="0" height="{$height}" width="{$width}" style="stroke: black;

stroke-width:0.5;stroke-opacity:0.5;fill:none"/>

</svg:g> </xsl:if>

<!– Изменяем пopядoк данных, чтобы кoмпенсиpoвaть пoвopoт. –> <!– См. сopтиpoвкy ниже –> <xsl:variable name="data-order"> <xsl:choose>

<xsl:when test="$orientation mod 360 >= 180">ascending</xsl:when> <xsl:otherwise>descending</xsl:otherwise> </xsl:choose> </xsl:variable>

<svg:g transform="translate({$offsetX},{$offsetY})

translate({$width div 2},{$height div 2}) rotate({$orientation – 180})

translate({-$width div 2},{-$height div 2}) scale(1,{$height div $max})">

<xsl:for-each select="$data">

<!– Сортируем по позиции, чтобы можно было обходить данные –> <!– в противоположном направлении, когда это требуется. –> <xsl:sort select="position( )" data-type="number" order="{$data-order}"/>

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

Ниже столбцы рисуются в виде отрезков прямых. Для изменения цвета и тол­щины линии следует переопределить шаблон BarStyle. Если бы мы использова­ли для рисования столбцов прямоугольники, то можно было бы дополнительно варьировать стиль рамки:

<svg:line x1="{$spacing * $pos}" y1="0"

x2="{$spacing * $pos}"

y2="{current( )}" id="{$context}_bar_{$pos}"> <xsl:attribute name="style">

<xsl:value-of select="concat(‘stroke-width: ‘,$barWidth,'; ‘)"/> <xsl:call-template name="svgu:barStyle">

<xsl:with-param name="pos" select="$pos"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:attribute> </svg:line>

<!– Если пользователю потребуются надписи, мы разместим текст над –> <!– столбцом. Чтобы текст выводился правильно, несмотря на поворот —> <!– и масштабирование системы координат, необходима сложная –> <!– последовательность преобразований. –> <xsl:if test="$barLabel">

<svg:text x="{$spacing * $pos}"

y="{current( ) * ($height div $max)}" transform="scale(1,{$max div $height}) translate(0,10)

translate({$spacing * $pos},{current( ) *

($height div $max)}) rotate({180 – $orientation}) translate({-$spacing * $pos}, {-current( ) * ($height div $max)})"

id="{$context}_barLabel_{$pos}"> <xsl:attribute name="style">

<xsl:call-template name="svgu:barLabelStyle"> <xsl:with-param name="pos" select="$pos"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:attribute> <xsl:value-of select="."/> </svg:text> </xsl:if> </xsl:for-each> </svg:g>

</xsl:template>

<xsl:template name="svgu:barStyle"> <xsl:param name="pos"/> <xsl:param name="context"/>

<xsl:variable name="colors" select="document(”)/*/svgu:color"/> <xsl:value-of

select="concat(‘stroke: ‘,$colors[($pos – 1 ) mod count($colors) + 1])"/>

</xsl:template>

<xsl:template name="svgu:barLabelStyle"> <xsl:param name="pos"/> <xsl:param name="context"/>

<xsl:value-of select=" ‘text-anchor: middle’ "/> </xsl:template>

Следующая таблица стилей строит столбчатую диаграмму по переданным ей данным. Результаты показаны на рис. 11.8.

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg"

xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils"

xmlns:test="http://www.ora.com/XSLTCookbook/ns/test"

exclude-result-prefixes="svgu">

<xsl:import href="svg-utils.xslt"/>

<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"/>

Рис. 11.8. Сгенерированная столбчатая диаграмма

<test:data>1.0</test:data>

<test:data>2.0</test:data>

<test:data>3.0</test:data>

<test:data>4.0</test:data>

<test:data>5.0</test:data>

<test:data>13.0</test:data>

<test:data>2.7</test:data>

<test:data>13.9</test:data>

<test:data>22.0</test:data>

<test:data>8.5</test:data>

<xsl:template match="/">

<svg:svg width="400" height="4 0 0">

<xsl:call-template name="svgu:bars">

<xsl:with-param name="data" select="document(”)/*/test:data"/> <xsl:with-param name="width" select=" ‘300’ "/> <xsl:with-param name="height" select=" ‘350’ "/> <xsl:with-param name="orientation" select=" ‘0’ "/> <xsl:with-param name="offsetX" select=" ’50’ "/> <xsl:with-param name="offsetY" select=" ’25’ "/>

<xsl:with-param name="boundingBox" select="1"/> <xsl:with-param name="barLabel" select="1"/> <xsl:with-param name="max" select="2 5"/> </xsl:call-template>

</svg:svg> </xsl:template>

<xsl:template name="svgu:barLabelStyle"> <xsl:param name="pos"/> <xsl:param name="context"/>

<xsl:text>text-anchor: middle; font-size: 8</xsl:text> </xsl:template>

</xsl:stylesheet>

А ниже показано, как создать горизонтальную диаграмму (рис. 11.9). В коде нет ограничений на угол поворота, хотя разумно, конечно, использовать только углы, кратные 90 градусам1.

Рис. 11.9. Повернутая столбчатая диаграмма

<xsl:call-template name="svgu:bars">

<xsl:with-param name="data" select="document(”)/*/test:data"/> <xsl:with-param name="width" select=" ‘300’ "/> <xsl:with-param name="height" select=" ‘350’ "/> <xsl:with-param name="orientation" select=" ’90’ "/> <xsl:with-param name="offsetX" select=" ’50’ "/> <xsl:with-param name="offsetY" select=" ’25’ "/> <xsl:with-param name="boundingBox" select="1"/> <xsl:with-param name="barLabel" select="1"/> <xsl:with-param name="max" select="25"/>

</xsl:call-template>

Линейные графики

Оси и координатные сетки бесполезны, если нельзя вывести данные. Один из самых распространенных способов представления данных – линейный график, на котором одно значение показано как функция другого. Можно написать утилиту, которая обрабатывает один набор данных, и вызвать ее несколько раз для рисова­ния различных графиков в одних и тех же осях:

<xsl:template name="svgu:xyPlot">

<xsl:param name="dataX" select="/.."/> <!— значения x —>

<xsl:param name="dataY" select="/.."/>

<xsl:param name="offsetX" select="0"/>

<xsl:param name="offsetY" select="0"/>

<xsl:param name="width" select="5 0 0"/>

<xsl:param name="height" select="5 0 0"/>

<xsl:param name="boundingBox" select="false( )"/>

<xsl:param name="context"/>

<xsl:param name="maxX">

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

<xsl:with-param name="nodes" select="$dataX"/> </xsl:call-template> </xsl:param> <xsl:param name="maxY">

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

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

<xsl:variable name="scaleX" select="$width div $maxX"/> <xsl:variable name="scaleY" select="$height div $maxY"/>

В этом разделе мы для простоты воспользовались написанной на Java фун­кцией расширения, но можно было бы реализовать функцию max и на XPath: select="($scaleX > $scaleY) * $scaleX + not($scaleX > $scaleY) * $scaleY)":

<xsl:variable name="scale" select="Math:max($scaleX,$scaleY)"/>

<xsl:if test="$boundingBox">

<svg:g transform="translate({$offsetX},{$offsetY})">

<svg:rect x="0" y="0" height="{$height}" width="{$width}" style="stroke: black;stroke-width:0.5;

stroke-opacity:0.5;fill:none"/>

</svg:g> </xsl:if>

Я представляю кривую в виде набора прямолинейных отрезков, но, применив кривые Безье, можно было бы сгладить ее, правда, ценой усложнения кода. По­скольку эта книга все-таки посвящена XSLT, а не SVG, то не будем жертвовать простотой. Для использования кубической кривой Безье можно было бы рисовать сразу три точки, выбрав центральную в качестве управляющей, но я эту идею не проверял:

<svg:path transform="translate({$offsetX},{$height + $offsetY})

scale({$scaleX},{-$scaleY})"> <xsl:attribute name="d">

<xsl:for-each select="$dataX">

<xsl:variable name="pos" select="position( )"/> <xsl:variable name="x" select="current( ) "/> <xsl:variable name="y" select="$dataY[$pos]"/> <xsl:choose>

<xsl:when test="$pos = 1"> <xsl:text>M </xsl:text> </xsl:when>

<xsl:otherwise> L </xsl:otherwise> </xsl:choose>

<xsl:value-of select="$x"/>,<xsl:value-of select="$y"/> </xsl:for-each> </xsl:attribute> <xsl:attribute name="style">

<xsl:call-template name="svgu:xyPlotStyle">

<xsl:with-param name="scale" select="$scale"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:attribute> </svg:path> </xsl:template>

<xsl:template name="svgu:xyPlotStyle">

<xsl:param name="context"/> <xsl:param name="scale"/>

<xsl:value-of select="concat(‘fill: none; stroke: black; stroke- width:’,1 div $scale,'; ‘)"/> </xsl:template>

В следующей таблице стилей тестируется шаблон для вывода линейного гра­фика. Полученные результаты показаны на рис. 11.10. Для простоты я поместил данные прямо в таблицу. В реальном приложении они должны извлекаться из другого XML-документа:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg"

xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu test">

<xsl:import href="svg-utils.xslt"/>

<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"/> <test:xdata>0</test:xdata>

Рис. 11.10. Вывод линейного графика с помощью SVG и XSLT

<test:xdata>5</test:xdata>

<test:xdata>10</test:xdata>

<test:xdata>15</test:xdata>

<test:xdata>20</test:xdata>

<test:xdata>25</test:xdata>

<test:xdata>30</test:xdata>

<!– Остальные координаты x опущены … –>

<test:ydata>0</test:ydata>

<test:ydata>0.087155743</test:ydata>

<test:ydata>0.173648178</test:ydata>

<test:ydata>0.258819045</test:ydata>

<test:ydata>0.342020143</test:ydata>

<test:ydata>0.422618262</test:ydata>

<test:ydata>0.5</test:ydata>

<!– Остальные координаты y опущены … –>

<xsl:variable name="w" select="4 0 0"/> <xsl:variable name="h" select="300"/> <xsl:variable name="pwidth" select="$w * 0.8"/> <xsl:variable name="pheight" select="$h * 0.8"/> <xsl:variable name="offsetX" select="($w – $pwidth) div 2"/> <xsl:variable name="offsetY" select="($h – $pheight) div 2"/>

<xsl:template match="/">

<svg:svg width="{$w}" height="{$h}">

<xsl:call-template name="svgu:xyPlot">

<xsl:with-param name="dataX" select="document(”)/*/test:xdata"/> <xsl:with-param name="dataY" select="document(”)/*/test:ydata"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/>

<xsl:with-param name="height" select="$pheight"/> <!–

<xsl:with-param name="minY" select="-1"/>

<xsl:with-param name="maxY" select="1"/> — >

</xsl:call-template>

<xsl:call-template name="svgu:xAxis">

<xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="3 60"/> <xsl:with-param name="offsetX" select="$offsetX"/>

<xsl:with-param name="offsetY" select="-$pheight div 2 + $offsetY"/>

<xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="majorTicks" select="6"/>

<!– Количество крупных делений по оси x –> <xsl:with-param name="minorTicks" select="4"/> <!– Количество мелких делений по оси x –> </xsl:call-template>

<xsl:call-template name="svgu:yAxis">

<xsl:with-param name="min" select="-1"/> <xsl:with-param name="max" select="1"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> </xsl:call-template> </svg:svg>

</xsl:template>

</xsl:stylesheet>

Следующая программа, результаты работы которой показаны на рис. 11.11, демонстрирует вывод нескольких графиков для разных наборов данных и измене­ния стиля линии за счет переопределения шаблона:

Рис. 11.11. Несколько графиков, сгенерированных с помощью XSLT

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg"

xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu test">

<xsl:import href="svg-utils.xslt"/>

<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="w" select="4 0 0"/> <xsl:variable name="h" select="300"/> <xsl:variable name="pwidth" select="$w * 0.8"/> <xsl:variable name="pheight" select="$h * 0.8"/> <xsl:variable name="offsetX" select="($w – $pwidth) div 2"/> <xsl:variable name="offsetY" select="($h – $pheight) div 2"/>

<xsl:template match="/">

<svg:svg width="{$w}" height="{$h}">

<xsl:call-template name="svgu:xyPlot">

<xsl:with-param name="dataX" select="document(”)/*/test:xdata"/> <xsl:with-param name="dataY" select="document(”)/*/test:ydata"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="maxY" select="4 0"/> </xsl:call-template>

<xsl:call-template name="svgu:xyPlot">

<xsl:with-param name="dataX" select="document(”)/*/test:xdata"/> <xsl:with-param name="dataY" select="document(”)/*/test:y2data"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="maxY" select="4 0"/>

<xsl:with-param name="context" select="2"/> </xsl:call-template>

<xsl:call-template name="svgu:xAxis">

<xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="6"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="majorTopExtent" select="$pheight" <xsl:with-param name="minorTopExtent" select="$pheight" </xsl:call-template>

<xsl:call-template name="svgu:yAxis">

<xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="40"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="majorRightExtent" select="$pwidth"/> <xsl:with-param name="minorRightExtent" select="$pwidth"/> </xsl:call-template>

</svg:svg>

</xsl:template>

<!– При определении специального стиля используется контекст, –> <!– позволяющий узнать, какая линия рисуется –> <xsl:template name="svgu:xyPlotStyle"> <xsl:param name="context"/> <xsl:param name="scale"/> <xsl:choose> <xsl:when test="$context = 2"> <xsl:value-of select="concat(‘fill: none; stroke: red; stroke-width:’,16 div $scale,'; ‘)"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="concat(‘fill: none; stroke: black; stroke-width:’,1 div $scale,'; ‘)"/> </xsl:otherwise> </xsl:choose>


</xsl:template>

</xsl:stylesheet>

Генерация секторных диаграмм

Еще один распространенный способ визуального сравнения данных – сектор­ные диаграммы. Можно написать утилиту, которая будет их генерировать. Для создания секторной диаграммы нужно иметь средства для создания одного сектора, а это неизбежно заводит нас в дебри тригонометрии. Поскольку в XSLT нет триго­нометрических функций, придется воспользоваться расширением на языке Java. Конечно, это сразу же ограничивает переносимость таблицы стилей. Если без пере­носимости никак нельзя, можно реализовать синус и косинус на XSLT (см. указа­ния в рецепте 3.5). А если переносимостью можно пожертвовать, то включите в таб­лицу показанный ниже код, где запрашиваются математические расширения на Java. Точный вид зависит от процессора, в главе 12 приведена дополнительная ин­формация по этому поводу. Этот пример ориентирован на процессор Saxon:

<xsl:stylesheet

<!– версия 1.1 объявлена не действующей, но Saxon поддерживает ее –>

<!– ради xsl:script. –>

version="1.1"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg"

xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:emath="http://www.exslt.org/math"

xmlns:Math="java:java.lang.Math" extension-element-prefixes="Math" exclude-result-prefixes="svgu">

<xsl:script implements-prefix="Math"

xmlns:Math="java:java.lang.Math"

language="java"

src="java:java.lang.Math"/>

<!— Применяем XSLT-код, разработанным ранее в главе 2 —> <xsl:include href="../math/math.max.xslt"/> <xsl:include href="../math/math.min.xslt"/>

</xsl:stylesheet>

В шаблоне svgu:pieSlice реализована большая часть необходимой мате­матики. Код основан на Perl-программе, приведенной в книге J. David Eisenberg SVG Essentials (O’Reilly, 2002). Рассказывать об основах тригонометрии в этой книге мы не будем, но по существу код позволяет нарисовать дуги (с помощью поворотов вокруг начала координат), компенсируя интуитивно не очевидную спецификацию дуг в SVG:

<xsl:variable name="svgu:pi" select="3.1415 927"/>

<xsl:template name="svgu:pieSlice">

<xsl:param name="cx" select="10 0"/> <!– Абсцисса центра –> <xsl:param name="cy" select="10 0"/> <!– Ордината центра –> <xsl:param name="r" select="50"/> <!– Радиус –>

<xsl:param name="theta" select="0"/> <!– Начальный угол в градусах —> <xsl:param name="delta" select="9 0"/> <!– Угловая длина дуги

в градусах — >

<xsl:param name="phi" select="0"/> <!– Угол поворота оси x –> <xsl:param name="style" select=" ‘fill: red;’ "/> <xsl:param name="num"/> <xsl:param name="context"/>

<!– Преобразуем градусы в радианы –> <xsl:variable name="theta1"

select="$theta * $svgu:pi div 180"/> <xsl:variable name="theta2"

select="($delta + $theta) * $svgu:pi div 180"/> <xsl:variable name="phi_r" select="$phi * $svgu:pi div 180"/>

<!– Находим координаты начала и конца дуги –> <xsl:variable name="x0"

select="$cx + Math:cos($phi_r) * $r * Math:cos($theta1) +

Math:sin(-$phi_r) * $r * Math:sin($theta1)"/ <xsl:variable name="y0"

select="$cy + Math:sin($phi_r) * $r * Math:cos($theta1) + Math:cos($phi_r) * $r * Math:sin($theta1)"/>

<xsl:variable name="x1"

select="$cx + Math:cos($phi_r) * $r * Math:cos($theta2) + Math:sin(-$phi_r) * $r * Math:sin($theta2)"/ <xsl:variable name="y1"

select="$cy + Math:sin($phi_r) * $r * Math:cos($theta2) + Math:cos($phi_r) * $r * Math:sin($theta2)"/>

<xsl:variable name="large-arc" select="($delta > 180) * 1"/> <xsl:variable name="sweep" select="($delta > 0) * 1"/>

<svg:path style="{$style} id="{$context}_pieSlice_{$num}"> <xsl:attribute name="d">

<xsl:value-of select="concat( ‘M ‘, $x0,’ ‘,$y0,

‘ A ‘, $r,’ ‘,$r,’,’, $phi,’,’,

$large-arc,’,’, $sweep,’,’, $x1,’ ‘,$y1, ‘ L ‘,$cx,’ ‘,$cy,

‘ L $x0,’ ‘,$y0)"/>

</xsl:attribute> </svg:path> </xsl:template>

<xsl:template name="svgu:pieSliceLabel">

<xsl:param name="label" />     <!– Надпись –>

<xsl:param name="cx" select="100"/><!– Абсцисса центра –> <xsl:param name="cy" select="100"/><!– Ордината центра –> <xsl:param name="r" select="5 0"/> <!– Радиус –>

<xsl:param name="theta" select="0"/> <!– Начальный угол в градусах —> <xsl:param name="delta" select="90"/> <!— Угловая длина дуги в градусах —> <xsl:param name="style" select=" ‘font-size: 18;’ "/> <xsl:param name="num"/> <xsl:param name="context"/>

<!– Преобразуем градусы в радианы –> <xsl:variable name="theta2"

select="(($delta + $theta) mod 360 + 360) mod 360"/> <!– Нормализуем углы –>

<xsl:variable name="theta2_r" select="$theta2 * $svgu:pi div 180"/> <xsl:variable name="x" select="$cx + $r * Math:cos($theta2_r)"/> <xsl:variable name="y" select="$cy + $r * Math:sin($theta2_r)"/>

<!– Вычисляем координаты начала текста с учетом положения сектора –> <!– в круге. Наша цель – расположить текст более-менее равномерно. —> <xsl:variable name="anchor"> <xsl:choose>

<xsl:when test="contains($style,’text-anchor’)"></xsl:when> <xsl:when test="$theta2 >= 0 and $theta2 &lt; = 45">start</xsl:when> <xsl:when test="$theta2 > 45 and

$theta2 &lt; = 135">middle</xsl:when> <xsl:when test="$theta2 > 135 and $theta2 &lt; = 225">end</xsl:when> <xsl:when test="$theta2 > 225 and

$theta2 &lt; = 315">middle</xsl:when> <xsl:otherwise>start</xsl:otherwise> </xsl:choose> </xsl:variable>

<svg:text x="{$x}" y="{$y}"

style="text-anchor:{$anchor};{$style}" id="{$context}_pieSliceLabel_{$num}"> <xsl:value-of select="$label"/> </svg:text> </xsl:template>

<xsl:template name="svgu:pie">

<xsl:param name="data" select="/.."/> <!– Данные для диаграммы –> <xsl:param name="cx" select="10 0"/> <!– Абсцисса центра –> <xsl:param name="cy" select="10 0"/> <!– Ордината центра –> <xsl:param name="r" select="50"/> <!– Радиус –>

<xsl:param name="theta" select="-90"/> <!– Начальный угол первого

сектора в градусах –> <xsl:param name="context"/>   <!– Пользовательские данные для

идентификации этого вызова –>

<xsl:call-template name="svgu:pieImpl">

<xsl:with-param name="data" select="$data"/> <xsl:with-param name="cx" select="$cx"/> <xsl:with-param name="cy" select="$cy"/> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="theta" select="$theta"/> <xsl:with-param name="sum" select="sum($data)"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template>

</xsl:template>

<!– Рекурсивная реализация –> <xsl:template name="svgu:pieImpl"> <xsl:param name="data" /> <xsl:param name="cx" /> <xsl:param name="cy" /> <xsl:param name="r" /> <xsl:param name="theta"/> <xsl:param name="sum"/> <xsl:param name="context"/> <xsl:param name="i" select="1"/>

<xsl:if test="count($data) >= $i">

<xsl:variable name="delta" select="($data[$i] * 360) div $sum"/>

<!– Рисуем сектор — >

<xsl:call-template name="svgu:pieSlice">

<xsl:with-param name="cx" select="$cx"/> <xsl:with-param name="cy" select="$cy"/> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="theta" select="$theta"/> <xsl:with-param name="delta" select="$delta"/> <xsl:with-param name="style">

<xsl:call-template name="svgu:pieSliceStyle"> <xsl:with-param name="i" select="$i"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:with-param>

<xsl:with-param name="num" select="$i"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template>

<!– Рекурсивный вызов для рисования следующего сектора –> <xsl:call-template name="svgu:pieImpl">

<xsl:with-param name="data" select="$data"/> <xsl:with-param name="cx" select="$cx"/> <xsl:with-param name="cy" select="$cy"/> <xsl:with-param name="r" select="$r"/>

<xsl:with-param name="theta" select="$theta + $delta"/> <xsl:with-param name="sum" select="$sum"/> <xsl:with-param name="context" select="$context"/> <xsl:with-param name="i" select="$i + 1"/> </xsl:call-template> </xsl:if>

</xsl:template>

<!– Расставляем надписи для каждого сектора –> <xsl:template name="svgu:pieLabels">

<xsl:param name="data" select="/.."/> <!– Данные для секторов –> <xsl:param name="labels" select="$data"/> <!– Набор узлов,

соответствующих надписям на диаграмме. По умолчанию data –> <xsl:param name="cx" select="100"/> <!— Абсцисса центра —> <xsl:param name="cy" select="100"/> <!— Ордината центра –> <xsl:param name="r" select="50"/> <!— Радиус —> <xsl:param name="theta" select="-90"/> <!– Начальный угол первого

сектора в градусах — > <xsl:param name="context"/> <!– Пользовательские данные для

идентификации этого вызова –>

<xsl:call-template name="svgu:pieLabelsImpl">

<xsl:with-param name="data" select="$data"/> <xsl:with-param name="labels" select="$labels"/> <xsl:with-param name="cx" select="$cx"/> <xsl:with-param name="cy" select="$cy"/> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="theta" select="$theta"/> <xsl:with-param name="sum" select="sum($data)"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template>

</xsl:template>

<xsl:template name="svgu:pieLabelsImpl"> <xsl:param name="data" /> <xsl:param name="labels"/> <xsl:param name="cx" /> <xsl:param name="cy" /> <xsl:param name="r" /> <xsl:param name="theta"/> <xsl:param name="sum"/> <xsl:param name="context"/> <xsl:param name="i" select="1"/>

<xsl:if test="count($data) >= $i">

<xsl:variable name="delta" select="($data[$i] * 360) div $sum"/>

<!– Выводим надпись для сектора –> <xsl:call-template name="svgu:pieSliceLabel"> <xsl:with-param name="label" select="$labels[$i]"/> <xsl:with-param name="cx" select="$cx"/> <xsl:with-param name="cy" select="$cy"/> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="theta" select="$theta"/> <xsl:with-param name="delta" select="$delta div 2"/> <xsl:with-param name="style">

<xsl:call-template name="svgu:pieSliceLabelStyle"> <xsl:with-param name="i" select="$i"/> <xsl:with-param name="value" select="$data[$i]"/> <xsl:with-param name="label" select="$labels[$i]"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:with-param>

<xsl:with-param name="num" select="$i"/> <xsl:with-param name="context" select="$context"/>

</xsl:call-template>

<!— Рекурсивный вызов для вывода следующей надписи —> <xsl:call-template name="svgu:pieLabelsImpl"> <xsl:with-param name="data" select="$data"/> <xsl:with-param name="labels" select="$labels"/> <xsl:with-param name="cx" select="$cx"/> <xsl:with-param name="cy" select="$cy"/> <xsl:with-param name="r" select="$r"/>

<xsl:with-param name="theta" select="$theta + $delta"/> <xsl:with-param name="sum" select="$sum"/> <xsl:with-param name="context" select="$context"/> <xsl:with-param name="i" select="$i + 1"/> </xsl:call-template> </xsl:if>

</xsl:template>

<!– Переопределить для изменения стиля сектора –> <xsl:template name="svgu:pieSliceStyle"> <xsl:param name="i"/> <xsl:param name="context"/>

<xsl:variable name="colors" select="document(”)/*/svgu:color"/> <xsl:value-of select="concat(‘stroke:black;

stroke-width:0.5;

fill: ‘,$colors[($i – 1 ) mod

count($colors) + 1])"/>

</xsl:template>

<!– Переопределить для изменения стиля надписи –> <xsl:template name="svgu:pieSliceLabelStyle"> <xsl:param name="i"/> <xsl:param name="value"/> <xsl:param name="label" /> <xsl:param name="context"/> <xsl:text>font-size: 16;</xsl:text> </xsl:template>

Следующая таблица стилей создает секторную диаграмму, показанную на рис. 11.12:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg"

xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils"

Рис. 11.12. Сгенерированная секторная диаграмма

xmlns:test="http://www.ora.com/XSLTCookbook/ns/test" exclude-result-prefixes="svgu test">

<xsl:include href="svg-utils.xslt"/>

<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"/>

<test:data>1.0</test:data> <test:data>2.0</test:data> <test:data>3.0</test:data> <test:data>4.0</test:data> <test:data>5.0</test:data> <test:data>13.0</test:data>

<xsl:template match="/">

<svg:svg width="500" height="5 0 0"> <xsl:call-template name="svgu:pie">

<xsl:with-param name="data" select="document(”)/*/test:data"/> <xsl:with-param name="cx" select="25 0"/>

<xsl:with-param name="cy" select="25 0"/> <xsl:with-param name="r" select="10 0"/> <xsl:with-param name="theta" select="-90"/> </xsl:call-template>

<xsl:call-template name="svgu:pieLabels">

<xsl:with-param name="data" select="document(”)/*/test:data"/> <xsl:with-param name="cx" select="25 0"/> <xsl:with-param name="cy" select="25 0"/> <xsl:with-param name="r" select="125"/> <xsl:with-param name="theta" select="-90"/> </xsl:call-template>

</svg:svg>

</xsl:template>

Графики вида «начало-максимум-минимум-конец»

Такие графики (Open-Hi-Lo-Close) обычно используются для представления данных о торгах ценными бумагами, но применимы и в других случаях (например, что­бы показать минимум, максимум, среднее и медиану). В шаблон данные передаются в виде четырех разных наборов узлов – по одному для каждой последовательности значений. Обязательны только наборы максимальных и минимальных значений. Шаблон умеет обрабатывать последовательности с отсутствующими данными.

<xsl:template name="svgu:openHiLoClose"> <xsl:param name="openData" select="/.."/> <xsl:param name="hiData" select="/.."/> <xsl:param name="loData" select="/.."/> <xsl:param name="closeData" select="/.."/> <xsl:param name="width" select=" ‘500’ "/> <xsl:param name="height" select=" ‘500’ "/> <xsl:param name="offsetX" select="0"/> <xsl:param name="offsetY" select="0"/> <xsl:param name="openCloseExtent" select="8"/> <xsl:param name="max">

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

<xsl:with-param name="nodes" select="$hiData"/> </xsl:call-template> </xsl:param> <xsl:param name="min">

<xsl:call-template name="emath:min">

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

<xsl:param name="context"/>

<xsl:variable name="hiCount" select="count($hiData)"/> <xsl:variable name="loCount" select="count($loData)"/> <xsl:variable name="openCount" select="count($openData)"/> <xsl:variable name="closeCount" select="count($closeData)"/>

<xsl:variable name="numBars" select="Math:min($hiCount, $loCount)"/:

<xsl:variable name="spacing" select="$width div ($numBars + 1)"/>

<xsl:variable name="range" select="$max – $min"/> <xsl:variable name="scale" select="$height div $range"/>

<svg:g transform="translate({$offsetX},{$offsetY+$height})

scale(1,{-$scale}) translate(0,{-$min})">

<xsl:for-each select="$hiData">

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

<!– pисyем линию максимум-минимум –> <svg:line x1="{$spacing * $pos}" y1="{$loData[$pos]}" x2="{$spacing * $pos}"

y2="{current( )}"id="{$context}_highLow_{$pos}"> <xsl:attribute name="style">

<xsl:call-template name="svgu:hiLoBarStyle">

<xsl:with-param name="pos" select="$pos"/>

<xsl:with-param name="context" select="$context"/>

</xsl:call-template>

</xsl:attribute>

</svg:line>

<!– Рисуем метку для данных на момент отбытия, если таковые имеются — >

<xsl:if test="$openCount >= $pos">

<svg:line x1="{$spacing * $pos – $openCloseExtent}" y1="{$openData[$pos]}" x2="{$spacing * $pos}" y2="{$openData[$pos]}" id="{$context}_open_{$pos}"> <xsl:attribute name="style">

<xsl:call-template name="svgu:openCloseBarStyle">

<xsl:with-param name="pos" select="$pos"/> <xsl:with-param name="scale" select="$scale"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:attribute> </svg:line> </xsl:if>

<!– Рисуем метку для данных на момент открытия, если таковые имеются –>

<xsl:if test="$closeCount >= $pos"> <svg:line x1="{$spacing * $pos}" y1="{$closeData[$pos]}"

x2="{$spacing * $pos + $openCloseExtent}" y2="{$closeData[$pos]}" id="{$context}_close_{$pos}"> <xsl:attribute name="style">

<xsl:call-template name="svgu:openCloseBarStyle"> <xsl:with-param name="pos" select="$pos"/> <xsl:with-param name="scale" select="$scale"/> <xsl:with-param name="context" select="$context"/> </xsl:call-template> </xsl:attribute> </svg:line> </xsl:if>

</xsl:for-each> </svg:g>

</xsl:template>

<xsl:template name="svgu:hiLoBarStyle"> <xsl:param name="pos"/> <xsl:param name="context"/>

<xsl:text>stroke: black; stroke-width: 1 </xsl:text> </xsl:template>

<xsl:template name="svgu:openCloseBarStyle"> <xsl:param name="pos"/> <xsl:param name="scale"/> <xsl:param name="context"/>

<xsl:text>stroke: black; stroke-width: </xsl:text> <xsl:value-of select="2 div $scale"/> </xsl:template>

С помощью этих шаблонов можно вывести график, показанный на рис. 11.13:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg"

xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" exclude-result-prefixes="svgu">

<xsl:include href="svg-utils.xslt"/>

<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:template match="/"> <svg:svg width="600" height="4 0 0">

<xsl:call-template name="svgu:openHiLoClose">

<xsl:with-param name="openData" select="*/row/open"/> <xsl:with-param name="hiData" select="*/row/high"/> <xsl:with-param name="loData" select="*/row/low"/> <xsl:with-param name="closeData" select="*/row/close"/>

Рис. 11.13. График «начало-максимум-минимум-конец», сгенерированный с помощью XSLT

<xsl:with-param name="min" select="3 0"/> <xsl:with-param name="max" select="8 0"/> <xsl:with-param name="width" select="60 0"/> <xsl:with-param name="height" select="35 0"/> <xsl:with-param name="offsetX" select="2 0"/> <xsl:with-param name="offsetY" select="2 0"/> <xsl:with-param name="boundingBox" select="1"/> </xsl:call-template>

<xsl:call-template name="svgu:yAxis">

<xsl:with-param name="min" select="3 0"/> <xsl:with-param name="max" select="8 0"/> <xsl:with-param name="offsetX" select="2 0"/> <xsl:with-param name="offsetY" select="2 0"/> <xsl:with-param name="width" select="60 0"/> <xsl:with-param name="height" select="35 0"/> </xsl:call-template>

</svg:svg>

</xsl:template>

</xsl:stylesheet>

XSLT 2.0

Для переноса этих рецептов с версии 1.0 на 2.0 нужны минимальные измене­ния. Поскольку у шаблонов довольно много параметров, то имеет смысл описать типы параметров и переменных, это уменьшит число ошибок во время выполне­ния. Параметры, описывающие размеры, например длина и ширина, должны иметь тип xs:double, а относящиеся к числу делений и т.п. – тип xs:integer. У некоторых параметров должен быть тип xs:boolean, но с ними все понятно, потому что они по умолчанию принимают значение true( ) или false().

Обсуждение

Преобразования XML в SVG обычно не тривиальны. Для графического пред­ставления данных даже в таких относительно простых случаях, как выше, требует­ся тщательное планирование. Начинать каждое преобразование с чистого листа было бы глупо – необходим комплект повторно используемых утилит. Я уделил внимание разработке таких утилит для построения диаграмм, а вы можете заняться инструментарием для других областей применения. Методика проектирования предполагает разбиение задачи на две части: создание готовых компонентов и шаб­лонов, в которых эти компоненты объединяются разными способами. Главное, что­бы каждый шаблон получал достаточно информации для преобразования системы координат так, чтобы она была совместима с графикой, подготовленной независи­мыми компонентами. Например, у большинства представленных шаблонов есть параметры $min и $max, хотя разумные значения можно было бы вычислить по входным данным. Но наличие таких параметров позволяет вызывающей программе переопределить умолчания и рассматривать диапазон отображаемых на графике данных как единое целое.

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

<xsl:template name="svgu:pieSliceStyle"> <xsl:param name="i"/> <xsl:param name="context"/>

<xsl:variable name="colors" select="document(”)/*/svgu:color"/> <xsl:value-of select="concat(‘stroke:black;

stroke-width:0.5;

fill: ‘,$colors[($i – 1 ) mod

count($colors) + 1])"/>

</xsl:template>

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

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

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg"

xmlns:svgu="http://www.ora.com/XSLTCookbook/ns/svg-utils" xmlns:emath="http://www.exslt.org/math"

Рис. 11.14. Сложная комбинация графиков

exclude-result-prefixes="svgu"> <xsl:include href="svg-utils.xslt"/>

<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="width" select="600"/> <xsl:variable name="height" select="5 0 0"/> <xsl:variable name="pwidth" select="$width * 0.8"/> <xsl:variable name="pheight" select="$height * 0.8"/> <xsl:variable name="offsetX" select="($width – $pwidth) div 2"/> <xsl:variable name="offsetY" select="10"/>

<xsl:variable name="dataMin">

<xsl:call-template name="emath:min">

<xsl:with-param name="nodes" select="//Low"/> </xsl:call-template>

</xsl:variable>

<xsl:variable name="dataMax">

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

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

<xsl:variable name="min" select="$dataMin * 0.9"/> <xsl:variable name="max" select="$dataMax * 1.1"/>

<xsl:template match="/">

<svg:svg width="{$width}" height="{$height}">

<svg:text x="{$width div 2}" y="{2 * $offsetY}"

style="text-anchor:middle; font-size:24">MSFT Stock Chart</svg:text> <svg:text x="{$width div 2}" y="{4 * $offsetY}"

style="text-anchor:middle; font-size:12">05/23/2002 to 08/16/2002 </svg:text> <!– ЦЕНА —>

<xsl:call-template name="svgu:openHiLoClose">

<xsl:with-param name="openData" select="*/row/Open"/> <xsl:with-param name="hiData" select="*/row/High"/> <xsl:with-param name="loData" select="*/row/Low"/> <xsl:with-param name="closeData" select="*/row/Close"/> <xsl:with-param name="min" select="$min"/> <xsl:with-param name="max" select="$max"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="boundingBox" select="1"/> </xsl:call-template>

<xsl:call-template name="svgu:yAxis">

<xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$pheight"/> <xsl:with-param name="min" select="$min"/> <xsl:with-param name="max" select="$max"/> <xsl:with-param name="context" select=" ‘price’ "/> </xsl:call-template>

<!– ОБЪЕМ –>

<xsl:variable name="vheight" select="100"/>

<xsl:call-template name="svgu:bars">

<xsl:with-param name="data" select="*/row/Volume"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$vheight"/> <xsl:with-param name="orientation" select="0"/> <xsl:with-param name="offsetX" select="$offsetX"/> <xsl:with-param name="offsetY" select="$pheight – $offsetY"/> <xsl:with-param name="barLabel" select="false( )"/> <xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="1500000"/> </xsl:call-template>

<!– Чтобы линейный график начинался в точке расположения первого –> <!– столбца и заканчивался там, где расположен последний –> <xsl:variable name="spacing" select="$pwidth div count(*/row/High) + 1"/>

<xsl:call-template name="svgu:xyPlot">

<xsl:with-param name="dataY" select="*/row/Vol10MA"/> <xsl:with-param name="width" select="$pwidth – 2 * $spacing"/> <xsl:with-param name="height" select="$vheight"/> <xsl:with-param name="offsetX" select="$offsetX + $spacing"/> <xsl:with-param name="offsetY" select="$pheight – $offsetY"/> <xsl:with-param name="minY" select="0"/> <xsl:with-param name="maxY" select="15 0 0 0 0 0"/> </xsl:call-template>

<xsl:call-template name="svgu:yAxis">

<xsl:with-param name="offsetX" select="$width – $offsetX"/> <xsl:with-param name="offsetY" select="$height – $vheight – $offsetY"/> <xsl:with-param name="width" select="$pwidth"/> <xsl:with-param name="height" select="$vheight"/> <xsl:with-param name="min" select="0"/> <xsl:with-param name="max" select="15 0 0 0 0 0"/> <xsl:with-param name="context" select=" ‘volume’ "/> </xsl:call-template>

</svg:svg>

</xsl:template>

<xsl:template name="svgu:barStyle">

<xsl:text>stroke: black; stroke-wdth: 0.15</xsl:text>

</xsl:template>

<xsl:template name="svgu:xyPlotStyle"> <xsl:param name="context"/> <xsl:param name="scale"/>

<xsl:value-of select="concat(‘fill: none; stroke: black; stroke-width:’,4 div $scale,'; ‘)"/> </xsl:template>

<xsl:template name="yAxisLabelStyle"> <xsl:param name="context"/> <xsl:choose>

<xsl:when test="$context = ‘price’">

<xsl:text>text-anchor:end;font-size:8;baseline-shift:-5 0%</xsl:text> </xsl:when> <xsl:otherwise>

<xsl:text>text-anchor:start;font-size:8;baseline-shift:-50%</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:template>

<!– Отодвинуть подписи на шкале объемов от делений –> <xsl:template name="yAxisLabelXOffset"> <xsl:param name="context"/> <xsl:if test="$context = ‘volume’">

<xsl:value-of select="6"/> </xsl:if> </xsl:template>

</xsl:stylesheet>

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

По теме:

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