Generating SVG Graphics in JSPs using JSTL & XSL(T) – from MySQL to Bar Chart and Pie Chart

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:
Simple Line-chart for Employee Salaries (no annotations, no tooltip and no second y-axis)

7 Comments

  1. Jimbo December 30, 2008
  2. Rick B June 9, 2005
  3. Lucas January 4, 2005
  4. Lucas January 4, 2005
  5. Zeger Hendrikse January 4, 2005
  6. Leon van Tegelen January 2, 2005
  7. Lucas January 2, 2005
  8. Pingback: » Enable SVG with Firefox February 21, 2005