Skip to content

Instantly share code, notes, and snippets.

@pastokes
Last active August 29, 2015 14:23
Show Gist options
  • Save pastokes/1b1b0c8f290db9cfec84 to your computer and use it in GitHub Desktop.
Save pastokes/1b1b0c8f290db9cfec84 to your computer and use it in GitHub Desktop.
Template to generate scatter graph or histogram in SVG
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0" exclude-result-prefixes="#all">
<!-- Parameters. All values are in pixles unless otherwise specified.
bins - the data to be displayed on the histogram. bin: label, value+
bin_inner_offset - number of pixels between the y axis and the edge of the first bin
bin_outer_offset - number of pixels between the edge of the last bin and the end of the x axis
bingap - number of pixels from the tick on the h axis to the edge of the bin rectangle. Space between bins is therefore
2 * bingap
binwidth - width of each bin rectangle
width, height - dimensions of the entire SVG image
origin_x, orign_y - coordinates of the intersection of the axes (*not* the orign of the SVG image as a whole!)
axiswidth, axisheight - length of the x and y axes, respectively.
ticklength - the length of each tick along the axes
vtick_yinit, htick_xinit - the y coord or x coord of the first vertical or horizontal tick respectively
vtickspace, htickspace - number of pixels between ticks on the vertical and horizontal axes respectively
vtick_x, htick_y - the x coord of the vertical ticks, y coord of the horizontal ticks respectively
labeloffset_x, labeloffset_y - the horizontal offset of the label along the vertical axis relative to the (origin of
the) tick marks; vertical offset along horizontal axis
vlabel_start, vlabel_increment, num_vlabels - parameters for establishing the labels along the vertical axis.
Being a histogram, the labels are assumed to be numbers, beginning with vlabel_start and with an increment of
vlabel_increment
-->
<xsl:param name="query"/>
<xsl:param name="charttype" select="scatter"/>
<xsl:param name="bins">
<xsl:choose>
<xsl:when test="$query != ''">
<xsl:for-each select="tokenize($query, '\+')">
<bin>
<xsl:analyze-string select="." regex="&apos;(.*)&apos;(,([\d\.])+)+">
<xsl:matching-substring>
<xsl:if test="regex-group(1) != '' and regex-group(2) != ''"/>
<label>
<xsl:value-of select="regex-group(1)"/>
</label>
<xsl:for-each select="tokenize(regex-group(2), ',')">
<xsl:if test=". != ''">
<value>
<xsl:value-of select="."/>
</value>
</xsl:if>
</xsl:for-each>
</xsl:matching-substring>
</xsl:analyze-string>
</bin>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<!-- Generate dummy histogram for test purposes -->
<bin>
<label>s. xi1/4</label>
<value>50</value>
<value>20</value>
</bin>
<bin>
<label>s. xi2/4</label>
<value>25</value>
<value>13</value>
</bin>
<bin>
<label>s. xi3/4</label>
<value>80</value>
<value>11</value>
</bin>
<bin>
<label>s. xi4/4</label>
<value>11</value>
<value>87</value>
</bin>
<bin>
<label>s. xi</label>
<value>24</value>
<value>65</value>
</bin>
<bin>
<label>s. ix</label>
<value>240</value>
<value>0</value>
</bin>
</xsl:otherwise>
</xsl:choose>
</xsl:param>
<!-- #DEFINEs -->
<!-- Overall geometry and axes -->
<xsl:param name="bin_inner_offset">10</xsl:param>
<xsl:param name="bin_outer_offset">20</xsl:param>
<xsl:param name="outer_margin">50</xsl:param>
<xsl:param name="pointwidth">4</xsl:param>
<xsl:param name="pointheight">4</xsl:param>
<xsl:param name="width" select="$origin_x + $axiswidth + $outer_margin"/>
<xsl:param name="height" select="$origin_y + $outer_margin"/>
<xsl:param name="origin_x">40</xsl:param>
<xsl:param name="origin_y">250</xsl:param>
<xsl:param name="axiswidth" select="$bin_inner_offset + $num_bins * $num_series * $htickspace + $bin_outer_offset"/>
<xsl:param name="axisheight">220</xsl:param>
<!-- Axis Ticks and Labels-->
<xsl:param name="ticklength">10</xsl:param>
<xsl:param name="vtickspace">-20</xsl:param>
<xsl:param name="vtick_yinit" select="$origin_y + $vtickspace"/>
<xsl:param name="vtick_x" select="$origin_x - ($ticklength div 2)"/>
<xsl:param name="labeloffset_x">25</xsl:param>
<xsl:param name="htickspace">
<xsl:choose>
<xsl:when test="$charttype='scatter'">
<xsl:value-of select="$pointwidth + 2*$bingap"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="50 * $num_series"/>
</xsl:otherwise>
</xsl:choose>
</xsl:param>
<xsl:param name="htick_xinit" select="$origin_x + ($htickspace div 2)"/>
<xsl:param name="htick_y" select="$origin_y"/>
<xsl:param name="labeloffset_y">-15</xsl:param>
<!-- Calculate labels on vertical axis -->
<!-- Must be a better way of doing this! -->
<xsl:variable name="maxval" select="max($bins/bin/value)"/>
<!--<xsl:param name="vlabel_start" select="floor($maxval div 10)">
<xsl:choose>
<xsl:when test="$maxval &lt;= 10">1</xsl:when>
<xsl:when test="$maxval &lt;= 50">5</xsl:when>
<xsl:when test="$maxval &lt;= 100">10</xsl:when>
<xsl:when test="$maxval &lt;= 250">25</xsl:when>
<xsl:when test="$maxval &lt;= 500">50</xsl:when>
<xsl:otherwise>100</xsl:otherwise>
</xsl:choose>
</xsl:param>-->
<xsl:param name="num_vlabels">10</xsl:param>
<!-- <xsl:param name="vlabel_increment">10</xsl:param>-->
<!-- Calculate increment, rounding to the nearest multiple of 10 -->
<xsl:param name="vlabel_increment" select="ceiling($maxval div $num_vlabels)"/>
<!-- select="ceiling($maxval div $num_vlabels div $vlabel_start) * $vlabel_start"/>-->
<xsl:param name="vlabels">
<xsl:for-each select="1 to $num_vlabels">
<xsl:variable name="i" select="."/>
<label>
<xsl:value-of select="$i * $vlabel_increment"/>
</label>
</xsl:for-each>
</xsl:param>
<xsl:variable name="num_vticks" select="count($vlabels/label)"/>
<xsl:variable name="num_hticks" select="count($bins/bin/label)"/>
<xsl:variable name="num_series" select="count($bins/bin[1]/value)"/>
<xsl:variable name="num_bins" select="count($bins/bin)"/>
<!-- Bins -->
<xsl:param name="bingap">2</xsl:param>
<xsl:variable name="binwidth">
<xsl:choose>
<xsl:when test="$charttype='scatter'">
<xsl:value-of select="$pointwidth"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$htickspace - (2 * $bingap)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Styles -->
<xsl:param name="linestyle">fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1</xsl:param>
<xsl:param name="textstyle"
>font-size:15px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans</xsl:param>
<xsl:param name="binstyle">fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1</xsl:param>
<xsl:param name="bincolours">
<colour>#0000ff</colour>
<colour>#00ff00</colour>
<colour>#ff0000</colour>
</xsl:param>
<xsl:template match="/">
<xsl:call-template name="svghist"/>
</xsl:template>
<xsl:template name="svghist">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{$width}" height="{$height}" id="svg2" version="1.1">
<g id="layer2" style="display:inline">
<!-- Axes layer -->
<g id="axeslayer" style="display:inline">
<g id="textlayer" style="{$textstyle}">
<!-- Vertical labels -->
<g id="vlabelslayer">
<xsl:for-each select="1 to $num_vticks">
<xsl:variable name="i" select="."/>
<text x="{$vtick_x - $labeloffset_x}" y="{$vtick_yinit + $vtickspace * ($i - 1)}" id="vtext{$i}">
<xsl:value-of select="$vlabels/label[$i]"/>
</text>
</xsl:for-each>
</g>
<!-- Horizontal layers. We need to rotate labels to avoid overlap-->
<!-- If it's a scatter then only do every five; otherwise it won't fit... -->
<g id="hlabelslayer">
<xsl:for-each select="1 to $num_hticks">
<xsl:variable name="i" select="."/>
<xsl:if test="$charttype != 'scatter' or ($charttype = 'scatter' and ($i mod 10 = 1))">
<xsl:variable name="x" select="$htick_xinit + $htickspace * ($i - 1)"/>
<xsl:variable name="y" select="$htick_y - $labeloffset_y"/>
<text x="{$x}" y="{$y}" transform="rotate(15, {$x}, {$y})" id="htext{$i}">
<xsl:value-of select="$bins/bin[$i]/label"/>
</text>
</xsl:if>
</xsl:for-each>
<!-- Axis units -->
<!-- <text x="{$axiswidth + $htick_xinit}" y="{$htick_y}" id="axlabel">Date</text>-->
</g>
</g>
<!-- Axes -->
<g id="lineslayer" style="{$linestyle}">
<!-- Horizontal -->
<path d="m {$origin_x},{$origin_y - $axisheight} 0,{$axisheight} {$axiswidth},0" id="axes"/>
<g id="haxeslayer" style="display:inline">
<g id="htickslayer">
<path d="m {$htick_xinit},{$htick_y} 0,{$ticklength}" id="htick0"/>
<xsl:for-each select="1 to $num_hticks">
<xsl:variable name="i" select="."/>
<xsl:if test="$charttype != 'scatter' or ($charttype = 'scatter' and ($i mod 10 = 0))">
<use x="{$i * $htickspace}" y="0" xlink:href="#htick0" id="htick{$i}"/>
</xsl:if>
</xsl:for-each>
</g>
</g>
<!-- Vertical -->
<g id="vaxeslayer">
<g id="vtickslayer">
<!-- Draw the first tick 'manually' and reuse for the others -->
<!-- Only put in every 5th tick for scatter graphs -->
<path d="m {$vtick_x},{$vtick_yinit} {$ticklength},0" id="vtick0"/>
<xsl:for-each select="1 to xs:integer($num_vticks - 1)">
<xsl:variable name="i" select="."/>
<use x="0" y="{$i * $vtickspace}" xlink:href="#vtick0" id="vtick{$i}"/>
</xsl:for-each>
</g>
</g>
</g>
</g>
<!-- The graph itself -->
<g id="graphlayer" style="{$binstyle}">
<!-- Display bins -->
<!-- Scale to maximum of 10 units if we're doing scatter graph -->
<xsl:variable name="yscale">
<xsl:choose>
<xsl:when test="$charttype='scatter'">
<xsl:value-of select="10 div max($bins/bin/value)"/>
</xsl:when>
<xsl:otherwise>1</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:for-each select="$bins/bin">
<xsl:variable name="bin_no" select="position()"/>
<xsl:for-each select="value">
<!-- Display series -->
<xsl:variable name="series" select="position()"/>
<xsl:variable name="binheight" select=". * (-$vtickspace) div $vlabel_increment * $yscale" as="xs:double"/>
<xsl:variable name="colour" select="$bincolours/colour[$series mod count($bincolours/colour)]"/>
<xsl:variable name="x" select="($htick_xinit + $bingap) + ($binwidth + 2 * $bingap) * ($bin_no - 1) + ($binwidth div $num_series) * ($series - 1)"/>
<xsl:variable name="y" select="$origin_y - $binheight"/>
<xsl:choose>
<xsl:when test="$charttype='scatter'">
<!-- Should be using smoothed curve, not scatter plot. See <http://www.codeproject.com/KB/graphics/BezierSpline.aspx> for worked example -->
<rect id="rect{position()}" style="fill:{$colour}" width="{$pointwidth div $num_series}" height="{$pointheight}" x="{$x}" y="{$y}"/>
</xsl:when>
<xsl:otherwise>
<rect id="rect{position()}" style="fill:{$colour}" width="{$binwidth div $num_series}" height="{$binheight}" x="{$x}" y="{$y}"/>
<!-- Add text for clarity -->
<text x="{$x + $htickspace div 2}" y="{$y + $labeloffset_y}" id="bin{position()}">
<xsl:value-of select="."/>
</text>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
</g>
</g>
</svg>
</xsl:template>
</xsl:stylesheet>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment