├── .gitignore ├── uglify.sh ├── MIT-LICENSE ├── es5-min.js ├── es5.js ├── json2-min.js ├── ReleaseNotes.txt ├── json2.js ├── index.html ├── ico-min.js ├── ico-es5-json2-min.js ├── README.textile └── ico.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /node_modules -------------------------------------------------------------------------------- /uglify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | uglifyjs ico.js > ico-min.js 4 | uglifyjs es5.js > es5-min.js 5 | uglifyjs json2.js > json2-min.js 6 | uglifyjs prototype.js > prototype-min.js 7 | 8 | cat es5-min.js json2-min.js ico-min.js > ico-es5-json2-min.js 9 | 10 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2011 Jean Vincent, Reverse Risk 2 | Copyright (c) 2009 Alex R. Young 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /es5-min.js: -------------------------------------------------------------------------------- 1 | // ECMAScript 5 compatibility functions 2 | // 3 | // Copyright (c) 2011, Jean Vincent 4 | // 5 | // Licensed under the MIT license 6 | // 7 | Object.keys||(Object.keys=function(e){var t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n);return t}),Object.create||(Object.create=function(e){function t(){}if(arguments.length>1)throw new Error("Object.create implementation only accepts the first parameter.");return t.prototype=e,new t}),Function.prototype.bind||(Function.prototype.bind=function(e){if(typeof this!="function")throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var t=this,n=Array.prototype.slice,r=n.call(arguments,1),i=function(){return t.apply(e||{},r.concat(n.call(arguments)))};return i.prototype=this.prototype,i}),Array.prototype.indexOf||(Array.prototype.indexOf=function(e,t){var n=this.length;if(n===0)return-1;t===undefined?t=0:t<0&&(t+=n)<0&&(t=0),t-=1;while(++t=n&&(t=n-1)}t+=1;while(--t>=0&&this[t]!==e);return t}),Array.prototype.forEach||(Array.prototype.forEach=function(e,t){t||(t=window);var n=-1;while(++n=0)n in this&&(t=e(t,this[n],n,this));return t}); -------------------------------------------------------------------------------- /es5.js: -------------------------------------------------------------------------------- 1 | // ECMAScript 5 compatibility functions 2 | // 3 | // Copyright (c) 2011, Jean Vincent 4 | // 5 | // Licensed under the MIT license 6 | // 7 | 8 | Object.keys || ( Object.keys = function( o ) { 9 | var keys = []; 10 | 11 | for ( var property in o ) if ( o.hasOwnProperty( property ) ) keys.push( property ); 12 | 13 | return keys; 14 | } ); 15 | 16 | Object.create || ( Object.create = function ( o ) { 17 | if ( arguments.length > 1 ) { 18 | throw new Error( 'Object.create implementation only accepts the first parameter.' ); 19 | } 20 | function O() {} 21 | O.prototype = o; 22 | return new O(); 23 | } ); 24 | 25 | Function.prototype.bind || ( Function.prototype.bind = function( o ) { 26 | if ( typeof this !== "function" ) { 27 | throw new TypeError( "Function.prototype.bind - what is trying to be bound is not callable" ) 28 | } 29 | var that = this 30 | , slice = Array.prototype.slice 31 | , a = slice.call( arguments, 1 ) 32 | , bound = function() { 33 | return that.apply( o || {}, a.concat( slice.call( arguments ) ) ); 34 | } 35 | ; 36 | bound.prototype = this.prototype; 37 | return bound; 38 | } ); 39 | 40 | Array.prototype.indexOf || ( Array.prototype.indexOf = function( v, i ) { 41 | var l = this.length; if ( l === 0 ) return -1; 42 | if ( i === undefined ) { 43 | i = 0 44 | } else if( i < 0 && ( i += l ) < 0 ) i = 0; 45 | i -= 1; while( ++i < l ) if ( this[ i ] === v ) return i; 46 | return -1; 47 | } ); 48 | 49 | Array.prototype.lastIndexOf || ( Array.prototype.lastIndexOf = function( v, i ) { 50 | var l = this.length; if ( l === 0 ) return -1; 51 | if ( i === undefined ) { 52 | i = l - 1 53 | } else if( i < 0 && ( i += l ) < 0 ) { 54 | return -1 55 | } else if ( i >= l ) i = l - 1; 56 | i += 1; while( --i >= 0 && this[ i ] !== v ); 57 | return i; 58 | } ); 59 | 60 | Array.prototype.forEach || ( Array.prototype.forEach = function( c, t ) { 61 | t || ( t = window ); 62 | var i = -1; while( ++i < this.length ) if ( i in this ) c.call( t, this[i], i, this ); 63 | } ); 64 | 65 | Array.prototype.every || ( Array.prototype.every = function( c, t ) { 66 | t || ( t = window ); 67 | var i = -1; while( ++i < this.length ) if ( i in this && ! c.call( t, this[i], i, this ) ) return false; 68 | return true; 69 | } ); 70 | 71 | Array.prototype.some || ( Array.prototype.some = function( c, t ) { 72 | t || ( t = window ); 73 | var i = -1; while( ++i < this.length ) if ( i in this && c.call( t, this[i], i, this ) ) return true; 74 | return false; 75 | } ); 76 | 77 | Array.prototype.map || ( Array.prototype.map = function( c, t ) { 78 | t || ( t = window ); 79 | var a = [], i = -1; while( ++i < this.length ) if ( i in this ) a [i] = c.call( t, this[i], i, this ); 80 | return a; 81 | } ); 82 | 83 | Array.prototype.filter || ( Array.prototype.filter = function( c, t ) { 84 | t || ( t = window ); 85 | var a = [], v, l = this.length, i = -1; while( ++i < l ) if ( i in this ) c.call( t, v = this[i], i, this ) && a.push( v ); 86 | return a; 87 | } ); 88 | 89 | Array.prototype.reduce || ( Array.prototype.reduce = function( c, v ) { 90 | var i = -1; if ( v === undefined ) v = this[++i]; 91 | while( ++i < this.length ) if ( i in this ) v = c( v, this[i], i, this ); 92 | return v; 93 | } ); 94 | 95 | Array.prototype.reduceRight || ( Array.prototype.reduceRight = function( c, v ) { 96 | var i = this.length; if ( v === undefined ) v = this[--i]; 97 | while( --i >= 0 ) if ( i in this ) v = c( v, this[i], i, this ); 98 | return v; 99 | } ); 100 | -------------------------------------------------------------------------------- /json2-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2011-02-23 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | *//*jslint evil: true, strict: false, regexp: false *//*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 148 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 149 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 150 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 151 | test, toJSON, toString, valueOf 152 | */// Create a JSON object only if one does not already exist. We create the 153 | // methods in a closure to avoid creating global variables. 154 | var JSON;JSON||(JSON={}),function(){"use strict";function f(e){return e<10?"0"+e:e}function quote(e){return escapable.lastIndex=0,escapable.test(e)?'"'+e.replace(escapable,function(e){var t=meta[e];return typeof t=="string"?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+e+'"'}function str(e,t){var n,r,i,s,o=gap,u,a=t[e];a&&typeof a=="object"&&typeof a.toJSON=="function"&&(a=a.toJSON(e)),typeof rep=="function"&&(a=rep.call(t,e,a));switch(typeof a){case"string":return quote(a);case"number":return isFinite(a)?String(a):"null";case"boolean":case"null":return String(a);case"object":if(!a)return"null";gap+=indent,u=[];if(Object.prototype.toString.apply(a)==="[object Array]"){s=a.length;for(n=0;n. To allow both IE7 compatibility when GCF 80 | is not installed, and GCF when installed use the following meta tag: 81 | 82 | 83 | Ico 0.95 84 | This release is a major pre-1.0 release adding many features, fixing bugs, improving the 85 | API, and DRYing up the code for improved maintainability, componentization and optimization. 86 | 87 | This release contains non-backward compatible (NBC) changes listed bellow as "NBC API change". 88 | These changes were necessary to ensure the most logical and intuitive API for version 1.0. 89 | After version 1.0 all API changes will ensure backward compatibility. 90 | 91 | Refer to the documentation for up-to-date API information. 92 | 93 | The complete list of changes: 94 | Format values with units when showing values on mouseover on dots, bars and average line. 95 | 96 | NBC API Change: Allow to specify bullet graphs target attributes: length, color, stroke-width. The 97 | previous way of specifying target_color as a separate parameter is no longer supported. 98 | 99 | Fix VML text positioning so that it matches SVG's baseline and avoid text getting out of its 100 | bounding box. 101 | 102 | Boost IE8 VML rendering performance by adding IE7 compatibility meta-header in index.html: 103 | 104 | Rendering speed is improved by 30% to 40% with IE7 compatibility mode. This only affects 105 | IE and is ignored by all other browsers. With this improvement, IE8 is only twice slower than 106 | Firefox and 2.7 times slower than Safari. 107 | 108 | Revisit value labels calculation to properly calculate the maximum number of steps based on actual 109 | min and max text with optional units and one spacing character for adjacent (<30 degrees) and 1.5 110 | character hight for stacked labels (>60 degrees). 111 | 112 | Revisit longest label calculation to find the actual largest label bounding box by drawing all labels 113 | at the time of calculation. For optimization purposes, drawn labels are not removed and only 114 | translated, anchored and rotated in draw(). 115 | 116 | Try / Catch errors in components to ease component debugging. 117 | 118 | Graph font attributes inherit from containing DOM element by default enabling CSS configuration of 119 | graph font attributes. Labels fonts inherit from graph but can now be set independently for labels 120 | and value labels. Allow font-size attributes to be set using the entire range of units allowed in 121 | CSS. 122 | 123 | Draw series from graph coordinates instead of canvas coordinates to DRY-up the code a bit more. 124 | Eventually we should be able to provide drawing functions independent of orientation. 125 | 126 | Fix cubic Bézier control points to allow smooth transitions using the previous to last and 127 | current points to calculate the first control point and previous and next points to calculate the 128 | second control point. The current implementation provided improbable curves when points followed 129 | the same upward or downward trend. 130 | 131 | Enable configuration of remaining line graphs attributes. Change default dot radius to 3 132 | and focus radius to 6. 133 | 134 | Bar graphs: prevent full bar overlap with multiple series 135 | 136 | Allow to specify grid and grid_attributes in Labels or ValueLabels while still allowing the 137 | global attributes. Attributes defined at the component level have higher precedence, allowing 138 | to override global attributes. This allows to specify label and value labels grid independently. 139 | 140 | Set minimum bar width to 5 pixels. 141 | 142 | Change calculate_value_step() to label_slots_count() to unify labels step calculation. 143 | 144 | DRY this.graph.x.start_offset, this.bar_width, and this.bar_base calculations for bars into Ico.Base. 145 | Eventually this will go into a bar serie class. 146 | 147 | NBC API change: Allow marker_size to be defined independently in labels and value_labels 148 | components hash, marker_size is no longer available in basic graph options. 149 | 150 | Exchange direction, labels. padding, and angle, attributes between this.x and this.y 151 | to improve code clarity and maintainability, thus removing one parameter from 152 | Ico.Component.Labels.draw_labels_grid() 153 | 154 | Allow more or less value labels according to orientation and angle. 155 | 156 | Workaround IE bug when displaying text (e.g. labels) in Raphael paper. This workaround 157 | requires to use a
instead of a . 158 | 159 | Fix value label precision for values with zero decimals. 160 | 161 | Allow up to 2 significant digits value steps limited to the following rounded-up values: 162 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90 163 | These steps are chosen to that one of their multiples inferior or equal to 10 is part of the set of 164 | allowed steps. 165 | 166 | Series can now be provided as an Array of Arrays. This new method allows to fix the drawing order 167 | of series which is guarantied to be first to last. This allows to control which serie is drawn on 168 | top of which other serie. The other methods ( single value, array of values and hash of arrays ) 169 | are still available but the hash of arrays method is deprecated and will be removed before version 1.0. 170 | 171 | NBC API change: Move units from value_labels component to Ico.Base, because units are linked to 172 | series values and should be available for tooltips even if no value label is displayed. 173 | 174 | Fix rounding error between highlight circle center and sparkline dot. 175 | 176 | Highlight bar in sparkbars instead of dot. 177 | 178 | Unify draw_series() for all graphs in Ico.Base. 179 | 180 | Migrate much of Ico.BaseGraph to Ico.Base to unify behavior and enable all components on all graphs. 181 | 182 | Allow value labels for bullet graphs. 183 | 184 | Rename Ico.VERSION to Ico.Version. 185 | 186 | Ico 0.94 187 | This pre-1.0 stable release contains many bug fixes, new features and API changes from the original 188 | version by Alex Young. 189 | 190 | The complete list of changes: 191 | Upgrade Raphael to version 1.2.8, remove path-methods as recommended from version 1.0. 192 | 193 | Allow to clear grpah canvas, typically to redraw in the same div.. 194 | 195 | Display value label units, optional units_position allows to set units before values. 196 | 197 | Define components layers for calculations and drawing. 198 | 199 | Allow graph area background to display bad, satisfactory and good attributes as a gradient. 200 | 201 | Implement Stephen Few Bullet Graphs. 202 | 203 | Reorganize code, componentize grpah elements, remove redundant code. 204 | 205 | Allow all (path, labels, grid, background, meanline, ...)-attributes configuration. 206 | 207 | Display mouse pointer as a cross when in graph area. 208 | 209 | Display data values and mean value on mouse over in status area. 210 | 211 | Change data and meanline stroke attribute on mouse over. 212 | 213 | Provide svg_path class method to round pixel values and add separators only when needed. 214 | 215 | Test suite: 216 | Perform all tests on IE 8.0, Firefox 3.5, Opera 10.0, Chrome, Safari 217 | 218 | API Changes 219 | 220 | Fix and optimize value labels range / start and stop calculations: 221 | - Always display zero value when in value range 222 | - Find best (step, number of steps) pairs to minimize display range and emphasize data values 223 | - Design significant digits calculation class method with options rounding anomymous function 224 | and optional string output to prevent float precision conversion errors 225 | - Build value labels as strings instead of float 226 | - Fix value step to one significant digit 227 | - Fix value labels to two significant digits 228 | - Fix value labels precision to maximum precision of all value labels 229 | - Determine number of maximum steps based on actual graph width and labels size 230 | - Provide scale attribute to scale values to actual graph height 231 | 232 | Fix bars base value to start at zero when in range and display negative values in reverse direction. 233 | 234 | Allow to draw labels at an angle. 235 | 236 | Design function to properly calculate text display size in pixels. 237 | 238 | Fix bottom and left offset calculations using actual text size. 239 | 240 | Remove sort used to determine longuest labels, iterate through array instead. 241 | 242 | Fix sort comparison function to determine longuest labels. 243 | 244 | Numerous other bug fixes. 245 | -------------------------------------------------------------------------------- /json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2011-02-23 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false, regexp: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | var JSON; 163 | if (!JSON) { 164 | JSON = {}; 165 | } 166 | 167 | (function () { 168 | "use strict"; 169 | 170 | function f(n) { 171 | // Format integers to have at least two digits. 172 | return n < 10 ? '0' + n : n; 173 | } 174 | 175 | if (typeof Date.prototype.toJSON !== 'function') { 176 | 177 | Date.prototype.toJSON = function (key) { 178 | 179 | return isFinite(this.valueOf()) ? 180 | this.getUTCFullYear() + '-' + 181 | f(this.getUTCMonth() + 1) + '-' + 182 | f(this.getUTCDate()) + 'T' + 183 | f(this.getUTCHours()) + ':' + 184 | f(this.getUTCMinutes()) + ':' + 185 | f(this.getUTCSeconds()) + 'Z' : null; 186 | }; 187 | 188 | String.prototype.toJSON = 189 | Number.prototype.toJSON = 190 | Boolean.prototype.toJSON = function (key) { 191 | return this.valueOf(); 192 | }; 193 | } 194 | 195 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 196 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | gap, 198 | indent, 199 | meta = { // table of character substitutions 200 | '\b': '\\b', 201 | '\t': '\\t', 202 | '\n': '\\n', 203 | '\f': '\\f', 204 | '\r': '\\r', 205 | '"' : '\\"', 206 | '\\': '\\\\' 207 | }, 208 | rep; 209 | 210 | 211 | function quote(string) { 212 | 213 | // If the string contains no control characters, no quote characters, and no 214 | // backslash characters, then we can safely slap some quotes around it. 215 | // Otherwise we must also replace the offending characters with safe escape 216 | // sequences. 217 | 218 | escapable.lastIndex = 0; 219 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 220 | var c = meta[a]; 221 | return typeof c === 'string' ? c : 222 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 223 | }) + '"' : '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : gap ? 307 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 308 | '[' + partial.join(',') + ']'; 309 | gap = mind; 310 | return v; 311 | } 312 | 313 | // If the replacer is an array, use it to select the members to be stringified. 314 | 315 | if (rep && typeof rep === 'object') { 316 | length = rep.length; 317 | for (i = 0; i < length; i += 1) { 318 | if (typeof rep[i] === 'string') { 319 | k = rep[i]; 320 | v = str(k, value); 321 | if (v) { 322 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 323 | } 324 | } 325 | } 326 | } else { 327 | 328 | // Otherwise, iterate through all of the keys in the object. 329 | 330 | for (k in value) { 331 | if (Object.prototype.hasOwnProperty.call(value, k)) { 332 | v = str(k, value); 333 | if (v) { 334 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 335 | } 336 | } 337 | } 338 | } 339 | 340 | // Join all of the member texts together, separated with commas, 341 | // and wrap them in braces. 342 | 343 | v = partial.length === 0 ? '{}' : gap ? 344 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 345 | '{' + partial.join(',') + '}'; 346 | gap = mind; 347 | return v; 348 | } 349 | } 350 | 351 | // If the JSON object does not yet have a stringify method, give it one. 352 | 353 | if (typeof JSON.stringify !== 'function') { 354 | JSON.stringify = function (value, replacer, space) { 355 | 356 | // The stringify method takes a value and an optional replacer, and an optional 357 | // space parameter, and returns a JSON text. The replacer can be a function 358 | // that can replace values, or an array of strings that will select the keys. 359 | // A default replacer method can be provided. Use of the space parameter can 360 | // produce text that is more easily readable. 361 | 362 | var i; 363 | gap = ''; 364 | indent = ''; 365 | 366 | // If the space parameter is a number, make an indent string containing that 367 | // many spaces. 368 | 369 | if (typeof space === 'number') { 370 | for (i = 0; i < space; i += 1) { 371 | indent += ' '; 372 | } 373 | 374 | // If the space parameter is a string, it will be used as the indent string. 375 | 376 | } else if (typeof space === 'string') { 377 | indent = space; 378 | } 379 | 380 | // If there is a replacer, it must be a function or an array. 381 | // Otherwise, throw an error. 382 | 383 | rep = replacer; 384 | if (replacer && typeof replacer !== 'function' && 385 | (typeof replacer !== 'object' || 386 | typeof replacer.length !== 'number')) { 387 | throw new Error('JSON.stringify'); 388 | } 389 | 390 | // Make a fake root object containing our value under the key of ''. 391 | // Return the result of stringifying the value. 392 | 393 | return str('', {'': value}); 394 | }; 395 | } 396 | 397 | 398 | // If the JSON object does not yet have a parse method, give it one. 399 | 400 | if (typeof JSON.parse !== 'function') { 401 | JSON.parse = function (text, reviver) { 402 | 403 | // The parse method takes a text and an optional reviver function, and returns 404 | // a JavaScript value if the text is a valid JSON text. 405 | 406 | var j; 407 | 408 | function walk(holder, key) { 409 | 410 | // The walk method is used to recursively walk the resulting structure so 411 | // that modifications can be made. 412 | 413 | var k, v, value = holder[key]; 414 | if (value && typeof value === 'object') { 415 | for (k in value) { 416 | if (Object.prototype.hasOwnProperty.call(value, k)) { 417 | v = walk(value, k); 418 | if (v !== undefined) { 419 | value[k] = v; 420 | } else { 421 | delete value[k]; 422 | } 423 | } 424 | } 425 | } 426 | return reviver.call(holder, key, value); 427 | } 428 | 429 | 430 | // Parsing happens in four stages. In the first stage, we replace certain 431 | // Unicode characters with escape sequences. JavaScript handles many characters 432 | // incorrectly, either silently deleting them, or treating them as line endings. 433 | 434 | text = String(text); 435 | cx.lastIndex = 0; 436 | if (cx.test(text)) { 437 | text = text.replace(cx, function (a) { 438 | return '\\u' + 439 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 440 | }); 441 | } 442 | 443 | // In the second stage, we run the text against regular expressions that look 444 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 445 | // because they can cause invocation, and '=' because it can cause mutation. 446 | // But just to be safe, we want to reject all unexpected forms. 447 | 448 | // We split the second stage into 4 regexp operations in order to work around 449 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 450 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 451 | // replace all simple value tokens with ']' characters. Third, we delete all 452 | // open brackets that follow a colon or comma or that begin the text. Finally, 453 | // we look to see that the remaining characters are only whitespace or ']' or 454 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 455 | 456 | if (/^[\],:{}\s]*$/ 457 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 458 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 459 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 460 | 461 | // In the third stage we use the eval function to compile the text into a 462 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 463 | // in JavaScript: it can begin a block or an object literal. We wrap the text 464 | // in parens to eliminate the ambiguity. 465 | 466 | j = eval('(' + text + ')'); 467 | 468 | // In the optional fourth stage, we recursively walk the new structure, passing 469 | // each name/value pair to a reviver function for possible transformation. 470 | 471 | return typeof reviver === 'function' ? 472 | walk({'': j}, '') : j; 473 | } 474 | 475 | // If the text is not JSON parseable, then a SyntaxError is thrown. 476 | 477 | throw new SyntaxError('JSON.parse'); 478 | }; 479 | } 480 | }()); 481 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ico / Raphael Test Suite 8 | 94 | 95 | 102 | 103 | 104 | 105 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
123 |

This Ico version test page is displayed using 124 | Raphaël.js version 125 | in mode 126 |

127 |

Here is a sparkline:, 128 | a sparkbar: with the same values. 129 |

130 |

131 | A bullet graph:. 132 |

133 |

Bullet graph with negative value, zero base and zero target:

134 | 137 |
138 | 139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | 150 |
151 |

A line of two dependent graphs on the same value range and legend

152 |

Click legend labels bellow to toggle (show/hide) series:

153 |

154 | This Year Performance 155 | Last Year Performance 156 | 5 Months Moving Average 157 | Intermitent 158 |

159 |
160 |
161 | 162 |
163 |

6 Horinzontal Bar Graphs connected through their grids, the left-most holding only the labels

164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | 172 |
173 |

Three non-continuous lines with no curves

174 |
175 |
176 | 177 | 536 | 537 | -------------------------------------------------------------------------------- /ico-min.js: -------------------------------------------------------------------------------- 1 | /* Ico Graph Prototype/Raphael library 2 | * 3 | * Copyright (c) 2009, Alex R. Young 4 | * Copyright (c) 2009-2012 Reverse Risk 5 | * Copyright (c) 2009-2013 Jean Vincent 6 | * 7 | * Licensed under the MIT license: http://github.com/uiteoi/ico/blob/master/MIT-LICENSE 8 | */"use strict";(function(e){var t=e.Ico={Version:"0.98.26",extend:function(e,t){for(var n in t)e[n]=t[n];return e}},n=!0,r;typeof debug=="function"?r=function(e){debug("Ico, "+e)}:typeof console=="object"&&typeof console.log=="function"?r=function(e){function n(e,t){return e="000"+e,e.substr(e.length-t)}var t=new Date;console.log(n(t.getMinutes(),2)+":"+n(t.getSeconds(),2)+"."+n(t.getMilliseconds(),3)+" - "+"Ico, "+e)}:n=!1,t.extend(t,{isArray:function(e){return typeof e=="object"&&e instanceof Array},Class:{add_methods:t.extend,create:function(e){var n=function(){this.initialize.apply(this,arguments)};n.subclasses=[];var r=-1;typeof e=="function"?(r+=1,(n.superclass=e).subclasses.push(n),n.prototype=Object.create(e.prototype)):n.superclass=null,n.prototype.constructor=n;while(++r0?e=0:i<-t?t+=i/2:t<0&&(t=0),[e,t]},series_min_max:function(e,i){n&&r("series_min_max: series length: "+e.length);var s,o=[],u=Array.prototype.concat.apply([],e).map(function(e){return e===s||e===null?null:(o.push(e),e)}),a=o.length;n&&r("series_min_max: no_null_values length: "+a);var f=a?[t.significant_digits_round(Math.min.apply(Math,o),2,Math.floor),t.significant_digits_round(Math.max.apply(Math,o),2,Math.ceil)]:[0,0];return i||(f=t.adjust_min_max.apply(t,f)),f.push(u),f},moving_average:function(e,n,r){var i=[],s=-1,o=-1,u;r&&t.isArray(u=r.previous_values)&&(e=u.concat(e),s+=u.length);for(var a=e.length;++s=l;)f+=e[s-l];i[++o]=t.significant_digits_round(f/l,3,Math.round,!0)}return i}}),t.Base=t.Class.create({initialize:function(e,t,i){return n&&r("Ico.Base.initialize( "+e+", "+JSON.stringify(t)+", "+JSON.stringify(i)+" )"),typeof e=="string"&&(e=document.getElementById(e)),this.element=e,this.series=t||[[0]],this.set_defaults(),this.set_series(),this.process_options(i),this.set_raphael(i=this.options),this.calculate(i),this.draw(i),n&&r("Ico.Base.initialize(), end"),this},set_series:function(){if(t.isArray(this.series))n&&r("set_series Array, element 0 is Array:"+t.isArray(this.series[0])),t.isArray(this.series[0])||(this.series=[this.series]);else{if(typeof this.series!="number")throw"Wrong type for series";this.series=[[this.series]]}this.data_samples=Math.max.apply(Math,this.series.map(function(e){return n&&r("serie length: "+e.length),e.length})),n&&r("set_series, data samples: "+this.data_samples);var e=t.series_min_max(this.series,!0);this.max=e[1],this.min=e[0],this.all_values=e[2],this.series_shapes=[]},set_defaults:function(){var e=this.options={width:parseInt(this.element.offsetWidth)-1,height:parseInt(this.element.offsetHeight)-1,x_padding_left:0,x_padding_right:0,y_padding_top:0,y_padding_bottom:0,color:t.get_style(this.element,"color"),mouseover_attributes:{stroke:"red"},units:"",units_position:1};n&&r("options: width: "+e.width+", height: "+e.height+", color: "+e.color)},process_options:function(e){n&&r("Ico.Base.process_options()");var i=this;e&&(e=t.extend(i.options,e)),typeof e.min!="undefined"&&(i.min=Math.min(i.min,e.min)),typeof e.max!="undefined"&&(i.max=Math.max(i.max,e.max)),i.range=i.max-i.min,i.x={direction:[1,0],start_offset:0,width:e.width},i.y={direction:[0,-1],start_offset:0,width:e.height},i.x.other=i.y,i.y.other=i.x,i.x.padding=[e.x_padding_left,e.x_padding_right],i.y.padding=[e.y_padding_top,e.y_padding_bottom],i.orientation=e.orientation||i.orientation||0,i.y_direction=i.orientation?-1:1,i.graph=i.orientation?{x:i.y,y:i.x}:{x:i.x,y:i.y},i.components=[];for(var s in t.Component.components){if(!t.Component.components.hasOwnProperty(s))continue;var o=i.options[s+"_attributes"],e=i.options[s],u=t.Component.components[s];e===!0&&o&&(e=i.options[s]=o);if(e){var a=u[1];i.components[a]||(i.components[a]=[]);try{i.components[a].push(i[s]=new u[0](i,e))}catch(f){i.error=f,n&&r("process_options(), exception: "+f)}}}},get_font:function(){var e=this.options;return this.font?this.font:(this.font={"font-family":t.get_style(this.element,"font-family"),"font-size":e.font_size||t.get_style(this.element,"font-size")||10,fill:t.get_style(this.element,"color")||"#666",stroke:"none"},e&&t.extend(this.font,e),this.font)},set_raphael:function(e){if(this.paper)return;this.paper=Raphael(this.element,e.width,e.height),this.svg=!(this.vml=Raphael.vml)},clear:function(){return this.paper&&(this.components_call("clear",this.options),this.paper.remove(),this.paper=null),this},calculate:function(e){n&&r("Ico.Base.calculate()"),this.paper||this.set_raphael(e),this.components_call("calculate",e),this.calculate_graph_len(this.graph.x),this.calculate_graph_len(this.graph.y),this.scale=this.y_direction*this.graph.y.len/this.range,this.graph.x.step=this.graph.x.len/this.label_slots_count(),this.x.start=this.x.padding[0],this.x.stop=this.x.start+this.x.len,this.y.stop=this.y.padding[0],this.y.start=this.y.stop+this.y.len},calculate_graph_len:function(e){e.len=e.width-e.padding[0]-e.padding[1]},calculate_bars:function(e){this.bar_width=this.graph.x.step-e.bar_padding,this.bar_width<5&&(this.bar_width=5),this.graph.x.start_offset=this.y_direction*this.graph.x.step/2,this.bar_base=this.graph.y.start-this.scale*((this.max<=0?this.max:0)-(this.min<0?this.min:0))},format_value:function(e,n){e!=0&&typeof n!="number"&&(n=t.root(e,1e3),n&&(e/=Math.pow(1e3,n)),e=t.significant_digits_round(e,3,Math.round,!0).toString()),e=""+e,n&&(e+=["","k","M","G","T","P","E","Z","Y"][n]||"e"+3*n);var r=this.options;return r.units?r.units_position?e+r.units:r.units+e:e},draw:function(e){return n&&r("Ico.Base.draw()"),this.paper||this.set_raphael(e),this.components_call("draw",e),this.draw_series(e),this},draw_series:function(e){for(var t=-1;++t1&&(e.bars_overlap=1)},calculate:function(e){t.BaseGraph.prototype.calculate.call(this,e),this.calculate_bars(e),e=this.bars_overlap=e.bars_overlap;var n=this.series.length,r=this.bars_width=this.bar_width/(n-(n-1)*e);this.bars_step=r*(1-e)},label_slots_count:function(){return this.data_samples},draw_value:function(e,n,r,i,s,o,u){var a=u.series_attributes[s],f=this.bars_width,l=this.bar_base,c;return Math.abs(r-l)<2&&(r=l+(r>=l?2:-2)),n+=this.bars_step*s-this.bar_width/2,this.show_label_onmouseover(c=this.paper.path(t.svg_path(this.orientation?["M",r,n,"H",l,"v",f,"H",r,"z"]:["M",n,r,"V",l,"h",f,"V",r,"z"])).attr(a),i,a,s,e),o.push(c),""}}),t.HorizontalBarGraph=t.Class.create(t.BarGraph,{set_defaults:function(){t.BarGraph.prototype.set_defaults.call(this),this.orientation=1}}),t.Component=t.Class.create({initialize:function(e,i){t.extend(this,{p:e,graph:e.graph,x:e.x,y:e.y,orientation:e.orientation});if(t.isArray(i))i={values:i};else if(typeof i=="number"||typeof i=="string")i={value:i};i=this.options=this.defaults?t.extend(this.defaults(e),i):i,n&&r("Ico.Component::initialize(), o: "+JSON.stringify(i)),this.process_options&&this.process_options(i,e)}}),t.Component.components={},t.Component.Background=t.Class.create(t.Component,{defaults:function(){return{corners:!0}},process_options:function(e){e.color&&!e.attributes&&(e.attributes={stroke:"none",fill:e.color}),e.attributes&&(e.color=e.attributes.fill),e.corners===!0&&(e.corners=Math.round(this.p.options.height/20))},draw:function(e,t){var n=t.options;this.shape=t.paper.rect(0,0,n.width,n.height,e.corners).attr(e.attributes)}}),t.Component.components.background=[t.Component.Background,0],t.Component.StatusBar=t.Class.create(t.Component,{defaults:function(){return{attributes:{"text-anchor":"end"}}},draw:function(e,t){var n=t.options,r=this.y;this.shape=t.paper.text(e.x||n.width-10,e.y||(r?r.padding[0]:n.height)/2,"").hide().attr(e.attributes)}}),t.Component.components.status_bar=[t.Component.StatusBar,2],t.Component.MousePointer=t.Class.create(t.Component,{defaults:function(){return{attributes:{stroke:"#666","stroke-dasharray":"--"}}},draw:function(e,n){if(!t.viewport_offset)return;var r=this.shape=n.paper.path().attr(e.attributes).hide(),i=this.x,s=this.y,o=n.element;o.onmousemove=function(e){e=e||window.event;var n=t.viewport_offset(o),u=e.clientX-n[0],a=e.clientY-n[1];u>=i.start&&u<=i.stop&&a>=s.stop&&a<=s.start?r.attr({path:t.svg_path(["M",i.start,a,"h",i.len,"M",u,s.stop,"v",s.len])}).show():r.hide()},o.onmouseout=function(){r.hide()}}}),t.Component.components.mouse_pointer=[t.Component.MousePointer,2],t.Component.GraphBackground=t.Class.create(t.Component,{defaults:function(){return{key_colors:["#aaa","#ccc","#eee"],key_values:[50,75],colors_transition:0}},draw:function(e,t){var n=this.x,r=this.y,i=t.range,s=e.colors_transition/i,o=100/i-s,u=e.key_colors,a=u.length,f=e.key_values,l=f.length,c=this.orientation?"0":"90";a>l+1&&(a=l+1);for(var h,p=0,d=-1;++d0^n.position?"start":"end"}else{var n=.6*f+s;e.f=[i*n,r*n],o[1]=a[1]*1.1+s,e.anchor="middle"}}var p=t^this.orientation^n.position;n.add_padding?e.other.padding[p]+=o[1]:e.other.padding[p]n&&(t.bbox=[n=e.width,e.height])}),this.bbox},clear:function(){this.labels=null},text_size:function(e,t){var n=this.p.paper.text(10,10,"").attr(t).attr({text:e}),r,i;return i=n.getBBox(),r=[i.width,i.height],n.remove(),r},draw:function(e){this.draw_labels_grid(this.graph.x,e)},draw_labels_grid:function(e,n){var r=this,i=e.direction[0],s=e.direction[1],o=e.step,u=r.x.start+r.x.start_offset*i,a=r.y.start-r.y.start_offset*s,f=n.marker_size,l=e.f[0],c=e.f[1],h=e.angle,p=this.p.paper,d=n.grid,v=[];if(d){var m=r[i?"y":"x"].len,g=0,y=0,b=e.labels,w=[];if(d.through){m+=e.padding[1]+10;var E=-e.padding[0]-this.bbox[0]-f-20;i?y=E:g=E}}i?(f="v"+f,m="v-"+m):(f="h-"+f,m="h"+m,h+=90),r.labels||this.get_labels_bounding_boxes(e);for(var S=-1,x=r.labels.length;++S0){var u=Math.round(o*i/s);u==0?u=1:u==o&&(u-=1);var a=o-u,f=t.significant_digits_round(Math.max(i/u,-e/a),2,function(e){e=Math.ceil(e);if(e<=10)return e;if(e<=12)return 12;var t;return e<=54?(t=e%5)?e-t+5:e:(t=e%10)?e-t+10:e});e=-f*a,i=f*u}else{var f=t.significant_digits_round(s/o,1,Math.ceil);i<=0?e=i-f*o:e>=0&&(i=e+f*o)}return{min:e,max:i,spaces:o,step:f,waste:o*f-s}}n&&r("Ico.Component.ValueLabels.calculate()");var s=this,o=i.max,u=i.min,a=o-u,f=e.spaces,l;i.calculate_graph_len(this.graph.y);if(!f){var c=Math.abs(e.angle),h;this.orientation&&c<30||!this.orientation&&c>60?h=Math.max.apply(Math,[u,o].map(function(e){return s.text_size("0"+t.significant_digits_round(e,3,Math.round,!0)+s.p.options.units,s.font)[0]})):h=1.5*this.text_size("0",this.font)[1];if(h===0)throw new Error("Ico.Component.ValueLabels(), min_step is zero, canvas is possibly not active");f=Math.round(this.graph.y.len/h);if(f>2){var p=a,d=f;for(var v=1;++v<=d;)l=T(u,o,a,v),l.waste<=p&&(p=l.waste,f=v)}}l=T(u,o,a,f),i.range=(i.max=l.max)-(i.min=l.min);var m=t.root(l.step*l.spaces,1e3);if(m){var g=Math.pow(1e3,m);l.step/=g,l.min/=g}var y=e.values=[],b=0;for(var w=l.min,E=-1;++E<=l.spaces;w+=l.step){var S=t.significant_digits_round(w,3,Math.round,!0).toString(),x=(S+".").split(".")[1].length;x>b&&(b=x),y.push(S)}y.forEach(function(e,t){var n=(e+".").split(".")[1].length;n1)throw new Error("Object.create implementation only accepts the first parameter.");return t.prototype=e,new t}),Function.prototype.bind||(Function.prototype.bind=function(e){if(typeof this!="function")throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var t=this,n=Array.prototype.slice,r=n.call(arguments,1),i=function(){return t.apply(e||{},r.concat(n.call(arguments)))};return i.prototype=this.prototype,i}),Array.prototype.indexOf||(Array.prototype.indexOf=function(e,t){var n=this.length;if(n===0)return-1;t===undefined?t=0:t<0&&(t+=n)<0&&(t=0),t-=1;while(++t=n&&(t=n-1)}t+=1;while(--t>=0&&this[t]!==e);return t}),Array.prototype.forEach||(Array.prototype.forEach=function(e,t){t||(t=window);var n=-1;while(++n=0)n in this&&(t=e(t,this[n],n,this));return t});/* 8 | http://www.JSON.org/json2.js 9 | 2011-02-23 10 | 11 | Public Domain. 12 | 13 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 14 | 15 | See http://www.JSON.org/js.html 16 | 17 | 18 | This code should be minified before deployment. 19 | See http://javascript.crockford.com/jsmin.html 20 | 21 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 22 | NOT CONTROL. 23 | 24 | 25 | This file creates a global JSON object containing two methods: stringify 26 | and parse. 27 | 28 | JSON.stringify(value, replacer, space) 29 | value any JavaScript value, usually an object or array. 30 | 31 | replacer an optional parameter that determines how object 32 | values are stringified for objects. It can be a 33 | function or an array of strings. 34 | 35 | space an optional parameter that specifies the indentation 36 | of nested structures. If it is omitted, the text will 37 | be packed without extra whitespace. If it is a number, 38 | it will specify the number of spaces to indent at each 39 | level. If it is a string (such as '\t' or ' '), 40 | it contains the characters used to indent at each level. 41 | 42 | This method produces a JSON text from a JavaScript value. 43 | 44 | When an object value is found, if the object contains a toJSON 45 | method, its toJSON method will be called and the result will be 46 | stringified. A toJSON method does not serialize: it returns the 47 | value represented by the name/value pair that should be serialized, 48 | or undefined if nothing should be serialized. The toJSON method 49 | will be passed the key associated with the value, and this will be 50 | bound to the value 51 | 52 | For example, this would serialize Dates as ISO strings. 53 | 54 | Date.prototype.toJSON = function (key) { 55 | function f(n) { 56 | // Format integers to have at least two digits. 57 | return n < 10 ? '0' + n : n; 58 | } 59 | 60 | return this.getUTCFullYear() + '-' + 61 | f(this.getUTCMonth() + 1) + '-' + 62 | f(this.getUTCDate()) + 'T' + 63 | f(this.getUTCHours()) + ':' + 64 | f(this.getUTCMinutes()) + ':' + 65 | f(this.getUTCSeconds()) + 'Z'; 66 | }; 67 | 68 | You can provide an optional replacer method. It will be passed the 69 | key and value of each member, with this bound to the containing 70 | object. The value that is returned from your method will be 71 | serialized. If your method returns undefined, then the member will 72 | be excluded from the serialization. 73 | 74 | If the replacer parameter is an array of strings, then it will be 75 | used to select the members to be serialized. It filters the results 76 | such that only members with keys listed in the replacer array are 77 | stringified. 78 | 79 | Values that do not have JSON representations, such as undefined or 80 | functions, will not be serialized. Such values in objects will be 81 | dropped; in arrays they will be replaced with null. You can use 82 | a replacer function to replace those with JSON values. 83 | JSON.stringify(undefined) returns undefined. 84 | 85 | The optional space parameter produces a stringification of the 86 | value that is filled with line breaks and indentation to make it 87 | easier to read. 88 | 89 | If the space parameter is a non-empty string, then that string will 90 | be used for indentation. If the space parameter is a number, then 91 | the indentation will be that many spaces. 92 | 93 | Example: 94 | 95 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 96 | // text is '["e",{"pluribus":"unum"}]' 97 | 98 | 99 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 100 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 101 | 102 | text = JSON.stringify([new Date()], function (key, value) { 103 | return this[key] instanceof Date ? 104 | 'Date(' + this[key] + ')' : value; 105 | }); 106 | // text is '["Date(---current time---)"]' 107 | 108 | 109 | JSON.parse(text, reviver) 110 | This method parses a JSON text to produce an object or array. 111 | It can throw a SyntaxError exception. 112 | 113 | The optional reviver parameter is a function that can filter and 114 | transform the results. It receives each of the keys and values, 115 | and its return value is used instead of the original value. 116 | If it returns what it received, then the structure is not modified. 117 | If it returns undefined then the member is deleted. 118 | 119 | Example: 120 | 121 | // Parse the text. Values that look like ISO date strings will 122 | // be converted to Date objects. 123 | 124 | myData = JSON.parse(text, function (key, value) { 125 | var a; 126 | if (typeof value === 'string') { 127 | a = 128 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 129 | if (a) { 130 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 131 | +a[5], +a[6])); 132 | } 133 | } 134 | return value; 135 | }); 136 | 137 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 138 | var d; 139 | if (typeof value === 'string' && 140 | value.slice(0, 5) === 'Date(' && 141 | value.slice(-1) === ')') { 142 | d = new Date(value.slice(5, -1)); 143 | if (d) { 144 | return d; 145 | } 146 | } 147 | return value; 148 | }); 149 | 150 | 151 | This is a reference implementation. You are free to copy, modify, or 152 | redistribute. 153 | *//*jslint evil: true, strict: false, regexp: false *//*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 154 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 155 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 156 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 157 | test, toJSON, toString, valueOf 158 | */// Create a JSON object only if one does not already exist. We create the 159 | // methods in a closure to avoid creating global variables. 160 | var JSON;JSON||(JSON={}),function(){"use strict";function f(e){return e<10?"0"+e:e}function quote(e){return escapable.lastIndex=0,escapable.test(e)?'"'+e.replace(escapable,function(e){var t=meta[e];return typeof t=="string"?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+e+'"'}function str(e,t){var n,r,i,s,o=gap,u,a=t[e];a&&typeof a=="object"&&typeof a.toJSON=="function"&&(a=a.toJSON(e)),typeof rep=="function"&&(a=rep.call(t,e,a));switch(typeof a){case"string":return quote(a);case"number":return isFinite(a)?String(a):"null";case"boolean":case"null":return String(a);case"object":if(!a)return"null";gap+=indent,u=[];if(Object.prototype.toString.apply(a)==="[object Array]"){s=a.length;for(n=0;n0?e=0:i<-t?t+=i/2:t<0&&(t=0),[e,t]},series_min_max:function(e,i){n&&r("series_min_max: series length: "+e.length);var s,o=[],u=Array.prototype.concat.apply([],e).map(function(e){return e===s||e===null?null:(o.push(e),e)}),a=o.length;n&&r("series_min_max: no_null_values length: "+a);var f=a?[t.significant_digits_round(Math.min.apply(Math,o),2,Math.floor),t.significant_digits_round(Math.max.apply(Math,o),2,Math.ceil)]:[0,0];return i||(f=t.adjust_min_max.apply(t,f)),f.push(u),f},moving_average:function(e,n,r){var i=[],s=-1,o=-1,u;r&&t.isArray(u=r.previous_values)&&(e=u.concat(e),s+=u.length);for(var a=e.length;++s=l;)f+=e[s-l];i[++o]=t.significant_digits_round(f/l,3,Math.round,!0)}return i}}),t.Base=t.Class.create({initialize:function(e,t,i){return n&&r("Ico.Base.initialize( "+e+", "+JSON.stringify(t)+", "+JSON.stringify(i)+" )"),typeof e=="string"&&(e=document.getElementById(e)),this.element=e,this.series=t||[[0]],this.set_defaults(),this.set_series(),this.process_options(i),this.set_raphael(i=this.options),this.calculate(i),this.draw(i),n&&r("Ico.Base.initialize(), end"),this},set_series:function(){if(t.isArray(this.series))n&&r("set_series Array, element 0 is Array:"+t.isArray(this.series[0])),t.isArray(this.series[0])||(this.series=[this.series]);else{if(typeof this.series!="number")throw"Wrong type for series";this.series=[[this.series]]}this.data_samples=Math.max.apply(Math,this.series.map(function(e){return n&&r("serie length: "+e.length),e.length})),n&&r("set_series, data samples: "+this.data_samples);var e=t.series_min_max(this.series,!0);this.max=e[1],this.min=e[0],this.all_values=e[2],this.series_shapes=[]},set_defaults:function(){var e=this.options={width:parseInt(this.element.offsetWidth)-1,height:parseInt(this.element.offsetHeight)-1,x_padding_left:0,x_padding_right:0,y_padding_top:0,y_padding_bottom:0,color:t.get_style(this.element,"color"),mouseover_attributes:{stroke:"red"},units:"",units_position:1};n&&r("options: width: "+e.width+", height: "+e.height+", color: "+e.color)},process_options:function(e){n&&r("Ico.Base.process_options()");var i=this;e&&(e=t.extend(i.options,e)),typeof e.min!="undefined"&&(i.min=Math.min(i.min,e.min)),typeof e.max!="undefined"&&(i.max=Math.max(i.max,e.max)),i.range=i.max-i.min,i.x={direction:[1,0],start_offset:0,width:e.width},i.y={direction:[0,-1],start_offset:0,width:e.height},i.x.other=i.y,i.y.other=i.x,i.x.padding=[e.x_padding_left,e.x_padding_right],i.y.padding=[e.y_padding_top,e.y_padding_bottom],i.orientation=e.orientation||i.orientation||0,i.y_direction=i.orientation?-1:1,i.graph=i.orientation?{x:i.y,y:i.x}:{x:i.x,y:i.y},i.components=[];for(var s in t.Component.components){if(!t.Component.components.hasOwnProperty(s))continue;var o=i.options[s+"_attributes"],e=i.options[s],u=t.Component.components[s];e===!0&&o&&(e=i.options[s]=o);if(e){var a=u[1];i.components[a]||(i.components[a]=[]);try{i.components[a].push(i[s]=new u[0](i,e))}catch(f){i.error=f,n&&r("process_options(), exception: "+f)}}}},get_font:function(){var e=this.options;return this.font?this.font:(this.font={"font-family":t.get_style(this.element,"font-family"),"font-size":e.font_size||t.get_style(this.element,"font-size")||10,fill:t.get_style(this.element,"color")||"#666",stroke:"none"},e&&t.extend(this.font,e),this.font)},set_raphael:function(e){if(this.paper)return;this.paper=Raphael(this.element,e.width,e.height),this.svg=!(this.vml=Raphael.vml)},clear:function(){return this.paper&&(this.components_call("clear",this.options),this.paper.remove(),this.paper=null),this},calculate:function(e){n&&r("Ico.Base.calculate()"),this.paper||this.set_raphael(e),this.components_call("calculate",e),this.calculate_graph_len(this.graph.x),this.calculate_graph_len(this.graph.y),this.scale=this.y_direction*this.graph.y.len/this.range,this.graph.x.step=this.graph.x.len/this.label_slots_count(),this.x.start=this.x.padding[0],this.x.stop=this.x.start+this.x.len,this.y.stop=this.y.padding[0],this.y.start=this.y.stop+this.y.len},calculate_graph_len:function(e){e.len=e.width-e.padding[0]-e.padding[1]},calculate_bars:function(e){this.bar_width=this.graph.x.step-e.bar_padding,this.bar_width<5&&(this.bar_width=5),this.graph.x.start_offset=this.y_direction*this.graph.x.step/2,this.bar_base=this.graph.y.start-this.scale*((this.max<=0?this.max:0)-(this.min<0?this.min:0))},format_value:function(e,n){e!=0&&typeof n!="number"&&(n=t.root(e,1e3),n&&(e/=Math.pow(1e3,n)),e=t.significant_digits_round(e,3,Math.round,!0).toString()),e=""+e,n&&(e+=["","k","M","G","T","P","E","Z","Y"][n]||"e"+3*n);var r=this.options;return r.units?r.units_position?e+r.units:r.units+e:e},draw:function(e){return n&&r("Ico.Base.draw()"),this.paper||this.set_raphael(e),this.components_call("draw",e),this.draw_series(e),this},draw_series:function(e){for(var t=-1;++t1&&(e.bars_overlap=1)},calculate:function(e){t.BaseGraph.prototype.calculate.call(this,e),this.calculate_bars(e),e=this.bars_overlap=e.bars_overlap;var n=this.series.length,r=this.bars_width=this.bar_width/(n-(n-1)*e);this.bars_step=r*(1-e)},label_slots_count:function(){return this.data_samples},draw_value:function(e,n,r,i,s,o,u){var a=u.series_attributes[s],f=this.bars_width,l=this.bar_base,c;return Math.abs(r-l)<2&&(r=l+(r>=l?2:-2)),n+=this.bars_step*s-this.bar_width/2,this.show_label_onmouseover(c=this.paper.path(t.svg_path(this.orientation?["M",r,n,"H",l,"v",f,"H",r,"z"]:["M",n,r,"V",l,"h",f,"V",r,"z"])).attr(a),i,a,s,e),o.push(c),""}}),t.HorizontalBarGraph=t.Class.create(t.BarGraph,{set_defaults:function(){t.BarGraph.prototype.set_defaults.call(this),this.orientation=1}}),t.Component=t.Class.create({initialize:function(e,i){t.extend(this,{p:e,graph:e.graph,x:e.x,y:e.y,orientation:e.orientation});if(t.isArray(i))i={values:i};else if(typeof i=="number"||typeof i=="string")i={value:i};i=this.options=this.defaults?t.extend(this.defaults(e),i):i,n&&r("Ico.Component::initialize(), o: "+JSON.stringify(i)),this.process_options&&this.process_options(i,e)}}),t.Component.components={},t.Component.Background=t.Class.create(t.Component,{defaults:function(){return{corners:!0}},process_options:function(e){e.color&&!e.attributes&&(e.attributes={stroke:"none",fill:e.color}),e.attributes&&(e.color=e.attributes.fill),e.corners===!0&&(e.corners=Math.round(this.p.options.height/20))},draw:function(e,t){var n=t.options;this.shape=t.paper.rect(0,0,n.width,n.height,e.corners).attr(e.attributes)}}),t.Component.components.background=[t.Component.Background,0],t.Component.StatusBar=t.Class.create(t.Component,{defaults:function(){return{attributes:{"text-anchor":"end"}}},draw:function(e,t){var n=t.options,r=this.y;this.shape=t.paper.text(e.x||n.width-10,e.y||(r?r.padding[0]:n.height)/2,"").hide().attr(e.attributes)}}),t.Component.components.status_bar=[t.Component.StatusBar,2],t.Component.MousePointer=t.Class.create(t.Component,{defaults:function(){return{attributes:{stroke:"#666","stroke-dasharray":"--"}}},draw:function(e,n){if(!t.viewport_offset)return;var r=this.shape=n.paper.path().attr(e.attributes).hide(),i=this.x,s=this.y,o=n.element;o.onmousemove=function(e){e=e||window.event;var n=t.viewport_offset(o),u=e.clientX-n[0],a=e.clientY-n[1];u>=i.start&&u<=i.stop&&a>=s.stop&&a<=s.start?r.attr({path:t.svg_path(["M",i.start,a,"h",i.len,"M",u,s.stop,"v",s.len])}).show():r.hide()},o.onmouseout=function(){r.hide()}}}),t.Component.components.mouse_pointer=[t.Component.MousePointer,2],t.Component.GraphBackground=t.Class.create(t.Component,{defaults:function(){return{key_colors:["#aaa","#ccc","#eee"],key_values:[50,75],colors_transition:0}},draw:function(e,t){var n=this.x,r=this.y,i=t.range,s=e.colors_transition/i,o=100/i-s,u=e.key_colors,a=u.length,f=e.key_values,l=f.length,c=this.orientation?"0":"90";a>l+1&&(a=l+1);for(var h,p=0,d=-1;++d0^n.position?"start":"end"}else{var n=.6*f+s;e.f=[i*n,r*n],o[1]=a[1]*1.1+s,e.anchor="middle"}}var p=t^this.orientation^n.position;n.add_padding?e.other.padding[p]+=o[1]:e.other.padding[p]n&&(t.bbox=[n=e.width,e.height])}),this.bbox},clear:function(){this.labels=null},text_size:function(e,t){var n=this.p.paper.text(10,10,"").attr(t).attr({text:e}),r,i;return i=n.getBBox(),r=[i.width,i.height],n.remove(),r},draw:function(e){this.draw_labels_grid(this.graph.x,e)},draw_labels_grid:function(e,n){var r=this,i=e.direction[0],s=e.direction[1],o=e.step,u=r.x.start+r.x.start_offset*i,a=r.y.start-r.y.start_offset*s,f=n.marker_size,l=e.f[0],c=e.f[1],h=e.angle,p=this.p.paper,d=n.grid,v=[];if(d){var m=r[i?"y":"x"].len,g=0,y=0,b=e.labels,w=[];if(d.through){m+=e.padding[1]+10;var E=-e.padding[0]-this.bbox[0]-f-20;i?y=E:g=E}}i?(f="v"+f,m="v-"+m):(f="h-"+f,m="h"+m,h+=90),r.labels||this.get_labels_bounding_boxes(e);for(var S=-1,x=r.labels.length;++S0){var u=Math.round(o*i/s);u==0?u=1:u==o&&(u-=1);var a=o-u,f=t.significant_digits_round(Math.max(i/u,-e/a),2,function(e){e=Math.ceil(e);if(e<=10)return e;if(e<=12)return 12;var t;return e<=54?(t=e%5)?e-t+5:e:(t=e%10)?e-t+10:e});e=-f*a,i=f*u}else{var f=t.significant_digits_round(s/o,1,Math.ceil);i<=0?e=i-f*o:e>=0&&(i=e+f*o)}return{min:e,max:i,spaces:o,step:f,waste:o*f-s}}n&&r("Ico.Component.ValueLabels.calculate()");var s=this,o=i.max,u=i.min,a=o-u,f=e.spaces,l;i.calculate_graph_len(this.graph.y);if(!f){var c=Math.abs(e.angle),h;this.orientation&&c<30||!this.orientation&&c>60?h=Math.max.apply(Math,[u,o].map(function(e){return s.text_size("0"+t.significant_digits_round(e,3,Math.round,!0)+s.p.options.units,s.font)[0]})):h=1.5*this.text_size("0",this.font)[1];if(h===0)throw new Error("Ico.Component.ValueLabels(), min_step is zero, canvas is possibly not active");f=Math.round(this.graph.y.len/h);if(f>2){var p=a,d=f;for(var v=1;++v<=d;)l=T(u,o,a,v),l.waste<=p&&(p=l.waste,f=v)}}l=T(u,o,a,f),i.range=(i.max=l.max)-(i.min=l.min);var m=t.root(l.step*l.spaces,1e3);if(m){var g=Math.pow(1e3,m);l.step/=g,l.min/=g}var y=e.values=[],b=0;for(var w=l.min,E=-1;++E<=l.spaces;w+=l.step){var S=t.significant_digits_round(w,3,Math.round,!0).toString(),x=(S+".").split(".")[1].length;x>b&&(b=x),y.push(S)}y.forEach(function(e,t){var n=(e+".").split(".")[1].length;nnew Ico.SparkLine( 41 | "market_trend_id", // DOM element where the sparkline is rendered 42 | [5, 2, -1, 17, 23, 15, 7, 6, -5, -2, 4, 7, 3, 9] // List of values for market trend 43 | ) 44 | 45 | Bullet graph with 3 graph background colors and gradient transtitions: 46 |
new Ico.BulletGraph(
 47 |   "gross_profit_id",      // DOM element where the bullet graph will be rendered
 48 |   85,                     // Actual gross profit for current period as a percentage of gross revenues
 49 |   {                       // Bullet graph options
 50 |     min: 0,               // Minimum gross profit value
 51 |     max: 100,             // Maximum gross profit value
 52 |     target: 65,           // Forecasted gross profit value
 53 |     graph_background: {   // Defines graph background for gross profit
 54 |       key_values: [50, 75],               // Less than 50% is bad, more than 75% is good, in between is ok
 55 |       key_colors: ['#555','#999','#ddd'], // The 3 colors representing the 3 quality zones
 56 |       colors_transition: 10               // 10% Gradient transition between the different zones
 57 |     }
 58 |   }
 59 | )
60 | 61 | Line graph with 2 lines, angled labels, $ units, meanline, mouse pointer, mouse-over value in status bar, and grid: 62 |
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
 63 | new Ico.LineGraph(
 64 |   "gross_profit_id",                               // DOM element where the graph will be rendered
 65 |   [                                                // The 2 series
 66 |     [31, 5, 1, -5, 15, 33, 20, 25, 1, 12, 25, -3], // Drawn first
 67 |     [18, -1, -7, 17, 15, 21, 1, 25, 3, 21, 16, 4]  // Drawn last, on top of previous series
 68 |   ],
 69 |   {                                                // Graph components' options
 70 |     colors: ['#228899', '#339933' ],               // Series' colors
 71 |     curve_amount: 10,                              // Slightly curve series' lines path using Cubic Bézier
 72 |     mouseover_attributes: { stroke: 'green' },     // When hovering over values
 73 |     font_size: 16,                                 // for both labels and value labels and other elements
 74 |     labels: { values: months, angle: 30 },         // Set labels at a 30 degres angle
 75 |     x_padding_right: 40,                           // Make more room on the right to properly display labels
 76 |     units: '$',                                    // $ units to display values
 77 |     units_position: 0                              // Render $ before values
 78 |     value_labels: {                                // Controls value labels
 79 |       marker_size: 4                               // Value labels markers set to 4 pixels instead of 5
 80 |     },
 81 |     background: { color: '#ccf', corners: 5 },     // Set entire div background color and corner size 
 82 |     meanline: true,                                // Display mean value of all series
 83 |     grid: true,                                    // Display a grid from labels and value labels
 84 |     mouse_pointer: true,                           // Display a cross mouse pointer in graph area
 85 |     status_bar : true,                             // Display status bar to show values on mouse over
 86 |   }
 87 | )
88 | 89 | See "index.html":http://github.com/uiteoi/ico/blob/master/index.html for more examples. 90 | 91 | h1. Documentation 92 | 93 | h2. Graph classes 94 | 95 | Graph classes provide defaults components appropriate for each type of graph: 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
Class nameBase classDescription
Ico.Basen/aBase class for all graphs, provides components architecture
Ico.SparkLineIco.BaseDraw a single serie as a line. Expected to be rendered as an inline-block
Ico.SparkBarIco.SparkLineDraw a single serie of vertical bars. Expected to be rendered as an inline-block
Ico.BulletGraphIco.BaseDraw a single value as a horizontal bar. Expected to be rendered as an inline-block
Ico.BaseGraphIco.BaseBase class for more complex, multi lines and bars graphs
Ico.LineGraphIco.BaseGraphRepresent series as lines
Ico.BarGraphIco.BaseGraphVertical Bars Graph
Ico.HorizontalBarGraphIco.BarGraphHorizontal Bars Graph
144 | 145 | To create and render a graph, call new Ico.desired_graph_type, providing three parameters: 146 | * The DOM element where the graph will be rendered 147 | * Data series 148 | * Options (optional) 149 | 150 | Example creating and rendering a line graph: 151 |
new Ico.LineGraph( dom_element, data_series, graph_options )
152 | 153 | The DOM element can be provided as a DOM Element object: 154 |
new Ico.LineGraph( $( "sales" ), data_series, graph_options )
155 | 156 | The DOM element can also be specified using a string containing a DOM id: 157 |
new Ico.LineGraph( "sales", data_series, graph_options )
158 | 159 | Data series can provided as: 160 | * A single value (for a bullet graph only) 161 | * An array of values for a single data serie 162 | * An array of array of values for multiple series data 163 | 164 | Examples: 165 |
// A single value for a bullet graph
166 | data_series = -20
167 | 
168 | // An array of values for a single serie graph
169 | data_series = [5, 2, -1, 17, 23, 15, 7, 6, -5, -2, 4, 7, 3, 9]
170 | 
171 | // An array of arrays of values for multiple series graphs
172 | data_series = [
173 |   [31, 5, 1, -5, 15, 33, 20, 25, 1, 12, 25, -3],
174 |   [18, -1, -7, 17, 15, 21, 1, 25, 3, 21, 16, 4]
175 | ]
176 | 177 | Options are provided as a hash of options where each key is the option name and each value is the option value. Options hashes can be nested typically to group options by components. 178 | 179 | Many options are "Raphaël attributes":http://raphaeljs.com/reference.html#Element.attr which rely on the "SVG Specification":http://www.w3.org/TR/SVG/ so please refer to these documents when further information is needed. 180 | 181 | Example of options: 182 |
graph_options = {                     // Graph components' options
183 |   colors: ['#228899', '#339933' ],               // Series' colors
184 |   curve_amount: 10,                              // Slightly curve series' lines path using Cubic Bézier
185 |   mouseover_attributes: { stroke: 'green' },     // When hovering over values
186 |   font_size: 16,                                 // for both labels and value labels and other elements
187 |   labels: { values: months, angle: 30 },         // Set labels at a 30 degres angle
188 |   x_padding_right: 40,                           // Make more room on the right to properly display labels
189 |   units: '$',                                    // $ units to display values
190 |   units_position: 0                              // Render $ before values
191 |   value_labels: {                                // Controls value labels
192 |     marker_size: 4                               // Value labels markers set to 4 pixels instead of 5
193 |   },
194 |   background: { color: '#ccf', corners: 5 },     // Set entire div background color and corner size 
195 |   meanline: true,                                // Display mean value of all series
196 |   grid: true,                                    // Display a grid from labels and value labels
197 |   mouse_pointer: true,                           // Display a cross mouse pointer in graph area
198 |   status_bar : true,                             // Display status bar to show values on mouse over
199 | }
200 | 201 | h3. Ico.Base options 202 | 203 | The following are options shared by all graphs: 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 |
Option nameDefault valueDescription
widthDOM element widthRaphaël Canvas width
heightDOM element heightRaphaël Canvas height
x_padding_left0 pixelsPadding left of the graph array
x_padding_right0 pixelsPadding right of the graph array
y_padding_top0 pixelsPadding above the graph array
y_padding_bottom0 pixelsPadding under the graph array
colorDOM element colorColor for single data serie
mouseover_attributessee bellowMouseover attributes for data series
mouseover_attributes.strokeredSVG stroke color
orientation00: horizontal, 1:vertical
unitsUnits to display with values
units_position1Position of units: 0 => before value, 1 => after value
font_sizeelement's font-familyText elements font size - e.g. '1cm'
fontSee belowFont attributes for all text elements
font['font-family']element's font-familyFont family - e.g. 'Arial'
font['font-size']font_size || element font-familyFont size - e.g. '1cm'
font.fillelement's colorFont fill - e.g. 'blue'
font.stroke'none'Font stroke color - e.g. 'black'
axisX/Y axis, true => display, hash => see 'axis' component option attribute
307 | 308 | h3. Ico.SparkLine and Ico.SparkBar options 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 |
Option nameDefault valueDescription
highlightfalseHiglights a value by displaying a point in sparklines or a bar in sparkbars. true => highlight last value, hash => provide additional options
highlight.indexIndex of the value to highlight, 0 => first value, null => last value
highlight.color'red'Highlight color
highlight.radius2Radius of the highlight circle, for sparklines only
337 | 338 | h3. Ico.BulletGraph options 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 |
Option nameDefault valueDescription
min0Minimum (start) value
max100Maximum (stop) value
color'#33e' (blue)Color for value bar
targetTarget rendered as a vertical line, number => target value, hash => see below
target.valueTarget value rendered as a vertical bar
target.color'#666' (dark grey)Color for target vertical bar
target.length0.8Length of target vertical bar, <= 1
target['stroke-width']2 (pixels)Width of vertical target line
graph_backgroundsee Ico.Component.Graph defaultsInclude graph background component, false => disable
392 | 393 | h3. Ico.BaseGraph options 394 | 395 | Ico.BaseGraph is used as a base for more complex graphs possibly with multiple series whereas sparklines and sparkbars are limited to one serie. 396 | 397 | Ico.BaseGraph extends Ico.Base with more default features such as value_labels, focus_hint and axis enabled by default. 398 | 399 | The following options apply as defaults for bar and Line graphs unless overrided: 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 |
Option nameDefault valueDescription
x_padding_left10 pixelsPadding left of the graph array
x_padding_right20 pixelsPadding right of the graph array
y_padding_top20 pixelsPadding above the graph array
y_padding_bottom10 pixelsPadding under the graph array
colors[serie_key]Ico.Base color || Random colorLine color for serie 'serie_key'
value_labelsSee Component.ValueLabelsDisplay value labels
focus_hinttrueDisplay focus hint if value labels do not start at zero. false => disable, hash => 'focus_hint' component options attribute
axistrueDisplay x and y axis, false => disable, hash => see 'axis' component option attribute
448 | 449 | h3. Ico.LineGraph options 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 |
Option nameDefault valueDescription
series_attributes[]See bellowAttributes by data series
series_attributes[serie_key].strokeIco.BaseGraph colors[serie_key]Color for line 'serie_key'
series_attributes[serie_key]['stroke-width']2 pixelsLine 'serie_key' width
curve_amount5 pixelsCubic Bézier curve amount, 0 => disable
dot_radius[]3 pixelsRadius of dots for values, 0 => no dot displayed, Array => per line values
dot_attributes[]See bellowDot attributes for all data series
dot_attributes[serie_key]['stroke-width']1 pixelDot stroke width for serie_key
dot_attributes[serie_key].strokeBackground component color || options.color[serie_key]Dot color for serie_key
dot_attributes[serie_key].filloptions.colorDot fill color for serie_key
focus_radius[]6 pixelsRadius of focus, 0 => disable mouse over action, Array => per line values
focus_attributes{ stroke: 'none', 'fill': 'white', 'fill-opacity' : 0 }Attributes hash for focus circle
513 | 514 | h3. Ico.BarGraph and Ico.HorizontalBarGraph options 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 |
Option nameDefault valueDescription
series_attributes[]See bellowArray by data series key for bar attributes
series_attributes[serie_key].stroke'none'Stroke color for bar
series_attributes[serie_key]['stroke-width']2 pixelsStroke width for bar
series_attributes[serie_key].gradientgradient based on option.color or random colorFill gradient for bar
bar_padding5 pixelsPadding between bars
bars_overlap1/2Overlapping between bars (unit fraction): 0 -> no overlap, 1/1 -> full overlap
553 | 554 | h2. Component Classes 555 | 556 | Graph features are driven by components. Each graph type defines different default components and graph orientation (horizontal or vertical) and other graph-specific options. Default components can be disabled by explicitly setting their option attribute to false. 557 | Each component is shown with the name of its options attribute hash: 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 |
Component ClassBase ClassOption Attribute NameDrawing LayerDescription
Ico.ComponentBase class of all graph components
Ico.Component.BackgroundIco.Componentbackground0Background of the entire canvas
Ico.Component.GridIco.Componentgrid0Grid
Ico.Component.GraphIco.Componentgraph_background1Graph area, excluding canvas padding areas, background
Ico.Component.MousePointerIco.Componentmouse_pointer2Display the mouse pointer as a cross when in graph area
Ico.Component.StatusBarIco.Componentstatus_bar2Display values on mouse over in status area
Ico.Component.MeanlineIco.Componentmeanline3Mean line at the mean of all data values accross all series
Ico.Component.LabelsIco.Componentlabels3Labels along the data series
Ico.Component.ValueLabelsIco.Component.Labelsvalue_labels4Label values accross data series
Ico.Component.AxisIco.Componentaxis4X and Y Axis
Ico.Component.FocusHintIco.Componentfocus_hint5Display hint if value labels do not contain zero
645 | 646 | h3. Component option attributes 647 | 648 | Component options can be set with the following types of values: 649 | * false => disable feature, useful when a graph type sets the option by default 650 | * true => get default options for the component 651 | * hash => attributes for the component 652 | 653 | If a hash is provided and one of the options name is 'attributes', it is a hash of "Raphaël attributes":http://raphaeljs.com/reference.html#Element.attr. Attribute option tables bellow define only the subset of Ico default attributes although all valid Raphaël attributes can be used. 654 | 655 | h3. 'labels' and 'value_labels' option attribute 656 | 657 | Labels are series' samples labels, while value labels are numerical labels associated with series' data. 658 | 659 | Labels are defined by the option attribute 'labels'. If an array is provided, it reprensents the values of the labels. If more label options are provided, labels must be provided as a hash of options. 660 | 661 | Label components always make room for themselves in the padding areas. 662 | 663 | Values labels are always calculated. The calculation is optimized according to the following criteria: 664 | * If zero is in the range (between min and max values), zero is always one of the value labels 665 | * The maximum number of value labels is limited by the size of the graph area, the font, angle and orientation of the graph 666 | * The number of labels is chosen to minimize unused areas to maximize the size of displayed data series 667 | * The step beetween two value labels has a maximum of 2 significant digits and is a multiple of 5 if the 2 significant digits are required 668 | * Values labels are limited to 3 significant digits using string manipulations to avoid floating-point rounding errors 669 | * Once value labels are calculated, precision is fixed, possibly adding zeros to nicely align labels 670 | * Finally, appropriate padding is added to make room for calculated labels using just as much space as neccesary and according to labels angle 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 |
Option nameDefault valueDescription
values[1, .., # of data samples -1]Label values, ignored for value_labels which are always calculated
fontIco.Base fontLabel font hash
marker_size5 pixelssize of labels markers, 0 => disable
markers_attributesSee bellowLabel markers attributes hash
markers_attributes.strokelabels font fillStroke color for label markers
markers_attributes['stroke-width']1 pixelStroke width for label markers
angle0Clockwise Angle to display labels in degrees (-90 to +90)
gridgrid componentGrid attributes, overrides grid component
719 | 720 | h3. 'graph_background' option attribute 721 | 722 | The graph area is the area where data series are rendered. This does not include padding areas where labels and other graph features are rendered. 723 | 724 | The graph_background option attribute, if present allows to display a grandient of colors in the graph area. The gradient is drawn along the value labels axis to add meaning to ranges of values. The default gradient meaning represents ranges where the values are 'bad', 'acceptable', and 'good'. The actual meaning of these background colors is application-specific and the same colors could mean 'small', 'average', and 'tall' or any other meaning. 725 | 726 | The graph_background attribute can be set to true to get the default background or a hash to provide specific attributes: 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 |
Option nameDefault valueDescription
key_colors['#aaa','#ccc','#eee']Colors of the different areas of the background
key_values[50, 75]Values where the transitions occur, there should be one less value than key_colors
colors_transition0Percentage of key_values where colors transition with a gradient
750 | 751 | h3. 'background' option attribute 752 | 753 | The background is the entire Raphaël canvas area. 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 |
Option nameDefault valueDescription
colorBackground color
attributesSee bellowAttribute hash for background
attributes.stroke'none'Stroke color
attributes.fillcolorFill color
cornersCanvas height / 20Radius of rounded corners, 0 to disable
787 | 788 | h3. 'grid' option attribute 789 | 790 | If the 'grid' option attribute is provided it can be set to true or a hash of additional options. 791 | 792 | All Raphaël Element Attributes are valid, only listed here are those whith default values: 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 |
Option nameDefault valueDescription
throughundefinedtrue: grid line extends to graph borders
stroke'#eee' (very light grey)Stroke color
'stroke-width'1 pixelGrid stroke width
816 | 817 | h3. 'axis' option attribute 818 | 819 | If the 'axis' option attribute is provided it can be set to true or a hash of additional options: 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 |
Option nameDefault valueDescription
attributesSee belowAxis attributes
attributes.stroke'#666' (grey)Stroke color
attributes['stroke-width']1 pixelAxis stroke width
843 | 844 | h3. 'meanline' option attribute 845 | 846 | If the 'meanline' attribute option is set to true or a hash of additional attributes, Ico calculates the mean of all values of all series and displays this mean value as line parallel to labels' axis. 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 |
Option nameDefault valueDescription
attributesSee belowMeanline attributes
attributes.stroke'#bbb' (light grey)Stroke color
attributes['stroke-width']2 pixelsMeanline stroke width
870 | 871 | h3. 'focus_hint' option attribute 872 | 873 | A focus hint is displayed as two short lines crossing the value labels axis if the value range does not include zero in order to attract the attention of the users that the graph does not start at zero nor contains zero. 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 |
Option nameDefault valueDescription
length6 pixelsLength of each focus hint line
coloroptions.labels_font.fillHint lines color
attributesSee belowHint lines attributes
attributes.strokecolorHint lines stroke color
attributes['stroke-width']2 pixelsHint lines stroke width
907 | 908 | h3. 'mouse_pointer' option attribute 909 | 910 | If the 'mouse_pointer' attribute option is set to true or a hash, a mouse pointer is displayed when the mouse is over the graph area as a cross spanning the entire width and height of the graph area. 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 |
Option nameDefault valueDescription
attributesSee belowMouse Pointer attributes
attributes.stroke'#666' (grey)Stroke color
attributes['stroke-dasharray']'--'SVG stroke pattern
934 | 935 | h3. 'status_bar' option attribute 936 | 937 | If the 'status_bar' attribute option is set to true or a hash, value are displayed on mouse over in the top padding area. 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 |
Option nameDefault valueDescription
attributesSee belowStatus bar attributes
attributes['text-anchor']'end'SVG anchor in the top padding area
956 | 957 | h1. Tested web browsers, known issues 958 | 959 | * Firefox: best compromize of quality and speed, some rendering problems of angled fonts 960 | * IE: best angled fonts rendering, slowest by far 961 | * Safari: fastest, bugs: while zooming 962 | * Google Chrome: good quality, bugs: while zooming 963 | * iPhone (Safari): pretty amazing, try it 964 | * Opera: good fonts rendering, bugs: zoom, mouse-over while scrolling 965 | 966 | h1. Wishlist 967 | 968 | This section outlines the projected improvements for upcoming releases. 969 | 970 | Features: 971 | * Display Value Labels Title either above the y axis, in the status bar or under value labels if horizontal graph 972 | * Allow greater control of labels positioning: left-justification, top, right 973 | * By default Draw labels on top (or right if orientation) if all values are negative 974 | * Enable values formatting with a function to allow custom numbers formatting, provide default formatters 975 | * Allow single axis (x or y) to be displayed 976 | * Display values in tooltips instead of status bar, tooltips implemented w/ an external DOM library to enable out-of-the-canvas tooltips 977 | * Zoom-in spark lines, spark bars, and bullet graphs on-mouse-over. 978 | * Display full graph of spark lines and spark bars on mouse-click, possibly in a tooltip 979 | * Facilitate user events definition on graph elements 980 | * Add zero line component to draw a line at zero when zero in the range. This can be done today by adding a dummy serie but this would make it easier. 981 | 982 | Architechture: 983 | * Componentize series in a Ico.Serie class to allow bars and lines in the same graph but also to allow new types of data series rendering 984 | 985 | Documentation: 986 | * Ico box model 987 | * methods beyond intitialization 988 | * (for contributors) Graph and Components extensions 989 | -------------------------------------------------------------------------------- /ico.js: -------------------------------------------------------------------------------- 1 | /* Ico Graph Prototype/Raphael library 2 | * 3 | * Copyright (c) 2009, Alex R. Young 4 | * Copyright (c) 2009-2012 Reverse Risk 5 | * Copyright (c) 2009-2013 Jean Vincent 6 | * 7 | * Licensed under the MIT license: http://github.com/uiteoi/ico/blob/master/MIT-LICENSE 8 | */ 9 | "use strict"; 10 | 11 | ( function( exports ) { 12 | var Ico = exports.Ico = { 13 | Version: "0.98.26", 14 | 15 | extend: function( d, s ) { 16 | for( var p in s ) d[p] = s[p]; 17 | 18 | return d 19 | } 20 | }; 21 | 22 | var de = true, ug; 23 | 24 | if ( typeof debug === "function" ) { 25 | ug = function( m ) { debug( "Ico, " + m ) }; 26 | } else if ( typeof console === "object" && typeof console.log === "function" ) { 27 | ug = function( m ) { 28 | var date = new Date; 29 | 30 | console.log( 31 | digits( date.getMinutes(), 2 ) 32 | + ':' + digits( date.getSeconds(), 2 ) 33 | + '.' + digits( date.getMilliseconds(), 3 ) 34 | + ' - ' + "Ico, " + m 35 | ); 36 | 37 | function digits( n, d ) { 38 | n = "000" + n; 39 | 40 | return n.substr( n.length - d ); 41 | } 42 | }; 43 | } else { 44 | de = false; 45 | } 46 | 47 | Ico.extend( Ico, { 48 | isArray: function( v ) { return typeof v === 'object' && v instanceof Array }, 49 | 50 | Class: { 51 | add_methods: Ico.extend, // could be changed to implement additional features such as $super 52 | 53 | create: function( p ) { 54 | var C = function() { this.initialize.apply( this, arguments ) }; 55 | C.subclasses = []; 56 | 57 | var i = -1; 58 | if ( typeof p === "function" ) { 59 | i += 1; 60 | ( C.superclass = p ).subclasses.push( C ); 61 | C.prototype = Object.create( p.prototype ); 62 | } else { 63 | C.superclass = null; 64 | } 65 | C.prototype.constructor = C; 66 | 67 | // Add instance properties and methods 68 | while ( ++i < arguments.length ) Ico.Class.add_methods( C.prototype, arguments[ i ] ); 69 | 70 | C.prototype.initialize || ( C.prototype.initialize = function() {} ); 71 | 72 | return C; 73 | } 74 | }, 75 | 76 | get_style : function( e, p ) { 77 | var v = ""; 78 | if ( document.defaultView && document.defaultView.getComputedStyle ) { 79 | v = document.defaultView.getComputedStyle( e, "" ).getPropertyValue( p ); 80 | } else if( e.currentStyle ) { 81 | p = p.replace(/\-(\w)/g, function( s, p ){ 82 | return p.toUpperCase(); 83 | } ); 84 | v = e.currentStyle[p]; 85 | } 86 | return v; 87 | }, 88 | 89 | // Element.viewportOffset from Prototype library (last dependency) 90 | // If not available, i.e. when prototype is not installed the only feature that will be disabled 91 | // is the mouse pointer component. 92 | viewport_offset : Element.viewportOffset, 93 | 94 | // These helper methods are good candidates for unit testing 95 | significant_digits_round: function( v, significant_digits, f, string ) { 96 | if ( v == 0 || v == Number.NEGATIVE_INFINITY || v == Number.POSITIVE_INFINITY ) return v; 97 | var sign = 1; 98 | if ( v < 0 ) v = -v, sign = -1; 99 | var p10 = Math.floor( Math.log( v ) / Math.LN10 ); 100 | if ( p10 < -14 ) return 0; // assume floating point rounding error => was meant to be zero 101 | p10 -= significant_digits - 1; 102 | v = ( f || Math.round )( sign * v / Math.pow( 10, p10 ) ); 103 | 104 | if ( string && p10 < 0 ) { 105 | v *= sign; // remove sign again 106 | while ( v % 10 == 0 ) { 107 | // remove non-significant zeros 12300 => 123 108 | p10 += 1; 109 | v /= 10; 110 | } 111 | v = '' + v; 112 | var len = v.length + p10; 113 | if ( len <= 0 ) { 114 | // value < 1 115 | return ( sign < 0 ? '-' : '' ) + '0.00000000000000'.substring(0, 2 - len ) + v; 116 | } else if ( p10 < 0 ) { 117 | // value has significant digits on both sides of the point 118 | return ( sign < 0 ? '-' : '' ) + v.substring( 0, len ) + '.' + v.substring( len ); 119 | } 120 | // value has no decimals 121 | v *= sign; 122 | } 123 | return v * Math.pow( 10, p10 ); 124 | }, 125 | 126 | root: function( v, p ) { 127 | return Math.floor( Math.log( Math.abs( v ) ) / Math.log( p ) ); 128 | }, // root() 129 | 130 | svg_path: function( a ) { 131 | var path = '', previous_isNumber = false; 132 | a.forEach( function( v ) { 133 | if ( typeof v === "number" ) { 134 | if ( previous_isNumber ) path += ' '; 135 | path += Math.round( v ); 136 | previous_isNumber = true; 137 | } else { 138 | path += v; 139 | previous_isNumber = false; 140 | } 141 | } ); 142 | 143 | // de&&ug( "svg_path(), path=" + path ); 144 | 145 | return path; 146 | }, // svg_path() 147 | 148 | adjust_min_max: function( min, max ) { 149 | de&&ug( "adjust_min_max(), min: " + min + ", max: " + max ); 150 | 151 | // Adjust start value to show zero if reasonable (e.g. range > min) 152 | var range = max - min; 153 | if ( range === 0 ) { 154 | min = 0; 155 | if ( max === 0 ) max = 1; 156 | } else if ( range < min ) { 157 | min -= range / 2; 158 | } else if ( min > 0 ) { 159 | min = 0; 160 | } else if ( range < -max ) { 161 | max += range / 2; 162 | } else if ( max < 0 ) { 163 | max = 0; 164 | } 165 | return [min, max]; 166 | }, 167 | 168 | series_min_max: function( series, dont_adjust ) { 169 | de&&ug( "series_min_max: series length: " + series.length ); 170 | var u, no_null_values = [] 171 | , values = Array.prototype.concat.apply( [], series ).map( function( v ) { 172 | if ( v === u || v === null ) return null; 173 | no_null_values.push( v ); 174 | return v 175 | } ) 176 | , l = no_null_values.length 177 | ; 178 | 179 | de&&ug( "series_min_max: no_null_values length: " + l ); 180 | var min_max = l ? [ 181 | Ico.significant_digits_round( Math.min.apply( Math, no_null_values ), 2, Math.floor ), 182 | Ico.significant_digits_round( Math.max.apply( Math, no_null_values ), 2, Math.ceil ) 183 | ] : [0, 0]; 184 | dont_adjust || ( min_max = Ico.adjust_min_max.apply( Ico, min_max ) ); 185 | min_max.push( values ); 186 | return min_max; 187 | }, 188 | 189 | moving_average: function( serie, samples, options ) { 190 | var ma = [], i = -1, j = -1, p; 191 | if ( options && Ico.isArray( p = options.previous_values ) ) { 192 | serie = p.concat( serie ); 193 | i += p.length 194 | } 195 | for ( var len = serie.length; ++i < len; ) { 196 | var a = 0; 197 | for ( var k = -1; ++k < samples && i >= k; ) { 198 | a += serie[i - k] 199 | } 200 | ma[++j] = Ico.significant_digits_round( a / k, 3, Math.round, true ) 201 | } 202 | return ma; 203 | } 204 | } ); // Ico 205 | 206 | Ico.Base = Ico.Class.create( { 207 | initialize: function( element, series, o ) { 208 | de&&ug( "Ico.Base.initialize( " + element + ", " + JSON.stringify( series ) + ", " + JSON.stringify( o ) + " )" ); 209 | 210 | if ( typeof element == "string" ) element = document.getElementById( element ); 211 | this.element = element; 212 | this.series = series || [[0]]; 213 | this.set_defaults(); 214 | this.set_series(); 215 | this.process_options( o ); 216 | this.set_raphael( o = this.options ); 217 | this.calculate( o ); 218 | this.draw( o ); 219 | 220 | de&&ug( "Ico.Base.initialize(), end" ); 221 | 222 | return this; 223 | }, 224 | 225 | set_series: function() { 226 | if ( Ico.isArray( this.series ) ) { 227 | de&&ug( "set_series Array, element 0 is Array:" + Ico.isArray( this.series[0] ) ); 228 | if ( ! Ico.isArray( this.series[0] ) ) this.series = [this.series]; 229 | } else if ( typeof this.series === "number" ) { 230 | this.series = [[this.series]]; 231 | } else { 232 | throw 'Wrong type for series'; 233 | } 234 | this.data_samples = Math.max.apply( Math, this.series.map( function(v) { de&&ug( "serie length: " + v.length ); return v.length } ) ); 235 | de&&ug( "set_series, data samples: " + this.data_samples ); 236 | var min_max = Ico.series_min_max( this.series, true ); 237 | this.max = min_max[1]; 238 | this.min = min_max[0]; 239 | this.all_values = min_max[2]; 240 | this.series_shapes = []; 241 | }, 242 | 243 | set_defaults: function() { 244 | var o = this.options = { 245 | // Canvas dimensions 246 | width: parseInt( this.element.offsetWidth ) -1, 247 | height: parseInt( this.element.offsetHeight ) -1, 248 | // Padding 249 | x_padding_left: 0, 250 | x_padding_right: 0, 251 | y_padding_top: 0, 252 | y_padding_bottom: 0, 253 | // Attributes 254 | color: Ico.get_style( this.element, "color" ), 255 | //color: this.element.getStyle( 'color' ), 256 | mouseover_attributes: { stroke: 'red' }, 257 | // Units 258 | units: '', 259 | units_position: 1 // 0 => before value e.g. $34, 1 => after value e.g. 45%. 260 | }; 261 | de&&ug( "options: width: " + o.width + ', height: ' + o.height + ', color: ' + o.color ); 262 | }, 263 | 264 | process_options: function( o ) { 265 | de&&ug( "Ico.Base.process_options()" ); 266 | 267 | var t = this; 268 | 269 | o && ( o = Ico.extend( t.options, o ) ); 270 | 271 | // Set min and max if overriden 272 | if ( typeof( o.min ) != 'undefined' ) t.min = Math.min( t.min, o.min ); 273 | if ( typeof( o.max ) != 'undefined' ) t.max = Math.max( t.max, o.max ); 274 | t.range = t.max - t.min; 275 | 276 | // Set x and y attributes 277 | t.x = { direction: [1, 0], start_offset: 0, width: o.width }; 278 | t.y = { direction: [0, -1], start_offset: 0, width: o.height }; 279 | t.x.other = t.y; t.y.other = t.x; 280 | t.x.padding = [o.x_padding_left, o.x_padding_right]; 281 | t.y.padding = [o.y_padding_top, o.y_padding_bottom]; 282 | 283 | t.orientation = ( o.orientation || t.orientation || 0 ); 284 | t.y_direction = t.orientation? -1 : 1; 285 | // t.graph.x => labels axis; t.graph.y => value labels axis 286 | t.graph = t.orientation? { x: t.y, y: t.x } : { x: t.x, y: t.y }; 287 | 288 | // Scan components and process their options 289 | t.components = []; 290 | for ( var k in Ico.Component.components ) { 291 | if ( ! Ico.Component.components.hasOwnProperty( k ) ) continue; 292 | var a = t.options[k + '_attributes'], o = t.options[k], v = Ico.Component.components[k]; 293 | if ( o === true && a ) o = t.options[k] = a; 294 | if ( o ) { 295 | var layer = v[1]; 296 | if ( ! t.components[layer] ) t.components[layer] = []; 297 | try { 298 | t.components[layer].push( t[k] = new (v[0])( t, o ) ) 299 | } catch( e ) { 300 | t.error = e; 301 | de&&ug( "process_options(), exception: " + e ); 302 | } 303 | } 304 | }; 305 | }, 306 | 307 | get_font: function() { 308 | var o = this.options; 309 | if( this.font ) return this.font; 310 | this.font = { 311 | 'font-family': Ico.get_style( this.element, 'font-family' ), 312 | 'font-size' : o.font_size || Ico.get_style( this.element, 'font-size' ) || 10, 313 | fill : Ico.get_style( this.element, 'color' ) || '#666', 314 | stroke : 'none' 315 | }; 316 | o && Ico.extend( this.font, o ); 317 | return this.font 318 | }, 319 | 320 | set_raphael: function( o ) { 321 | if ( this.paper ) return; 322 | this.paper = Raphael( this.element, o.width, o.height ); 323 | this.svg = ! ( this.vml = Raphael.vml ); 324 | }, 325 | 326 | clear: function() { 327 | if ( this.paper ) { 328 | this.components_call( 'clear', this.options ); 329 | this.paper.remove(); 330 | this.paper = null; 331 | } 332 | return this 333 | }, 334 | 335 | calculate: function( o ) { 336 | de&&ug( "Ico.Base.calculate()" ); 337 | 338 | this.paper || this.set_raphael( o ); 339 | 340 | // component calculations may modify padding to make room for themselves 341 | this.components_call( 'calculate', o ); 342 | 343 | // calculate graph area dimensions 344 | this.calculate_graph_len( this.graph.x ); 345 | this.calculate_graph_len( this.graph.y ); 346 | 347 | // calculate graph plotting attributes 348 | this.scale = this.y_direction * this.graph.y.len / this.range; 349 | this.graph.x.step = this.graph.x.len / this.label_slots_count(); 350 | 351 | // calculate start and stop graph canvas coordinates 352 | this.x.start = this.x.padding[0]; 353 | this.x.stop = this.x.start + this.x.len; 354 | 355 | this.y.stop = this.y.padding[0]; 356 | this.y.start = this.y.stop + this.y.len; 357 | }, 358 | 359 | calculate_graph_len: function( d ) { 360 | d.len = d.width - d.padding[0] - d.padding[1]; 361 | }, 362 | 363 | calculate_bars: function( o ) { 364 | this.bar_width = this.graph.x.step - o.bar_padding; 365 | if ( this.bar_width < 5 ) this.bar_width = 5; 366 | this.graph.x.start_offset = this.y_direction * this.graph.x.step / 2; 367 | this.bar_base = this.graph.y.start - this.scale * ( 368 | ( this.max <= 0? this.max : 0 ) - 369 | ( this.min < 0? this.min : 0 ) 370 | ) 371 | }, 372 | 373 | format_value: function( v, p1000 ) { 374 | if ( v != 0 && typeof p1000 !== "number" ) { // !! v can be the string "0" 375 | p1000 = Ico.root( v, 1000 ); 376 | p1000 && ( v /= Math.pow( 1000, p1000 ) ); 377 | v = Ico.significant_digits_round( v, 3, Math.round, true ).toString() 378 | } 379 | v = '' + v; 380 | p1000 && ( v += ['','k','M','G','T','P','E','Z','Y'][p1000] || 'e' + 3 * p1000 ); 381 | var o = this.options; 382 | if ( o.units ) return o.units_position? v + o.units : o.units + v; 383 | return v 384 | }, 385 | 386 | draw: function( o ) { 387 | de&&ug( "Ico.Base.draw()" ); 388 | 389 | this.paper || this.set_raphael( o ); 390 | this.components_call( 'draw', o ); 391 | this.draw_series( o ); 392 | return this 393 | }, 394 | 395 | draw_series: function( o ) { 396 | for ( var i = -1; ++i < this.series.length; ) { 397 | this.series_shapes[i] = { 398 | shape: this.draw_serie( this.series[i], i, o ), 399 | visible: true 400 | } 401 | }; 402 | this.highlight && this.draw_highlight(); // make highlight a component 403 | }, 404 | 405 | get_serie: function( s ) { 406 | s = this.series_shapes[s]; 407 | if ( s ) return s; 408 | throw 'Undefined serie'; 409 | }, 410 | 411 | toggle_serie: function( s ) { 412 | ( ( s = this.get_serie( s ) ).visible ^= 1 ) && s.shape.show() || s.shape.hide(); 413 | }, 414 | 415 | show_serie: function( s ) { 416 | ( s = this.get_serie( s ) ).shape.show(); 417 | s.visible = true; 418 | }, 419 | 420 | hide_serie: function( s ) { 421 | ( s = this.get_serie( s ) ).shape.hide(); 422 | s.visible = false; 423 | }, 424 | 425 | components_call: function( f, o ) { 426 | for( var i = -1; ++i < this.components.length; ) { 427 | var layer = this.components[i]; 428 | if ( ! layer ) continue; 429 | for( var j = -1; ++j < layer.length; ) { 430 | var c = layer[j]; 431 | try { 432 | c[f] && c[f]( c.options, this ) 433 | } catch( e ) { 434 | de&&ug( "Ico::Base::components_call(), exception " + e ); 435 | this.set_raphael( o ) 436 | this.errors = ( this.errors || 0 ) + 1; 437 | this.paper.text( 0, 12 * this.errors, "Error in " + f + "(): " + ( this.error = e ) ) 438 | .attr( { 439 | 'text-anchor': 'start', 440 | 'font-size': 10, fill: 'black', stroke:'none', 'font-family': 'Arial' 441 | } 442 | ) 443 | } 444 | } 445 | }; 446 | }, 447 | 448 | plot: function( v ) { 449 | return ( v - this.min ) * this.scale; 450 | }, 451 | 452 | show_label_onmouseover : function( o, value, attr, serie, i, name ) { 453 | var t = this, v = ''; 454 | if ( this.status_bar ) { 455 | if ( ! name ) { 456 | var labels = this.labels, label, 457 | names = this.options.series_names 458 | ; 459 | if ( labels && ( labels = labels.options.long_values || labels.options.values ) && ( label = labels[i] ) ) { 460 | v += label + ', ' 461 | } 462 | names && ( name = names[serie] ); 463 | } 464 | name && ( v += name + ': ' ); 465 | v += this.format_value( value ); 466 | } 467 | 468 | o.node.onmouseout = function() { 469 | t.status_bar && t.status_bar.shape.hide(); 470 | o.attr( attr ); 471 | }; 472 | 473 | o.node.onmouseover = function() { 474 | t.status_bar && t.status_bar.shape.attr( { 'text': v } ).show(); 475 | o.attr( t.options.mouseover_attributes ); 476 | }; 477 | } 478 | } ); 479 | 480 | Ico.SparkLine = Ico.Class.create( Ico.Base, { 481 | process_options: function( o ) { 482 | Ico.Base.prototype.process_options.call( this, o ); 483 | 484 | this.graph.y.padding[1] += 1; 485 | var highlight = this.options.highlight; 486 | if ( highlight ) { 487 | this.highlight = { index: this.data_samples - 1, color: 'red', radius: 2 }; 488 | Ico.extend( this.highlight, highlight == true? {} : highlight ); 489 | if ( this.highlight.index == this.data_samples - 1 ) this.graph.x.padding[1] += this.highlight.radius + 1; 490 | } 491 | }, 492 | 493 | label_slots_count: function() { return this.data_samples - 1 }, 494 | 495 | draw_serie: function( serie, i, o ) { 496 | var t = this, x = this.x.start + this.x.start_offset, p; 497 | serie.forEach( function( v ) { 498 | p = Ico.svg_path( 499 | [( p ? p + 'L' : 'M' ), x, t.y.start + t.y.start_offset - t.plot( v )] 500 | ); 501 | x += t.x.step; 502 | } ); 503 | return this.paper.path( p ).attr( { stroke: o.color } ); 504 | }, 505 | 506 | draw_highlight: function() { 507 | var i = this.highlight.index, x = this.x, y = this.y; 508 | this.paper.circle( 509 | Math.round( x.start + x.start_offset + i * x.step ), 510 | Math.round( y.start + y.start_offset - this.plot( this.series[0][i] ) ), 511 | this.highlight.radius 512 | ).attr( { stroke: 'none', fill: this.highlight.color } ); 513 | } 514 | } ); 515 | 516 | Ico.SparkBar = Ico.Class.create( Ico.SparkLine, { 517 | label_slots_count: function() { return this.data_samples }, 518 | 519 | calculate: function( o ) { 520 | Ico.SparkLine.prototype.calculate.call( this, o ); 521 | 522 | this.calculate_bars( o ) 523 | }, 524 | 525 | draw_serie: function( serie, i, o ) { 526 | var t = this, x = this.x.start + this.x.start_offset, p = ''; 527 | serie.forEach( function( v ) { 528 | p += Ico.svg_path( ['M', x, t.bar_base, 'v', - t.scale * v ] ); 529 | x += t.x.step; 530 | } ) 531 | return this.paper.path( p ).attr( { 'stroke-width': this.graph.x.step, stroke: o.color } ); 532 | }, 533 | 534 | draw_highlight: function() { 535 | var i = this.highlight.index, x = this.x; 536 | this.paper.path( Ico.svg_path( [ 537 | 'M', x.start + x.start_offset + i * x.step, this.bar_base, 538 | 'v', - this.scale * this.series[0][i] 539 | ] ) ).attr( { 'stroke-width': this.graph.x.step, stroke: this.highlight.color } ); 540 | } 541 | } ); 542 | 543 | Ico.BulletGraph = Ico.Class.create( Ico.Base, { 544 | set_defaults: function() { 545 | Ico.Base.prototype.set_defaults.call( this ); 546 | 547 | this.orientation = 1; 548 | 549 | Ico.extend( this.options, { 550 | min : 0, 551 | max : 100, 552 | color : '#33e', 553 | graph_background : true 554 | }) 555 | }, 556 | 557 | process_options: function( o ) { 558 | Ico.Base.prototype.process_options.call( this, o ); 559 | 560 | var t = this.target = { color: '#666', length: 0.8, 'stroke-width' : 2 }; 561 | if ( typeof o.target === "number" ) { 562 | t.value = o.target 563 | } else { 564 | Ico.extend( t, o.target || {} ); 565 | } 566 | }, 567 | 568 | label_slots_count: function() { return 1 }, 569 | 570 | calculate: function( o ) { 571 | Ico.Base.prototype.calculate.call( this, o ); 572 | 573 | o.bar_padding || ( o.bar_padding = 2 * this.graph.x.len / 3 ); 574 | this.calculate_bars( o ) 575 | }, 576 | 577 | draw_series: function( o ) { 578 | var x = this.x.start + this.x.start_offset, 579 | y = this.y.start + this.y.start_offset, 580 | value = this.series[0][0] 581 | ; 582 | 583 | // this is a bar value => new Ico.Serie.Line(serie).draw() 584 | var a, p = this.series_shapes[0] = this.paper.path( Ico.svg_path( 585 | ['M', x - this.plot( value ), y - this.bar_width / 2, 586 | 'H', this.bar_base, 'v', this.bar_width, 587 | 'H', x - this.plot( value ), 'z'] 588 | ) ).attr( a = { fill: o.color, 'stroke-width' : 1, stroke: 'none' } ); 589 | 590 | this.show_label_onmouseover( p, value, a, 0, 0 ); 591 | 592 | // target should be a component, or might be a graph background option 593 | if ( typeof( this.target.value ) != 'undefined' ) { 594 | var t = this.target, padding = 1 - t.length, y = this.y; 595 | this.paper.path( Ico.svg_path( 596 | ['M', x - this.plot( t.value ), y.len * padding / 2, 'v', y.len * ( 1 - padding )] 597 | ) ).attr( { stroke: t.color, 'stroke-width' : t['stroke-width'] } ) 598 | } 599 | } 600 | } ); 601 | 602 | Ico.BaseGraph = Ico.Class.create( Ico.Base, { 603 | set_defaults: function() { 604 | Ico.Base.prototype.set_defaults.call( this ); 605 | 606 | Ico.extend( this.options, { 607 | // Padding options 608 | y_padding_top: 15, 609 | y_padding_bottom: 10, 610 | x_padding_left: 10, 611 | x_padding_right: 10, 612 | // Series options 613 | colors: [], // List of colors for line graphs 614 | series_attributes: [], // List of attributes for lines or bars 615 | // Other options 616 | value_labels: {}, // allow values, labels, false => disable 617 | focus_hint: true, 618 | axis: true 619 | } ); 620 | }, 621 | 622 | process_options: function( options ) { 623 | var t = this, min_max = Ico.adjust_min_max( this.min, this.max ); 624 | this.min = min_max[0]; 625 | this.max = min_max[1]; 626 | 627 | // !! process superclass options after min and max adjustments 628 | Ico.Base.prototype.process_options.call( this, options ); 629 | 630 | // Set default colors[] for individual series 631 | this.series.forEach( function( serie, i ) { 632 | t.options.colors[i] || ( 633 | t.options.colors[i] = t.options.color || Raphael.hsb2rgb( Math.random(), 1, .75 ).hex 634 | ) 635 | } ); 636 | }, 637 | 638 | draw_serie: function( serie, index, o ) { 639 | var x = this.graph.x.start + this.graph.x.start_offset, 640 | y = this.graph.y.start + this.graph.y.start_offset + this.scale * this.min, 641 | p = this.paper.path(), 642 | path = '', 643 | set = this.paper.set() 644 | ; 645 | for( var i = -1; ++i < serie.length; ) { 646 | var v = serie[i]; 647 | if ( v == null ) { 648 | this.last = null; 649 | } else { 650 | path += this.draw_value( i, x, y - this.scale * v, v, index, set, o ); 651 | } 652 | x += this.y_direction * this.graph.x.step; 653 | }; 654 | if ( path != '' ) { // only for line graphs 655 | de&&ug( "draw_serie(), path: " + path ); 656 | 657 | p.attr( { path: path } ).attr( o.series_attributes[index] || 658 | { stroke: o.colors[index], 'stroke-width': o.stroke_width || 3 } 659 | ); 660 | set.push( p ) 661 | } 662 | return set; 663 | } // draw_serie() 664 | } ); 665 | 666 | Ico.LineGraph = Ico.Class.create( Ico.BaseGraph, { 667 | set_defaults: function() { 668 | Ico.BaseGraph.prototype.set_defaults.call( this ); 669 | 670 | Ico.extend( this.options, { 671 | curve_amount: 5, // 0 => disable 672 | dot_radius: 3, // 0 => no dot 673 | dot_attributes: [], // List of attributes for dots 674 | focus_radius: 6, // 0 => disable mouseover action 675 | focus_attributes: { stroke: 'none', 'fill': 'white', 'fill-opacity' : 0 } 676 | } 677 | ) }, 678 | 679 | process_options: function( options ) { 680 | Ico.BaseGraph.prototype.process_options.call( this, options ); 681 | 682 | var t = this; 683 | 684 | this.series.forEach( function( serie, i ) { 685 | var color = t.options.colors[i]; 686 | 687 | if ( ! t.options.series_attributes[i] ) { 688 | t.options.series_attributes[i] = { 689 | stroke: color, 'stroke-width': 2 690 | }; 691 | } 692 | // line dots attribute apply only to line series 693 | if ( ! t.options.dot_attributes[i] ) { 694 | t.options.dot_attributes[i] = { 695 | 'stroke-width': 1, 696 | stroke: t.background ? t.background.options.color : color, 697 | fill: color 698 | }; 699 | } 700 | } ); 701 | }, 702 | 703 | label_slots_count: function() { return this.data_samples - 1 }, 704 | 705 | draw_value: function( i, x, y, value, serie, set, o ) { 706 | var radius = o.dot_radius, 707 | focus = o.focus_radius, 708 | t 709 | ; 710 | 711 | this.orientation && ( t = x, x = y, y = t ); 712 | 713 | if ( typeof radius == 'object' ) radius = radius[ serie ]; 714 | if ( radius ) { 715 | set.push( this.paper.circle( x, y, radius ).attr( o.dot_attributes[ serie ] ) ); 716 | } 717 | 718 | if ( typeof focus == 'object' ) focus = focus[ serie ]; 719 | if ( focus ) { 720 | var a = o.focus_attributes, 721 | c = this.paper.circle( x, y, focus ).attr( a ) 722 | ; 723 | set.push( c ); 724 | this.show_label_onmouseover( c, value, a, serie, i ); 725 | } 726 | 727 | var p, w = o.curve_amount, last = this.last; 728 | 729 | if ( i === 0 || !last ) { 730 | p = ['M', x, y]; 731 | } else if ( w ) { 732 | serie = this.series[serie]; 733 | // Calculate cubic Bezier control points relative coordinates based on the two previous, current 734 | // and next points 735 | var scale = this.scale * w / 2 / this.graph.x.step, 736 | ym1 = serie[i - 1], ym2 = serie[i - 2], y0 = serie[i], y1 = serie[i + 1], 737 | d0 = [w, ( ym2 !== undefined ? ( ym2 - y0 ) : ( ym1 - y0 ) * 2 ) * scale], 738 | d1 = [w, ( y1 !== undefined ? ( ym1 - y1 ) : ( ym1 - y0 ) * 2 ) * scale] 739 | ; 740 | 741 | this.orientation && ( d0 = [d0[1], -w], d1 = [d1[0], -w] ); 742 | 743 | var x0 = last[0] + d0[0], y0 = last[1] + d0[1] 744 | , x1 = x - d1[0], y1 = y - d1[1] 745 | ; 746 | 747 | // Display control points and lines 748 | if ( o.debug && serie === this.series[0] ) { 749 | var a0 = { 'stroke':'black' }, a1 = { 'stroke':'red' }; 750 | 751 | this.paper.circle( x0, y0, 1 ).attr( a0 ); 752 | this.paper.path( Ico.svg_path( ['M', last[0], last[1], 'L', x0, y0 ] ) ).attr( a0 ); 753 | this.paper.circle( x1, y1, 1 ).attr( a1 ); 754 | this.paper.path( Ico.svg_path( ['M', x, y, 'L', x1, y1 ] ) ).attr( a1 ); 755 | } 756 | p = ["C", x0, y0, x1, y1, x, y]; 757 | } else { 758 | p = ['L', x, y]; 759 | } 760 | 761 | this.last = [x, y]; 762 | 763 | return Ico.svg_path( p ); 764 | } // draw_value() 765 | } ); 766 | 767 | Ico.BarGraph = Ico.Class.create( Ico.BaseGraph, { 768 | set_defaults: function() { 769 | Ico.BaseGraph.prototype.set_defaults.call( this ); 770 | var o = this.options; 771 | 772 | o.bar_padding = 5; 773 | o.bars_overlap = 1 / 2; 774 | }, 775 | 776 | process_options: function( o ) { 777 | Ico.BaseGraph.prototype.process_options.call( this, o ); 778 | var t = this; o = t.options; 779 | 780 | this.series.forEach( function( serie, i ) { 781 | var a = o.series_attributes; 782 | 783 | a[i] || ( a[i] = { 784 | stroke: 'none', 'stroke-width': 2, 785 | gradient: '' + ( t.orientation ? 270 : 0 ) + '-' + o.colors[i] + ':20-#555555' 786 | } ); 787 | } ); 788 | 789 | o.bars_overlap > 1 && ( o.bars_overlap = 1 ); 790 | }, 791 | 792 | calculate: function( o ) { 793 | Ico.BaseGraph.prototype.calculate.call( this, o ); 794 | 795 | this.calculate_bars( o ); 796 | o = this.bars_overlap = o.bars_overlap; 797 | 798 | var n = this.series.length 799 | , w = this.bars_width = this.bar_width / ( n - ( n - 1 ) * o ) 800 | ; 801 | 802 | this.bars_step = w * ( 1 - o ); 803 | }, 804 | 805 | label_slots_count: function() { return this.data_samples }, 806 | 807 | draw_value: function( i, x, y, v, serie, set, o ) { 808 | var a = o.series_attributes[serie], 809 | w = this.bars_width, 810 | b = this.bar_base, 811 | bar 812 | ; 813 | if ( Math.abs( y - b ) < 2 ) y = b + ( y >= b ? 2 : -2 ); 814 | x += this.bars_step * serie - this.bar_width / 2; 815 | this.show_label_onmouseover( bar = this.paper.path( Ico.svg_path( this.orientation? 816 | ['M', y, x, 'H', b, 'v', w, 'H', y, 'z'] : 817 | ['M', x, y, 'V', b, 'h', w, 'V', y, 'z'] 818 | ) ).attr( a ), v, a, serie, i 819 | ); 820 | set.push( bar ); 821 | return ''; 822 | } 823 | } ); 824 | 825 | Ico.HorizontalBarGraph = Ico.Class.create( Ico.BarGraph, { 826 | set_defaults: function() { 827 | Ico.BarGraph.prototype.set_defaults.call( this ); 828 | 829 | this.orientation = 1; 830 | } 831 | }); 832 | 833 | // ---------------- 834 | // Chart components 835 | // ---------------- 836 | 837 | Ico.Component = Ico.Class.create( { 838 | initialize: function( p, o ) { 839 | Ico.extend( this, { 840 | p : p, 841 | graph : p.graph, 842 | x : p.x, 843 | y : p.y, 844 | orientation : p.orientation 845 | } ); 846 | if( Ico.isArray( o ) ) o = { values: o }; 847 | else if( typeof o === "number" || typeof o === "string" ) o = { value: o }; 848 | o = this.options = ( this.defaults ? Ico.extend( this.defaults( p ), o ) : o ); 849 | de&&ug( "Ico.Component::initialize(), o: " + JSON.stringify( o ) ); 850 | this.process_options && this.process_options( o, p ); 851 | } 852 | } ); 853 | 854 | Ico.Component.components = {}; 855 | 856 | /* 857 | Ico.Component.Template = Ico.Class.create( Ico.Component, { 858 | defaults: function() { return {} }, 859 | process_options: function() {}, 860 | calculate: function() {}, 861 | draw: function() {}, 862 | clear: function() {} 863 | } ); 864 | 865 | Ico.Component.components.template = [Ico.Component.Template, 0]; 866 | */ 867 | 868 | Ico.Component.Background = Ico.Class.create( Ico.Component, { 869 | defaults: function() { return { corners: true } }, 870 | 871 | process_options: function( o ) { 872 | if ( o.color && ! o.attributes ) { 873 | o.attributes = { stroke: 'none', fill: o.color }; 874 | } 875 | if ( o.attributes ) o.color = o.attributes.fill; 876 | if ( o.corners === true ) o.corners = Math.round( this.p.options.height / 20 ); 877 | }, 878 | 879 | draw: function( o, p ) { 880 | var O = p.options; 881 | 882 | this.shape = p.paper.rect( 0, 0, O.width, O.height, o.corners ) 883 | .attr( o.attributes ); 884 | } 885 | } ); 886 | 887 | Ico.Component.components.background = [Ico.Component.Background, 0]; 888 | 889 | Ico.Component.StatusBar = Ico.Class.create( Ico.Component, { 890 | defaults: function() { return { attributes: { 'text-anchor': 'end' } } }, 891 | 892 | draw: function( o, p ) { 893 | var O = p.options, y = this.y; 894 | 895 | this.shape = p.paper 896 | .text( o.x || O.width - 10, o.y || ( y ? y.padding[0] : O.height ) / 2, '' ) 897 | .hide().attr( o.attributes ) 898 | ; 899 | } 900 | } ); 901 | 902 | Ico.Component.components.status_bar = [Ico.Component.StatusBar, 2]; 903 | 904 | Ico.Component.MousePointer = Ico.Class.create( Ico.Component, { 905 | defaults: function() { return { attributes: { stroke: '#666', 'stroke-dasharray': '--' } } }, 906 | 907 | draw: function( o, p ) { 908 | if ( ! Ico.viewport_offset ) return; 909 | 910 | var s = this.shape = p.paper.path().attr( o.attributes ).hide() 911 | , X = this.x, Y = this.y, c = p.element 912 | ; 913 | 914 | c.onmousemove = function( e ) { 915 | e = e || window.event; 916 | 917 | var o = Ico.viewport_offset( c ), x = e.clientX - o[0], y = e.clientY - o[1]; 918 | // Google chrome: if the view does not start at 0, 0, there is an offset 919 | // IE: Slow moves 920 | // FF provides the best result with smooth moves 921 | if ( x >= X.start && x <= X.stop && y >= Y.stop && y <= Y.start ) { 922 | s.attr( { path: Ico.svg_path( 923 | [ 'M', X.start, y, 'h', X.len, 'M', x, Y.stop , 'v', Y.len ] 924 | ) } ).show(); 925 | } else { 926 | s.hide(); 927 | } 928 | }; 929 | 930 | c.onmouseout = function() { s.hide() }; 931 | } 932 | } ); 933 | 934 | Ico.Component.components.mouse_pointer = [Ico.Component.MousePointer, 2]; 935 | 936 | Ico.Component.GraphBackground = Ico.Class.create( Ico.Component, { 937 | defaults: function() { 938 | return { 939 | key_colors : ['#aaa','#ccc','#eee'], // e.g. bad, satisfactory, and good colors 940 | key_values : [50, 75], // e.g. satisfactory, and good values thresholds 941 | colors_transition : 0 // <= 50 gradient size in percent of each section 942 | }; 943 | }, 944 | 945 | draw: function( o, p ) { 946 | var x = this.x, y = this.y 947 | , r = p.range, t = o.colors_transition / r, u = 100 / r - t 948 | , C = o.key_colors, l = C.length, V = o.key_values, L = V.length 949 | , g = this.orientation ? '0' : '90' 950 | ; 951 | l > L + 1 && ( l = L + 1 ); // too many key colors vs key values 952 | 953 | for ( var v, a = 0, i = -1; ++i < l; a = v ) { 954 | var s = i < L 955 | , b = s ? v = V[ i ] - p.min : r 956 | , c = '-' + C[ i ] + ':' 957 | ; 958 | 959 | i && ( g += c + Math.round( a * u + b * t ) ); 960 | s && ( g += c + Math.round( a * t + b * u ) ); 961 | } 962 | this.shape = p.paper 963 | .rect( x.start, y.stop, x.len, y.len ) 964 | .attr( { gradient: g, stroke: 'none' } ) 965 | ; 966 | } 967 | } ); 968 | 969 | Ico.Component.components.graph_background = [Ico.Component.GraphBackground, 1]; 970 | 971 | Ico.Component.Grid = Ico.Class.create( Ico.Component, { 972 | defaults: function() { return { stroke: '#eee', 'stroke-width': 1 } } 973 | } ); 974 | Ico.Component.components.grid = [Ico.Component.Grid, 0]; 975 | 976 | Ico.Component.Labels = Ico.Class.create( Ico.Component, { 977 | defaults: function( p ) { 978 | return { 979 | marker_size: 5 // 0 to disable 980 | , angle: 0 // degrees, clockwise 981 | , add_padding: true 982 | , position: 0 // labels position, 0 => ( left / bottom ), 1 => ( right / top ) 983 | // this is under development, so don't use it yet 984 | , grid: p.grid ? p.grid.options : undefined 985 | } 986 | }, 987 | 988 | process_options: function( o, p ) { 989 | var title = o.title; 990 | 991 | Ico.extend( this.font = Ico.extend( {}, p.get_font() ), o.font || {} ); 992 | this.markers_attributes = { stroke: this.font.fill, 'stroke-width': 1 }; 993 | Ico.extend( this.markers_attributes, o.markers_attributes ); 994 | if ( title ) { 995 | this.title = title.value; 996 | Ico.extend( this.title_font = this.font, o.title_font ); 997 | } 998 | this.x.angle = 0; 999 | this.y.angle = -90; // default vertical labels angle vs vertical axis 1000 | }, 1001 | 1002 | calculate: function( o ) { // value labels should overload without calling super 1003 | this.calculate_labels_padding( this.graph.x, 1, o ); 1004 | }, 1005 | 1006 | calculate_labels_padding: function( d, position, o ) { 1007 | var dx = d.direction[0], dy = d.direction[1] 1008 | , marker = o.marker_size, padding = [] 1009 | ; 1010 | if ( ( d.labels = o.values ) === undefined ) { 1011 | o.values = d.labels = []; 1012 | for ( i = 0; ++i <= this.data_samples; ) d.labels[ i ] = i; 1013 | } 1014 | var angle = d.angle += o.angle; 1015 | if ( d.labels ) { 1016 | var bbox = this.get_labels_bounding_boxes( d ), font_size = d.font_size = bbox[1]; 1017 | if ( angle % 180 ) { 1018 | angle = angle * Math.PI / 180; 1019 | var sin = Math.abs( Math.sin( angle ) ), cos = Math.abs( Math.cos( angle ) ); 1020 | d.f = [font_size * cos / 2, font_size * sin / 2 + marker]; 1021 | padding[1] = Math.round( bbox[0] * sin + d.f[1] + d.f[0] ); 1022 | // padding[0] = Math.round( bbox[0] * cos + d.f[1] ); 1023 | if ( dx ) { 1024 | angle < 0 ^ o.position && ( d.f[0] = -d.f[0] ) 1025 | } else { 1026 | d.f = [-d.f[1], 0]; 1027 | } 1028 | if ( this.p.vml ) { // Fix VML vs SVG text positionning 1029 | var offset = 2.2; //+ font_size / 30; 1030 | if ( dy ) angle += Math.PI / 2; 1031 | d.f[0] -= offset * Math.sin( angle ); 1032 | d.f[1] += offset * Math.cos( angle ) 1033 | } 1034 | d.anchor = dy? ( o.position? 'start' : 'end' ) 1035 | : ( angle > 0 ^ o.position? 'start' : 'end' ) 1036 | } else { 1037 | // Labels parallel to axis 1038 | var o = 0.6 * font_size + marker; 1039 | d.f = [ dy * o, dx * o]; 1040 | padding[1] = bbox[1] * 1.1 + marker; 1041 | // padding[0] = bbox[0] / 2; 1042 | d.anchor = 'middle' 1043 | } 1044 | } 1045 | var i = position ^ this.orientation ^ o.position; 1046 | if ( o.add_padding ) { 1047 | d.other.padding[i] += padding[1] 1048 | } else if ( d.other.padding[i] < padding[1] ) { 1049 | d.other.padding[i] = padding[1] 1050 | } 1051 | }, 1052 | 1053 | get_labels_bounding_boxes: function( d ) { 1054 | if ( this.labels ) return this.bbox; 1055 | this.labels = []; this.bboxes = []; this.bbox = [0, 0]; 1056 | var t = this, longuest = 0; d.labels.forEach( 1057 | function ( l ) { 1058 | if ( typeof( l ) == 'undefined' ) l = ""; 1059 | t.labels.push( l = t.p.paper.text( 10, 10, l.toString() ).attr( t.font ) ); 1060 | t.bboxes.push( l = l.getBBox() ); 1061 | l.width > longuest && ( t.bbox = [longuest = l.width, l.height] ) 1062 | } 1063 | ); 1064 | return this.bbox; 1065 | }, 1066 | 1067 | clear: function() { 1068 | this.labels = null; 1069 | }, 1070 | 1071 | text_size: function( text, attr ) { 1072 | var t = this.p.paper.text( 10, 10, '' ).attr( attr ).attr( { 'text' : text } ); 1073 | var d, bbox; 1074 | //if ( this.vml ) { 1075 | //t.shape.style.display = "inline"; 1076 | //d = [t.shape.offsetWidth, t.shape.offsetHeight]; 1077 | //} else { 1078 | bbox = t.getBBox(); 1079 | d = [bbox.width, bbox.height]; 1080 | //} 1081 | t.remove(); 1082 | return d; 1083 | }, 1084 | 1085 | draw: function( o ) { this.draw_labels_grid( this.graph.x, o ); }, 1086 | 1087 | draw_labels_grid: function( d, o ) { 1088 | var t = this, dx = d.direction[0], dy = d.direction[1], step = d.step, 1089 | x = t.x.start + t.x.start_offset * dx, 1090 | y = t.y.start - t.y.start_offset * dy, 1091 | marker = o.marker_size, fx = d.f[0], fy = d.f[1], angle = d.angle, 1092 | paper = this.p.paper, grid = o.grid, path = [] 1093 | ; 1094 | 1095 | if ( grid ) { 1096 | var grid_len = t[ dx ? "y" : "x" ].len 1097 | , grid_x = 0, grid_y = 0 1098 | , labels = d.labels 1099 | , grid_path = [] 1100 | ; 1101 | 1102 | if ( grid.through ) { 1103 | grid_len += d.padding[ 1 ] + 10; 1104 | 1105 | var offset = -d.padding[ 0 ] - this.bbox[ 0 ] - marker - 20; 1106 | 1107 | dx ? grid_y = offset : grid_x = offset; 1108 | } 1109 | } 1110 | 1111 | if ( dx ) { 1112 | marker = 'v' + marker; 1113 | grid_len = 'v-' + grid_len; 1114 | } else { 1115 | marker = 'h-' + marker; 1116 | grid_len = 'h'+ grid_len; 1117 | angle += 90; 1118 | } 1119 | 1120 | t.labels || this.get_labels_bounding_boxes( d ); 1121 | 1122 | for ( var i = -1, l = t.labels.length; ++i < l; ) { 1123 | var label = t.labels[ i ]; 1124 | 1125 | marker && path.push( 'M', x, y, marker ); 1126 | 1127 | if ( grid ) { 1128 | if ( labels[ i ] ) { 1129 | grid_path.push( 'M', x, y, grid_len ); 1130 | } else { 1131 | grid_path.push( 'M', x + grid_x, y + grid_y, grid_len ); 1132 | } 1133 | } 1134 | 1135 | var x_anchor = x + fx; 1136 | var y_anchor = y + fy; 1137 | 1138 | // label is already drawn, only anchor then rotate here 1139 | label.attr( { x: x_anchor, y: y_anchor, 'text-anchor': d.anchor } ).toFront(); 1140 | // !!! set 'text-anchor' attribute before rotation 1141 | angle && label.rotate( angle, x_anchor, y_anchor ); // !!! then rotate around anchor 1142 | 1143 | dx && ( x += step ); 1144 | dy && ( y -= step ); 1145 | } 1146 | 1147 | marker && paper.path( Ico.svg_path( path ) ).attr( this.markers_attributes ); 1148 | 1149 | if ( grid ) { 1150 | dx && grid_path.push( 'M', this.x.start, ' ', this.y.stop, 'h', this.x.len, 'v', this.y.len ); 1151 | 1152 | paper.path( Ico.svg_path( grid_path ) ).attr( grid ); 1153 | } 1154 | } 1155 | } ); 1156 | 1157 | Ico.Component.components.labels = [Ico.Component.Labels, 3]; 1158 | 1159 | Ico.Component.ValueLabels = Ico.Class.create( Ico.Component.Labels, { 1160 | calculate: function( o, p ) { 1161 | de&&ug( "Ico.Component.ValueLabels.calculate()" ); 1162 | 1163 | var t = this, max = p.max, min = p.min, range = max - min 1164 | , spaces = o.spaces, params 1165 | ; 1166 | 1167 | p.calculate_graph_len( this.graph.y ); 1168 | if ( ! spaces ) { 1169 | // Calculate minimal step between labels 1170 | var angle = Math.abs( o.angle ), min_step; 1171 | if ( ( this.orientation && angle < 30 ) || ( !this.orientation && angle > 60 ) ) { 1172 | min_step = Math.max.apply( Math, [min, max].map( function( v ) { 1173 | return t.text_size( 1174 | '0' + Ico.significant_digits_round( v, 3, Math.round, true ) + t.p.options.units, 1175 | t.font 1176 | )[0] 1177 | } ) ); 1178 | } else { 1179 | min_step = 1.5 * this.text_size( '0', this.font )[1]; // allow 1/2 character height spacing 1180 | } 1181 | if ( min_step === 0 ) throw new Error( "Ico.Component.ValueLabels(), min_step is zero, canvas is possibly not active" ); 1182 | 1183 | // Calculate maximum number of labels spaces 1184 | spaces = Math.round( this.graph.y.len / min_step ); 1185 | // Search (trial/error method) for the best number of spaces yiedling the lowest waste 1186 | if ( spaces > 2 ) { 1187 | var min_waste = range, max_spaces = spaces; 1188 | 1189 | // de&&ug( "Ico.Component.ValueLabels.calculate(), max_spaces: " + max_spaces + ", min_step: " + min_step ); 1190 | 1191 | for ( var tried_spaces = 1; ++tried_spaces <= max_spaces; ) { 1192 | params = calculate_value_labels_params( min, max, range, tried_spaces ); 1193 | if ( params.waste <= min_waste ) { 1194 | min_waste = params.waste; 1195 | spaces = tried_spaces; 1196 | } 1197 | } 1198 | } 1199 | } 1200 | params = calculate_value_labels_params( min, max, range, spaces ); 1201 | p.range = ( p.max = params.max ) - ( p.min = params.min ); 1202 | 1203 | var p1000 = Ico.root( params.step * params.spaces, 1000 ); 1204 | if ( p1000 ) { 1205 | var dividand = Math.pow( 1000, p1000 ); 1206 | params.step /= dividand; 1207 | params.min /= dividand; 1208 | }; 1209 | 1210 | // Finally build value labels array 1211 | var labels = o.values = []; 1212 | var precision = 0; 1213 | for ( var label = params.min, i = -1; ++i <= params.spaces; label += params.step ) { 1214 | var l = Ico.significant_digits_round( label, 3, Math.round, true ).toString(); 1215 | var len = ( l + '.' ).split( '.' )[1].length; 1216 | if ( len > precision ) precision = len; // get longuest precision 1217 | labels.push( l ); 1218 | } 1219 | // Then fix value labels precision and add units 1220 | labels.forEach( function( l, i ) { 1221 | var len = ( l + '.' ).split( '.' )[1].length; 1222 | if ( len < precision ) { 1223 | if ( len == 0 ) l += '.'; 1224 | l += '0000'.substring( 0, precision - len ); 1225 | } 1226 | labels[i] = t.p.format_value( l, p1000 ) 1227 | } ); 1228 | 1229 | this.graph.y.step = this.graph.y.len / params.spaces; 1230 | this.calculate_labels_padding( this.graph.y, 0, o ); 1231 | 1232 | function calculate_value_labels_params( min, max, range, spaces ) { 1233 | de&&ug( "Ico.Component.ValueLabels.calculate_value_labels_params()" 1234 | + ", min: " + min 1235 | + ", max: " + max 1236 | + ", range: " + range 1237 | + ", space: " + spaces 1238 | ); 1239 | 1240 | if ( min < 0 && max > 0 ) { 1241 | var spaces_above_zero = Math.round( spaces * max / range ); 1242 | if ( spaces_above_zero == 0 ) { 1243 | spaces_above_zero = 1; 1244 | } else if ( spaces_above_zero == spaces ) { 1245 | spaces_above_zero -= 1; 1246 | } 1247 | var spaces_under_zero = spaces - spaces_above_zero; 1248 | var step = Ico.significant_digits_round( Math.max( max / spaces_above_zero, - min / spaces_under_zero ), 2, 1249 | function( v ) { // the 2 digits rounding function 1250 | v = Math.ceil( v ); 1251 | if ( v <= 10 ) return v; 1252 | if ( v <= 12 ) return 12; 1253 | var mod; 1254 | // allows only multiples of five until 50 => allows 15, 20, 25, 30, 35, 40, 45, 50 1255 | if ( v <= 54 ) return ( mod = v % 5 )? v - mod + 5 : v; // always round above 1256 | // allow only multiples of 10 thereafter 1257 | return ( mod = v % 10 )? v - mod + 10 : v 1258 | } 1259 | ); 1260 | min = -step * spaces_under_zero; 1261 | max = step * spaces_above_zero; 1262 | } else { 1263 | var step = Ico.significant_digits_round( range / spaces, 1, Math.ceil ); 1264 | if ( max <= 0 ) min = max - step * spaces; 1265 | else if ( min >= 0 ) max = min + step * spaces; 1266 | } 1267 | return { min: min, max: max, spaces: spaces, step: step, waste: spaces * step - range }; 1268 | } // calculate_value_labels_params() 1269 | }, // calculate() 1270 | 1271 | draw: function( o ) { this.draw_labels_grid( this.graph.y, o ) } 1272 | } ); 1273 | 1274 | Ico.Component.components.value_labels = [Ico.Component.ValueLabels, 4]; 1275 | 1276 | Ico.Component.Meanline = Ico.Class.create( Ico.Component, { 1277 | defaults: function() { return { attributes: { stroke: '#bbb', 'stroke-width': 2 } } }, 1278 | 1279 | calculate: function() { 1280 | var values = this.p.all_values; 1281 | this.mean = values.reduce( function( sum, v ) { return sum + v }, 0 ); 1282 | de&&ug( "Ico.Component.Meanline, calculate, len: " + values.length + ", mean=" + this.mean ); 1283 | this.mean = Ico.significant_digits_round( 1284 | this.mean / values.length, 3, Math.round, true 1285 | ); 1286 | de&&ug( "Ico.Component.Meanline, calculate, len: " + values.length + ", mean=" + this.mean ); 1287 | }, 1288 | 1289 | draw: function( o, p ) { 1290 | var a = o.attributes; 1291 | if ( ! a ) return; 1292 | var mean = this.graph.y.start - p.plot( this.mean ); 1293 | this.graph.y.mean = { start: mean, stop: mean }; 1294 | this.graph.x.mean = this.graph.x; // for .start and .stop 1295 | this.shape = p.paper.path( Ico.svg_path( 1296 | ['M', this.x.mean.start, this.y.mean.start, 'L', this.x.mean.stop, this.y.mean.stop] 1297 | ) ).attr( a ); 1298 | p.show_label_onmouseover( this.shape, this.mean, a, 0, 0, 'Average' ); 1299 | } 1300 | } ); 1301 | 1302 | Ico.Component.components.meanline = [Ico.Component.Meanline, 3]; 1303 | 1304 | Ico.Component.FocusHint = Ico.Class.create( Ico.Component, { 1305 | defaults: function() { return { 1306 | length: 6, 1307 | attributes: { 'stroke-width': 2, stroke: this.p.get_font().fill } 1308 | } }, 1309 | 1310 | draw: function( o, p ) { 1311 | if ( p.min == 0 ) return; 1312 | var len = o.length, l = Ico.svg_path( ['l', len, len] ); 1313 | this.shape = p.paper.path( Ico.svg_path( this.orientation ? 1314 | ['M', this.x.start, this.y.start - len / 2, l, 'm0-', len, l] : 1315 | ['M', this.x.start - len / 2, this.y.start - 2 * len, l + 'm-', len, ' 0' + l] 1316 | ) ).attr( o.attributes ); 1317 | } 1318 | } ); 1319 | 1320 | Ico.Component.components.focus_hint = [Ico.Component.FocusHint, 5]; 1321 | 1322 | Ico.Component.Axis = Ico.Class.create( Ico.Component, { 1323 | defaults: function() { return { attributes: { stroke: '#666', 'stroke-width': 1 } } }, 1324 | draw: function( o ) { 1325 | var x = this.x, y = this.y; 1326 | this.shape = this.p.paper.path( Ico.svg_path( ['M', x.start, y.stop, 'v', y.len, 'h', x.len] ) ) 1327 | .attr( o.attributes ); 1328 | } 1329 | } ); 1330 | 1331 | Ico.Component.components.axis = [Ico.Component.Axis, 4]; 1332 | } )( this ); 1333 | --------------------------------------------------------------------------------