flotr 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ Introduction
2
+ ============
3
+ Flotr (pron.: like "plotter") is a plot/chart facility to be used within Ruby.
4
+
5
+ It is intended to be as portable as possible, and is thus aimed to generate HTML plots thanks to the great [flot library](http://ryanfunduk.com/flot).
6
+
7
+ Installation
8
+ ============
9
+ First, you have to be sure to have the Github repository added to your rubygems configuration. This step only has to be performed the first time you install a gem hosted on Github:
10
+
11
+ gem sources -a http://gems.github.com
12
+
13
+ Next, simply install the gem:
14
+
15
+ sudo gem install pbosetti-flotr
16
+
17
+ Usage
18
+ =====
19
+ The following code produces the `flotr.html` file located in the current folder:
20
+
21
+ require "rubygems"
22
+ require "flotr"
23
+ # Create a new plot
24
+ plot = Flotr::Plot.new("Test plot")
25
+
26
+ # Create two empty series
27
+ sin = Flotr::Data.new(:label => "Sin(x)", :color => "red")
28
+ cos = Flotr::Data.new(:label => "Cos(x)", :color => "blue")
29
+
30
+ # Push data into the two series
31
+ 100.times do |i|
32
+ sin << [i, Math::sin(Math::PI / 100 * i)]
33
+ cos << [i, Math::cos(Math::PI / 100 * i)]
34
+ end
35
+
36
+ plot << sin << cos
37
+ plot.show
38
+
39
+ At the moment, the `Flotr::Plot.show` method automatically opens the plot within a browser window under OS X and Windows. On other platforms (Linux) you have to open the generated file by hands.
40
+
41
+ The default template (since v1.3) allows zooming the plot. Reload the page to reset to full view.
42
+
43
+ Templates
44
+ =========
45
+ Flotr uses templates for formatting the plots. Currently there are the following templates available:
46
+
47
+ 1. `interacting`: a simple layout that shows point coordinates on mouse hover
48
+ 2. `zooming`: a smaller overview is added on the right side of the main plot, allowing zooming of different plot areas
49
+
50
+ The current template can be selected as follows (amongst those installed by default):
51
+
52
+ p = Flotr::Plot.new
53
+ p.std_template #=> "zooming"
54
+ p.std_templates #=> ["interacting" , "zooming"]
55
+ p.std_template = "interacting"
56
+ p.std_template #=> "interacting"
57
+
58
+ Custom templates can be selected this way:
59
+
60
+ p.template = "full/path/to/template.rhtml"
61
+
62
+ Look within `lib/*.rhtml` for template examples. Templates follow the Erubis syntax.
63
+
64
+ Example
65
+ =======
66
+
67
+ ![Obligatory example](http://cloud.github.com/downloads/pbosetti/flotr/plot.png)
68
+
69
+ Thanks to
70
+ =========
71
+ The author of jflot, [Ryan Funduk](http://ryanfunduk.com/flot)
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Paolo Bosetti on 2009-04-27.
4
+ # Copyright (c) 2009 University of Trento. All rights
5
+ # reserved.
6
+
7
+ require "../lib/flotr"
8
+
9
+ sin = Flotr::Data.new(:label => "Sin(x)", :color => "red")
10
+ cos = Flotr::Data.new(:label => "Cos(x)", :color => "blue")
11
+
12
+ 100.times do |i|
13
+ cos << [i, Math::cos(Math::PI / 100 * i)]
14
+ sin << [i, Math::sin(Math::PI / 100 * i)]
15
+ end
16
+
17
+ plot = Flotr::Plot.new("Test plot")
18
+ plot.comment = "This is a test plot made with Flotr"
19
+ plot.options = {:legend_position => "ne", :points => 'true'}
20
+ plot.height = 480
21
+ plot.width = 640
22
+ plot.label = {:X => "X"} # :Y label seems not working on Safari
23
+ plot.ylim = {:min=> -1.5, :max => 1.5}
24
+ plot << sin
25
+ plot << cos
26
+ plot.show
@@ -0,0 +1,785 @@
1
+ // Copyright 2006 Google Inc.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+
16
+ // Known Issues:
17
+ //
18
+ // * Patterns are not implemented.
19
+ // * Radial gradient are not implemented. The VML version of these look very
20
+ // different from the canvas one.
21
+ // * Clipping paths are not implemented.
22
+ // * Coordsize. The width and height attribute have higher priority than the
23
+ // width and height style values which isn't correct.
24
+ // * Painting mode isn't implemented.
25
+ // * Canvas width/height should is using content-box by default. IE in
26
+ // Quirks mode will draw the canvas using border-box. Either change your
27
+ // doctype to HTML5
28
+ // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
29
+ // or use Box Sizing Behavior from WebFX
30
+ // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
31
+ // * Optimize. There is always room for speed improvements.
32
+
33
+ // only add this code if we do not already have a canvas implementation
34
+ if (!window.CanvasRenderingContext2D) {
35
+
36
+ (function () {
37
+
38
+ // alias some functions to make (compiled) code shorter
39
+ var m = Math;
40
+ var mr = m.round;
41
+ var ms = m.sin;
42
+ var mc = m.cos;
43
+
44
+ // this is used for sub pixel precision
45
+ var Z = 10;
46
+ var Z2 = Z / 2;
47
+
48
+ var G_vmlCanvasManager_ = {
49
+ init: function (opt_doc) {
50
+ var doc = opt_doc || document;
51
+ if (/MSIE/.test(navigator.userAgent) && !window.opera) {
52
+ var self = this;
53
+ doc.attachEvent("onreadystatechange", function () {
54
+ self.init_(doc);
55
+ });
56
+ }
57
+ },
58
+
59
+ init_: function (doc) {
60
+ if (doc.readyState == "complete") {
61
+ // create xmlns
62
+ if (!doc.namespaces["g_vml_"]) {
63
+ doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
64
+ }
65
+
66
+ // setup default css
67
+ var ss = doc.createStyleSheet();
68
+ ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
69
+ // default size is 300x150 in Gecko and Opera
70
+ "text-align:left;width:300px;height:150px}" +
71
+ "g_vml_\\:*{behavior:url(#default#VML)}";
72
+
73
+ // find all canvas elements
74
+ var els = doc.getElementsByTagName("canvas");
75
+ for (var i = 0; i < els.length; i++) {
76
+ if (!els[i].getContext) {
77
+ this.initElement(els[i]);
78
+ }
79
+ }
80
+ }
81
+ },
82
+
83
+ fixElement_: function (el) {
84
+ // in IE before version 5.5 we would need to add HTML: to the tag name
85
+ // but we do not care about IE before version 6
86
+ var outerHTML = el.outerHTML;
87
+
88
+ var newEl = el.ownerDocument.createElement(outerHTML);
89
+ // if the tag is still open IE has created the children as siblings and
90
+ // it has also created a tag with the name "/FOO"
91
+ if (outerHTML.slice(-2) != "/>") {
92
+ var tagName = "/" + el.tagName;
93
+ var ns;
94
+ // remove content
95
+ while ((ns = el.nextSibling) && ns.tagName != tagName) {
96
+ ns.removeNode();
97
+ }
98
+ // remove the incorrect closing tag
99
+ if (ns) {
100
+ ns.removeNode();
101
+ }
102
+ }
103
+ el.parentNode.replaceChild(newEl, el);
104
+ return newEl;
105
+ },
106
+
107
+ /**
108
+ * Public initializes a canvas element so that it can be used as canvas
109
+ * element from now on. This is called automatically before the page is
110
+ * loaded but if you are creating elements using createElement you need to
111
+ * make sure this is called on the element.
112
+ * @param {HTMLElement} el The canvas element to initialize.
113
+ * @return {HTMLElement} the element that was created.
114
+ */
115
+ initElement: function (el) {
116
+ el = this.fixElement_(el);
117
+ el.getContext = function () {
118
+ if (this.context_) {
119
+ return this.context_;
120
+ }
121
+ return this.context_ = new CanvasRenderingContext2D_(this);
122
+ };
123
+
124
+ // do not use inline function because that will leak memory
125
+ el.attachEvent('onpropertychange', onPropertyChange);
126
+ el.attachEvent('onresize', onResize);
127
+
128
+ var attrs = el.attributes;
129
+ if (attrs.width && attrs.width.specified) {
130
+ // TODO: use runtimeStyle and coordsize
131
+ // el.getContext().setWidth_(attrs.width.nodeValue);
132
+ el.style.width = attrs.width.nodeValue + "px";
133
+ } else {
134
+ el.width = el.clientWidth;
135
+ }
136
+ if (attrs.height && attrs.height.specified) {
137
+ // TODO: use runtimeStyle and coordsize
138
+ // el.getContext().setHeight_(attrs.height.nodeValue);
139
+ el.style.height = attrs.height.nodeValue + "px";
140
+ } else {
141
+ el.height = el.clientHeight;
142
+ }
143
+ //el.getContext().setCoordsize_()
144
+ return el;
145
+ }
146
+ };
147
+
148
+ function onPropertyChange(e) {
149
+ var el = e.srcElement;
150
+
151
+ switch (e.propertyName) {
152
+ case 'width':
153
+ el.style.width = el.attributes.width.nodeValue + "px";
154
+ el.getContext().clearRect();
155
+ break;
156
+ case 'height':
157
+ el.style.height = el.attributes.height.nodeValue + "px";
158
+ el.getContext().clearRect();
159
+ break;
160
+ }
161
+ }
162
+
163
+ function onResize(e) {
164
+ var el = e.srcElement;
165
+ if (el.firstChild) {
166
+ el.firstChild.style.width = el.clientWidth + 'px';
167
+ el.firstChild.style.height = el.clientHeight + 'px';
168
+ }
169
+ }
170
+
171
+ G_vmlCanvasManager_.init();
172
+
173
+ // precompute "00" to "FF"
174
+ var dec2hex = [];
175
+ for (var i = 0; i < 16; i++) {
176
+ for (var j = 0; j < 16; j++) {
177
+ dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
178
+ }
179
+ }
180
+
181
+ function createMatrixIdentity() {
182
+ return [
183
+ [1, 0, 0],
184
+ [0, 1, 0],
185
+ [0, 0, 1]
186
+ ];
187
+ }
188
+
189
+ function matrixMultiply(m1, m2) {
190
+ var result = createMatrixIdentity();
191
+
192
+ for (var x = 0; x < 3; x++) {
193
+ for (var y = 0; y < 3; y++) {
194
+ var sum = 0;
195
+
196
+ for (var z = 0; z < 3; z++) {
197
+ sum += m1[x][z] * m2[z][y];
198
+ }
199
+
200
+ result[x][y] = sum;
201
+ }
202
+ }
203
+ return result;
204
+ }
205
+
206
+ function copyState(o1, o2) {
207
+ o2.fillStyle = o1.fillStyle;
208
+ o2.lineCap = o1.lineCap;
209
+ o2.lineJoin = o1.lineJoin;
210
+ o2.lineWidth = o1.lineWidth;
211
+ o2.miterLimit = o1.miterLimit;
212
+ o2.shadowBlur = o1.shadowBlur;
213
+ o2.shadowColor = o1.shadowColor;
214
+ o2.shadowOffsetX = o1.shadowOffsetX;
215
+ o2.shadowOffsetY = o1.shadowOffsetY;
216
+ o2.strokeStyle = o1.strokeStyle;
217
+ o2.arcScaleX_ = o1.arcScaleX_;
218
+ o2.arcScaleY_ = o1.arcScaleY_;
219
+ }
220
+
221
+ function processStyle(styleString) {
222
+ var str, alpha = 1;
223
+
224
+ styleString = String(styleString);
225
+ if (styleString.substring(0, 3) == "rgb") {
226
+ var start = styleString.indexOf("(", 3);
227
+ var end = styleString.indexOf(")", start + 1);
228
+ var guts = styleString.substring(start + 1, end).split(",");
229
+
230
+ str = "#";
231
+ for (var i = 0; i < 3; i++) {
232
+ str += dec2hex[Number(guts[i])];
233
+ }
234
+
235
+ if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
236
+ alpha = guts[3];
237
+ }
238
+ } else {
239
+ str = styleString;
240
+ }
241
+
242
+ return [str, alpha];
243
+ }
244
+
245
+ function processLineCap(lineCap) {
246
+ switch (lineCap) {
247
+ case "butt":
248
+ return "flat";
249
+ case "round":
250
+ return "round";
251
+ case "square":
252
+ default:
253
+ return "square";
254
+ }
255
+ }
256
+
257
+ /**
258
+ * This class implements CanvasRenderingContext2D interface as described by
259
+ * the WHATWG.
260
+ * @param {HTMLElement} surfaceElement The element that the 2D context should
261
+ * be associated with
262
+ */
263
+ function CanvasRenderingContext2D_(surfaceElement) {
264
+ this.m_ = createMatrixIdentity();
265
+
266
+ this.mStack_ = [];
267
+ this.aStack_ = [];
268
+ this.currentPath_ = [];
269
+
270
+ // Canvas context properties
271
+ this.strokeStyle = "#000";
272
+ this.fillStyle = "#000";
273
+
274
+ this.lineWidth = 1;
275
+ this.lineJoin = "miter";
276
+ this.lineCap = "butt";
277
+ this.miterLimit = Z * 1;
278
+ this.globalAlpha = 1;
279
+ this.canvas = surfaceElement;
280
+
281
+ var el = surfaceElement.ownerDocument.createElement('div');
282
+ el.style.width = surfaceElement.clientWidth + 'px';
283
+ el.style.height = surfaceElement.clientHeight + 'px';
284
+ el.style.overflow = 'hidden';
285
+ el.style.position = 'absolute';
286
+ surfaceElement.appendChild(el);
287
+
288
+ this.element_ = el;
289
+ this.arcScaleX_ = 1;
290
+ this.arcScaleY_ = 1;
291
+ }
292
+
293
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
294
+ contextPrototype.clearRect = function() {
295
+ this.element_.innerHTML = "";
296
+ this.currentPath_ = [];
297
+ };
298
+
299
+ contextPrototype.beginPath = function() {
300
+ // TODO: Branch current matrix so that save/restore has no effect
301
+ // as per safari docs.
302
+
303
+ this.currentPath_ = [];
304
+ };
305
+
306
+ contextPrototype.moveTo = function(aX, aY) {
307
+ this.currentPath_.push({type: "moveTo", x: aX, y: aY});
308
+ this.currentX_ = aX;
309
+ this.currentY_ = aY;
310
+ };
311
+
312
+ contextPrototype.lineTo = function(aX, aY) {
313
+ this.currentPath_.push({type: "lineTo", x: aX, y: aY});
314
+ this.currentX_ = aX;
315
+ this.currentY_ = aY;
316
+ };
317
+
318
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
319
+ aCP2x, aCP2y,
320
+ aX, aY) {
321
+ this.currentPath_.push({type: "bezierCurveTo",
322
+ cp1x: aCP1x,
323
+ cp1y: aCP1y,
324
+ cp2x: aCP2x,
325
+ cp2y: aCP2y,
326
+ x: aX,
327
+ y: aY});
328
+ this.currentX_ = aX;
329
+ this.currentY_ = aY;
330
+ };
331
+
332
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
333
+ // the following is lifted almost directly from
334
+ // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
335
+ var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
336
+ var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
337
+ var cp2x = cp1x + (aX - this.currentX_) / 3.0;
338
+ var cp2y = cp1y + (aY - this.currentY_) / 3.0;
339
+ this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
340
+ };
341
+
342
+ contextPrototype.arc = function(aX, aY, aRadius,
343
+ aStartAngle, aEndAngle, aClockwise) {
344
+ aRadius *= Z;
345
+ var arcType = aClockwise ? "at" : "wa";
346
+
347
+ var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
348
+ var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
349
+
350
+ var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
351
+ var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
352
+
353
+ // IE won't render arches drawn counter clockwise if xStart == xEnd.
354
+ if (xStart == xEnd && !aClockwise) {
355
+ xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
356
+ // that can be represented in binary
357
+ }
358
+
359
+ this.currentPath_.push({type: arcType,
360
+ x: aX,
361
+ y: aY,
362
+ radius: aRadius,
363
+ xStart: xStart,
364
+ yStart: yStart,
365
+ xEnd: xEnd,
366
+ yEnd: yEnd});
367
+
368
+ };
369
+
370
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
371
+ this.moveTo(aX, aY);
372
+ this.lineTo(aX + aWidth, aY);
373
+ this.lineTo(aX + aWidth, aY + aHeight);
374
+ this.lineTo(aX, aY + aHeight);
375
+ this.closePath();
376
+ };
377
+
378
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
379
+ // Will destroy any existing path (same as FF behaviour)
380
+ this.beginPath();
381
+ this.moveTo(aX, aY);
382
+ this.lineTo(aX + aWidth, aY);
383
+ this.lineTo(aX + aWidth, aY + aHeight);
384
+ this.lineTo(aX, aY + aHeight);
385
+ this.closePath();
386
+ this.stroke();
387
+ };
388
+
389
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
390
+ // Will destroy any existing path (same as FF behaviour)
391
+ this.beginPath();
392
+ this.moveTo(aX, aY);
393
+ this.lineTo(aX + aWidth, aY);
394
+ this.lineTo(aX + aWidth, aY + aHeight);
395
+ this.lineTo(aX, aY + aHeight);
396
+ this.closePath();
397
+ this.fill();
398
+ };
399
+
400
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
401
+ var gradient = new CanvasGradient_("gradient");
402
+ return gradient;
403
+ };
404
+
405
+ contextPrototype.createRadialGradient = function(aX0, aY0,
406
+ aR0, aX1,
407
+ aY1, aR1) {
408
+ var gradient = new CanvasGradient_("gradientradial");
409
+ gradient.radius1_ = aR0;
410
+ gradient.radius2_ = aR1;
411
+ gradient.focus_.x = aX0;
412
+ gradient.focus_.y = aY0;
413
+ return gradient;
414
+ };
415
+
416
+ contextPrototype.drawImage = function (image, var_args) {
417
+ var dx, dy, dw, dh, sx, sy, sw, sh;
418
+
419
+ // to find the original width we overide the width and height
420
+ var oldRuntimeWidth = image.runtimeStyle.width;
421
+ var oldRuntimeHeight = image.runtimeStyle.height;
422
+ image.runtimeStyle.width = 'auto';
423
+ image.runtimeStyle.height = 'auto';
424
+
425
+ // get the original size
426
+ var w = image.width;
427
+ var h = image.height;
428
+
429
+ // and remove overides
430
+ image.runtimeStyle.width = oldRuntimeWidth;
431
+ image.runtimeStyle.height = oldRuntimeHeight;
432
+
433
+ if (arguments.length == 3) {
434
+ dx = arguments[1];
435
+ dy = arguments[2];
436
+ sx = sy = 0;
437
+ sw = dw = w;
438
+ sh = dh = h;
439
+ } else if (arguments.length == 5) {
440
+ dx = arguments[1];
441
+ dy = arguments[2];
442
+ dw = arguments[3];
443
+ dh = arguments[4];
444
+ sx = sy = 0;
445
+ sw = w;
446
+ sh = h;
447
+ } else if (arguments.length == 9) {
448
+ sx = arguments[1];
449
+ sy = arguments[2];
450
+ sw = arguments[3];
451
+ sh = arguments[4];
452
+ dx = arguments[5];
453
+ dy = arguments[6];
454
+ dw = arguments[7];
455
+ dh = arguments[8];
456
+ } else {
457
+ throw "Invalid number of arguments";
458
+ }
459
+
460
+ var d = this.getCoords_(dx, dy);
461
+
462
+ var w2 = sw / 2;
463
+ var h2 = sh / 2;
464
+
465
+ var vmlStr = [];
466
+
467
+ var W = 10;
468
+ var H = 10;
469
+
470
+ // For some reason that I've now forgotten, using divs didn't work
471
+ vmlStr.push(' <g_vml_:group',
472
+ ' coordsize="', Z * W, ',', Z * H, '"',
473
+ ' coordorigin="0,0"' ,
474
+ ' style="width:', W, ';height:', H, ';position:absolute;');
475
+
476
+ // If filters are necessary (rotation exists), create them
477
+ // filters are bog-slow, so only create them if abbsolutely necessary
478
+ // The following check doesn't account for skews (which don't exist
479
+ // in the canvas spec (yet) anyway.
480
+
481
+ if (this.m_[0][0] != 1 || this.m_[0][1]) {
482
+ var filter = [];
483
+
484
+ // Note the 12/21 reversal
485
+ filter.push("M11='", this.m_[0][0], "',",
486
+ "M12='", this.m_[1][0], "',",
487
+ "M21='", this.m_[0][1], "',",
488
+ "M22='", this.m_[1][1], "',",
489
+ "Dx='", mr(d.x / Z), "',",
490
+ "Dy='", mr(d.y / Z), "'");
491
+
492
+ // Bounding box calculation (need to minimize displayed area so that
493
+ // filters don't waste time on unused pixels.
494
+ var max = d;
495
+ var c2 = this.getCoords_(dx + dw, dy);
496
+ var c3 = this.getCoords_(dx, dy + dh);
497
+ var c4 = this.getCoords_(dx + dw, dy + dh);
498
+
499
+ max.x = Math.max(max.x, c2.x, c3.x, c4.x);
500
+ max.y = Math.max(max.y, c2.y, c3.y, c4.y);
501
+
502
+ vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
503
+ "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
504
+ filter.join(""), ", sizingmethod='clip');");
505
+ } else {
506
+ vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;");
507
+ }
508
+
509
+ vmlStr.push(' ">' ,
510
+ '<g_vml_:image src="', image.src, '"',
511
+ ' style="width:', Z * dw, ';',
512
+ ' height:', Z * dh, ';"',
513
+ ' cropleft="', sx / w, '"',
514
+ ' croptop="', sy / h, '"',
515
+ ' cropright="', (w - sx - sw) / w, '"',
516
+ ' cropbottom="', (h - sy - sh) / h, '"',
517
+ ' />',
518
+ '</g_vml_:group>');
519
+
520
+ this.element_.insertAdjacentHTML("BeforeEnd",
521
+ vmlStr.join(""));
522
+ };
523
+
524
+ contextPrototype.stroke = function(aFill) {
525
+ var lineStr = [];
526
+ var lineOpen = false;
527
+ var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
528
+ var color = a[0];
529
+ var opacity = a[1] * this.globalAlpha;
530
+
531
+ var W = 10;
532
+ var H = 10;
533
+
534
+ lineStr.push('<g_vml_:shape',
535
+ ' fillcolor="', color, '"',
536
+ ' filled="', Boolean(aFill), '"',
537
+ ' style="position:absolute;width:', W, ';height:', H, ';"',
538
+ ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
539
+ ' stroked="', !aFill, '"',
540
+ ' strokeweight="', this.lineWidth, '"',
541
+ ' strokecolor="', color, '"',
542
+ ' path="');
543
+
544
+ var newSeq = false;
545
+ var min = {x: null, y: null};
546
+ var max = {x: null, y: null};
547
+
548
+ for (var i = 0; i < this.currentPath_.length; i++) {
549
+ var p = this.currentPath_[i];
550
+
551
+ if (p.type == "moveTo") {
552
+ lineStr.push(" m ");
553
+ var c = this.getCoords_(p.x, p.y);
554
+ lineStr.push(mr(c.x), ",", mr(c.y));
555
+ } else if (p.type == "lineTo") {
556
+ lineStr.push(" l ");
557
+ var c = this.getCoords_(p.x, p.y);
558
+ lineStr.push(mr(c.x), ",", mr(c.y));
559
+ } else if (p.type == "close") {
560
+ lineStr.push(" x ");
561
+ } else if (p.type == "bezierCurveTo") {
562
+ lineStr.push(" c ");
563
+ var c = this.getCoords_(p.x, p.y);
564
+ var c1 = this.getCoords_(p.cp1x, p.cp1y);
565
+ var c2 = this.getCoords_(p.cp2x, p.cp2y);
566
+ lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
567
+ mr(c2.x), ",", mr(c2.y), ",",
568
+ mr(c.x), ",", mr(c.y));
569
+ } else if (p.type == "at" || p.type == "wa") {
570
+ lineStr.push(" ", p.type, " ");
571
+ var c = this.getCoords_(p.x, p.y);
572
+ var cStart = this.getCoords_(p.xStart, p.yStart);
573
+ var cEnd = this.getCoords_(p.xEnd, p.yEnd);
574
+
575
+ lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
576
+ mr(c.y - this.arcScaleY_ * p.radius), " ",
577
+ mr(c.x + this.arcScaleX_ * p.radius), ",",
578
+ mr(c.y + this.arcScaleY_ * p.radius), " ",
579
+ mr(cStart.x), ",", mr(cStart.y), " ",
580
+ mr(cEnd.x), ",", mr(cEnd.y));
581
+ }
582
+
583
+
584
+ // TODO: Following is broken for curves due to
585
+ // move to proper paths.
586
+
587
+ // Figure out dimensions so we can do gradient fills
588
+ // properly
589
+ if(c) {
590
+ if (min.x == null || c.x < min.x) {
591
+ min.x = c.x;
592
+ }
593
+ if (max.x == null || c.x > max.x) {
594
+ max.x = c.x;
595
+ }
596
+ if (min.y == null || c.y < min.y) {
597
+ min.y = c.y;
598
+ }
599
+ if (max.y == null || c.y > max.y) {
600
+ max.y = c.y;
601
+ }
602
+ }
603
+ }
604
+ lineStr.push(' ">');
605
+
606
+ if (typeof this.fillStyle == "object") {
607
+ var focus = {x: "50%", y: "50%"};
608
+ var width = (max.x - min.x);
609
+ var height = (max.y - min.y);
610
+ var dimension = (width > height) ? width : height;
611
+
612
+ focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
613
+ focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
614
+
615
+ var colors = [];
616
+
617
+ // inside radius (%)
618
+ if (this.fillStyle.type_ == "gradientradial") {
619
+ var inside = (this.fillStyle.radius1_ / dimension * 100);
620
+
621
+ // percentage that outside radius exceeds inside radius
622
+ var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
623
+ } else {
624
+ var inside = 0;
625
+ var expansion = 100;
626
+ }
627
+
628
+ var insidecolor = {offset: null, color: null};
629
+ var outsidecolor = {offset: null, color: null};
630
+
631
+ // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
632
+ // won't interpret it correctly
633
+ this.fillStyle.colors_.sort(function (cs1, cs2) {
634
+ return cs1.offset - cs2.offset;
635
+ });
636
+
637
+ for (var i = 0; i < this.fillStyle.colors_.length; i++) {
638
+ var fs = this.fillStyle.colors_[i];
639
+
640
+ colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
641
+
642
+ if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
643
+ insidecolor.offset = fs.offset;
644
+ insidecolor.color = fs.color;
645
+ }
646
+
647
+ if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
648
+ outsidecolor.offset = fs.offset;
649
+ outsidecolor.color = fs.color;
650
+ }
651
+ }
652
+ colors.pop();
653
+
654
+ lineStr.push('<g_vml_:fill',
655
+ ' color="', outsidecolor.color, '"',
656
+ ' color2="', insidecolor.color, '"',
657
+ ' type="', this.fillStyle.type_, '"',
658
+ ' focusposition="', focus.x, ', ', focus.y, '"',
659
+ ' colors="', colors.join(""), '"',
660
+ ' opacity="', opacity, '" />');
661
+ } else if (aFill) {
662
+ lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
663
+ } else {
664
+ lineStr.push(
665
+ '<g_vml_:stroke',
666
+ ' opacity="', opacity,'"',
667
+ ' joinstyle="', this.lineJoin, '"',
668
+ ' miterlimit="', this.miterLimit, '"',
669
+ ' endcap="', processLineCap(this.lineCap) ,'"',
670
+ ' weight="', this.lineWidth, 'px"',
671
+ ' color="', color,'" />'
672
+ );
673
+ }
674
+
675
+ lineStr.push("</g_vml_:shape>");
676
+
677
+ this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
678
+
679
+ //this.currentPath_ = [];
680
+ };
681
+
682
+ contextPrototype.fill = function() {
683
+ this.stroke(true);
684
+ };
685
+
686
+ contextPrototype.closePath = function() {
687
+ this.currentPath_.push({type: "close"});
688
+ };
689
+
690
+ /**
691
+ * @private
692
+ */
693
+ contextPrototype.getCoords_ = function(aX, aY) {
694
+ return {
695
+ x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
696
+ y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
697
+ }
698
+ };
699
+
700
+ contextPrototype.save = function() {
701
+ var o = {};
702
+ copyState(this, o);
703
+ this.aStack_.push(o);
704
+ this.mStack_.push(this.m_);
705
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
706
+ };
707
+
708
+ contextPrototype.restore = function() {
709
+ copyState(this.aStack_.pop(), this);
710
+ this.m_ = this.mStack_.pop();
711
+ };
712
+
713
+ contextPrototype.translate = function(aX, aY) {
714
+ var m1 = [
715
+ [1, 0, 0],
716
+ [0, 1, 0],
717
+ [aX, aY, 1]
718
+ ];
719
+
720
+ this.m_ = matrixMultiply(m1, this.m_);
721
+ };
722
+
723
+ contextPrototype.rotate = function(aRot) {
724
+ var c = mc(aRot);
725
+ var s = ms(aRot);
726
+
727
+ var m1 = [
728
+ [c, s, 0],
729
+ [-s, c, 0],
730
+ [0, 0, 1]
731
+ ];
732
+
733
+ this.m_ = matrixMultiply(m1, this.m_);
734
+ };
735
+
736
+ contextPrototype.scale = function(aX, aY) {
737
+ this.arcScaleX_ *= aX;
738
+ this.arcScaleY_ *= aY;
739
+ var m1 = [
740
+ [aX, 0, 0],
741
+ [0, aY, 0],
742
+ [0, 0, 1]
743
+ ];
744
+
745
+ this.m_ = matrixMultiply(m1, this.m_);
746
+ };
747
+
748
+ /******** STUBS ********/
749
+ contextPrototype.clip = function() {
750
+ // TODO: Implement
751
+ };
752
+
753
+ contextPrototype.arcTo = function() {
754
+ // TODO: Implement
755
+ };
756
+
757
+ contextPrototype.createPattern = function() {
758
+ return new CanvasPattern_;
759
+ };
760
+
761
+ // Gradient / Pattern Stubs
762
+ function CanvasGradient_(aType) {
763
+ this.type_ = aType;
764
+ this.radius1_ = 0;
765
+ this.radius2_ = 0;
766
+ this.colors_ = [];
767
+ this.focus_ = {x: 0, y: 0};
768
+ }
769
+
770
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
771
+ aColor = processStyle(aColor);
772
+ this.colors_.push({offset: 1-aOffset, color: aColor});
773
+ };
774
+
775
+ function CanvasPattern_() {}
776
+
777
+ // set up externs
778
+ G_vmlCanvasManager = G_vmlCanvasManager_;
779
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
780
+ CanvasGradient = CanvasGradient_;
781
+ CanvasPattern = CanvasPattern_;
782
+
783
+ })();
784
+
785
+ } // if