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

Lucas Jellema 10

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)

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

  1. 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.

  2. Great graphs, even with marker labels… A worthy add-on for the Blog! (I still have to read this post though).

  3. 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.

Comments are closed.

Next Post

JDeveloper 10.1.2 has arrived

Facebook0TwitterLinkedinJDeveloper release 10.1.2 is finally available for download (here). This is a maintenance release with some new features and over 1,000 bug fixes for JDeveloper 9.5.0.2. Related posts: Uitnodiging: Kom kennismaken met AMIS Apache My Faces Trinidad: triggering Partial Page Refresh ("AJAX") from non Trinidad components Java: Generating PDF and […]