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

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)

1 2 3
Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

10 Comments

  1. Would be OK with you if I leveraged your gauge examples in a real application?

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

  3. Zeger Hendrikse on

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

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

  5. Pingback: » Enable SVG with Firefox

  6. Pingback: » SVG, XSLT and JSP/JSTL: Generating Digital Gauge or Speedometer Chart based on dynamic data

  7. Pingback: » AMIS Technology Weblog - Statistics, Graphics and Becoming an Author (’blogger’)