Last active
August 29, 2015 14:23
-
-
Save pastokes/1b1b0c8f290db9cfec84 to your computer and use it in GitHub Desktop.
Template to generate scatter graph or histogram in SVG
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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="'(.*)'(,([\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 <= 10">1</xsl:when> | |
<xsl:when test="$maxval <= 50">5</xsl:when> | |
<xsl:when test="$maxval <= 100">10</xsl:when> | |
<xsl:when test="$maxval <= 250">25</xsl:when> | |
<xsl:when test="$maxval <= 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