├── 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 |
88 |
TufteGraph
89 |
Make pretty graphs with javascript , using jQuery
90 |
97 |
104 |
105 |
It is different from other javascript charting libraries because:
106 |
107 | Configuration is by dynamic functions, allowing for a really compact API (very few options)
108 | Non-core layout is done via CSS rather than code
109 |
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 |
181 | You need to be using jQuery and also the jQuery enumerable plugin (included). View source on this page if you don't quite get it.
182 | By default your graph won't be that pretty because it needs to be styled. I recommend tufte-graph.css as a good starting point.
183 | Find beautiful colors for your graphs at ColourLovers .
184 | I have tested this in Firefox 3 and Safari 3 and nothing else. Buyer beware.
185 | Read Tufte's The Visual Display of Quantitative Information , it'll blow your mind. Your graphs will never be the same again.
186 | Email me the cool graphs you make, I really dig graphs.
187 | If you're going to make changes to this library, please fork on github and share the love.
188 |
189 |
192 |
193 |
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 = 'empty label
';
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 |
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 |
252 |
253 | Error Handling
254 |
255 |
256 |
257 |
258 | Test
259 | Result
260 |
261 | Non-numeric y-value for bar graph Not run
262 | Non-numeric y-value for stacked graph Not run
263 |
264 |
265 | formatNumber
266 |
276 | Using data from table
277 |
278 |
279 |
280 | Label
281 | Amount
282 |
283 | A 1.0
284 | B 1.3
285 | C 3.6
286 | D 4.0
287 | E 5.2
288 | F 7.0
289 |
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 | };
--------------------------------------------------------------------------------