PHP Classes

File: SVGraph.js

Recommend this page to a friend!
  Classes of Victor Bolshov   SVGraph   SVGraph.js   Download  
File: SVGraph.js
Role: Auxiliary data
Content type: text/plain
Description: SVGraph class - javascript analogue
Class: SVGraph
Generate bar charts in SVG format
Author: By
Last change: Added method setPaddings() for greater customization, refactored the code.
Date: 16 years ago
Size: 9,687 bytes
 

Contents

Class file image Download
/** * SVGraph is a JS class for creating SVG-diagrams displaying numeric data */ /** Constructor * @param int width * @param int height */ SVGraph = function(width, height) { this.outerWidth = width this.outerHeight = height this.innerWidth = null this.innerHeight = null this.paddingLeft = null this.paddingBottom = null this.paddingTop = null this.paddingRight = null this.x = [] this.y = [] this.legends = {} this.max = null this.sf = null this.factors = {} // if you have to render more than 10 lines, use setColor() to add your own colors. // You may as well overwrite default colors with setColor() this.colors = ['red', 'green', 'blue', 'orange', 'magenta', 'darkblue', 'maroon', 'indigo', 'peru', 'teal'] this.svg = false this.rendered = false this.setDefaultPaddings() } SVGraph.prototype = { /** * Import user data * @param Array[] - an array of data-records, each record being itself an array like this: [x, y1, y2,.. ,yN] */ load: function(data) { // reset data. // NOTE: current setup (factors, legends, colors is NOT reset!!!) this.y = [] this.x = [] this.max = null this.min = null// @todo: use this for zero-point to make layout more flexible this.sf = null for (var i = 0; i < data.length; ++i) { this.x[i] = data[i][0]// X-axis for (var j = 1; j < data[i].length; ++j) { // Y-axis - for all lines we have to render var v = parseFloat(data[i][j]) if ((j - 1) in this.factors) { v = v / this.factors[j - 1] } if ((this.max == null) || (v > this.max)) { this.max = v this.sf = v / this.innerHeight } if (this.y.length < j) { this.y[j - 1] = [] } this.y[j - 1][i] = v } } return this }, /** * Set paddings. * * Each value may come in the form of * - integer: number of pixels * - float: a part of outer size * - string (N%): a percentage of outer size * * @param int | float | string $top * @param int | float | string $right * @param int | float | string $bottom * @param int | float | string $left */ setPaddings: function(top, right, bottom, left) { top = this.absolutePadding(top, this.outerHeight); right = this.absolutePadding(right, this.outerWidth); bottom = this.absolutePadding(bottom, this.outerHeight); left = this.absolutePadding(left, this.outerWidth); this.paddingLeft = left; this.paddingBottom = bottom; this.paddingRight = right; this.paddingTop = top; this.innerWidth = this.outerWidth - left - right; var oldInnerHeight = this.innerHeight; this.innerHeight = this.outerHeight - top - bottom; // update scale factor if ((oldInnerHeight != this.innerHeight) && null !== this.sf) { this.sf = this.sf * (this.innerHeight / oldInnerHeight); } }, absolutePadding: function(value, outerSize) { if ('number' == typeof(value)) { if (value < 1) { return value * outerSize; } else { return value } } else { return (parseInt(value) * outerSize) / 100; } }, setDefaultPaddings: function() { this.setPaddings(.1, .1, .25, .15); }, // @access private sy: function(v) { return this.outerHeight - (this.paddingBottom + (v / this.sf)); }, sx: function(x) { return this.paddingLeft + x; }, /** * Set legend for line #n * @param int n * @param string legend */ setLegend: function(n, legend) { this.legends[n] = legend return this }, /** * Set factor for line #n - so that every data value is devided by this factor before being rendered * @param int n * @param numeric factor */ setFactor: function(n, factor) { this.factors[n] = factor return this }, /** * Set color for line #n * @param int n * @param string color */ setColor: function(n, color) { this.colors[n] = color return this }, /** * Once SVGraph is rendered, it marks itself as "rendered". * With renderOnce() method you can always be sure that your SVGraph instance * only renders itself once. */ renderOnce: function(container) { if (this.rendered) return; this.render(container) }, /** * Render graphic inside container element * @param string container the container element #ID */ render: function(container) { if (1 >= this.x.length) {// nothing to render return } container = document.getElementById(container) // root SVG element this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.svg.setAttribute('width', this.outerWidth); this.svg.setAttribute('height', this.outerHeight); var xstep = Math.round(this.innerWidth / (this.x.length - 1)) this.renderGrid(xstep) // main cycle var x1, x2, y1, y2, c for (var j = 0; j < this.y.length; ++j) { c = this.getColor(j) // wrap every line-set in a group element, // so that we are able to menipulate with it via scritp later var group = document.createElementNS('http://www.w3.org/2000/svg', 'g') group.setAttribute('id', 'g' + j) var title = group.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'title')) var titleText = this.getLegend(j) title.appendChild(document.createTextNode(titleText)) group.setAttribute('title', titleText)// FF doesn't show <title> otherwise :( for (var i = 0; i < this.x.length; ++i) { if (i) { // create a line part x1 = (i - 1) * xstep; x2 = i * xstep; y1 = this.y[j][i - 1]; y2 = this.y[j][i] var line = this.line(group, x1, y1, x2, y2) this.style(line, "stroke:" + c + ";stroke-width:2;") // create data point var point = group.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'circle')) this.style(point, "stroke:none;fill:" + c + ";cursor:pointer;") point.setAttribute('cx', this.sx(x2)) point.setAttribute('cy', this.sy(y2)) point.setAttribute('r', 5) // data point <title> var displayedValue = this.y[j][i] if (j in this.factors) { displayedValue = displayedValue * this.factors[j] } var text = this.getLegend(j, false) + " = " + displayedValue var pointTitle = point.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'title')) pointTitle.appendChild(document.createTextNode(text)) point.setAttribute('title', text) } } this.style(group, "display:block;") this.svg.appendChild(group) } container.appendChild(this.svg) this.renderLegends(container) this.rendered = true }, sx: function(x) { return this.paddingLeft + x; }, // @access private sy: function(v) { return this.outerHeight - (this.paddingBottom + (v / this.sf)); }, // @access private line: function(parent, x1, y1, x2, y2) { var line = document.createElementNS('http://www.w3.org/2000/svg', 'line') line.setAttribute('x1', this.sx(x1)) line.setAttribute('y1', this.sy(y1)) line.setAttribute('x2', this.sx(x2)) line.setAttribute('y2', this.sy(y2)) return parent.appendChild(line) }, // @access private style: function(el, s) { el.setAttribute('style', s) }, // @access private getLegend: function(index, respectFactor) { if (arguments.length < 2) respectFactor = true var l = (index in this.legends) ? this.legends[index] : 'Curve #' + j if (respectFactor && (index in this.factors)) { l += " (x" + this.factors[index] + ")" } return l }, // @access private getColor: function(index) { return (index < this.colors.length) ? this.colors[index] : 'black' }, // @access private renderGrid: function(xstep) { // X-Y axis var x0 = this.line(this.svg, 0, 0, 0, this.innerHeight * this.sf) this.style(x0, 'stroke:black;') var y0 = this.line(this.svg, 0, 0, this.innerWidth, 0) this.style(y0, 'stroke:black;') // vertical grid for (var i = 0; i < this.x.length; ++i) { var text = document.createElementNS('http://www.w3.org/2000/svg', 'text') text.appendChild(document.createTextNode(this.x[i])) var x = i * xstep var tx = this.sx(x) var ty = this.outerHeight - this.paddingBottom + 10 text.setAttribute('x', tx) text.setAttribute('y', ty) text.setAttribute('transform', 'rotate(45,' + tx + ',' + ty + ')') this.style(text, "font-size:6;") this.svg.appendChild(text) var xmarker = this.line(this.svg, x, 0, x, this.innerHeight * this.sf) this.style(xmarker, 'stroke:black;stroke-width:.2;') } // horizontal grid var ystep = Math.pow(10, Math.floor(Math.log(this.max) / Math.log(10))) for (var j = ystep; j <= this.max; j += ystep) { var text = document.createElementNS('http://www.w3.org/2000/svg', 'text') text.appendChild(document.createTextNode(j)) text.setAttribute('x', '1') text.setAttribute('y', this.sy(j)) this.svg.appendChild(text) var line = this.line(this.svg, 0, j, this.innerWidth, j) this.style(line, 'stroke:black;stroke-width:.2;') } }, // @access private renderLegends: function(container) { var div = container.appendChild(document.createElement('div')) for (var j = 0; j < this.y.length; ++j) { // @todo: style some properies of the legends via CSS-classes, not with embedded style defs var a = document.createElement('a') a.setAttribute('href', '') a.setAttribute('onclick', 'SVGraph.toggle("g' + j + '"); return false;') a.style.marginRight = '1em' a.style.padding = '.2em .5em' a.style.cursor = 'pointer' a.style.backgroundColor = this.getColor(j) a.style.color = 'white' a.innerHTML = this.getLegend(j) div.appendChild(a) } } } // toggle a line SVGraph.toggle = function(id) { var el = document.getElementById(id) if ('none' == el.style.display) { el.style.display = 'block' } else { el.style.display = 'none' } }