├── README ├── test ├── integration-expected-result.png └── integration.html ├── tufte-graph.css ├── LICENSE ├── jquery.enumerable.js ├── index.html ├── jquery.tufte-graph.js └── raphael.js /README: -------------------------------------------------------------------------------- 1 | See index.html 2 | -------------------------------------------------------------------------------- /test/integration-expected-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaviershay/tufte-graph/HEAD/test/integration-expected-result.png -------------------------------------------------------------------------------- /tufte-graph.css: -------------------------------------------------------------------------------- 1 | .graph { 2 | margin-bottom: 50px; 3 | margin-top: 30px; 4 | clear: both; 5 | font-family: georgia, serif; 6 | font-size: 14px; 7 | } 8 | 9 | .graph .label { 10 | text-align: center; 11 | padding-top: 5px; 12 | padding-bottom: 5px; 13 | } 14 | .graph-header h3 { 15 | margin-bottom: 0px; 16 | } 17 | .graph-header { 18 | text-align: center; 19 | } 20 | .graph-header p { 21 | margin-top: 0px; 22 | } 23 | 24 | .graph .legend td, .graph .legend, .graph .legend tr { 25 | padding: 0px; 26 | margin: 0px; 27 | border-collapse:collapse; 28 | } 29 | 30 | .graph .legend { 31 | margin-left: 10px; 32 | } 33 | .graph .legend td { 34 | padding-right: 5px; 35 | } 36 | 37 | .graph .legend .color-box { 38 | width: 14px; 39 | height: 10px; 40 | overflow: hidden; 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2008 Xavier Shay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 10 | THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /jquery.enumerable.js: -------------------------------------------------------------------------------- 1 | (function ( $ ) { 2 | var methods = { 3 | // $([1,2,3]).collect(function() { return this * this }) // => [1, 4, 9] 4 | collect: function(enumerable, callback) { 5 | var result = []; 6 | $.each(enumerable, function(index) { 7 | result.push(callback.call(this, index)); 8 | }); 9 | return result; 10 | }, 11 | 12 | // $([1,2,3]).inject(0, function(a) { return a + this }) // => 6 13 | inject: function(enumerable, initialValue, callback) { 14 | var accumulator = initialValue; 15 | 16 | $.each(enumerable, function (index) { 17 | accumulator = callback.call(this, accumulator, index); 18 | }); 19 | return accumulator; 20 | }, 21 | 22 | // $([1,2,3]).select(function() { return this % 2 == 1 }) // => [1, 3] 23 | select: function(enumerable, callback) { 24 | var result = []; 25 | $.each(enumerable, function(index) { 26 | if (callback.call(this, index)) 27 | result.push(this); 28 | }); 29 | return result; 30 | }, 31 | 32 | // $([1,2,3]).reject(function() { return this % 2 == 1 }) // => [2] 33 | reject: function(enumerable, callback) { 34 | return $.select(enumerable, negate(callback)); 35 | }, 36 | 37 | // $([1,2]).any(function() { return this == 1 }) // => true 38 | any: function(enumerable, callback) { 39 | return $.inject(enumerable, false, function(accumulator, index) { 40 | return accumulator || callback.call(this, index); 41 | }); 42 | }, 43 | 44 | // $([1,1]).any(function() { return this == 1 }) // => true 45 | all: function(enumerable, callback) { 46 | return $.inject(enumerable, true, function(accumulator, index) { 47 | return accumulator && callback.call(this, index); 48 | }); 49 | }, 50 | 51 | // $([1,2,3]).sum() // => 6 52 | sum: function(enumerable) { 53 | return $.inject(enumerable, 0, function(accumulator) { 54 | return accumulator + this; 55 | }); 56 | } 57 | }; 58 | 59 | var staticFunctions = {}; 60 | var iteratorFunctions = {}; 61 | $.each( methods, function(name, f){ 62 | staticFunctions[name] = makeStaticFunction(f); 63 | iteratorFunctions[name] = makeIteratorFunction(staticFunctions[name]); 64 | }); 65 | $.extend(staticFunctions); 66 | $.fn.extend(iteratorFunctions); 67 | 68 | // Private methods 69 | function makeStaticFunction(f) { 70 | return function() { 71 | if (arguments.length > 1) // The first argument is the enumerable 72 | validateCallback(arguments[arguments.length - 1]); 73 | 74 | return f.apply(this, arguments); 75 | } 76 | } 77 | 78 | function makeIteratorFunction(staticFunction) { 79 | return function() { 80 | // arguments isn't a real array, concat doesn't work 81 | // unless you explicitly convert it 82 | function toArray() { 83 | var result = [] 84 | for (var i = 0; i < this.length; i++) 85 | result.push(this[i]) 86 | return(result) 87 | } 88 | return staticFunction.apply(this, [this].concat(toArray.apply(arguments))) 89 | } 90 | } 91 | 92 | function validateCallback(callback) { 93 | if (!jQuery.isFunction(callback)) 94 | throw("callback needs to be a function, it was: " + callback); 95 | } 96 | 97 | function negate(f) { 98 | return function() { 99 | return !f.apply(this, arguments) 100 | } 101 | } 102 | })( jQuery ); 103 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TufteGraph: beautiful charts with jQuery 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 46 | 47 | 82 | 83 | 84 |
85 |
86 | Download (tarball | zip) 87 |
88 |

TufteGraph

89 |

Make pretty graphs with javascript, using jQuery

90 |
91 |
92 |

Animal awesomeness

93 |

Measured by standard combo multiplier

94 |
95 |
96 |
97 |
98 |
99 |

Sales by region

100 |

Total revenue per year

101 |
102 |
103 |
104 | 105 |

It is different from other javascript charting libraries because:

106 | 110 | 111 |

Introduction

112 |
(Introduction to TufteGraph - if your eyes aren't that good, here's a high-res version) 113 |

API

114 | 115 |
jQuery(document).ready(function () {
116 |   jQuery('#awesome-graph').tufteBar({
117 |     data: [
118 |       // First element is the y-value
119 |       // Other elements are arbitary - they are not used by the lib
120 |       // but are passed back into callback functions
121 |       [1.0, {label: 'Dog'}],
122 |       [1.3, {label: 'Raccoon'}],
123 |       // etc...
124 | 
125 |       // For stacked graphs, pass an array of non-cumulative y values
126 |       [[1.5, 1.0, 0.51], {label: '2005'}]
127 |     ],
128 | 
129 |     // Any of the following properties can be either static values 
130 |     // or a function that will be called for each data point. 
131 |     // For functions, 'this' will be set to the current data element, 
132 |     // just like jQuery's $.each
133 | 
134 |     // Bar width in arbitrary units, 1.0 means the bars will be snuggled
135 |     // up next to each other
136 |     barWidth: 0.8, 
137 | 
138 |     // The label on top of the bar - can contain HTML
139 |     // formatNumber inserts commas as thousands separators in a number
140 |     barLabel:  function(index) { 
141 |       return $.tufteBar.formatNumber(this[0]) + 'x' 
142 |     }, 
143 | 
144 |     // The label on the x-axis - can contain HTML
145 |     axisLabel: function(index) { return this[1].label }, 
146 | 
147 |     // The color of the bar
148 |     color:     function(index) { 
149 |       return ['#E57536', '#82293B'][index % 2] 
150 |     },
151 | 
152 |     // Stacked graphs also pass a stackedIndex parameter
153 |     color:     function(index, stackedIndex) { 
154 |       return ['#E57536', '#82293B'][stackedIndex % 2] 
155 |     },
156 | 
157 |     // Alternatively, you can just override the default colors and keep
158 |     // the built in color functions
159 |     colors: ['#82293B', '#E57536', '#FFBE33'],
160 |  
161 |     // Legend is optional
162 |     legend: {
163 |       // Data can be an array of any type of object, but the default
164 |       // formatter works with strings
165 |       data: ["North", "East", "West"],
166 | 
167 |       // By default, the colors of the graph are used
168 |       color: function(index) { 
169 |         return ['#E57536', '#82293B'][index % 2] 
170 |       },
171 | 
172 |       // You can customize the element label - can contain HTML
173 |       label: function(index) { return this }
174 |     }
175 |   });
176 | });
177 | 
178 |

That's it. Have a look at the integration tests for a few combinations.

179 |

Hot Tips

180 | 189 | 192 |
193 | Fork me on GitHub 194 | 195 | 196 | -------------------------------------------------------------------------------- /jquery.tufte-graph.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | // 3 | // Public interface 4 | // 5 | 6 | // The main event - creates a pretty graph. See index.html for documentation. 7 | $.fn.tufteBar = function(options) { 8 | var defaultCopy = $.extend(true, {}, $.fn.tufteBar.defaults); 9 | var options = $.extend(true, defaultCopy, options); 10 | 11 | return this.each(function () { 12 | draw(makePlot($(this), options), options); 13 | }); 14 | } 15 | 16 | // Defaults are exposed publically so you can reuse bits that you find 17 | // handy (the colors, for instance) 18 | $.fn.tufteBar.defaults = { 19 | barWidth: 0.8, 20 | colors: ['#07093D', '#0C0F66', '#476FB2'], 21 | color: function(index, stackedIndex, options) { return options.colors[stackedIndex % options.colors.length]; }, 22 | barLabel: function(index, stackedIndex) { 23 | return $.tufteBar.formatNumber(totalValue(this[0])); 24 | }, 25 | axisLabel: function(index, stackedIndex) { return index; }, 26 | legend: { 27 | color: function(index, options) { return options.colors[index % options.colors.length]; }, 28 | label: function(index) { return this; } 29 | } 30 | } 31 | 32 | $.tufteBar = { 33 | // Add thousands separators to a number to make it look pretty. 34 | // 1000 -> 1,000 35 | formatNumber: function(nStr) { 36 | // http://www.mredkj.com/javascript/nfbasic.html 37 | nStr += ''; 38 | x = nStr.split('.'); 39 | x1 = x[0]; 40 | x2 = x.length > 1 ? '.' + x[1] : ''; 41 | var rgx = /(\d+)(\d{3})/; 42 | while (rgx.test(x1)) { 43 | x1 = x1.replace(rgx, '$1' + ',' + '$2'); 44 | } 45 | return x1 + x2; 46 | } 47 | } 48 | 49 | // 50 | // Private functions 51 | // 52 | 53 | // This function should be applied to any option used from the options hash. 54 | // It allows options to be provided as either static values or functions which are 55 | // evaluated each time they are used 56 | function resolveOption(option, element) { 57 | // the @arguments@ special variable looks like an array, but really isn't, so we 58 | // need to transform it in order to perform array function on it 59 | function toArray() { 60 | var result = [] 61 | for (var i = 0; i < this.length; i++) 62 | result.push(this[i]) 63 | return(result) 64 | } 65 | 66 | return $.isFunction(option) ? option.apply(element, toArray.apply(arguments).slice(2, arguments.length)) : option; 67 | } 68 | 69 | // Returns the total value of a bar, for labeling or plotting. Y values can either be 70 | // a single number (for a normal graph), or an array of numbers (for a stacked graph) 71 | function totalValue(value) { 72 | if (value instanceof Array) 73 | return $.sum(value); 74 | else 75 | return value; 76 | } 77 | 78 | function draw(plot, options) { 79 | var ctx = plot.ctx; 80 | var axis = plot.axis; 81 | 82 | // Iterate over each bar 83 | $(options.data).each(function (i) { 84 | var element = this; 85 | var x = i + 0.5; 86 | var all_y = null; 87 | 88 | if (element[0] instanceof Array) { 89 | // This is a stacked bar, so the data is all good to go 90 | all_y = element[0]; 91 | } else { 92 | // This is a normal bar, wrap in an array to make it a stacked bar with one data point 93 | all_y = [element[0]]; 94 | } 95 | 96 | if ($(all_y).any(function() { return isNaN(+this); })) { 97 | throw("Non-numeric value provided for y: " + element[0]); 98 | } 99 | 100 | var lastY = 0; 101 | 102 | pixel_scaling_function = function(axis) { 103 | var scale = axis.pixelLength / (Math.abs(axis.max - axis.min)); 104 | return function (value) { 105 | return value * scale; 106 | } 107 | } 108 | 109 | // These functions transform a value from plot coordinates to pixel coordinates 110 | var t = {} 111 | t.W = pixel_scaling_function(axis.x); 112 | t.H = pixel_scaling_function(axis.y); 113 | t.X = t.W; 114 | // Y needs to invert the result since 0 in plot coords is bottom left, but 0 in pixel coords is top left 115 | t.Y = function(y) { return axis.y.pixelLength - t.H(y) }; 116 | 117 | // Iterate over each data point for this bar and render a rectangle for each 118 | $(all_y).each(function(stackedIndex) { 119 | var optionResolver = function(option) { // Curry resolveOption for convenience 120 | return resolveOption(option, element, i, stackedIndex, options); 121 | } 122 | 123 | var y = all_y[stackedIndex]; 124 | var halfBar = optionResolver(options.barWidth) / 2; 125 | var left = x - halfBar, 126 | width = halfBar * 2, 127 | height = Math.abs(y); 128 | 129 | // Need to both fill and stroke the rect to make sure the whole area is covered 130 | // You get nasty artifacts otherwise 131 | var color = optionResolver(options.color); 132 | if( y > 0 ) { 133 | var top = lastY + y + Math.abs(axis.y.min); 134 | var coords = [t.X(left), t.Y(top), t.W(width), t.H(height)]; 135 | } else { 136 | var top = lastY + Math.abs(axis.y.min); 137 | var coords = [t.X(left), t.Y(top), t.W(width), t.H(height)]; 138 | } 139 | 140 | ctx.rect(coords[0], coords[1], coords[2], coords[3]).attr({stroke: color, fill: color}); 141 | 142 | if( (lastY >= 0 && y >= 0) || (lastY <= 0 && y <= 0) ) { 143 | lastY = lastY + y; 144 | } else { 145 | lastY = 0; 146 | } 147 | 148 | }); 149 | 150 | addLabel = function(klass, text, pos) { 151 | html = '
' + text + "
"; 152 | $(html).css(pos).appendTo( plot.target ); 153 | } 154 | 155 | getLabelHeight = function( klass ) { 156 | // create an invisible div to get the height 157 | if( $('.' + klass).length === 0 ) { 158 | html = ''; 159 | $(html).appendTo( plot.target ); 160 | } 161 | return $('.' + klass).height(); 162 | } 163 | 164 | var optionResolver = function(option) { // Curry resolveOption for convenience 165 | return resolveOption(option, element, i, options); 166 | } 167 | 168 | var barLabelBottom = (lastY >= 0) ? 169 | t.H(lastY + Math.abs(axis.y.min)) : 170 | t.H(lastY + Math.abs(axis.y.min)) - 2 * getLabelHeight('bar-label') 171 | 172 | addLabel('bar-label', optionResolver(options.barLabel), { 173 | left: t.X(x - 0.5), 174 | bottom: barLabelBottom, 175 | width: t.W(1) 176 | }); 177 | addLabel('axis-label', optionResolver(options.axisLabel), { 178 | left: t.X(x - 0.5), 179 | top: t.Y(0) + 2 * getLabelHeight('axis-label'), 180 | width: t.W(1) 181 | }); 182 | }); 183 | addLegend(plot, options); 184 | } 185 | 186 | // If legend data has been provided, transform it into an 187 | // absolutely positioned table placed at the top right of the graph 188 | function addLegend(plot, options) { 189 | if (options.legend.data) { 190 | elements = $(options.legend.data).collect(function(i) { 191 | var optionResolver = (function (element) { 192 | return function(option) { // Curry resolveOption for convenience 193 | return resolveOption(option, element, i, options); 194 | } 195 | })(this); 196 | 197 | var colorBox = '
'; 198 | var label = optionResolver(options.legend.label); 199 | 200 | return "" + colorBox + "" + label + ""; 201 | }); 202 | 203 | $('' + elements.reverse().join("") + '
').css({ 204 | position: 'absolute', 205 | top: '0px', 206 | left: plot.width + 'px' 207 | }).appendTo( plot.target ); 208 | } 209 | } 210 | 211 | // Calculates the range of the graph by looking for the 212 | // maximum y-value 213 | function makeAxis(options) { 214 | var axis = { 215 | x: {}, 216 | y: {} 217 | } 218 | 219 | axis.x.min = 0 220 | axis.x.max = options.data.length; 221 | axis.y.min = 0; 222 | axis.y.max = 0; 223 | 224 | $(options.data).each(function() { 225 | var y = totalValue(this[0]); 226 | if( y > axis.y.max ) axis.y.max = y; 227 | if( y < axis.y.min ) axis.y.min = y; 228 | }); 229 | 230 | if( axis.x.max <= 0) throw("You must have at least one data point"); 231 | 232 | return axis; 233 | } 234 | 235 | // Creates the canvas object to draw on, and set up the axes 236 | function makePlot(target, options) { 237 | var plot = {}; 238 | plot.target = target; 239 | plot.width = target.width(); 240 | plot.height = target.height(); 241 | target.html( '' ).css( 'position', 'relative' ); 242 | 243 | if( plot.width <= 0 || plot.height <= 0 ) { 244 | throw "Invalid dimensions for plot, width = " + plot.width + ", height = " + plot.height; 245 | } 246 | 247 | // the canvas 248 | plot.ctx = Raphael(target[0].id, plot.width, plot.height); 249 | 250 | plot.axis = makeAxis(options); 251 | plot.axis.x.pixelLength = plot.width; 252 | plot.axis.y.pixelLength = plot.height; 253 | 254 | return plot; 255 | } 256 | } )( jQuery ); 257 | -------------------------------------------------------------------------------- /test/integration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TufteGraph - Integration tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 22 | 23 | 202 | 203 | 204 |

TufteGraph - Integration Tests

205 |

Bar

206 |
207 |

Default

208 |
209 |
210 |
211 |

Static properties

212 |
213 |
214 |
215 |

Dynamic properties

216 |
217 |
218 |
219 |

Negative Values

220 |
221 |
222 |

Stacked Bar

223 |
224 |

Default

225 |
226 |
227 |
228 |

Dynamic

229 |
230 |
231 |
232 |

Negative

233 |
234 |
235 |

Legends

236 |
237 |

Default

238 |
239 |
240 |
241 |

Dynamic

242 |
243 |
244 |
245 |

Use graph colors

246 |
247 |
248 |

Colors from CSS

249 |
250 |
251 |
252 | 253 |

Error Handling

254 |
255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 |
TestResult
Non-numeric y-value for bar graphNot run
Non-numeric y-value for stacked graphNot run
264 |
265 |

formatNumber

266 |
267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 |
InputExpectedActualStatus
275 |
276 |

Using data from table

277 |
278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 |
LabelAmount
A1.0
B1.3
C3.6
D4.0
E5.2
F7.0
290 |
291 | 292 | 293 | -------------------------------------------------------------------------------- /raphael.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Raphael 0.5.12 - JavaScript Vector Library 3 | * 4 | * Copyright (c) 2008 Dmitry Baranovskiy (http://raphaeljs.com) 5 | * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 6 | */ 7 | var Raphael = (function (type) { 8 | var r = function () { 9 | return r._create.apply(r, arguments); 10 | }; 11 | r.version = "0.5.12"; 12 | r.type = type; 13 | var C = {}; 14 | function Matrix(m11, m12, m21, m22, dx, dy) { 15 | this.m = [ 16 | [m11 || 1, m12 || 0, 0], 17 | [m21 || 0, m22 || 1, 0], 18 | [dx || 0, dy || 0, 1] 19 | ]; 20 | } 21 | 22 | C._getX = C._getY = C._getW = C._getH = function (x) { return x; }; 23 | 24 | if (type == "VML") { 25 | Matrix.prototype.toString = function () { 26 | return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.m[0][0] + 27 | ", M12=" + this.m[1][0] + ", M21=" + this.m[0][1] + ", M22=" + this.m[1][1] + 28 | ", Dx=" + this.m[2][0] + ", Dy=" + this.m[2][1] + ", sizingmethod='auto expand', filtertype='bilinear')"; 29 | }; 30 | var thePath = function (params, pathString, VML) { 31 | var g = document.createElement("rvml:group"), gl = g.style; 32 | gl.position = "absolute"; 33 | gl.left = 0; 34 | gl.top = 0; 35 | gl.width = VML.width + "px"; 36 | gl.height = VML.height + "px"; 37 | var el = document.createElement("rvml:shape"), ol = el.style; 38 | ol.width = VML.width + "px"; 39 | ol.height = VML.height + "px"; 40 | el.path = ""; 41 | if (params["class"]) { 42 | el.className = params["class"]; 43 | } 44 | el.coordsize = this.coordsize; 45 | el.coordorigin = this.coordorigin; 46 | g.appendChild(el); 47 | VML.canvas.appendChild(g); 48 | var p = new Element(el, g, VML); 49 | setFillAndStroke(p, params); 50 | if (params.gradient) { 51 | addGrdientFill(p, params.gradient); 52 | } 53 | p.isAbsolute = true; 54 | p.type = "path"; 55 | p.path = []; 56 | p.last = {x: 0, y: 0, bx: 0, by: 0, isAbsolute: true}; 57 | p.Path = ""; 58 | p.absolutely = function () { 59 | this.isAbsolute = true; 60 | return this; 61 | }; 62 | p.relatively = function () { 63 | this.isAbsolute = false; 64 | return this; 65 | }; 66 | p.redraw = function () { 67 | this.Path = ""; 68 | var oldPath = this.path; 69 | this.path = []; 70 | for (var i = 0, ii = oldPath.length; i < ii; i++) { 71 | if (oldPath[i].type != "end") { 72 | this[oldPath[i].type + "To"].apply(this, oldPath[i].arg); 73 | } else { 74 | this.andClose(); 75 | } 76 | } 77 | return this; 78 | }; 79 | p.moveTo = function (x, y) { 80 | var d = this.isAbsolute?"m":"t"; 81 | var _getX = this.isAbsolute ? VML._getX : VML._getW; 82 | var _getY = this.isAbsolute ? VML._getY : VML._getH; 83 | d += Math.round(_getX(parseFloat(x, 10))) + " " + Math.round(_getY(parseFloat(y, 10))); 84 | this[0].path = this.Path += d; 85 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + _getX(parseFloat(x, 10)); 86 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + _getY(parseFloat(y, 10)); 87 | this.last.isAbsolute = this.isAbsolute; 88 | this.path.push({type: "move", arg: [].slice.call(arguments, 0), pos: this.isAbsolute}); 89 | return this; 90 | }; 91 | p.lineTo = function (x, y) { 92 | var d = this.isAbsolute?"l":"r"; 93 | var _getX = this.isAbsolute ? VML._getX : VML._getW; 94 | var _getY = this.isAbsolute ? VML._getY : VML._getH; 95 | d += Math.round(_getX(parseFloat(x, 10))) + " " + Math.round(_getY(parseFloat(y, 10))); 96 | this[0].path = this.Path += d; 97 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + _getX(parseFloat(x, 10)); 98 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + _getY(parseFloat(y, 10)); 99 | this.last.isAbsolute = this.isAbsolute; 100 | this.path.push({type: "line", arg: [].slice.call(arguments, 0), pos: this.isAbsolute}); 101 | return this; 102 | }; 103 | p.arcTo = function (rx, ry, large_arc_flag, sweep_flag, x2, y2) { 104 | // for more information of where this math came from visit: 105 | // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes 106 | x2 = (this.isAbsolute ? 0 : this.last.x) + x2; 107 | y2 = (this.isAbsolute ? 0 : this.last.y) + y2; 108 | var x1 = this.last.x, 109 | y1 = this.last.y, 110 | x = (x1 - x2) / 2, 111 | y = (y1 - y2) / 2, 112 | k = (large_arc_flag == sweep_flag ? -1 : 1) * 113 | Math.sqrt((rx * rx * ry * ry - rx * rx * y * y - ry * ry * x * x) / (rx * rx * y * y + ry * ry * x * x)), 114 | cx = k * rx * y / ry + (x1 + x2) / 2, 115 | cy = k * -ry * x / rx + (y1 + y2) / 2, 116 | d = sweep_flag ? (this.isAbsolute?"wa":"wr") : (this.isAbsolute?"at":"ar"), 117 | _getX = this.isAbsolute ? VML._getX : VML._getW, 118 | _getY = this.isAbsolute ? VML._getY : VML._getH, 119 | left = Math.round(cx - rx), 120 | top = Math.round(cy - ry); 121 | d += [left, top, Math.round(left + rx * 2), Math.round(top + ry * 2), Math.round(x1), Math.round(y1), Math.round(_getX(parseFloat(x2, 10))), Math.round(_getX(parseFloat(y2, 10)))].join(", "); 122 | this[0].path = this.Path += d; 123 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + _getX(parseFloat(x2, 10)); 124 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + _getY(parseFloat(y2, 10)); 125 | this.last.isAbsolute = this.isAbsolute; 126 | this.path.push({type: "arc", arg: [].slice.call(arguments, 0), pos: this.isAbsolute}); 127 | return this; 128 | }; 129 | p.cplineTo = function (x1, y1, w1) { 130 | if (!w1) { 131 | return this.lineTo(x1, y1); 132 | } else { 133 | var p = {}; 134 | p._getX = this.isAbsolute ? VML._getX : VML._getW; 135 | p._getY = this.isAbsolute ? VML._getY : VML._getH; 136 | var x = Math.round(p._getX(Math.round(parseFloat(x1, 10) * 100) / 100)); 137 | var y = Math.round(p._getY(Math.round(parseFloat(y1, 10) * 100) / 100)); 138 | var w = Math.round(VML._getW(Math.round(parseFloat(w1, 10) * 100) / 100)); 139 | var d = this.isAbsolute?"c":"v"; 140 | var attr = [Math.round(this.last.x) + w, Math.round(this.last.y), x - w, y, x, y]; 141 | d += attr.join(" ") + " "; 142 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + attr[4]; 143 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + attr[5]; 144 | this.last.bx = attr[2]; 145 | this.last.by = attr[3]; 146 | this[0].path = this.Path += d; 147 | this.path.push({type: "cpline", arg: [].slice.call(arguments, 0), pos: this.isAbsolute}); 148 | return this; 149 | } 150 | }; 151 | p.curveTo = function () { 152 | var d = this.isAbsolute?"c":"v"; 153 | var _getX = this.isAbsolute ? VML._getX : VML._getW; 154 | var _getY = this.isAbsolute ? VML._getY : VML._getH; 155 | if (arguments.length == 6) { 156 | this.last.bx = (this.isAbsolute ? 0 : this.last.x) + _getX(parseFloat(arguments[2], 10)); 157 | this.last.by = (this.isAbsolute ? 0 : this.last.y) + _getY(parseFloat(arguments[3], 10)); 158 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + _getX(parseFloat(arguments[4], 10)); 159 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + _getY(parseFloat(arguments[5], 10)); 160 | d += Math.round(_getX(parseFloat(arguments[0], 10))) + " " + 161 | Math.round(_getY(parseFloat(arguments[1], 10))) + " " + 162 | Math.round(_getX(parseFloat(arguments[2], 10))) + " " + 163 | Math.round(_getY(parseFloat(arguments[3], 10))) + " " + 164 | Math.round(_getX(parseFloat(arguments[4], 10))) + " " + 165 | Math.round(_getY(parseFloat(arguments[5], 10))) + " "; 166 | this.last.isAbsolute = this.isAbsolute; 167 | } 168 | if (arguments.length == 4) { 169 | var bx = this.last.x * 2 - this.last.bx; 170 | var by = this.last.y * 2 - this.last.by; 171 | this.last.bx = (this.isAbsolute ? 0 : this.last.x) + _getX(parseFloat(arguments[0], 10)); 172 | this.last.by = (this.isAbsolute ? 0 : this.last.y) + _getY(parseFloat(arguments[1], 10)); 173 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + _getX(parseFloat(arguments[2], 10)); 174 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + _getY(parseFloat(arguments[3], 10)); 175 | d += [Math.round(bx), Math.round(by), 176 | Math.round(_getX(parseFloat(arguments[0], 10))), 177 | Math.round(_getY(parseFloat(arguments[1], 10))), 178 | Math.round(_getX(parseFloat(arguments[2], 10))), 179 | Math.round(_getY(parseFloat(arguments[3], 10)))].join(" "); 180 | } 181 | this[0].path = this.Path += d; 182 | this.path.push({type: "curve", arg: [].slice.call(arguments, 0), pos: this.isAbsolute}); 183 | return this; 184 | }; 185 | p.addRoundedCorner = function (r, dir) { 186 | var R = .5522 * r, rollback = this.isAbsolute, o = this; 187 | if (rollback) { 188 | this.relatively(); 189 | rollback = function () { 190 | o.absolutely(); 191 | }; 192 | } else { 193 | rollback = function () {}; 194 | } 195 | var actions = { 196 | l: function () { 197 | return { 198 | u: function () { 199 | o.curveTo(-R, 0, -r, -(r - R), -r, -r); 200 | }, 201 | d: function () { 202 | o.curveTo(-R, 0, -r, r - R, -r, r); 203 | } 204 | }; 205 | }, 206 | r: function () { 207 | return { 208 | u: function () { 209 | o.curveTo(R, 0, r, -(r - R), r, -r); 210 | }, 211 | d: function () { 212 | o.curveTo(R, 0, r, r - R, r, r); 213 | } 214 | }; 215 | }, 216 | u: function () { 217 | return { 218 | r: function () { 219 | o.curveTo(0, -R, -(R - r), -r, r, -r); 220 | }, 221 | l: function () { 222 | o.curveTo(0, -R, R - r, -r, -r, -r); 223 | } 224 | }; 225 | }, 226 | d: function () { 227 | return { 228 | r: function () { 229 | o.curveTo(0, R, -(R - r), r, r, r); 230 | }, 231 | l: function () { 232 | o.curveTo(0, R, R - r, r, -r, r); 233 | } 234 | }; 235 | } 236 | }; 237 | actions[dir.charAt(0)]()[dir.charAt(1)](); 238 | rollback(); 239 | return o; 240 | }; 241 | p.andClose = function () { 242 | this[0].path = (this.Path += "x e"); 243 | return this; 244 | }; 245 | if (typeof pathString == "string") { 246 | p.absolutely(); 247 | C.pathfinder(p, pathString); 248 | } 249 | return p; 250 | }; 251 | var setFillAndStroke = function (o, params) { 252 | var s = o[0].style; 253 | o.attrs = o.attrs || {}; 254 | for (var par in params) { 255 | o.attrs[par] = params[par]; 256 | } 257 | params["font-family"] && (s.fontFamily = params["font-family"]); 258 | params["font-size"] && (s.fontSize = params["font-size"]); 259 | params["font"] && (s.font = params["font"]); 260 | params["font-weight"] && (s.fontWeight = params["font-weight"]); 261 | if (typeof params.opacity != "undefined" || typeof params["stroke-width"] != "undefined" || typeof params.fill != "undefined" || typeof params.stroke != "undefined") { 262 | o = o.shape || o[0]; 263 | var fill = (o.getElementsByTagName("fill") && o.getElementsByTagName("fill")[0]) || document.createElement("rvml:fill"); 264 | if ("fill-opacity" in params || "opacity" in params) { 265 | fill.opacity = ((params["fill-opacity"] + 1 || 2) - 1) * ((params.opacity + 1 || 2) - 1); 266 | } 267 | if (params.fill) { 268 | fill.on = true; 269 | } 270 | if (fill.on == undefined || params.fill == "none") { 271 | fill.on = false; 272 | } 273 | if (fill.on && params.fill) { 274 | var isURL = params.fill.match(/^url\(([^\)]+)\)$/i); 275 | if (isURL) { 276 | fill.src = isURL[1]; 277 | fill.type = "tile"; 278 | } else { 279 | fill.color = params.fill; 280 | fill.src = ""; 281 | fill.type = "solid"; 282 | } 283 | } 284 | o.appendChild(fill); 285 | var stroke = (o.getElementsByTagName("stroke") && o.getElementsByTagName("stroke")[0]) || document.createElement("rvml:stroke"); 286 | if ((params.stroke && params.stroke != "none") || params["stroke-width"] || params["stroke-opacity"] || params["stroke-dasharray"]) { 287 | stroke.on = true; 288 | } 289 | if (params.stroke == "none" || typeof stroke.on == "undefined") { 290 | stroke.on = false; 291 | } 292 | if (stroke.on && params.stroke) { 293 | stroke.color = params.stroke; 294 | } 295 | stroke.opacity = ((params["stroke-opacity"] + 1 || 2) - 1) * ((params.opacity + 1 || 2) - 1); 296 | params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter"); 297 | stroke.miterlimit = params["stroke-miterlimit"] || 8; 298 | params["stroke-linecap"] && (stroke.endcap = {butt: "flat", square: "square", round: "round"}[params["stroke-linecap"]] || "miter"); 299 | params["stroke-width"] && (stroke.weight = (parseFloat(params["stroke-width"], 10) || 1) * 12 / 16); 300 | if (params["stroke-dasharray"]) { 301 | var dasharray = { 302 | "-": "shortdash", 303 | ".": "shortdot", 304 | "-.": "shortdashdot", 305 | "-..": "shortdashdotdot", 306 | ". ": "dot", 307 | "- ": "dash", 308 | "--": "longdash", 309 | "- .": "dashdot", 310 | "--.": "longdashdot", 311 | "--..": "longdashdotdot" 312 | }; 313 | stroke.dashstyle = dasharray[params["stroke-dasharray"]] || ""; 314 | } 315 | o.appendChild(stroke); 316 | } 317 | }; 318 | var addGrdientFill = function (o, gradient) { 319 | o.attrs = o.attrs || {}; 320 | o.attrs.gradient = gradient; 321 | o = o.shape || o[0]; 322 | var fill = o.getElementsByTagName("fill"); 323 | if (fill.length) { 324 | fill = fill[0]; 325 | } else { 326 | fill = document.createElement("rvml:fill"); 327 | } 328 | if (gradient.dots.length) { 329 | fill.on = true; 330 | fill.method = "none"; 331 | fill.type = (gradient.type.toLowerCase() == "linear") ? "gradient" : "gradientTitle"; 332 | if (typeof gradient.dots[0].color != "undefined") { 333 | fill.color = gradient.dots[0].color || "#000"; 334 | } 335 | if (typeof gradient.dots[gradient.dots.length - 1].color != "undefined") { 336 | fill.color2 = gradient.dots[gradient.dots.length - 1].color || "#000"; 337 | } 338 | var colors = []; 339 | for (var i = 0, ii = gradient.dots.length; i < ii; i++) { 340 | if (gradient.dots[i].offset) { 341 | colors.push(gradient.dots[i].offset + " " + gradient.dots[i].color); 342 | } 343 | }; 344 | var fillOpacity = gradient.dots[0].opacity || 1; 345 | var fillOpacity2 = gradient.dots[gradient.dots.length - 1].opacity || 1; 346 | if (colors) { 347 | fill.colors.value = colors.join(","); 348 | fillOpacity2 += fillOpacity; 349 | fillOpacity = fillOpacity2 - fillOpacity; 350 | fillOpacity2 -= fillOpacity; 351 | } 352 | fill.setAttribute("opacity", fillOpacity); 353 | fill.setAttribute("opacity2", fillOpacity2); 354 | if (gradient.vector) { 355 | var angle = Math.round(Math.atan((parseFloat(gradient.vector[3], 10) - parseFloat(gradient.vector[1], 10)) / (parseFloat(gradient.vector[2], 10) - parseFloat(gradient.vector[0], 10))) * 57.29) || 0; 356 | fill.angle = 270 - angle; 357 | } 358 | if (gradient.type.toLowerCase() == "radial") { 359 | fill.focus = "100%"; 360 | fill.focusposition = "0.5 0.5"; 361 | } 362 | } 363 | }; 364 | var Element = function (node, group, vml) { 365 | var Rotation = 0, 366 | RotX = 0, 367 | RotY = 0, 368 | Scale = 1; 369 | this[0] = node; 370 | this.node = node; 371 | this.X = 0; 372 | this.Y = 0; 373 | this.attrs = {}; 374 | this.Group = group; 375 | this.vml = vml; 376 | this.rotate = function (deg) { 377 | if (deg == undefined) { 378 | return Rotation; 379 | } 380 | Rotation += deg; 381 | this.Group.style.rotation = Rotation; 382 | return this; 383 | }; 384 | }; 385 | Element.prototype.setBox = function (params) { 386 | var gs = this.Group.style, 387 | os = this[0].style; 388 | for (var i in params) { 389 | this.attrs[i] = params[i]; 390 | } 391 | var attr = this.attrs, x, y, w, h; 392 | switch (this.type) { 393 | case "circle": 394 | x = attr.cx - attr.r; 395 | y = attr.cy - attr.r; 396 | w = h = attr.r * 2; 397 | break; 398 | case "ellipse": 399 | x = attr.cx - attr.rx; 400 | y = attr.cy - attr.ry; 401 | w = attr.rx * 2; 402 | h = attr.ry * 2; 403 | break; 404 | case "rect": 405 | case "image": 406 | x = attr.x; 407 | y = attr.y; 408 | w = attr.w; 409 | h = attr.h; 410 | break; 411 | case "text": 412 | this.textpath.v = ["m", Math.round(attr.x), ", ", Math.round(attr.y - 2), "l", Math.round(attr.x) + 1, ", ", Math.round(attr.y - 2)].join(""); 413 | return; 414 | default: 415 | return; 416 | } 417 | var left = this.vml.width / 2 - w / 2, 418 | top = this.vml.height / 2 - h / 2; 419 | gs.position = "absolute"; 420 | gs.left = x - left + "px"; 421 | gs.top = y - top + "px"; 422 | this.X = x - left; 423 | this.Y = y - top; 424 | this.W = w; 425 | this.H = h; 426 | gs.width = this.vml.width + "px"; 427 | gs.height = this.vml.height + "px"; 428 | os.position = "absolute"; 429 | os.top = top + "px"; 430 | os.left = left + "px"; 431 | os.width = w + "px"; 432 | os.height = h + "px"; 433 | }; 434 | Element.prototype.hide = function () { 435 | this.Group.style.display = "none"; 436 | return this; 437 | }; 438 | Element.prototype.show = function () { 439 | this.Group.style.display = "block"; 440 | return this; 441 | }; 442 | Element.prototype.translate = function (x, y) { 443 | if (x == undefined && y == undefined) { 444 | return {x: this.X, y: this.Y}; 445 | } 446 | this.X += x; 447 | this.Y += y; 448 | this.Group.style.left = this.X + "px"; 449 | this.Group.style.top = this.Y + "px"; 450 | return this; 451 | }; 452 | // depricated 453 | Element.prototype.matrix = function (xx, xy, yx, yy, dx, dy) { 454 | tMatrix = new Matrix(xx, xy, yx, yy, dx, dy); 455 | this.Group.style.filter = tMatrix; 456 | return this; 457 | }; 458 | Element.prototype.scale = function (x, y) { 459 | if (x == undefined && y == undefined) { 460 | return ; 461 | // TODO 462 | } 463 | y = y || x; 464 | if (x != 0 && !(x == 1 && y == 1)) { 465 | var dirx = Math.round(x / Math.abs(x)), 466 | diry = Math.round(y / Math.abs(y)), 467 | s = this[0].style; 468 | if (dirx != 1 || diry != 1) { 469 | s.filter = new Matrix(dirx, 0, 0, diry, 0, 0); 470 | } 471 | var width = parseInt(s.width, 10) * x * dirx; 472 | var height = parseInt(s.height, 10) * y * diry; 473 | var left = parseInt(s.left, 10); 474 | var top = parseInt(s.top, 10); 475 | s.left = this.X = left + this.W / 2 - width / 2; 476 | s.top = this.Y = top + this.H / 2 - height / 2; 477 | s.width = this.W = width; 478 | s.height = this.H = height; 479 | } 480 | return this; 481 | }; 482 | Element.prototype.getBBox = function () { 483 | return { 484 | x: this.Group.offsetLeft, 485 | y: this.Group.offsetTop, 486 | width: this.Group.offsetWidth, 487 | height: this.Group.offsetHeight 488 | }; 489 | }; 490 | Element.prototype.remove = function () { 491 | this[0].parentNode.removeChild(this[0]); 492 | this.Group.parentNode.removeChild(this.Group); 493 | this.shape && this.shape.parentNode.removeChild(this.shape); 494 | }; 495 | Element.prototype.attr = function () { 496 | if (arguments.length == 1 && typeof arguments[0] == "string") { 497 | return this.attrs[arguments[0]]; 498 | } 499 | if (this.attrs && arguments.length == 1 && arguments[0] instanceof Array) { 500 | var values = {}; 501 | for (var i = 0, ii = arguments[0].length; i < ii; i++) { 502 | values[arguments[0][i]] = this.attrs[arguments[0][i]]; 503 | }; 504 | return values; 505 | } 506 | if (this[0].tagName.toLowerCase() == "group") { 507 | var children = this[0].childNodes; 508 | this.attrs = this.attrs || {}; 509 | if (arguments.length == 2) { 510 | this.attrs[arguments[0]] = arguments[1]; 511 | } else if (arguments.length == 1 || typeof arguments[0] == "object") { 512 | for (var j in arguments[0]) { 513 | this.attrs[j] = arguments[0][j]; 514 | } 515 | } 516 | for (var i = 0, ii = children.length; i < ii; i++) { 517 | this.attr.apply(new item(children[i], this[0], this.vml), arguments); 518 | } 519 | } else { 520 | var params; 521 | if (arguments.length == 2) { 522 | params = {}; 523 | params[arguments[0]] = arguments[1]; 524 | } 525 | if (arguments.length == 1 && typeof arguments[0] == "object") { 526 | params = arguments[0]; 527 | } 528 | if (params) { 529 | setFillAndStroke(this, params); 530 | this.setBox(params); 531 | if (params.gradient) { 532 | addGrdientFill(this, params.gradient); 533 | } 534 | if (params.text && this.type == "text") { 535 | this[0].string = params.text; 536 | } 537 | if (params.id) { 538 | this[0].id = params.id; 539 | } 540 | } 541 | } 542 | return this; 543 | }; 544 | Element.prototype.toFront = function () { 545 | this.Group.parentNode.appendChild(this.Group); 546 | return this; 547 | }; 548 | Element.prototype.toBack = function () { 549 | if (this.Group.parentNode.firstChild != this.Group) { 550 | this.Group.parentNode.insertBefore(this.Group, this.Group.parentNode.firstChild); 551 | } 552 | return this; 553 | }; 554 | var theCircle = function (vml, x, y, r) { 555 | var g = document.createElement("rvml:group"); 556 | var o = document.createElement("rvml:oval"); 557 | g.appendChild(o); 558 | vml.canvas.appendChild(g); 559 | var res = new Element(o, g, vml); 560 | setFillAndStroke(res, {stroke: "#000", fill: "none"}); 561 | res.setBox({x: x - r, y: y - r, w: r * 2, h: r * 2}); 562 | res.attrs.cx = x; 563 | res.attrs.cy = y; 564 | res.attrs.r = r; 565 | res.type = "circle"; 566 | return res; 567 | }; 568 | var theRect = function (vml, x, y, w, h, r) { 569 | var g = document.createElement("rvml:group"); 570 | var o = document.createElement(r ? "rvml:roundrect" : "rvml:rect"); 571 | if (r) { 572 | o.arcsize = r / (Math.min(w, h)); 573 | } 574 | g.appendChild(o); 575 | vml.canvas.appendChild(g); 576 | var res = new Element(o, g, vml); 577 | setFillAndStroke(res, {stroke: "#000"}); 578 | res.setBox({x: x, y: y, w: w, h: h}); 579 | res.attrs.x = x; 580 | res.attrs.y = y; 581 | res.attrs.w = w; 582 | res.attrs.h = h; 583 | res.attrs.r = r; 584 | res.type = "rect"; 585 | return res; 586 | }; 587 | var theEllipse = function (vml, x, y, rx, ry) { 588 | var g = document.createElement("rvml:group"); 589 | var o = document.createElement("rvml:oval"); 590 | g.appendChild(o); 591 | vml.canvas.appendChild(g); 592 | var res = new Element(o, g, vml); 593 | setFillAndStroke(res, {stroke: "#000"}); 594 | res.setBox({x: x - rx, y: y - ry, w: rx * 2, h: ry * 2}); 595 | res.attrs.cx = x; 596 | res.attrs.cy = y; 597 | res.attrs.rx = rx; 598 | res.attrs.ry = ry; 599 | res.type = "ellipse"; 600 | return res; 601 | }; 602 | var theImage = function (vml, src, x, y, w, h) { 603 | var g = document.createElement("rvml:group"); 604 | var o = document.createElement("rvml:image"); 605 | o.src = src; 606 | g.appendChild(o); 607 | vml.canvas.appendChild(g); 608 | var res = new Element(o, g, vml); 609 | res.type = "image"; 610 | res.setBox({x: x, y: y, w: w, h: h}); 611 | res.attrs.x = x; 612 | res.attrs.y = y; 613 | res.attrs.w = w; 614 | res.attrs.h = h; 615 | return res; 616 | }; 617 | var theText = function (vml, x, y, text) { 618 | // @TODO: setTheBox 619 | var g = document.createElement("rvml:group"), gs = g.style; 620 | var el = document.createElement("rvml:shape"), ol = el.style; 621 | var path = document.createElement("rvml:path"), ps = path.style; 622 | path.v = ["m", Math.round(x), ", ", Math.round(y - 2), "l", Math.round(x) + 1, ", ", Math.round(y - 2)].join(""); 623 | path.textpathok = true; 624 | ol.width = vml.width; 625 | ol.height = vml.height; 626 | gs.position = "absolute"; 627 | gs.left = 0; 628 | gs.top = 0; 629 | gs.width = vml.width; 630 | gs.height = vml.height; 631 | var o = document.createElement("rvml:textpath"); 632 | o.string = text; 633 | o.on = true; 634 | o.coordsize = vml.coordsize; 635 | o.coordorigin = vml.coordorigin; 636 | el.appendChild(o); 637 | el.appendChild(path); 638 | g.appendChild(el); 639 | vml.canvas.appendChild(g); 640 | var res = new Element(o, g, vml); 641 | res.shape = el; 642 | res.textpath = path; 643 | res.type = "text"; 644 | res.attrs.x = x; 645 | res.attrs.y = y; 646 | res.attrs.w = 1; 647 | res.attrs.h = 1; 648 | return res; 649 | }; 650 | var theGroup = function (vml) { 651 | var el = document.createElement("rvml:group"), els = el.style; 652 | els.position = "absolute"; 653 | els.left = 0; 654 | els.top = 0; 655 | els.width = vml.width; 656 | els.height = vml.height; 657 | if (vml.canvas) { 658 | vml.canvas.appendChild(el); 659 | } 660 | var res = new Element(el, el, vml); 661 | for (var f in vml) { 662 | if (f.charAt(0) != "_" && typeof vml[f] == "function") { 663 | res[f] = (function (f) { 664 | return function () { 665 | var e = vml[f].apply(vml, arguments); 666 | el.appendChild(e[0].parentNode); 667 | return e; 668 | }; 669 | })(f); 670 | } 671 | } 672 | res.type = "group"; 673 | return res; 674 | }; 675 | r._create = function () { 676 | // container, width, height 677 | // x, y, width, height 678 | var container, width, height; 679 | if (typeof arguments[0] == "string") { 680 | container = document.getElementById(arguments[0]); 681 | width = arguments[1]; 682 | height = arguments[2]; 683 | } 684 | if (typeof arguments[0] == "object") { 685 | container = arguments[0]; 686 | width = arguments[1]; 687 | height = arguments[2]; 688 | } 689 | if (typeof arguments[0] == "number") { 690 | container = 1; 691 | x = arguments[0]; 692 | y = arguments[1]; 693 | width = arguments[2]; 694 | height = arguments[3]; 695 | } 696 | if (!container) { 697 | throw new Error("VML container not found."); 698 | } 699 | if (!document.namespaces["rvml"]) { 700 | document.namespaces.add("rvml","urn:schemas-microsoft-com:vml"); 701 | document.createStyleSheet().addRule("rvml\\:*", "behavior:url(#default#VML)"); 702 | } 703 | var c = document.createElement("div"), 704 | r = C.canvas = document.createElement("rvml:group"), 705 | cs = c.style, rs = r.style; 706 | C.width = width; 707 | C.height = height; 708 | width = width || "320px"; 709 | height = height || "200px"; 710 | cs.clip = "rect(0 " + width + " " + height + " 0)"; 711 | cs.position = "absolute"; 712 | rs.width = width; 713 | rs.height = height; 714 | r.coordsize = (width == "100%" ? width : parseFloat(width)) + " " + (height == "100%" ? height : parseFloat(height)); 715 | r.coordorigin = "0 0"; 716 | 717 | var b = document.createElement("rvml:rect"), bs = b.style; 718 | bs.left = bs.top = 0; 719 | bs.width = rs.width; 720 | bs.height = rs.height; 721 | b.filled = b.stroked = "f"; 722 | 723 | r.appendChild(b); 724 | c.appendChild(r); 725 | if (container == 1) { 726 | document.body.appendChild(c); 727 | cs.position = "absolute"; 728 | cs.left = x + "px"; 729 | cs.top = y + "px"; 730 | cs.width = width; 731 | cs.height = height; 732 | container = { 733 | style: { 734 | width: width, 735 | height: height 736 | } 737 | }; 738 | } else { 739 | cs.width = container.style.width = width; 740 | cs.height = container.style.height = height; 741 | if (container.firstChild) { 742 | container.insertBefore(c, container.firstChild); 743 | } else { 744 | container.appendChild(c); 745 | } 746 | } 747 | for (var prop in C) { 748 | container[prop] = C[prop]; 749 | } 750 | container.clear = function () { 751 | var todel = []; 752 | for (var i = 0, ii = r.childNodes.length; i < ii; i++) { 753 | if (r.childNodes[i] != b) { 754 | todel.push(r.childNodes[i]); 755 | } 756 | } 757 | for (i = 0, ii = todel.length; i < ii; i++) { 758 | r.removeChild(todel[i]); 759 | } 760 | }; 761 | return container; 762 | }; 763 | C.remove = function () { 764 | C.canvas.parentNode.parentNode.removeChild(C.canvas.parentNode); 765 | }; 766 | } 767 | if (type == "SVG") { 768 | Matrix.prototype.toString = function () { 769 | return "matrix(" + this.m[0][0] + 770 | ", " + this.m[1][0] + ", " + this.m[0][1] + ", " + this.m[1][1] + 771 | ", " + this.m[2][0] + ", " + this.m[2][1] + ")"; 772 | }; 773 | var thePath = function (params, pathString, SVG) { 774 | var el = document.createElementNS(SVG.svgns, "path"); 775 | el.setAttribute("fill", "none"); 776 | if (SVG.canvas) { 777 | SVG.canvas.appendChild(el); 778 | } 779 | var p = new Element(el, SVG); 780 | if (params) { 781 | setFillAndStroke(p, params); 782 | } 783 | p.isAbsolute = true; 784 | p.path = []; 785 | p.last = {x: 0, y: 0, bx: 0, by: 0}; 786 | p.absolutely = function () { 787 | this.isAbsolute = true; 788 | return this; 789 | }; 790 | p.relatively = function () { 791 | this.isAbsolute = false; 792 | return this; 793 | }; 794 | p.redraw = function () { 795 | this[0].setAttribute("d", "M0 0"); 796 | var oldPath = this.path; 797 | this.path = []; 798 | for (var i = 0, ii = oldPath.length; i < ii; i++) { 799 | if (oldPath[i].type != "end") { 800 | this[oldPath[i].type + "To"].apply(this, oldPath[i].arg); 801 | } else { 802 | this.andClose(); 803 | } 804 | } 805 | return this; 806 | }; 807 | p.moveTo = function (x, y) { 808 | var d = this.isAbsolute?"M":"m"; 809 | var _getX = this.isAbsolute ? SVG._getX : SVG._getW; 810 | var _getY = this.isAbsolute ? SVG._getY : SVG._getH; 811 | d += _getX(parseFloat(x, 10)) + " " + _getY(parseFloat(y, 10)) + " "; 812 | var oldD = this[0].getAttribute("d") || ""; 813 | this[0].setAttribute("d", oldD + d); 814 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + SVG._getX(parseFloat(x, 10)); 815 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + SVG._getY(parseFloat(y, 10)); 816 | this.path.push({type: "move", arg: arguments, pos: this.isAbsolute}); 817 | return this; 818 | }; 819 | p.lineTo = function (x, y) { 820 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + SVG._getX(parseFloat(x, 10)); 821 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + SVG._getY(parseFloat(y, 10)); 822 | var d = this.isAbsolute?"L":"l"; 823 | var _getX = this.isAbsolute ? SVG._getX : SVG._getW; 824 | var _getY = this.isAbsolute ? SVG._getY : SVG._getH; 825 | d += _getX(parseFloat(x, 10)) + " " + _getY(parseFloat(y, 10)) + " "; 826 | var oldD = this[0].getAttribute("d") || ""; 827 | this[0].setAttribute("d", oldD + d); 828 | this.path.push({type: "line", arg: arguments, pos: this.isAbsolute}); 829 | return this; 830 | }; 831 | p.arcTo = function (rx, ry, large_arc_flag, sweep_flag, x, y) { 832 | var d = this.isAbsolute ? "A" : "a"; 833 | var _getX = this.isAbsolute ? SVG._getX : SVG._getW; 834 | var _getY = this.isAbsolute ? SVG._getY : SVG._getH; 835 | d += [SVG._getW(parseFloat(rx, 10)), SVG._getH(parseFloat(ry, 10)), 0, large_arc_flag, sweep_flag, _getX(parseFloat(x, 10)), _getY(parseFloat(y, 10))].join(" "); 836 | var oldD = this[0].getAttribute("d") || ""; 837 | this[0].setAttribute("d", oldD + d); 838 | this.last.x = SVG._getX(parseFloat(x, 10)); 839 | this.last.y = SVG._getY(parseFloat(y, 10)); 840 | this.path.push({type: "arc", arg: arguments, pos: this.isAbsolute}); 841 | return this; 842 | }; 843 | p.cplineTo = function (x1, y1, w1) { 844 | if (!w1) { 845 | return this.lineTo(x1, y1); 846 | } else { 847 | var p = {}; 848 | p._getX = this.isAbsolute ? SVG._getX : SVG._getW; 849 | p._getY = this.isAbsolute ? SVG._getY : SVG._getH; 850 | var x = p._getX(Math.round(parseFloat(x1, 10) * 100) / 100); 851 | var y = p._getY(Math.round(parseFloat(y1, 10) * 100) / 100); 852 | var w = SVG._getW(Math.round(parseFloat(w1, 10) * 100) / 100); 853 | var d = this.isAbsolute?"C":"c"; 854 | var attr = [this.last.x + w, this.last.y, x - w, y, x, y]; 855 | for (var i = 0, ii = attr.length; i < ii; i++) { 856 | d += attr[i] + " "; 857 | } 858 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + attr[4]; 859 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + attr[5]; 860 | this.last.bx = attr[2]; 861 | this.last.by = attr[3]; 862 | var oldD = this[0].getAttribute("d") || ""; 863 | this[0].setAttribute("d", oldD + d); 864 | this.path.push({type: "cpline", arg: arguments, pos: this.isAbsolute}); 865 | return this; 866 | } 867 | }; 868 | p.curveTo = function () { 869 | var p = {}; 870 | p._getX = this.isAbsolute ? SVG._getX : SVG._getW; 871 | p._getY = this.isAbsolute ? SVG._getY : SVG._getH; 872 | if (arguments.length == 6) { 873 | var d = this.isAbsolute?"C":"c"; 874 | for (var i = 0, ii = arguments.length; i < ii; i++) { 875 | d += p[(i % 2 == 0) ? "_getX" : "_getY"](Math.round(parseFloat(arguments[i], 10) * 100) / 100) + " "; 876 | } 877 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + p._getX((parseFloat(arguments[4], 10) * 100) / 100); 878 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + p._getY((parseFloat(arguments[5], 10) * 100) / 100); 879 | this.last.bx = p._getX((parseFloat(arguments[2], 10) * 100) / 100); 880 | this.last.by = p._getY((parseFloat(arguments[3], 10) * 100) / 100); 881 | } else { 882 | if (arguments.length == 4) { 883 | var d = this.isAbsolute?"S":"s"; 884 | for (var i = 0, ii = arguments.length; i < ii; i++) { 885 | d += p[i % 2 == 0 ? "_getX" : "_getY"]((parseFloat(arguments[i], 10) * 100) / 100) + " "; 886 | } 887 | } 888 | this.last.x = (this.isAbsolute ? 0 : this.last.x) + p._getX((parseFloat(arguments[2], 10) * 100) / 100); 889 | this.last.y = (this.isAbsolute ? 0 : this.last.y) + p._getY((parseFloat(arguments[3], 10) * 100) / 100); 890 | this.last.bx = p._getX((parseFloat(arguments[0], 10) * 100) / 100); 891 | this.last.by = p._getY((parseFloat(arguments[1], 10) * 100) / 100); 892 | } 893 | var oldD = this[0].getAttribute("d") || ""; 894 | this[0].setAttribute("d", oldD + d); 895 | this.path.push({type: "curve", arg: arguments, pos: this.isAbsolute}); 896 | return this; 897 | }; 898 | p.addRoundedCorner = function (r, dir) { 899 | var R = .5522 * r, rollback = this.isAbsolute, o = this; 900 | if (rollback) { 901 | this.relatively(); 902 | rollback = function () { 903 | o.absolutely(); 904 | }; 905 | } else { 906 | rollback = function () {}; 907 | } 908 | var actions = { 909 | l: function () { 910 | return { 911 | u: function () { 912 | o.curveTo(-R, 0, -r, -(r - R), -r, -r); 913 | }, 914 | d: function () { 915 | o.curveTo(-R, 0, -r, r - R, -r, r); 916 | } 917 | }; 918 | }, 919 | r: function () { 920 | return { 921 | u: function () { 922 | o.curveTo(R, 0, r, -(r - R), r, -r); 923 | }, 924 | d: function () { 925 | o.curveTo(R, 0, r, r - R, r, r); 926 | } 927 | }; 928 | }, 929 | u: function () { 930 | return { 931 | r: function () { 932 | o.curveTo(0, -R, -(R - r), -r, r, -r); 933 | }, 934 | l: function () { 935 | o.curveTo(0, -R, R - r, -r, -r, -r); 936 | } 937 | }; 938 | }, 939 | d: function () { 940 | return { 941 | r: function () { 942 | o.curveTo(0, R, -(R - r), r, r, r); 943 | }, 944 | l: function () { 945 | o.curveTo(0, R, R - r, r, -r, r); 946 | } 947 | }; 948 | } 949 | }; 950 | actions[dir[0]]()[dir[1]](); 951 | rollback(); 952 | return o; 953 | }; 954 | p.andClose = function () { 955 | var oldD = this[0].getAttribute("d") || ""; 956 | this[0].setAttribute("d", oldD + "Z "); 957 | this.path.push({type: "end"}); 958 | return this; 959 | }; 960 | if (typeof pathString == "string") { 961 | p.absolutely(); 962 | C.pathfinder(p, pathString); 963 | } 964 | return p; 965 | }; 966 | var addGrdientFill = function (o, gradient, SVG) { 967 | var el = document.createElementNS(SVG.svgns, gradient.type + "Gradient"); 968 | el.id = "raphael-gradient-" + SVG.gradients++; 969 | if (gradient.vector && gradient.vector.length) { 970 | el.setAttribute("x1", gradient.vector[0]); 971 | el.setAttribute("y1", gradient.vector[1]); 972 | el.setAttribute("x2", gradient.vector[2]); 973 | el.setAttribute("y2", gradient.vector[3]); 974 | } 975 | SVG.defs.appendChild(el); 976 | for (var i = 0, ii = gradient.dots.length; i < ii; i++) { 977 | var stop = document.createElementNS(SVG.svgns, "stop"); 978 | stop.setAttribute("offset", gradient.dots[i].offset ? gradient.dots[i].offset : (i == 0) ? "0%" : "100%"); 979 | stop.setAttribute("stop-color", gradient.dots[i].color || "#fff"); 980 | if (typeof gradient.dots[i].opacity != "undefined") { 981 | stop.setAttribute("stop-opacity", gradient.dots[i].opacity); 982 | } 983 | el.appendChild(stop); 984 | }; 985 | o.setAttribute("fill", "url(#" + el.id + ")"); 986 | }; 987 | var updatePosition = function (o) { 988 | if (o.pattern) { 989 | var bbox = o.node.getBBox(); 990 | o.pattern.setAttribute("patternTransform", "translate(" + [bbox.x, bbox.y].join(",") + ")"); 991 | } 992 | }; 993 | var setFillAndStroke = function (o, params) { 994 | var dasharray = { 995 | "-": [3, 1], 996 | ".": [1, 1], 997 | "-.": [3, 1, 1, 1], 998 | "-..": [3, 1, 1, 1, 1, 1], 999 | ". ": [1, 3], 1000 | "- ": [4, 3], 1001 | "--": [8, 3], 1002 | "- .": [4, 3, 1, 3], 1003 | "--.": [8, 3, 1, 3], 1004 | "--..": [8, 3, 1, 3, 1, 3] 1005 | }; 1006 | for (var att in params) { 1007 | var value = params[att]; 1008 | o.attrs[att] = value; 1009 | switch (att) { 1010 | case "rx": 1011 | case "cx": 1012 | case "x": 1013 | o[0].setAttribute(att, o.svg._getX(value)); 1014 | updatePosition(o); 1015 | break; 1016 | case "ry": 1017 | case "cy": 1018 | case "y": 1019 | o[0].setAttribute(att, o.svg._getY(value)); 1020 | updatePosition(o); 1021 | break; 1022 | case "width": 1023 | o[0].setAttribute(att, o.svg._getW(value)); 1024 | break; 1025 | case "height": 1026 | o[0].setAttribute(att, o.svg._getH(value)); 1027 | break; 1028 | case "gradient": 1029 | addGrdientFill(o[0], value, o.svg); 1030 | break; 1031 | case "stroke-dasharray": 1032 | value = dasharray[value.toLowerCase()]; 1033 | if (value) { 1034 | var width = params["stroke-width"] || o.attr("stroke-width") || "1", 1035 | butt = {round: width, square: width, butt: 0}[o.attr("stroke-linecap")] || 0, 1036 | dashes = []; 1037 | for (var i = 0, ii = value.length; i < ii; i++) { 1038 | dashes.push(value[i] * width + ((i % 2) ? 1 : -1) * butt); 1039 | } 1040 | value = dashes.join(","); 1041 | o[0].setAttribute(att, value); 1042 | } 1043 | break; 1044 | case "text": 1045 | if (o.type == "text") { 1046 | o[0].childNodes.length && o[0].removeChild(o[0].firstChild); 1047 | o[0].appendChild(document.createTextNode(value)); 1048 | } 1049 | break; 1050 | case "fill": 1051 | var isURL = value.match(/^url\(([^\)]+)\)$/i); 1052 | if (isURL) { 1053 | var el = document.createElementNS(o.svg.svgns, "pattern"); 1054 | var ig = document.createElementNS(o.svg.svgns, "image"); 1055 | el.id = "raphael-pattern-" + o.svg.gradients++; 1056 | el.setAttribute("x", 0); 1057 | el.setAttribute("y", 0); 1058 | el.setAttribute("patternUnits", "userSpaceOnUse"); 1059 | ig.setAttribute("x", 0); 1060 | ig.setAttribute("y", 0); 1061 | ig.setAttributeNS(o.svg.xlink, "href", isURL[1]); 1062 | el.appendChild(ig); 1063 | 1064 | var img = document.createElement("img"); 1065 | img.style.position = "absolute"; 1066 | img.style.top = "-9999em"; 1067 | img.style.left = "-9999em"; 1068 | img.onload = function () { 1069 | el.setAttribute("width", this.offsetWidth); 1070 | el.setAttribute("height", this.offsetHeight); 1071 | ig.setAttribute("width", this.offsetWidth); 1072 | ig.setAttribute("height", this.offsetHeight); 1073 | document.body.removeChild(this); 1074 | C.safari(); 1075 | }; 1076 | document.body.appendChild(img); 1077 | img.src = isURL[1]; 1078 | o.svg.defs.appendChild(el); 1079 | o[0].style.fill = "url(#" + el.id + ")"; 1080 | o[0].setAttribute("fill", "url(#" + el.id + ")"); 1081 | o.pattern = el; 1082 | updatePosition(o); 1083 | break; 1084 | } 1085 | default : 1086 | var cssrule = att.replace(/(\-.)/g, function (w) { 1087 | return w.substring(1).toUpperCase(); 1088 | }); 1089 | o[0].style[cssrule] = value; 1090 | // Need following line for Firefox 1091 | o[0].setAttribute(att, value); 1092 | break; 1093 | } 1094 | } 1095 | }; 1096 | var Element = function (node, svg) { 1097 | var X = 0, 1098 | Y = 0, 1099 | Rotation = {deg: 0, x: 0, y: 0}, 1100 | ScaleX = 1, 1101 | ScaleY = 1, 1102 | tMatrix = null; 1103 | this[0] = node; 1104 | this.node = node; 1105 | this.svg = svg; 1106 | this.attrs = this.attrs || {}; 1107 | this.transformations = []; // rotate, translate, scale, matrix 1108 | this.rotate = function (deg) { 1109 | if (deg == undefined) { 1110 | return Rotation.deg; 1111 | } 1112 | var bbox = this.getBBox(); 1113 | Rotation.deg += deg; 1114 | if (Rotation.deg) { 1115 | this.transformations[0] = ("rotate(" + Rotation.deg + " " + (bbox.x + bbox.width / 2) + " " + (bbox.y + bbox.height / 2) + ")"); 1116 | } else { 1117 | this.transformations[0] = ""; 1118 | } 1119 | this[0].setAttribute("transform", this.transformations.join(" ")); 1120 | return this; 1121 | }; 1122 | this.translate = function (x, y) { 1123 | if (x == undefined && y == undefined) { 1124 | return {x: X, y: Y}; 1125 | } 1126 | X += x; 1127 | Y += y; 1128 | if (X || Y) { 1129 | this.transformations[1] = "translate(" + X + "," + Y + ")"; 1130 | } else { 1131 | this.transformations[1] = ""; 1132 | } 1133 | this[0].setAttribute("transform", this.transformations.join(" ")); 1134 | return this; 1135 | }; 1136 | this.scale = function (x, y) { 1137 | if (x == undefined && y == undefined) { 1138 | return {x: ScaleX, y: ScaleY}; 1139 | } 1140 | y = y || x; 1141 | if (x != 0 && !(x == 1 && y == 1)) { 1142 | ScaleX *= x; 1143 | ScaleY *= y; 1144 | if (!(ScaleX == 1 && ScaleY == 1)) { 1145 | var bbox = this.getBBox(), 1146 | dx = bbox.x * (1 - ScaleX) + (bbox.width / 2 - bbox.width * ScaleX / 2), 1147 | dy = bbox.y * (1 - ScaleY) + (bbox.height / 2 - bbox.height * ScaleY / 2); 1148 | this.transformations[2] = new Matrix(ScaleX, 0, 0, ScaleY, dx, dy); 1149 | } else { 1150 | this.transformations[2] = ""; 1151 | } 1152 | this[0].setAttribute("transform", this.transformations.join(" ")); 1153 | } 1154 | return this; 1155 | }; 1156 | }; 1157 | Element.prototype.hide = function () { 1158 | this[0].style.display = "none"; 1159 | return this; 1160 | }; 1161 | Element.prototype.show = function () { 1162 | this[0].style.display = "block"; 1163 | return this; 1164 | }; 1165 | // depricated 1166 | Element.prototype.matrix = function (xx, xy, yx, yy, dx, dy) { 1167 | this.transformations[3] = new Matrix(xx, xy, yx, yy, dx, dy); 1168 | this[0].setAttribute("transform", this.transformations.join(" ")); 1169 | return this; 1170 | }; 1171 | Element.prototype.remove = function () { 1172 | this[0].parentNode.removeChild(this[0]); 1173 | }; 1174 | Element.prototype.getBBox = function () { 1175 | return this[0].getBBox(); 1176 | }; 1177 | Element.prototype.attr = function () { 1178 | if (arguments.length == 1 && typeof arguments[0] == "string") { 1179 | return this[0].getAttribute(arguments[0]); 1180 | } 1181 | if (arguments.length == 1 && arguments[0] instanceof Array) { 1182 | var values = {}; 1183 | for (var j in arguments[0]) { 1184 | values[arguments[0][j]] = this.attrs[arguments[0][j]]; 1185 | } 1186 | return values; 1187 | } 1188 | if (arguments.length == 2) { 1189 | var params = {}; 1190 | params[arguments[0]] = arguments[1]; 1191 | setFillAndStroke(this, params); 1192 | } else if (arguments.length == 1 && typeof arguments[0] == "object") { 1193 | setFillAndStroke(this, arguments[0]); 1194 | } 1195 | return this; 1196 | }; 1197 | Element.prototype.toFront = function () { 1198 | this[0].parentNode.appendChild(this[0]); 1199 | return this; 1200 | }; 1201 | Element.prototype.toBack = function () { 1202 | if (this[0].parentNode.firstChild != this[0]) { 1203 | this[0].parentNode.insertBefore(this[0], this[0].parentNode.firstChild); 1204 | } 1205 | return this; 1206 | }; 1207 | var theCircle = function (svg, x, y, r) { 1208 | var el = document.createElementNS(svg.svgns, "circle"); 1209 | el.setAttribute("cx", svg._getX(x)); 1210 | el.setAttribute("cy", svg._getY(y)); 1211 | el.setAttribute("r", r); 1212 | el.setAttribute("fill", "none"); 1213 | el.setAttribute("stroke", "#000"); 1214 | if (svg.canvas) { 1215 | svg.canvas.appendChild(el); 1216 | } 1217 | var res = new Element(el, svg); 1218 | res.attrs = res.attrs || {}; 1219 | res.attrs.cx = x; 1220 | res.attrs.cy = y; 1221 | res.attrs.r = r; 1222 | res.attrs.stroke = "#000"; 1223 | res.type = "circle"; 1224 | return res; 1225 | }; 1226 | var theRect = function (svg, x, y, w, h, r) { 1227 | var el = document.createElementNS(svg.svgns, "rect"); 1228 | el.setAttribute("x", svg._getX(x)); 1229 | el.setAttribute("y", svg._getY(y)); 1230 | el.setAttribute("width", svg._getW(w)); 1231 | el.setAttribute("height", svg._getH(h)); 1232 | if (r) { 1233 | el.setAttribute("rx", r); 1234 | el.setAttribute("ry", r); 1235 | } 1236 | el.setAttribute("fill", "none"); 1237 | el.setAttribute("stroke", "#000"); 1238 | if (svg.canvas) { 1239 | svg.canvas.appendChild(el); 1240 | } 1241 | var res = new Element(el, svg); 1242 | res.attrs = res.attrs || {}; 1243 | res.attrs.x = x; 1244 | res.attrs.y = y; 1245 | res.attrs.width = w; 1246 | res.attrs.height = h; 1247 | res.attrs.stroke = "#000"; 1248 | if (r) { 1249 | res.attrs.rx = res.attrs.ry = r; 1250 | } 1251 | res.type = "rect"; 1252 | return res; 1253 | }; 1254 | var theEllipse = function (svg, x, y, rx, ry) { 1255 | var el = document.createElementNS(svg.svgns, "ellipse"); 1256 | el.setAttribute("cx", svg._getX(x)); 1257 | el.setAttribute("cy", svg._getY(y)); 1258 | el.setAttribute("rx", svg._getW(rx)); 1259 | el.setAttribute("ry", svg._getH(ry)); 1260 | el.setAttribute("fill", "none"); 1261 | el.setAttribute("stroke", "#000"); 1262 | if (svg.canvas) { 1263 | svg.canvas.appendChild(el); 1264 | } 1265 | var res = new Element(el, svg); 1266 | res.attrs = res.attrs || {}; 1267 | res.attrs.cx = x; 1268 | res.attrs.cy = y; 1269 | res.attrs.rx = rx; 1270 | res.attrs.ry = ry; 1271 | res.attrs.stroke = "#000"; 1272 | res.type = "ellipse"; 1273 | return res; 1274 | }; 1275 | var theImage = function (svg, src, x, y, w, h) { 1276 | var el = document.createElementNS(svg.svgns, "image"); 1277 | el.setAttribute("x", svg._getX(x)); 1278 | el.setAttribute("y", svg._getY(y)); 1279 | el.setAttribute("width", svg._getW(w)); 1280 | el.setAttribute("height", svg._getH(h)); 1281 | el.setAttributeNS(svg.xlink, "href", src); 1282 | if (svg.canvas) { 1283 | svg.canvas.appendChild(el); 1284 | } 1285 | var res = new Element(el, svg); 1286 | res.attrs = res.attrs || {}; 1287 | res.attrs.x = x; 1288 | res.attrs.y = y; 1289 | res.attrs.width = w; 1290 | res.attrs.height = h; 1291 | res.type = "image"; 1292 | return res; 1293 | }; 1294 | var theText = function (svg, x, y, text) { 1295 | var el = document.createElementNS(svg.svgns, "text"); 1296 | el.setAttribute("x", x); 1297 | el.setAttribute("y", y); 1298 | el.setAttribute("text-anchor", "middle"); 1299 | el.setAttribute("fill", "#000"); 1300 | if (text) { 1301 | el.appendChild(document.createTextNode(text)); 1302 | } 1303 | if (svg.canvas) { 1304 | svg.canvas.appendChild(el); 1305 | } 1306 | var res = new Element(el, svg); 1307 | res.attrs = res.attrs || {}; 1308 | res.attrs.x = x; 1309 | res.attrs.y = y; 1310 | res.attrs.fill = "#000"; 1311 | res.type = "text"; 1312 | return res; 1313 | }; 1314 | var theGroup = function (svg) { 1315 | var el = document.createElementNS(svg.svgns, "g"); 1316 | if (svg.canvas) { 1317 | svg.canvas.appendChild(el); 1318 | } 1319 | var i = new Element(el, svg); 1320 | for (var f in svg) { 1321 | if (f[0] != "_" && typeof svg[f] == "function") { 1322 | i[f] = (function (f) { 1323 | return function () { 1324 | var e = svg[f].apply(svg, arguments); 1325 | el.appendChild(e[0]); 1326 | return e; 1327 | }; 1328 | })(f); 1329 | } 1330 | } 1331 | i.type = "group"; 1332 | return i; 1333 | }; 1334 | r._create = function () { 1335 | // container, width, height 1336 | // x, y, width, height 1337 | if (typeof arguments[0] == "string") { 1338 | var container = document.getElementById(arguments[0]); 1339 | var width = arguments[1]; 1340 | var height = arguments[2]; 1341 | } 1342 | if (typeof arguments[0] == "object") { 1343 | var container = arguments[0]; 1344 | var width = arguments[1]; 1345 | var height = arguments[2]; 1346 | } 1347 | if (typeof arguments[0] == "number") { 1348 | var container = 1, 1349 | x = arguments[0], 1350 | y = arguments[1], 1351 | width = arguments[2], 1352 | height = arguments[3]; 1353 | } 1354 | if (!container) { 1355 | throw new Error("SVG container not found."); 1356 | } 1357 | C.canvas = document.createElementNS(C.svgns, "svg"); 1358 | C.canvas.setAttribute("width", width || 320); 1359 | C.width = width || 320; 1360 | C.canvas.setAttribute("height", height || 200); 1361 | C.height = height || 200; 1362 | if (container == 1) { 1363 | document.body.appendChild(C.canvas); 1364 | C.canvas.style.position = "absolute"; 1365 | C.canvas.style.left = x + "px"; 1366 | C.canvas.style.top = y + "px"; 1367 | } else { 1368 | if (container.firstChild) { 1369 | container.insertBefore(C.canvas, container.firstChild); 1370 | } else { 1371 | container.appendChild(C.canvas); 1372 | } 1373 | } 1374 | container = { 1375 | canvas: C.canvas, 1376 | clear: function () { 1377 | while (this.canvas.firstChild) { 1378 | this.canvas.removeChild(this.canvas.firstChild); 1379 | } 1380 | this.defs = document.createElementNS(C.svgns, "defs"); 1381 | this.gradients = 0; 1382 | this.canvas.appendChild(this.defs); 1383 | } 1384 | }; 1385 | for (var prop in C) { 1386 | if (prop != "create") { 1387 | container[prop] = C[prop]; 1388 | } 1389 | } 1390 | container.clear(); 1391 | return container; 1392 | }; 1393 | C.remove = function () { 1394 | C.canvas.parentNode.removeChild(C.canvas); 1395 | }; 1396 | C.svgns = "http://www.w3.org/2000/svg"; 1397 | C.xlink = "http://www.w3.org/1999/xlink"; 1398 | } 1399 | if (type == "VML" || type == "SVG") { 1400 | C.circle = function (x, y, r) { 1401 | return theCircle(this, x, y, r); 1402 | }; 1403 | C.rect = function (x, y, w, h, r) { 1404 | return theRect(this, x, y, w, h, r); 1405 | }; 1406 | C.ellipse = function (x, y, rx, ry) { 1407 | return theEllipse(this, x, y, rx, ry); 1408 | }; 1409 | C.path = function (params, pathString) { 1410 | return thePath(params, pathString, this); 1411 | }; 1412 | C.image = function (src, x, y, w, h) { 1413 | return theImage(this, src, x, y, w, h); 1414 | }; 1415 | C.text = function (x, y, text) { 1416 | return theText(this, x, y, text); 1417 | }; 1418 | C.group = function () { 1419 | return theGroup(this); 1420 | }; 1421 | C.linerect = function (x, y, w, h, r) { 1422 | if (r && parseInt(r, 10)) { 1423 | return this.path({stroke: "#000"}).moveTo(x + r, y).lineTo(x + w - r, y).addRoundedCorner(r, "rd").lineTo(x + w, y + h - r).addRoundedCorner(r, "dl").lineTo(x + r, y + h).addRoundedCorner(r, "lu").lineTo(x, y + r).addRoundedCorner(r, "ur").andClose(); 1424 | } 1425 | return this.path({stroke: "#000"}).moveTo(x, y).lineTo(x + w, y).lineTo(x + w, y + h).lineTo(x, y + h).andClose(); 1426 | }; 1427 | C.drawGrid = function (x, y, w, h, wv, hv, color) { 1428 | color = color || "#000"; 1429 | var p = this.path({stroke: color, "stroke-width": 1}) 1430 | .moveTo(x, y).lineTo(x + w, y).lineTo(x + w, y + h).lineTo(x, y + h).lineTo(x, y), 1431 | rowHeight = h / hv, 1432 | columnWidth = w / wv; 1433 | for (var i = 1; i < hv; i++) { 1434 | p.moveTo(x, y + i * rowHeight).lineTo(x + w, y + i * rowHeight); 1435 | } 1436 | for (var i = 1; i < wv; i++) { 1437 | p.moveTo(x + i * columnWidth, y).lineTo(x + i * columnWidth, y + h); 1438 | } 1439 | return p; 1440 | }; 1441 | C.setGrid = function (xmin, ymin, xmax, ymax, w, h) { 1442 | var xc = (xmax - xmin) / w; 1443 | var yc = (ymax - ymin) / h; 1444 | this._getX = function (x) { 1445 | return xmin + x * xc; 1446 | }; 1447 | this._getY = function (y) { 1448 | return ymin + y * yc; 1449 | }; 1450 | this._getW = function (w) { 1451 | return w * xc; 1452 | }; 1453 | this._getH = function (h) { 1454 | return h * yc; 1455 | }; 1456 | }; 1457 | C.clearGrid = function () { 1458 | this._getX = this._getY = this._getW = this._getH = function (x) { return x; }; 1459 | }; 1460 | C.safari = function () { 1461 | if (r.type == "SVG") { 1462 | var rect = C.rect(-C.width, -C.height, C.width * 3, C.height * 3).attr({stroke: "none"}); 1463 | setTimeout(function () {rect.remove();}, 0); 1464 | } 1465 | }; 1466 | Element.prototype.animateTo = function (x, y, ms, callback) { 1467 | clearTimeout(this.animation_in_progress); 1468 | if ("cx" in this.attrs || "x" in this.attrs) { 1469 | var is_round = ("cx" in this.attrs), 1470 | X = this.attrs.cx || this.attrs.x, 1471 | Y = this.attrs.cy || this.attrs.y; 1472 | if (x == X && y == Y) { 1473 | return this; 1474 | } 1475 | var dy = y - Y, 1476 | dx = x - X, 1477 | coeff = dy / dx, 1478 | plus = Y - coeff * X, 1479 | alpha = Math.atan(this.coeff); 1480 | this.xs = this.step * Math.cos(alpha); 1481 | if (x < X) { 1482 | this.xs = -this.xs; 1483 | } 1484 | var start = new Date(), 1485 | that = this; 1486 | (function () { 1487 | var time = (new Date()).getTime() - start.getTime(); 1488 | if (time < ms) { 1489 | var x1 = X + time * dx / ms; 1490 | var y1 = x1 * coeff + plus; 1491 | that.attr(is_round ? {cx: x1, cy: y1} : {x: x1, y: y1}); 1492 | that.animation_in_progress = setTimeout(arguments.callee, 1); 1493 | C.safari(); 1494 | } else { 1495 | that.attr(is_round ? {cx: x, cy: y} : {x: x, y: y}); 1496 | C.safari(); 1497 | callback && callback.call(that); 1498 | } 1499 | })(); 1500 | } 1501 | return this; 1502 | }; 1503 | C.pathfinder = function (p, path) { 1504 | var commands = { 1505 | M: function (x, y) { 1506 | this.moveTo(x, y); 1507 | }, 1508 | m: function (x, y) { 1509 | this.moveTo(this.last.x + x, this.last.y + y); 1510 | }, 1511 | C: function (x1, y1, x2, y2, x3, y3) { 1512 | this.curveTo(x1, y1, x2, y2, x3, y3); 1513 | }, 1514 | c: function (x1, y1, x2, y2, x3, y3) { 1515 | this.curveTo(this.last.x + x1, this.last.y + y1, this.last.x + x2, this.last.y + y2, this.last.x + x3, this.last.y + y3); 1516 | }, 1517 | S: function (x1, y1, x2, y2) { 1518 | p.curveTo(x1, y1, x2, y2); 1519 | }, 1520 | s: function (x1, y1, x2, y2) { 1521 | this.curveTo(this.last.x + x1, this.last.y + y1, this.last.x + x2, this.last.y + y2); 1522 | }, 1523 | L: function (x, y) { 1524 | p.lineTo(x, y); 1525 | }, 1526 | l: function (x, y) { 1527 | this.lineTo(this.last.x + x, this.last.y + y); 1528 | }, 1529 | H: function (x) { 1530 | this.lineTo(x, this.last.y); 1531 | }, 1532 | h: function (x) { 1533 | this.lineTo(this.last.x + x, this.last.y); 1534 | }, 1535 | V: function (y) { 1536 | this.lineTo(this.last.x, y); 1537 | }, 1538 | v: function (y) { 1539 | this.lineTo(this.last.x, this.last.y + y); 1540 | }, 1541 | A: function (rx, ry, xaxisrotation, largearcflag, sweepflag, x, y) { 1542 | this.arcTo(rx, ry, largearcflag, sweepflag, x, y); 1543 | }, 1544 | a: function (rx, ry, xaxisrotation, largearcflag, sweepflag, x, y) { 1545 | this.arcTo(this.last.x + rx, this.last.y + ry, largearcflag, sweethisflag, this.last.x + x, this.last.y + y); 1546 | }, 1547 | z: function () { 1548 | this.andClose(); 1549 | } 1550 | }; 1551 | path.replace(/([mzlhvcsqta])\s*((-?\d*\.?\d*\s*,?\s*)+)/ig, function (a, b, c) { 1552 | var params = []; 1553 | c.replace(/(-?\d*\.?\d*)\s*,?\s*/ig, function (a, b) { 1554 | b && params.push(+b); 1555 | }); 1556 | while (params.length >= commands[b].length) { 1557 | commands[b].apply(p, params.splice(0, commands[b].length)); 1558 | if (!commands[b].length) { 1559 | break; 1560 | }; 1561 | } 1562 | }); 1563 | }; 1564 | return r; 1565 | } else { 1566 | return function () {}; 1567 | } 1568 | })((!(window.SVGPreserveAspectRatio && window.SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN == 2)) ? "VML" : "SVG"); 1569 | 1570 | 1571 | Raphael.vml = !(Raphael.svg = (Raphael.type == "SVG")); 1572 | if (Raphael.vml && window.CanvasRenderingContext2D) { 1573 | Raphael.type = "Canvas only"; 1574 | Raphael.vml = Raphael.svg = false; 1575 | } 1576 | Raphael.toString = function () { 1577 | return "Your browser supports " + this.type + ".\nYou are running " + unescape("Rapha%EBl%20") + this.version; 1578 | }; 1579 | // generic utilities 1580 | Raphael.hsb2rgb = function (hue, saturation, brightness) { 1581 | if (typeof hue == "object" && "h" in hue && "s" in hue && "b" in hue) { 1582 | brightness = hue.b; 1583 | saturation = hue.s; 1584 | hue = hue.h; 1585 | } 1586 | var red, 1587 | green, 1588 | blue; 1589 | if (brightness == 0) { 1590 | return {r: 0, g: 0, b: 0, hex: "#000"}; 1591 | } else { 1592 | var i = Math.floor(hue * 6), 1593 | f = (hue * 6) - i, 1594 | p = brightness * (1 - saturation), 1595 | q = brightness * (1 - (saturation * f)), 1596 | t = brightness * (1 - (saturation * (1 - f))); 1597 | [ 1598 | function () {red = brightness; green = t; blue = p;}, 1599 | function () {red = q; green = brightness; blue = p;}, 1600 | function () {red = p; green = brightness; blue = t;}, 1601 | function () {red = p; green = q; blue = brightness;}, 1602 | function () {red = t; green = p; blue = brightness;}, 1603 | function () {red = brightness; green = p; blue = q;}, 1604 | function () {red = brightness; green = t; blue = p;} 1605 | ][i](); 1606 | } 1607 | var rgb = {r: red, g: green, b: blue}; 1608 | red *= 255; 1609 | green *= 255; 1610 | blue *= 255; 1611 | var r = Math.round(red).toString(16); 1612 | if (r.length == 1) { 1613 | r = "0" + r; 1614 | } 1615 | var g = Math.round(green).toString(16); 1616 | if (g.length == 1) { 1617 | g = "0" + g; 1618 | } 1619 | var b = Math.round(blue).toString(16); 1620 | if (b.length == 1) { 1621 | b = "0" + b; 1622 | } 1623 | rgb.hex = "#" + r + g + b; 1624 | return rgb; 1625 | }; 1626 | Raphael.rgb2hsb = function (red, green, blue) { 1627 | if (typeof red == "object" && "r" in red && "g" in red && "b" in red) { 1628 | blue = red.b; 1629 | green = red.g; 1630 | red = red.r; 1631 | } 1632 | if (typeof red == "string" && red.charAt(0) == "#") { 1633 | if (red.length == 4) { 1634 | blue = parseInt(red.substring(3), 16); 1635 | green = parseInt(red.substring(2, 3), 16); 1636 | red = parseInt(red.substring(1, 2), 16); 1637 | } else { 1638 | blue = parseInt(red.substring(5), 16); 1639 | green = parseInt(red.substring(3, 5), 16); 1640 | red = parseInt(red.substring(1, 3), 16); 1641 | } 1642 | } 1643 | if (red > 1 || green > 1 || blue > 1) { 1644 | red /= 255; 1645 | green /= 255; 1646 | blue /= 255; 1647 | } 1648 | var max = Math.max(red, green, blue), 1649 | min = Math.min(red, green, blue), 1650 | hue, 1651 | saturation, 1652 | brightness = max; 1653 | if (min == max) { 1654 | return {h: 0, s: 0, b: max}; 1655 | } else { 1656 | var delta = (max - min); 1657 | saturation = delta / max; 1658 | if (red == max) { 1659 | hue = (green - blue) / delta; 1660 | } else if (green == max) { 1661 | hue = 2 + ((blue - red) / delta); 1662 | } else { 1663 | hue = 4 + ((red - green) / delta); 1664 | } 1665 | hue /= 6; 1666 | if (hue < 0) { 1667 | hue += 1; 1668 | } 1669 | if (hue > 1) { 1670 | hue -= 1; 1671 | } 1672 | } 1673 | return {h: hue, s: saturation, b: brightness}; 1674 | }; 1675 | Raphael.getColor = function (value) { 1676 | var start = arguments.callee.start = arguments.callee.start || {h: 0, s: 1, b: value || .75}; 1677 | var rgb = this.hsb2rgb(start.h, start.s, start.b); 1678 | start.h += .075; 1679 | if (start.h > 1) { 1680 | start.h = 0; 1681 | start.s -= .2; 1682 | if (start.s <= 0) { 1683 | arguments.callee.start = {h: 0, s: 1, b: start.b}; 1684 | } 1685 | } 1686 | return rgb.hex; 1687 | }; 1688 | Raphael.getColor.reset = function () { 1689 | this.start = undefined; 1690 | }; --------------------------------------------------------------------------------