2014-03-31 20:33:49 +02:00
/ * T i m e l i n e . j s
* Author : Phyks ( http : //phyks.me)
* http : //phyks.github.io/timeline.js
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* "THE NO-ALCOHOL BEER-WARE LICENSE" ( Revision 42 ) :
* Phyks ( webmaster @ phyks . me ) wrote this file . As long as you retain this notice you
* can do whatever you want with this stuff ( and you can also do whatever you want
* with this stuff without retaining it , but that ' s not cool ... ) . If we meet some
* day , and you think this stuff is worth it , you can buy me a < del > beer < / d e l > s o d a
* in return .
* Phyks
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
* /
2014-04-19 09:25:40 +02:00
/ * I n i t i a l i z a t i o n :
* arg is an object with :
* id = id of the parent block
* height / width = size of the svg
* grid = small / big / both
* x _axis = true / false to show or hide x axis
* line = none / line / dashed to choose line type
* rounded = true / false to use splines to smoothen the graph
* x _callback = function ( args ) { } or false is called to display the legend on the x axis
* fill = true / false to fill below the graph or not
* /
function Timeline ( arg ) {
this . ns = "http://www.w3.org/2000/svg" ;
this . xlinkns = "http://www.w3.org/1999/xlink" ;
this . marginBottom = 10 ;
this . marginTop = 15 ;
this . marginLeft = 10 ;
this . marginRight = 10 ;
this . rounded = false ;
this . x _axis = false ;
this . fill = true ;
this . line = 'line' ;
this . dashed _style = '5, 5' ;
this . parent _holder = false ;
this . holder = false ;
this . g = false ;
this . axis = false ;
this . graphs = [ ] ;
this . raw _points = [ ] ;
this . x _callback = false ;
2014-04-19 14:56:47 +02:00
var old = window . onresize || function ( ) { } ;
var obj = this ;
window . onresize = function ( ) {
old ( ) ;
2014-06-29 00:52:25 +02:00
obj . resize ( obj . parent _holder . offsetWidth , obj . parent _holder . offsetHeight ) ;
}
2014-04-19 14:56:47 +02:00
2014-04-19 09:25:40 +02:00
if ( ! document . implementation . hasFeature ( "http://www.w3.org/TR/SVG11/feature#Image" , "1.1" ) ) {
alert ( "ERROR : Your browser does not support embedded SVG." ) ;
}
this . parent _holder = document . getElementById ( arg . id ) ;
2014-04-19 16:08:30 +02:00
this . parent _holder . style . width = arg . width ;
this . parent _holder . style . height = arg . height ;
var svg = this . createElement ( 'svg:svg' , { 'width' : '100%' , 'height' : '100%' } ) ;
2014-04-19 09:25:40 +02:00
svg . setAttributeNS ( 'http://www.w3.org/2000/xmlns/' , 'xmlns:xlink' , this . xlinkns ) ;
this . parent _holder . appendChild ( svg ) ;
this . holder = this . parent _holder . querySelector ( 'svg' ) ;
defs = this . createElement ( 'defs' , { } ) ;
this . holder . appendChild ( defs ) ;
if ( arg . grid === 'small' || arg . grid === 'both' ) {
var small _grid _pattern = this . createElement ( 'pattern' , { 'id' : 'smallGrid' , 'width' : 8 , 'height' : 8 , 'patternUnits' : 'userSpaceOnUse' } ) ;
var small _grid _path = this . createElement ( 'path' , { 'd' : 'M 8 0 L 0 0 0 8' , 'fill' : 'none' , 'stroke' : 'gray' , 'stroke-width' : '0.5' } ) ;
small _grid _pattern . appendChild ( small _grid _path ) ;
defs . appendChild ( small _grid _pattern ) ;
}
if ( arg . grid === 'big' || arg . grid === 'both' ) {
var grid _pattern = this . createElement ( 'pattern' , { 'id' : 'grid' , 'width' : 80 , 'height' : 80 , 'patternUnits' : 'userSpaceOnUse' } ) ;
if ( arg . grid === 'both' ) {
var grid _rect = this . createElement ( 'rect' , { 'width' : 80 , 'height' : 80 , 'fill' : 'url(#smallGrid)' } ) ;
grid _pattern . appendChild ( grid _rect ) ;
}
var grid _path = this . createElement ( 'path' , { 'd' : 'M 80 0 L 0 0 0 80' , 'fill' : 'none' , 'stroke' : 'gray' , 'stroke-width' : '1' } ) ;
grid _pattern . appendChild ( grid _path ) ;
defs . appendChild ( grid _pattern ) ;
}
this . grid = arg . grid ;
var marker = this . createElement ( 'marker' , { 'id' : 'markerArrow' , 'markerWidth' : 13 , 'markerHeight' : 13 , 'refX' : 2 , 'refY' : 6 , 'orient' : 'auto' } ) ;
var marker _path = this . createElement ( 'path' , { 'd' : 'M2,2 L2,11 L10,6 L2,2' , 'fill' : 'gray' } ) ;
marker . appendChild ( marker _path ) ;
defs . appendChild ( marker ) ;
this . g = this . createElement ( 'g' , { 'transform' : 'translate(0, ' + this . parent _holder . offsetHeight + ') scale(1, -1)' } ) ;
this . holder . appendChild ( this . g ) ;
if ( arg . x _axis === true ) {
this . axis = this . createElement ( 'line' , { 'x1' : this . marginLeft , 'y1' : this . parent _holder . offsetHeight / 2 + 1.5 , 'x2' : this . parent _holder . offsetWidth - 13 - this . marginRight , 'y2' : this . parent _holder . offsetHeight / 2 + 1.5 , 'stroke' : 'gray' , 'stroke-width' : 3 , 'marker-end' : 'url("#markerArrow")' } ) ;
this . g . appendChild ( this . axis ) ;
}
if ( this . grid !== "none" ) {
var grid = this . createElement ( 'rect' , { 'width' : '100%' , 'height' : '100%' } ) ;
if ( this . grid === 'big' || this . grid === 'both' ) {
grid . setAttribute ( 'fill' , 'url(#grid)' ) ;
}
else {
grid . setAttribute ( 'fill' , 'url(#smallGrid)' ) ;
}
this . g . appendChild ( grid ) ;
}
this . rounded = arg . rounded ;
this . x _axis = arg . x _axis ;
this . line = arg . line ;
this . fill = arg . fill ;
this . x _callback = arg . x _callback ;
}
2014-03-28 01:06:01 +01:00
2014-06-29 00:52:25 +02:00
// Resize the SVG
Timeline . prototype . resize = function ( new _width , new _height ) {
if ( this . g !== false ) {
this . g . setAttribute ( 'transform' , 'translate(0, ' + new _height + ') scale(1, -1)' ) ;
if ( this . x _axis === true ) {
this . axis . setAttribute ( 'x2' , new _width - this . marginLeft - 3 - this . marginRight ) ;
}
[ ] . forEach . call ( this . holder . querySelectorAll ( '.label, .over, .point, .line, .graph, .legend_x' ) , function ( el ) {
el . parentNode . removeChild ( el ) ;
} ) ;
this . draw ( ) ;
}
} ;
2014-04-06 18:28:02 +02:00
// Create an element "element" with the attributes "attrs"
2014-04-19 09:25:40 +02:00
Timeline . prototype . createElement = function ( element , attrs ) {
var el = document . createElementNS ( this . ns , element ) ;
2014-04-06 18:28:02 +02:00
for ( attr in attrs ) {
el . setAttribute ( attr , attrs [ attr ] ) ;
}
return el ;
} ;
// Check wether the element "element" has class "class"
2014-04-19 09:25:40 +02:00
Timeline . prototype . hasClass = function ( element , cls ) {
2014-04-06 18:28:02 +02:00
return ( ' ' + element . getAttribute ( 'class' ) + ' ' ) . indexOf ( ' ' + cls + ' ' ) > - 1 ;
} ;
2014-04-18 21:54:24 +02:00
// Add a new graph to the Timeline
2014-04-19 09:25:40 +02:00
Timeline . prototype . addGraph = function ( graph , color ) {
this . graphs [ graph ] = color ;
2014-04-06 18:28:02 +02:00
} ;
// Test wether a graph of name "graph" already exists
2014-04-19 09:25:40 +02:00
Timeline . prototype . hasGraph = function ( graph ) {
if ( typeof ( this . graphs [ graph ] ) === 'undefined' ) {
2014-04-06 18:28:02 +02:00
return false ;
}
else {
return true ;
}
} ;
// Clear the specified graph data, or completely clear all the graph data
2014-04-19 09:25:40 +02:00
Timeline . prototype . clearGraph = function ( graph ) {
2014-04-06 18:28:02 +02:00
if ( typeof ( graph ) === 'undefined' ) {
2014-04-19 09:25:40 +02:00
this . raw _points = [ ] ;
this . graphs = [ ] ;
2014-04-06 18:28:02 +02:00
}
else {
2014-04-19 09:25:40 +02:00
for ( var i = 0 ; i < this . raw _points . length ; i ++ ) {
if ( this . raw _points [ i ] . graph === graph ) {
this . raw _points [ i ] = undefined ;
2014-04-08 20:08:33 +02:00
}
}
2014-04-06 18:28:02 +02:00
}
} ;
// Add points to the specified graph
2014-04-19 09:25:40 +02:00
Timeline . prototype . addPoints = function ( graph , data ) {
2014-04-08 20:08:33 +02:00
for ( var point = 0 ; point < data . length ; point ++ ) {
var insert = { 'graph' : graph , 'x' : data [ point ] . x , 'y' : data [ point ] . y } ;
if ( typeof ( data [ point ] . label ) !== 'undefined' ) {
insert . label = data [ point ] . label ;
}
else {
insert . label = '' ;
}
if ( typeof ( data [ point ] . click ) !== 'undefined' ) {
insert . click = data [ point ] . click ;
}
else {
insert . click = false ;
}
2014-04-19 09:25:40 +02:00
this . raw _points . push ( insert ) ;
2014-04-08 20:08:33 +02:00
}
2014-04-19 09:25:40 +02:00
this . raw _points . sort ( function ( a , b ) {
2014-04-06 18:28:02 +02:00
if ( a . x < b . x ) {
return - 1 ;
}
else if ( a . x == b . x ) {
return 0 ;
}
else {
return 1 ;
}
} ) ;
} ;
// Compute new coordinates, knowing the min and max value to fit the graph in the container
2014-04-19 09:25:40 +02:00
Timeline . prototype . newCoordinate = function ( value , min , max , minValue , maxValue ) {
2014-04-06 18:28:02 +02:00
var a = ( maxValue - minValue ) / ( max - min ) ;
2014-04-06 22:06:14 +02:00
return a * ( value - min ) + minValue ;
2014-04-06 18:28:02 +02:00
} ;
// Compute new X and Y values
2014-04-19 09:25:40 +02:00
Timeline . prototype . getNewXY = function ( minX , maxX , minY , maxY ) {
var obj = this ;
2014-04-06 18:28:02 +02:00
return function ( x , y ) {
2014-06-29 00:52:25 +02:00
return {
2014-04-19 09:25:40 +02:00
'x' : obj . newCoordinate ( x , minX , maxX , obj . marginLeft , obj . parent _holder . offsetWidth - obj . marginRight ) ,
'y' : obj . newCoordinate ( y , minY , maxY , 2 * obj . marginBottom , obj . parent _holder . offsetHeight - obj . marginTop )
2014-04-06 18:28:02 +02:00
} ;
} ;
} ;
// Get the necessary control points to smoothen the graph, is rounded is true
2014-04-19 09:25:40 +02:00
Timeline . prototype . getControlPoints = function ( data ) {
2014-04-06 18:28:02 +02:00
// From http://www.particleincell.com/wp-content/uploads/2012/06/bezier-spline.js
var p1 = new Array ( ) ;
var p2 = new Array ( ) ;
var n = data . length - 1 ;
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
/*rhs vector*/
var a = new Array ( ) ;
var b = new Array ( ) ;
var c = new Array ( ) ;
var r = new Array ( ) ;
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
/*left most segment*/
a [ 0 ] = 0 ;
b [ 0 ] = 2 ;
c [ 0 ] = 1 ;
r [ 0 ] = data [ 0 ] + 2 * data [ 1 ] ;
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
/*internal segments*/
for ( var i = 1 ; i < n - 1 ; i ++ ) {
a [ i ] = 1 ;
b [ i ] = 4 ;
c [ i ] = 1 ;
r [ i ] = 4 * data [ i ] + 2 * data [ i + 1 ] ;
}
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
/*right segment*/
a [ n - 1 ] = 2 ;
b [ n - 1 ] = 7 ;
c [ n - 1 ] = 0 ;
r [ n - 1 ] = 8 * data [ n - 1 ] + data [ n ] ;
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
/*solves Ax=b with the Thomas algorithm (from Wikipedia)*/
var m ;
for ( var i = 1 ; i < n ; i ++ ) {
m = a [ i ] / b [ i - 1 ] ;
b [ i ] = b [ i ] - m * c [ i - 1 ] ;
r [ i ] = r [ i ] - m * r [ i - 1 ] ;
}
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
p1 [ n - 1 ] = r [ n - 1 ] / b [ n - 1 ] ;
for ( var i = n - 2 ; i >= 0 ; -- i ) {
p1 [ i ] = ( r [ i ] - c [ i ] * p1 [ i + 1 ] ) / b [ i ] ;
}
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
/*we have p1, now compute p2*/
for ( var i = 0 ; i < n - 1 ; i ++ ) {
p2 [ i ] = 2 * data [ i + 1 ] - p1 [ i + 1 ] ;
}
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
p2 [ n - 1 ] = 0.5 * ( data [ n ] + p1 [ n - 1 ] ) ;
2014-06-29 00:52:25 +02:00
2014-04-06 18:28:02 +02:00
return { p1 : p1 , p2 : p2 } ;
} ;
2014-03-30 21:18:42 +02:00
2014-04-06 18:28:02 +02:00
// Get the scale so that graph fits with window
2014-04-19 09:25:40 +02:00
Timeline . prototype . scale = function ( data ) {
2014-04-06 18:28:02 +02:00
var empty = true ;
for ( graph in data ) {
empty = false ;
break ;
}
if ( empty ) {
return false ;
}
2014-04-08 20:08:33 +02:00
var minX = false , minY = 0 ;
var maxX = false , maxY = false ;
2014-03-28 01:06:01 +01:00
var circle = false , last _point = false , line = false ;
2014-04-07 21:54:32 +02:00
// Look for max and min values for both axis
2014-04-08 20:08:33 +02:00
for ( var point = 0 ; point < data . length ; point ++ ) {
if ( data [ point ] . x < minX || minX === false ) {
minX = data [ point ] . x ;
}
if ( data [ point ] . x > maxX || maxX === false ) {
maxX = data [ point ] . x ;
}
if ( data [ point ] . y < minY ) {
minY = data [ point ] . y ;
}
if ( data [ point ] . y > maxY || maxY === false ) {
maxY = data [ point ] . y ;
2014-03-28 01:06:01 +01:00
}
}
2014-04-07 21:54:32 +02:00
// Scale the grid, if needed
2014-04-19 09:25:40 +02:00
var scale = this . getNewXY ( minX , maxX , minY , maxY ) ;
2014-04-07 21:54:32 +02:00
var tmp = scale ( Math . pow ( 10 , Math . floor ( Math . log ( maxX - minX ) / Math . log ( 10 ) ) ) , Math . pow ( 10 , Math . floor ( Math . log ( maxY - minY ) / Math . log ( 10 ) ) ) ) ;
var origin = scale ( 0 , 0 ) ;
var coordinates = { 'x' : tmp . x - origin . x , 'y' : tmp . y - origin . y } ;
2014-04-19 09:25:40 +02:00
if ( this . grid === 'big' || this . grid === 'both' ) {
var grid = this . holder . getElementById ( 'grid' ) ;
2014-04-07 21:54:32 +02:00
grid . setAttribute ( 'width' , coordinates . x ) ;
grid . setAttribute ( 'height' , coordinates . y ) ;
var big _coords = scale ( Math . floor ( minX / Math . pow ( 10 , Math . floor ( Math . log ( maxX - minX ) / Math . log ( 10 ) ) ) ) * Math . pow ( 10 , Math . floor ( Math . log ( maxX - minX ) / Math . log ( 10 ) ) ) , Math . floor ( minY / Math . pow ( 10 , Math . floor ( Math . log ( maxY - minY ) / Math . log ( 10 ) ) ) ) * Math . pow ( 10 , Math . floor ( Math . log ( maxY - minY ) / Math . log ( 10 ) ) ) ) ;
grid . setAttribute ( 'y' , big _coords . y ) ;
grid . setAttribute ( 'x' , big _coords . x ) ;
grid . querySelector ( 'path' ) . setAttribute ( 'd' , 'M ' + coordinates . x + ' 0 L 0 0 0 ' + coordinates . y ) ;
2014-03-29 17:28:20 +01:00
2014-04-19 09:25:40 +02:00
if ( this . grid === 'both' ) {
2014-04-07 21:54:32 +02:00
grid . querySelector ( 'rect' ) . setAttribute ( 'width' , coordinates . x ) ;
grid . querySelector ( 'rect' ) . setAttribute ( 'height' , coordinates . y ) ;
2014-03-29 17:28:20 +01:00
}
}
2014-04-19 09:25:40 +02:00
if ( this . grid === 'small' || this . grid === 'both' ) {
2014-04-06 18:28:02 +02:00
coordinates . x = coordinates . x / 10 ;
coordinates . y = coordinates . y / 10 ;
2014-04-19 09:25:40 +02:00
var grid = this . holder . getElementById ( 'smallGrid' ) ;
2014-04-07 21:54:32 +02:00
grid . setAttribute ( 'width' , coordinates . x ) ;
grid . setAttribute ( 'height' , coordinates . y ) ;
2014-04-19 09:25:40 +02:00
if ( this . grid === 'small' ) {
2014-04-06 18:28:02 +02:00
var small _coords = scale ( Math . floor ( minX / Math . pow ( 10 , Math . floor ( Math . log ( maxX - minX ) / Math . log ( 10 ) ) ) ) * Math . pow ( 10 , Math . floor ( Math . log ( maxX - minX ) / Math . log ( 10 ) ) ) , Math . floor ( minY / Math . pow ( 10 , Math . floor ( Math . log ( maxY - minY ) / Math . log ( 10 ) ) ) ) * Math . pow ( 10 , Math . floor ( Math . log ( maxY - minY ) / Math . log ( 10 ) ) ) ) ;
2014-04-07 21:54:32 +02:00
grid . setAttribute ( 'y' , small _coords . y ) ;
grid . setAttribute ( 'x' , small _coords . x ) ;
2014-03-29 17:28:20 +01:00
}
2014-04-07 21:54:32 +02:00
grid . querySelector ( 'path' ) . setAttribute ( 'd' , 'M ' + coordinates . x + ' 0 L 0 0 0 ' + coordinates . y ) ;
2014-03-29 17:28:20 +01:00
}
2014-03-28 01:06:01 +01:00
/* Draw axis */
2014-04-19 09:25:40 +02:00
if ( this . x _axis === true ) {
2014-04-06 18:28:02 +02:00
y = scale ( 0 , 0 ) . y ;
2014-04-19 09:25:40 +02:00
this . axis . setAttribute ( 'y1' , y ) ;
this . axis . setAttribute ( 'y2' , y ) ;
2014-03-28 20:52:04 +01:00
}
2014-03-28 01:06:01 +01:00
2014-04-06 18:28:02 +02:00
return scale ;
2014-04-06 15:28:12 +02:00
} ;
2014-04-06 18:28:02 +02:00
// Draw graphs
2014-04-19 09:25:40 +02:00
Timeline . prototype . draw = function ( ) {
var scale = this . scale ( this . raw _points ) ;
2014-04-08 20:08:33 +02:00
var points = [ ] , path ;
2014-04-06 18:28:02 +02:00
var px , py ;
var element ;
2014-04-19 09:25:40 +02:00
var obj = this ;
2014-04-06 18:28:02 +02:00
2014-04-19 09:25:40 +02:00
for ( var point = 0 ; point < this . raw _points . length ; point ++ ) {
var tmp = scale ( this . raw _points [ point ] . x , this . raw _points [ point ] . y ) ;
points . push ( { 'id' : point , 'x' : tmp . x , 'y' : tmp . y , 'graph' : this . raw _points [ point ] . graph , 'click' : this . raw _points [ point ] . click , 'label' : this . raw _points [ point ] . label } ) ;
2014-04-08 20:08:33 +02:00
}
2014-04-06 18:28:02 +02:00
2014-04-08 20:08:33 +02:00
// Draw each graph
2014-04-19 09:25:40 +02:00
for ( var graph in this . graphs ) {
2014-04-08 20:08:33 +02:00
var filtered _points = points . filter ( function ( el ) { return el . graph == graph ; } ) ;
path = '' ;
2014-03-28 01:06:01 +01:00
2014-04-08 20:08:33 +02:00
// Draw line
2014-04-19 09:25:40 +02:00
if ( this . rounded === true ) {
2014-04-08 20:47:35 +02:00
var x = new Array ( ) , y = new Array ( ) ;
for ( var point = 0 ; point < filtered _points . length ; point ++ ) {
x . push ( filtered _points [ point ] . x ) ;
y . push ( filtered _points [ point ] . y ) ;
}
2014-04-19 09:25:40 +02:00
px = this . getControlPoints ( x ) ;
py = this . getControlPoints ( y ) ;
2014-04-08 20:47:35 +02:00
for ( var point = 0 ; point < filtered _points . length - 1 ; point ++ ) {
path += 'C ' + px . p1 [ point ] + ' ' + py . p1 [ point ] + ' ' + px . p2 [ point ] + ' ' + py . p2 [ point ] + ' ' + filtered _points [ point + 1 ] . x + ' ' + filtered _points [ point + 1 ] . y + ' ' ;
2014-03-30 00:47:52 +01:00
}
2014-03-28 15:02:55 +01:00
}
2014-03-30 00:47:52 +01:00
else {
2014-04-08 20:08:33 +02:00
for ( var point = 1 ; point < filtered _points . length ; point ++ ) {
path += 'L ' + filtered _points [ point ] . x + ' ' + filtered _points [ point ] . y + ' ' ;
2014-03-30 00:47:52 +01:00
}
2014-03-28 15:02:55 +01:00
}
2014-03-30 00:47:52 +01:00
2014-06-01 01:10:17 +02:00
if ( this . line !== 'none' && filtered _points . length !== 0 ) {
2014-04-19 09:25:40 +02:00
element = this . createElement ( 'path' , { 'class' : 'line' , 'stroke' : this . graphs [ graph ] , 'stroke-width' : 2 , 'fill' : 'none' , 'd' : 'M ' + filtered _points [ 0 ] . x + ' ' + filtered _points [ 0 ] . y + ' ' + path } ) ;
if ( this . line === 'dashed' ) {
element . setAttribute ( 'style' , 'stroke-dasharray: ' + this . dashed _style ) ;
2014-04-08 10:46:15 +02:00
}
2014-04-19 09:25:40 +02:00
this . g . appendChild ( element ) ;
2014-04-08 10:46:15 +02:00
}
2014-03-28 20:49:56 +01:00
2014-04-08 20:08:33 +02:00
// Draw fill
2014-04-19 09:25:40 +02:00
if ( this . fill ) {
element = this . createElement ( 'path' , { 'class' : 'graph' , 'fill' : this . graphs [ graph ] , 'opacity' : '0.25' , 'stroke' : 'none' , 'd' : 'M ' + filtered _points [ 0 ] . x + ' ' + 2 * this . marginBottom + ' L ' + filtered _points [ 0 ] . x + ' ' + filtered _points [ 0 ] . y + ' ' + path + ' L ' + filtered _points [ filtered _points . length - 1 ] . x + ' ' + 2 * this . marginBottom + ' Z' } ) ;
this . g . insertBefore ( element , this . g . querySelectorAll ( '.over' ) [ 0 ] ) ;
2014-04-08 20:08:33 +02:00
}
}
2014-03-29 00:19:12 +01:00
2014-04-08 20:47:35 +02:00
// Hover effect
2014-04-08 22:19:05 +02:00
var prev = 0 ;
for ( var point = 0 ; point < points . length ; ) {
2014-04-19 09:25:40 +02:00
var rect = this . createElement ( 'rect' , { 'class' : 'over' , 'id' : 'over_' + point , 'y' : 0 , 'fill' : 'white' , 'opacity' : 0 , 'height' : '100%' } ) ;
2014-04-08 20:47:35 +02:00
var currents = [ point ] ;
2014-04-08 22:19:05 +02:00
var next = point + 1 ;
2014-04-08 20:47:35 +02:00
if ( point < points . length - 1 ) {
2014-04-08 22:19:05 +02:00
while ( points [ next ] . x == points [ point ] . x ) {
2014-04-08 20:47:35 +02:00
if ( i > points . length ) {
break ;
}
2014-04-08 22:19:05 +02:00
next ++ ;
2014-04-08 20:47:35 +02:00
}
}
2014-04-08 22:19:05 +02:00
for ( var i = prev + 1 ; i < next ; i ++ ) {
currents . push ( i ) ;
2014-04-08 20:47:35 +02:00
}
2014-04-08 20:08:33 +02:00
if ( point == 0 ) {
rect . setAttribute ( 'x' , 0 ) ;
}
else {
2014-04-08 22:19:05 +02:00
rect . setAttribute ( 'x' , ( points [ point ] . x + points [ prev ] . x ) / 2 ) ;
2014-04-08 20:08:33 +02:00
}
2014-04-06 18:28:02 +02:00
2014-04-08 20:47:35 +02:00
if ( point == points . length - 1 ) {
2014-04-19 09:25:40 +02:00
rect . setAttribute ( 'width' , this . parent _holder . offsetWidth - ( points [ point ] . x + points [ point - 1 ] . x ) / 2 + 1 ) ;
2014-03-28 20:49:56 +01:00
}
2014-04-08 20:08:33 +02:00
else if ( point == 0 ) {
2014-04-19 09:25:40 +02:00
rect . setAttribute ( 'width' , ( points [ 1 ] . x + points [ 0 ] . x ) / 2 + this . marginLeft + 1 ) ;
2014-04-08 20:08:33 +02:00
}
else {
2014-04-08 22:28:46 +02:00
rect . setAttribute ( 'width' , ( points [ next ] . x - points [ prev ] . x ) / 2 + 1 ) ;
2014-04-08 20:08:33 +02:00
}
2014-04-08 20:47:35 +02:00
2014-04-19 09:25:40 +02:00
this . g . appendChild ( rect ) ;
2014-04-08 20:47:35 +02:00
rect . addEventListener ( 'mouseover' , ( function ( arg ) {
return function ( ) {
2014-04-08 22:00:01 +02:00
for ( var i = 0 ; i < arg . length ; i ++ ) {
2014-04-19 09:25:40 +02:00
obj . holder . getElementById ( 'point_' + arg [ i ] ) . setAttribute ( 'r' , '6' ) ;
obj . holder . getElementById ( 'label_' + arg [ i ] ) . setAttribute ( 'display' , 'block' ) ;
2014-04-08 22:00:01 +02:00
}
} ;
} ) ( currents ) ) ;
rect . addEventListener ( 'mouseout' , function ( ) {
2014-04-08 20:47:35 +02:00
// Reinitialize all states
2014-04-19 09:25:40 +02:00
[ ] . forEach . call ( obj . holder . querySelectorAll ( '.point' ) , function ( el ) {
2014-04-08 20:47:35 +02:00
el . setAttribute ( 'r' , '4' ) ;
} ) ;
2014-04-19 09:25:40 +02:00
[ ] . forEach . call ( obj . holder . querySelectorAll ( '.label' ) , function ( el ) {
2014-04-08 20:47:35 +02:00
el . setAttribute ( 'display' , 'none' ) ;
} ) ;
2014-04-08 22:00:01 +02:00
} ) ;
2014-04-08 20:47:35 +02:00
2014-04-19 16:10:20 +02:00
// Clear also the labels when moving outside the SVG element
this . holder . addEventListener ( 'mouseout' , function ( ) {
// Reinitialize all states
[ ] . forEach . call ( obj . holder . querySelectorAll ( '.point' ) , function ( el ) {
el . setAttribute ( 'r' , '4' ) ;
} ) ;
[ ] . forEach . call ( obj . holder . querySelectorAll ( '.label' ) , function ( el ) {
el . setAttribute ( 'display' , 'none' ) ;
} ) ;
} ) ;
2014-04-19 09:25:40 +02:00
if ( this . x _callback !== false && points [ point ] . x + 2.5 < this . parent _holder . offsetWidth - this . marginRight ) {
element = this . createElement ( 'text' , { 'class' : 'legend_x' , 'fill' : 'gray' , 'transform' : 'translate(0, ' + this . parent _holder . offsetHeight + ') scale(1, -1)' } ) ;
element . appendChild ( document . createTextNode ( this . x _callback ( this . raw _points [ point ] . x ) ) ) ;
this . g . appendChild ( element ) ;
2014-04-08 22:28:46 +02:00
element . setAttribute ( 'x' , points [ point ] . x - element . getBoundingClientRect ( ) . width / 2 + 2.5 ) ;
var y _zero = scale ( 0 , 0 ) . y ;
2014-04-19 09:25:40 +02:00
element . setAttribute ( 'y' , this . parent _holder . offsetHeight - this . marginBottom - y _zero ) ;
2014-04-08 20:47:35 +02:00
2014-04-19 09:25:40 +02:00
element = this . createElement ( 'line' , { 'class' : 'legend_x' , 'stroke' : 'gray' , 'stroke-width' : 2 , 'x1' : points [ point ] . x , 'x2' : points [ point ] . x , 'y1' : y _zero - 5 , 'y2' : y _zero + 5 } ) ;
this . g . appendChild ( element ) ;
2014-04-08 22:28:46 +02:00
}
2014-04-08 22:19:05 +02:00
prev = next - 1 ;
point = next ;
2014-04-08 20:47:35 +02:00
}
2014-04-08 22:00:01 +02:00
2014-04-08 22:19:05 +02:00
// Draw points and labels
2014-04-19 09:25:40 +02:00
for ( var graph in this . graphs ) {
2014-04-08 22:00:01 +02:00
var filtered _points = points . filter ( function ( el ) { return el . graph == graph ; } ) ;
for ( var point = 0 ; point < filtered _points . length ; point ++ ) {
2014-04-19 09:25:40 +02:00
element = this . createElement ( 'circle' , { 'class' : 'point' , 'id' : 'point_' + filtered _points [ point ] . id , 'cx' : filtered _points [ point ] . x , 'cy' : filtered _points [ point ] . y , 'r' : 4 , 'fill' : '#333' , 'stroke' : this . graphs [ graph ] , 'stroke-width' : 2 } ) ;
this . g . insertBefore ( element , this . g . querySelectorAll ( '.label' ) [ 0 ] ) ;
2014-04-08 22:00:01 +02:00
if ( filtered _points [ point ] . click !== false ) {
element . onclick = filtered _points [ point ] . click ;
}
2014-04-08 22:19:05 +02:00
element . addEventListener ( 'mouseover' , function ( ) {
this . setAttribute ( 'r' , '6' ) ;
2014-04-19 09:25:40 +02:00
obj . holder . getElementById ( this . getAttribute ( 'id' ) . replace ( 'point' , 'label' ) ) . setAttribute ( 'display' , 'block' ) ;
2014-04-08 22:19:05 +02:00
} ) ;
2014-04-08 22:00:01 +02:00
if ( filtered _points [ point ] . label !== '' ) {
2014-04-19 09:25:40 +02:00
var g = this . createElement ( 'g' , { 'class' : 'label' , 'id' : 'label_' + filtered _points [ point ] . id , 'transform' : 'translate(0, ' + this . parent _holder . offsetHeight + ') scale(1, -1)' } ) ;
this . g . appendChild ( g ) ;
2014-04-08 22:00:01 +02:00
2014-04-08 22:19:05 +02:00
g . addEventListener ( 'mouseover' , function ( ) {
2014-04-19 09:25:40 +02:00
obj . holder . getElementById ( this . getAttribute ( 'id' ) . replace ( 'label' , 'point' ) ) . setAttribute ( 'r' , '6' ) ;
2014-04-08 22:19:05 +02:00
this . setAttribute ( 'display' , 'block' ) ;
} ) ;
2014-04-19 09:25:40 +02:00
element = this . createElement ( 'text' , { } ) ;
2014-04-08 22:00:01 +02:00
var text = filtered _points [ point ] . label . replace ( '</sup>' , '<sup>' ) . split ( '<sup>' ) ;
for ( var i = 0 ; i < text . length ; i ++ ) {
2014-04-19 09:25:40 +02:00
text [ i ] = text [ i ] . replace ( /(<([^>]+)>)/ig , "" ) . replace ( '%y' , this . raw _points [ filtered _points [ point ] . id ] . y ) . replace ( '%x' , this . raw _points [ filtered _points [ point ] . id ] . x ) ;
2014-04-08 22:00:01 +02:00
if ( i % 2 == 0 ) {
element . appendChild ( document . createTextNode ( text [ i ] ) ) ;
}
else {
2014-04-19 09:25:40 +02:00
var tmp = this . createElement ( 'tspan' , { 'dy' : '-5' } ) ;
2014-04-08 22:00:01 +02:00
tmp . appendChild ( document . createTextNode ( text [ i ] ) ) ;
element . appendChild ( tmp ) ;
}
}
2014-04-19 09:25:40 +02:00
path = this . createElement ( 'path' , { 'stroke' : 'black' , 'stroke-width' : 2 , 'fill' : 'white' , 'opacity' : 0.5 } ) ;
2014-04-08 22:00:01 +02:00
// Append here to have them with the good z-index, update their attributes later
g . appendChild ( path ) ;
g . appendChild ( element ) ;
var x _text = filtered _points [ point ] . x - element . getBoundingClientRect ( ) . width / 2 ;
2014-04-22 11:55:48 +02:00
var y _text = this . parent _holder . offsetHeight - filtered _points [ point ] . y - this . marginTop - this . marginBottom ;
2014-04-08 22:00:01 +02:00
var element _width = element . getBoundingClientRect ( ) . width ;
var element _height = element . getBoundingClientRect ( ) . height ;
if ( filtered _points [ point ] . x - element . getBoundingClientRect ( ) . width / 2 < 0 ) {
2014-04-22 11:58:17 +02:00
x _text = filtered _points [ point ] . x + this . marginLeft + this . marginRight ;
2014-04-19 09:25:40 +02:00
y _text = this . parent _holder . offsetHeight - filtered _points [ point ] . y + 5 ;
2014-04-08 22:00:01 +02:00
path . setAttribute ( 'd' , 'M ' + ( x _text - 5 ) + ' ' + ( y _text + 5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height / 2 + 7.5 ) + ' L ' + ( x _text - 10 ) + ' ' + ( y _text - element _height / 2 + 5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height / 2 + 2.5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text + 5 ) + ' Z' ) ;
}
2014-04-22 11:55:48 +02:00
else if ( filtered _points [ point ] . y + element . getBoundingClientRect ( ) . height + this . marginBottom + 2 > this . parent _holder . offsetHeight ) {
x _text = filtered _points [ point ] . x + this . marginLeft + this . marginRight ;
2014-04-19 09:25:40 +02:00
y _text = this . parent _holder . offsetHeight - filtered _points [ point ] . y + 5 ;
2014-04-22 11:55:48 +02:00
path . setAttribute ( 'd' , 'M ' + ( x _text - 5 ) + ' ' + ( y _text + 5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height / 2 + 7.5 ) + ' L ' + ( x _text - this . marginBottom ) + ' ' + ( y _text - element _height / 2 + 5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height / 2 + 2.5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text + 5 ) + ' Z' ) ;
2014-04-08 22:00:01 +02:00
2014-04-19 09:25:40 +02:00
if ( x _text + element _width > this . parent _holder . offsetWidth ) {
2014-04-22 11:55:48 +02:00
x _text = filtered _points [ point ] . x - element _width - this . marginLeft - this . marginRight ;
2014-04-19 09:25:40 +02:00
y _text = this . parent _holder . offsetHeight - filtered _points [ point ] . y + 5 ;
2014-04-08 22:00:01 +02:00
path . setAttribute ( 'd' , 'M ' + ( x _text - 5 ) + ' ' + ( y _text + 5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height / 2 + 2.5 ) + ' L ' + ( x _text + element _width + 10 ) + ' ' + ( y _text - element _height / 2 + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height / 2 + 7.5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text + 5 ) + ' Z' ) ;
}
}
2014-04-22 11:55:48 +02:00
else if ( filtered _points [ point ] . x + element _width / 2 + this . marginLeft + 2 > this . parent _holder . offsetWidth ) {
x _text = filtered _points [ point ] . x - element _width - this . marginLeft - this . marginRight ;
2014-04-19 09:25:40 +02:00
y _text = this . parent _holder . offsetHeight - filtered _points [ point ] . y + 5 ;
2014-04-08 22:00:01 +02:00
path . setAttribute ( 'd' , 'M ' + ( x _text - 5 ) + ' ' + ( y _text + 5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height / 2 + 2.5 ) + ' L ' + ( x _text + element _width + 10 ) + ' ' + ( y _text - element _height / 2 + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height / 2 + 7.5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text + 5 ) + ' Z' ) ;
}
else {
path . setAttribute ( 'd' , 'M ' + ( x _text - 5 ) + ' ' + ( y _text + 5 ) + ' L ' + ( x _text - 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text - element _height + 5 ) + ' L ' + ( x _text + element _width + 5 ) + ' ' + ( y _text + 5 ) + ' L ' + ( x _text + element _width / 2 + 2.5 ) + ' ' + ( y _text + 5 ) + ' L ' + ( x _text + element _width / 2 ) + ' ' + ( y _text + 10 ) + ' L ' + ( x _text + element _width / 2 - 2.5 ) + ' ' + ( y _text + 5 ) + ' Z' ) ;
}
element . setAttribute ( 'x' , x _text ) ;
element . setAttribute ( 'y' , y _text ) ;
2014-06-29 00:52:25 +02:00
2014-04-08 22:00:01 +02:00
g . setAttribute ( 'display' , 'none' ) ;
}
}
}
2014-04-06 18:28:02 +02:00
} ;