-- 作者:leomay
-- 发布时间:6/17/2005 9:13:00 AM
-- (转贴)csdn上高手给出的svg例子(画饼图),求职应聘,必备良药
利用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图象如下图所示。 此主题相关图片如下:
|