Transform generic Measures into SVG Line Graph
The stylesheet buildSVGLineChart.xsl will take a source document in the proper format – with a graphData root element, sets- and set-child elements and finally measure-elements that contain the actual graphdata – and transform it to a SVG object: a proper line chart. Here I will show a few sections from this stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xlink="http://www.w3.org/2000/xlink/namespace/" version="1.0"> <xsl:output method="image/svg+xml" omit-xml-declaration="yes"/> <xsl:template match="/graphData"> <xsl:variable name="max"> <xsl:value-of select="maxy"/> </xsl:variable> <xsl:variable name="min"> <xsl:value-of select="miny"/> </xsl:variable> <xsl:variable name="maxx"> <xsl:value-of select="maxx"/> </xsl:variable> <xsl:variable name="minx"> <xsl:value-of select="minx"/> </xsl:variable> <svg width="1200" height="1200"> <defs> <!-- definitions of different types of markers --> <g id="star" transform="scale(0.21)"> <polyline points="48,16,16,96,96,48,0,48,80,96"> </polyline> </g> <g id="triangle" transform="scale(0.7)"> <path id="Triangle" d="M 0 25 L 10 15 L 20 25 z" style="stroke:none"/> </g> <g id="square" transform="scale(1)"> <rect width="18" height="18"> </rect> </g> <g id="rectangle" transform="scale(1)"> <rect width="7" height="22"> </rect> </g> </defs> <!-- create some room around the graph (border of 150 wide to the left and 50 high on top) --> <g transform="translate(150,50) scale(0.5)"> <!--Heading--> <text x="5" y="-40" text-anchor="left" font-weight="bolder" font-size="40" fill="maroon" text-decoration="underline"> <xsl:value-of select="title"/> </text> <!--Caption (Vertical)--> <g transform="translate(-220, 80) rotate(270, 0, 0)"> <text x="0" y="0" text-anchor="middle" font-weight="bolder" font-size="36" fill="black"> <xsl:value-of select="ytitle"/> </text> </g> <!--Caption (Horizontal)--> <text x="1070" y="1000" font-size="36" font-weight="bolder" fill="black"> <xsl:value-of select="xtitle"/> </text> <!-- Now Draw the main X and Y axis --> <g style="stroke-width:5; stroke:black"> <!-- X Axis --> <path d="M 0 1000 L 1000 1000 Z"/> <!-- Y Axis --> <path d="M 0 0 L 0 1000 Z"/> </g> <xsl:for-each select="sets/set"> <!-- match the /graphData/sets/set elements in the source document --> ... (left out: writing the markers and labels on the x-axis and y-axis ) <!-- go and draw the line of the chart itself --> <g> <xsl:attribute name="style"> stroke:<xsl:value-of select="@color"/>;stroke-width: 3; fill : none; </xsl:attribute> <!-- draw a line from the previous to each new point --> <xsl:for-each select="measure"> <xsl:variable name="x"> <xsl:value-of select=" 1000* ((xvalue - ($minx)) div ($maxx - $minx))"/> </xsl:variable> <xsl:variable name="y"> <xsl:value-of select="1000 - 1000* ((yvalue - ($min)) div ($max - $min))"/> </xsl:variable> <xsl:if test="not(../@showline='false')"> <xsl:if test="(position() > 1)"> <line> <xsl:attribute name="x1"> <xsl:value-of select=" 1000* ((preceding-sibling::measure[position()=1]/xvalue - ($minx)) div ($maxx - $minx))"/> </xsl:attribute> <xsl:attribute name="y1"> <xsl:value-of select="1000 - 1000* ((preceding-sibling::measure[position()=1]/yvalue - ($min)) div ($max - $min))"/> </xsl:attribute> <xsl:attribute name="x2"> <xsl:value-of select="$x"/> </xsl:attribute> <xsl:attribute name="y2"> <xsl:value-of select="$y"/> </xsl:attribute> </line> </xsl:if> </xsl:if> <xsl:if test="xgrid = 'true'"> <xsl:call-template name="gridline"> <xsl:with-param name="x1" select="$x"/> <xsl:with-param name="y1" select="$y"/> <xsl:with-param name="type">vertical</xsl:with-param> </xsl:call-template> </xsl:if> <xsl:if test="ygrid = 'true'"> <xsl:call-template name="gridline"> <xsl:with-param name="x1" select="$x"/> <xsl:with-param name="y1" select="$y"/> <xsl:with-param name="type">horizontal</xsl:with-param> <xsl:with-param name="yaxis"> <xsl:choose> <xsl:when test="yvalue">1</xsl:when> <xsl:otherwise>2</xsl:otherwise> </xsl:choose> </xsl:with-param> </xsl:call-template> </xsl:if> </xsl:for-each> <!-- measure --> </g> </xsl:for-each> <!-- sets --> <!-- now again traverse all measures to place markers by doing this in a second go, we ensure (according to the 'painters algoritm' (see: http://wiki.svg.org/index.php/ChangingDrawingOrder)) that the annotations and markers are on top of everything else. --> <xsl:for-each select="sets/set"> <xsl:for-each select="measure"> <xsl:variable name="x"> <xsl:value-of select=" 1000* ((xvalue - ($minx)) div ($maxx - $minx))"/> </xsl:variable> <xsl:variable name="y"> <xsl:value-of select="1000 - 1000* ((yvalue - ($min)) div ($max - $min))"/> </xsl:variable> <g > <xsl:attribute name="style">stroke:<xsl:value-of select="../@color"/> stroke-width: 3; fill : none;</xsl:attribute> <!-- draw a marker --> <xsl:call-template name="marker"> <xsl:with-param name="x" select="$x"/> <xsl:with-param name="y" select="$y"/> <xsl:with-param name="marker" select="../@marker-type"/> <xsl:with-param name="color" select="../@color"/> </xsl:call-template> </g> </xsl:for-each> <!-- measures in set --> </xsl:for-each> <!-- sets --> ... (left out: creation of the legend-box) </g> </svg> </xsl:template> ... (left out: templates for labels and markers on the x-axis and y-axis) <xsl:template name="marker"> <xsl:param name="x"/> <xsl:param name="y"/> <xsl:param name="marker">circle</xsl:param> <xsl:param name="color">red</xsl:param> <g transform="scale(1)"> <xsl:attribute name="style"> stroke:<xsl:value-of select="$color"/>;fill:<xsl:value-of select="$color"/> </xsl:attribute> <xsl:choose> <xsl:when test="$marker='square'"> <use xlink:href="#square"> <xsl:attribute name="x"> <xsl:value-of select="$x -9"/> </xsl:attribute> <xsl:attribute name="y"> <xsl:value-of select="$y -9"/> </xsl:attribute> </use> </xsl:when> <xsl:when test="$marker='triangle'"> <use xlink:href="#triangle"> <xsl:attribute name="x"> <xsl:value-of select="$x -9"/> </xsl:attribute> <xsl:attribute name="y"> <xsl:value-of select="$y -9"/> </xsl:attribute> </use> </xsl:when> <xsl:when test="$marker='rectangle'"> <use xlink:href="#rectangle"> <xsl:attribute name="x"> <xsl:value-of select="$x -4"/> </xsl:attribute> <xsl:attribute name="y"> <xsl:value-of select="$y -4"/> </xsl:attribute> </use> </xsl:when> <xsl:when test="$marker='star'"> <use xlink:href="#star"> <xsl:attribute name="x"> <xsl:value-of select="$x -9"/> </xsl:attribute> <xsl:attribute name="y"> <xsl:value-of select="$y -9"/> </xsl:attribute> </use> </xsl:when> <xsl:when test="$marker='diamond'"> <!-- diamond is just a square rotated about its own center for 45 degrees --> <use xlink:href="#square"> <xsl:attribute name="x"> <xsl:value-of select="$x -9"/> </xsl:attribute> <xsl:attribute name="y"> <xsl:value-of select="$y -9"/> </xsl:attribute> <xsl:attribute name="transform"> rotate(45,<xsl:value-of select="$x"/>,<xsl:value-of select="$y "/>) </xsl:attribute> </use> </xsl:when> <xsl:when test="$marker='circle'"> <circle r="9"> <xsl:attribute name="cx"> <xsl:value-of select="$x"/> </xsl:attribute> <xsl:attribute name="cy"> <xsl:value-of select="$y"/> </xsl:attribute> </circle> </xsl:when> <xsl:when test="$marker='smallcircle'"> <circle r="4"> <xsl:attribute name="cx"> <xsl:value-of select="$x"/> </xsl:attribute> <xsl:attribute name="cy"> <xsl:value-of select="$y"/> </xsl:attribute> </circle> </xsl:when> <xsl:otherwise> </xsl:otherwise> </xsl:choose> </g> </xsl:template> <xsl:template name="gridline"> <xsl:param name="x1"/> <xsl:param name="y1"/> <xsl:param name="type"/> <xsl:param name="yaxis">1</xsl:param> <line style="fill:none; stroke:#B0B0B0; stroke-width:2; stroke-dasharray:2 4"> <xsl:attribute name="x1"> <xsl:value-of select="$x1"/> </xsl:attribute> <xsl:choose> <xsl:when test="$type='horizontal'"> <xsl:attribute name="x2"><xsl:value-of select="($yaxis -1)* 1000 "/></xsl:attribute> <xsl:attribute name="y2"> <xsl:value-of select="$y1"/> </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="y2">1000</xsl:attribute> <xsl:attribute name="x2"> <xsl:value-of select="$x1"/> </xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:attribute name="y1"> <xsl:value-of select="$y1"/> </xsl:attribute> </line> </xsl:template> <!-- end gridline --> </xsl:stylesheet>
The result of the transformation with this stylesheet to the SVG object can be downloaded here:EmpSalLineGraph.svg. The graph that is displayed as a result from these transformations:
Please take a look at this chart
http://www.treebuilder.de/xslt1/examples/example14/example14.xml
Would be OK with you if I leveraged your gauge examples in a real application?
Linegraph, displaying the number of posts per week as well as a running count: Line Graph with Post Overview
Another example of using the techniques described in this article can be found in the Post Report that displays a graph illustrating the number of reads of articles (or posts) on our Blog. See the Post report for this Post.
Great graphs, even with marker labels… A worthy add-on for the Blog! (I still have to read this post though).
I added a link to this JSP in the Blog menu under Site.
For an example of applying the approach discussed in this article, you may want to take a look at http://technology.amis.nl/statistics/BlogStats.jsp, a live feed of the weblog-statistics for the AMIS Technology Weblog. It will demonstrate how – in SVG enabled browsers – a line graph is built displaying the number of hits in the weblog for today (per hour) as well as the total number since the start of our weblog in July 2004.