利用XSLT生成SVG格式的饼图
孟宪会
2002-3-27 13:34:47
--------------------------------------------------------------------------------
上次介绍了如何利用XSLT来生成SVG格式的普通柱形图,这次我们介绍如何生成饼图。饼图也是数据统计时常用的表现方式之一。
首先,我们准备所需要的数据,格式是XML的。由于目前许多大型数据库都提供了对XML查询结果的支持,因此,您可以直接利用数据库来生成XML数据文件,也可以经过查询,再用编写组件的方法来生成需要的XML数据文件。我们的XML数据文件Data.xml格式如下:
<?xml version="1.0" encoding="UTF-16"?>
<?xml-stylesheet type="text/xsl" href="PieSVGGraph.xsl"?>
<总销售额>
<月销售额 月数="1">
<产品销售额 产品编号="Mxh001" 销售数量="5" 销售额="1500.00"/>
<产品销售额 产品编号="Mxh002" 销售数量="10" 销售额="500.00"/>
<产品销售额 产品编号="Mxh003" 销售数量="1" 销售额="700.00"/>
<产品销售额 产品编号="Mxh004" 销售数量="2" 销售额="1800.00"/>
<产品销售额 产品编号="Mxh005" 销售数量="2" 销售额="1000.00"/>
<产品销售额 产品编号="Mxh006" 销售数量="12" 销售额="1400.00"/>
</月销售额>
<月销售额 月数="2">
<产品销售额 产品编号="Mxh001" 销售数量="4" 销售额="1400.00"/>
<产品销售额 产品编号="Mxh002" 销售数量="12" 销售额="2300.00"/>
<产品销售额 产品编号="Mxh003" 销售数量="2" 销售额="1500.00"/>
<产品销售额 产品编号="Mxh004" 销售数量="2" 销售额="1800.00"/>
<产品销售额 产品编号="Mxh005" 销售数量="1" 销售额="1050.00"/>
<产品销售额 产品编号="Mxh006" 销售数量="8" 销售额="1600.00"/>
</月销售额>
<!-- 限于篇幅所限,我们这里省略了3月份到12月份的数据,如果浏览时,请自行添加上,格式同前 -->
</总销售额>
下面,我们看看最关键的XSLT部分,由于在这里用到了XSLT,为了能够更好地看到本例的效果,请安装最新的 MsXML3.0+SP2或MsXML4.0,可以到微软的网站下载:http://www.microsoft.com/xml/。文中每一代码段都进行了注释说明。PieSVGGraph.xsl:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="1.0" exclude-result-prefixes="xsl Sin Cos"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:Sin="http://lucky.myrice.com/Sin"
xmlns:Cos="http://lucky.myrice.com/Cos">
<xsl:output method="xml" indent="yes" media-type="image/svg+xml"/>
<!-- 先定义三角函数计算模板 -->
<!--
==========================================================================
计算sin(x)、cos(x)值的模板,用来返回三角函数sin(x)、cos(x)的值
参数说明:
$X - 输入的角度值,单位是弧度或角度,由下面的Unit参数确定。
$Unit - [可选参数] 角度 $X 的单位,可选择的值如下:
'角度' 代表$X的单位是角度;
'弧度' 代表$X的单位是弧度(默认值)。
$EPS - [可选参数] 计算结果的精度。增加小数位数可以更大的精确度,但会降低性能。
========================================================================== -->
<xsl:template name="sin">
<xsl:param name="X"/>
<xsl:param name="Unit" select="'弧度'"/>
<xsl:param name="EPS" select="0.00000001"/>
<xsl:call-template name="FUNC_Cal">
<xsl:with-param name="Fun" select="$DOC/Sin:*[1]"/>
<xsl:with-param name="X" select="$X"/>
<xsl:with-param name="Unit" select="$Unit"/>
<xsl:with-param name="EPS" select="$EPS"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="cos">
<xsl:param name="X"/>
<xsl:param name="Unit" select="'弧度'"/>
<xsl:param name="EPS" select="0.00000001"/>
<xsl:call-template name="FUNC_Cal">
<xsl:with-param name="Fun" select="$DOC/Cos:*[1]"/>
<xsl:with-param name="X" select="$X"/>
<xsl:with-param name="Unit" select="$Unit"/>
<xsl:with-param name="EPS" select="$EPS"/>
</xsl:call-template>
</xsl:template>
<!-- 定义常量pi,代替π -->
<xsl:variable name="pi" select="3.1415926535897932384626433832795"/>
<!-- 下面我们利用sin(x)和cos(x)的展开式来近似计算他们的值。 -->
<!-- 采用循环的办法是计算,循环结束的条件是前后两次的结果差值小于我们给定的精度。 -->
<Sin:Sin/>
<Cos:Cos/>
<xsl:variable name="DOC" select="document('')/*"/>
<xsl:template name="FUNC_Cal">
<xsl:param name="Fun" select="/.."/>
<xsl:param name="X"/>
<xsl:param name="Unit" select="'弧度'"/>
<xsl:param name="EPS" select="0.00000001"/>
<!-- 把角度转换成弧度 -->
<xsl:variable name="Rads" select="(($Unit = '弧度') * $X) + ((not($Unit = '弧度')) * ($X * $pi div 180))"/>
<!-- 应用相应的函数 -->
<xsl:apply-templates select="$Fun">
<xsl:with-param name="X" select="$Rads"/>
<xsl:with-param name="EPS" select="$EPS"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template name="Func_sin" match="Sin:*">
<xsl:param name="X"/>
<xsl:param name="EPS" select="0.00000001"/>
<xsl:variable name="Y">
<xsl:choose>
<xsl:when test="not(0 <= $X and $pi*2 > $X)">
<xsl:call-template name="COSIN">
<xsl:with-param name="LEN" select="$pi*2"/>
<xsl:with-param name="X" select="$X"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$X"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="SINE">
<xsl:with-param name="X2" select="$Y*$Y"/>
<xsl:with-param name="Result" select="$Y"/>
<xsl:with-param name="Element" select="$Y"/>
<xsl:with-param name="N" select="1"/>
<xsl:with-param name="EPS" select="$EPS"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="SINE">
<xsl:param name="X2"/>
<xsl:param name="Result"/>
<xsl:param name="Element"/>
<xsl:param name="N"/>
<xsl:param name="EPS"/>
<xsl:variable name="varN" select="$N+2"/>
<xsl:variable name="varNewElement" select="-$Element*$X2 div ($varN*($varN - 1))"/>
<xsl:variable name="varNewResult" select="$Result + $varNewElement"/>
<xsl:variable name="varDiffResult" select="$varNewResult - $Result"/>
<xsl:choose>
<xsl:when test="$varDiffResult > $EPS or $varDiffResult < -$EPS">
<xsl:call-template name="SINE">
<xsl:with-param name="X2" select="$X2"/>
<xsl:with-param name="Result" select="$varNewResult"/>
<xsl:with-param name="Element" select="$varNewElement"/>
<xsl:with-param name="N" select="$varN"/>
<xsl:with-param name="EPS" select="$EPS"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$varNewResult"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="Func_cos" match="Cos:*">
<xsl:param name="X"/>
<xsl:param name="EPS" select="0.00000001"/>
<xsl:call-template name="Func_sin">
<xsl:with-param name="X" select="$pi div 2 - $X"/>
<xsl:with-param name="EPS" select="$EPS"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="COSIN">
<xsl:param name="LEN"/>
<xsl:param name="X"/>
<xsl:variable name="SignX">
<xsl:choose>
<xsl:when test="$X >= 0">1</xsl:when>
<xsl:otherwise>-1</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="varDiff" select="$LEN*floor($X div $LEN) -$X"/>
<xsl:choose>
<xsl:when test="$varDiff*$X > 0">
<xsl:value-of select="$SignX*$varDiff"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="-$SignX*$varDiff"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- 定义饼图的大小 -->
<xsl:param name="pie_RX" select="number(200)"/>
<xsl:param name="pie_RY" select="$pie_RX"/>
<xsl:param name="pie_explode_RX" select="number(20)"/>
<xsl:param name="pie_explode_RY" select="$pie_explode_RX"/>
<!-- 确定饼图的中心坐标点 -->
<xsl:param name="pie_CX" select="$pie_RX + 150"/>
<xsl:param name="pie_CY" select="$pie_RY + 50"/>
<xsl:param name="pie_segment_explode" select="number(0)"/>
<!-- 确定饼图中第一块圆弧的角度 -->
<xsl:param name="pie_start_angle" select="number(0)"/>
<!-- 定义最小显示的百分比数 -->
<xsl:param name="Lowest_Must_Show" select="number(4)"/>
<xsl:key name="kProdSales" match="产品销售额" use="@产品编号"/>
<!-- 样式单模板开始定义 -->
<xsl:template match="/">
<!-- 得到所有产品的列表 -->
<xsl:variable name="ProdSales" select="总销售额/月销售额/产品销售额[generate-id() = generate-id(key('kProdSales',@产品编号))]"/>
<!-- 得到所有产品的销售总值 -->
<xsl:variable name="TotalSalesvalue" select="sum(总销售额/月销售额/产品销售额/@销售额)"/>
<!-- SVG 图象开始 -->
<svg id="body" viewBox="0 0 -100 {$pie_CY + $pie_RY + 75}">
<!-- 递归遍历所有产品 -->
<xsl:call-template name="draw_segments">
<xsl:with-param name="Every_Prod_Sales" select="$ProdSales"/>
<xsl:with-param name="total_sales" select="$TotalSalesvalue"/>
</xsl:call-template>
<!-- 饼图的标题 -->
<text text-anchor="middle" stroke="#000099" font-size-adjust="+1" x="{$pie_CX - $pie_RX}" y="{$pie_CY + $pie_RY + 50}" dx="{2 * $pie_RX}">
X 公司个产品销售情况
</text>
</svg>
</xsl:template>
<!-- 递归模板,画出每一部分 -->
<xsl:template name="draw_segments">
<xsl:param name="Every_Prod_Sales"/>
<xsl:param name="total_sales"/>
<!-- 递归调用时的参数 -->
<xsl:param name="total_perc_so_far" select="number(0)"/>
<xsl:param name="node_Num" select="number(1)"/>
<!-- 产品节点数 -->
<xsl:param name="drawn_Num" select="number(1)"/>
<!-- 实际画出的数 -->
<xsl:param name="Other_Perc" select="number(0)"/>
<xsl:choose>
<xsl:when test="$Every_Prod_Sales[$node_Num]">
<!-- 计算一种产品的总销售额 -->
<xsl:variable name="this_prod_sales" select="sum(key('kProdSales',$Every_Prod_Sales[$node_Num]/@产品编号)/@销售额)"/>
<!-- 计算该产品在总销售额中的百分比 -->
<xsl:variable name="perc_of_total" select="$this_prod_sales div $total_sales"/>
<!-- 实际画出的产品数:如果百分比小于设定的最小百分比,就忽略掉,放到其它一项中 -->
<xsl:choose>
<xsl:when test="$perc_of_total < ($Lowest_Must_Show div 100)">
<xsl:call-template name="draw_segments">
<xsl:with-param name="Every_Prod_Sales" select="$Every_Prod_Sales"/>
<xsl:with-param name="total_sales" select="$total_sales"/>
<xsl:with-param name="total_perc_so_far" select="$total_perc_so_far"/>
<xsl:with-param name="node_Num" select="$node_Num + 1"/>
<xsl:with-param name="drawn_Num" select="$drawn_Num"/>
<xsl:with-param name="Other_Perc" select="$Other_Perc + $perc_of_total"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="draw_pie_part">
<xsl:with-param name="angle" select="360 * $perc_of_total"/>
<xsl:with-param name="prev_angle" select="$pie_start_angle + (360 * $total_perc_so_far)"/>
<xsl:with-param name="label" select="concat($Every_Prod_Sales[$node_Num]/@产品编号,' (',format-number($perc_of_total * 100,'0.0'),'%)')"/>
<xsl:with-param name="explode" select="($pie_segment_explode = $drawn_Num) or ($pie_segment_explode = -1)"/>
<xsl:with-param name="fill_color">
<xsl:call-template name="Pie_Color">
<xsl:with-param name="Rel_Pos" select="$drawn_Num"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="RX" select="$pie_RX"/>
<xsl:with-param name="radiusY" select="$pie_RY"/>
<xsl:with-param name="centerX" select="$pie_CX"/>
<xsl:with-param name="centerY" select="$pie_CY"/>
<xsl:with-param name="explode_RX" select="$pie_explode_RX"/>
<xsl:with-param name="explode_radiusY" select="$pie_explode_RY"/>
</xsl:call-template>
<!-- 统计下一个节点 -->
<xsl:call-template name="draw_segments">
<xsl:with-param name="Every_Prod_Sales" select="$Every_Prod_Sales"/>
<xsl:with-param name="total_sales" select="$total_sales"/>
<xsl:with-param name="total_perc_so_far" select="$total_perc_so_far + $perc_of_total"/>
<xsl:with-param name="node_Num" select="$node_Num + 1"/>
<xsl:with-param name="drawn_Num" select="$drawn_Num + 1"/>
<xsl:with-param name="Other_Perc" select="$Other_Perc"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$Other_Perc > 0">
<xsl:call-template name="draw_pie_part">
<xsl:with-param name="angle" select="360 * $Other_Perc"/>
<xsl:with-param name="prev_angle" select="$pie_start_angle + (360 * $total_perc_so_far)"/>
<xsl:with-param name="label" select="concat('其它 (',format-number($Other_Perc * 100,'0.0'),'%)')"/>
<xsl:with-param name="explode" select="($pie_segment_explode < 0)"/>
<xsl:with-param name="fill_color">
<xsl:call-template name="Pie_Color">
<xsl:with-param name="Rel_Pos" select="$drawn_Num"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="RX" select="$pie_RX"/>
<xsl:with-param name="radiusY" select="$pie_RY"/>
<xsl:with-param name="centerX" select="$pie_CX"/>
<xsl:with-param name="centerY" select="$pie_CY"/>
<xsl:with-param name="explode_RX" select="$pie_explode_RX"/>
<xsl:with-param name="explode_radiusY" select="$pie_explode_RY"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
<!-- 画出每一块单独的弧 -->
<xsl:template name="draw_pie_part">
<xsl:param name="angle"/>
<xsl:param name="prev_angle"/>
<xsl:param name="label"/>
<xsl:param name="explode" select="false()"/>
<xsl:param name="fill_color" select="'#000099'"/>
<xsl:param name="line_color" select="'#000000'"/>
<xsl:param name="RX" select="number(200)"/>
<xsl:param name="radiusY" select="number(200)"/>
<xsl:param name="centerX" select="$RX + 50"/>
<xsl:param name="centerY" select="$radiusY + 50"/>
<xsl:param name="explode_RX" select="number(20)"/>
<xsl:param name="explode_radiusY" select="number(20)"/>
<!-- 计算一种产品占的角度 -->
<xsl:variable name="full_angle" select="$prev_angle + $angle"/>
<!-- 计算图例文字所在的角度位置 -->
<xsl:variable name="half_angle" select="$prev_angle + ($angle div 2)"/>
<xsl:variable name="X0">
<xsl:choose>
<xsl:when test="$explode">
<xsl:variable name="sinX">
<xsl:call-template name="sin">
<xsl:with-param name="X" select="$half_angle"/>
<xsl:with-param name="Unit" select="'角度'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$centerX + ($sinX * $explode_RX)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$centerX"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="Y0">
<xsl:choose>
<xsl:when test="$explode">
<xsl:variable name="cosX">
<xsl:call-template name="cos">
<xsl:with-param name="X" select="$half_angle"/>
<xsl:with-param name="Unit" select="'角度'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$centerY + (0 - ($cosX * $explode_radiusY))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$centerY"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- 计算坐标点 -->
<xsl:variable name="X1">
<xsl:variable name="sinX">
<xsl:call-template name="sin">
<xsl:with-param name="X" select="$prev_angle"/>
<xsl:with-param name="Unit" select="'角度'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="($sinX * $RX)"/>
</xsl:variable>
<xsl:variable name="Y1">
<xsl:variable name="cosX">
<xsl:call-template name="cos">
<xsl:with-param name="X" select="$prev_angle"/>
<xsl:with-param name="Unit" select="'角度'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="0 - ($cosX * $radiusY)"/>
</xsl:variable>
<xsl:variable name="X2">
<xsl:variable name="sinX">
<xsl:call-template name="sin">
<xsl:with-param name="X" select="$full_angle"/>
<xsl:with-param name="Unit" select="'角度'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="($sinX * $RX) - $X1"/>
</xsl:variable>
<xsl:variable name="Y2">
<xsl:variable name="cosX">
<xsl:call-template name="cos">
<xsl:with-param name="X" select="$full_angle"/>
<xsl:with-param name="Unit" select="'角度'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="(0 - ($cosX * $radiusY)) - $Y1"/>
</xsl:variable>
<!-- 计算图例出现的象限位置,决定图例文字出现的位置 -->
<xsl:variable name="label_quandrant" select="floor(($half_angle mod 360) div 90)"/>
<xsl:variable name="textXalign">
<xsl:choose>
<xsl:when test="$label_quandrant < 2">
<xsl:text>start</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>end</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="textDY">
<xsl:choose>
<xsl:when test="($label_quandrant = 0) or ($label_quandrant = 3)">
<xsl:text>-10</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>15</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- 计算图例文字的x、y坐标 -->
<xsl:variable name="XTxt">
<xsl:variable name="sinX">
<xsl:call-template name="sin">
<xsl:with-param name="X" select="$half_angle"/>
<xsl:with-param name="Unit" select="'角度'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$X0 + ($sinX * ($RX + 10))"/>
</xsl:variable>
<xsl:variable name="YTxt">
<xsl:variable name="cosX">
<xsl:call-template name="cos">
<xsl:with-param name="X" select="$half_angle"/>
<xsl:with-param name="Unit" select="'角度'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$Y0 + (0 - ($cosX * $radiusY))"/>
</xsl:variable>
<!-- 画出每个产品所占的部分 -->
<g>
<!-- 画出图 -->
<path fill="{$fill_color}" stroke="{$line_color}" d="M{$X0},{$Y0} l{$X1},{$Y1} a{$RX},{$radiusY} 0 0 1 {$X2},{$Y2} L{$X0},{$Y0}"/>
<!-- 画出旁边的图例文字 -->
<text text-anchor="{$textXalign}" x="{$XTxt}" y="{$YTxt}" dy="{$textDY}">
<xsl:value-of select="$label"/>
</text>
</g>
</xsl:template>
<!-- 计算饼图每部分的颜色 -->
<xsl:template name="Pie_Color">
<xsl:param name="Rel_Pos" select="number(0)"/>
<xsl:variable name="ColorLight" select="'F0E0A09070503010E0C0A08060402000'"/>
<xsl:text>#</xsl:text>
<xsl:choose>
<xsl:when test="($Rel_Pos mod 3) = 0">EE</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($ColorLight,(($Rel_Pos mod 16) * 2)+1,2)"/>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="($Rel_Pos mod 3) = 1">EE</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($ColorLight,(($Rel_Pos mod 16) * 2)+1,2)"/>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="($Rel_Pos mod 3) = 2">EE</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($ColorLight,(($Rel_Pos mod 16) * 2)+1,2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
我们最后生成的SVG图象如下图所示。
此主题相关图片如下: