html5jp_graphs 0.0.1

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.
Files changed (40) hide show
  1. data/.gitigore +2 -0
  2. data/README.rdoc +82 -0
  3. data/Rakefile +22 -0
  4. data/html5jp_graphs.gemspec +17 -0
  5. data/lib/examples/sample_graph_circle_1.html +54 -0
  6. data/lib/examples/sample_graph_circle_2.html +61 -0
  7. data/lib/examples/sample_graph_line_1.html +44 -0
  8. data/lib/examples/sample_graph_line_2.html +53 -0
  9. data/lib/examples/sample_graph_radar_1.html +47 -0
  10. data/lib/examples/sample_graph_radar_2.html +52 -0
  11. data/lib/examples/sample_graph_vbar_1.html +51 -0
  12. data/lib/examples/sample_graph_vbar_2.html +52 -0
  13. data/lib/examples/sample_graph_vbar_3.html +52 -0
  14. data/lib/examples/sample_graph_vbar_4.html +52 -0
  15. data/lib/generators/html5jp_graphs/USAGE +8 -0
  16. data/lib/generators/html5jp_graphs/html5jp_graphs_generator.rb +12 -0
  17. data/lib/generators/html5jp_graphs/templates/excanvas/AUTHORS +10 -0
  18. data/lib/generators/html5jp_graphs/templates/excanvas/COPYING +202 -0
  19. data/lib/generators/html5jp_graphs/templates/excanvas/README +22 -0
  20. data/lib/generators/html5jp_graphs/templates/excanvas/examples/example1.html +93 -0
  21. data/lib/generators/html5jp_graphs/templates/excanvas/examples/example2.html +513 -0
  22. data/lib/generators/html5jp_graphs/templates/excanvas/examples/example3.html +284 -0
  23. data/lib/generators/html5jp_graphs/templates/excanvas/examples/ff.jpg +0 -0
  24. data/lib/generators/html5jp_graphs/templates/excanvas/excanvas-compressed.js +19 -0
  25. data/lib/generators/html5jp_graphs/templates/excanvas/excanvas.js +785 -0
  26. data/lib/generators/html5jp_graphs/templates/excanvas/testcases/arc.html +49 -0
  27. data/lib/generators/html5jp_graphs/templates/excanvas/testcases/linewidth.html +47 -0
  28. data/lib/generators/html5jp_graphs/templates/excanvas/testcases/overflow.html +37 -0
  29. data/lib/generators/html5jp_graphs/templates/excanvas/testcases/quadraticcurve.html +74 -0
  30. data/lib/generators/html5jp_graphs/templates/excanvas/testcases/resizing.html +65 -0
  31. data/lib/generators/html5jp_graphs/templates/graph/circle.js +407 -0
  32. data/lib/generators/html5jp_graphs/templates/graph/line.js +577 -0
  33. data/lib/generators/html5jp_graphs/templates/graph/radar.js +545 -0
  34. data/lib/generators/html5jp_graphs/templates/graph/vbar.js +1156 -0
  35. data/lib/html5jp_graphs.rb +1 -0
  36. data/lib/html5jp_graphs/version.rb +3 -0
  37. data/lib/html5jp_graphs_helper.rb +255 -0
  38. data/tasks/html5jp_graphs_tasks.rake +4 -0
  39. data/test/html5jp_graphs_test.rb +8 -0
  40. metadata +88 -0
@@ -0,0 +1,22 @@
1
+ ExplorerCanvas
2
+ Copyright 2006 Google Inc.
3
+
4
+ -------------------------------------------------------------------------------
5
+ DESCRIPTION
6
+
7
+ Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based
8
+ drawing operations. ExplorerCanvas brings the same functionality to Internet
9
+ Explorer; web developers only need to include a single script tag in their
10
+ existing canvas webpages to enable this support.
11
+
12
+
13
+ -------------------------------------------------------------------------------
14
+ INSTALLATION
15
+
16
+ Include the ExplorerCanvas tag in the same directory as your HTML files, and
17
+ add the following code to your page, preferably in the <head> tag.
18
+
19
+ <!--[if IE]><script type="text/javascript" src="excanvas.js"></script><![endif]-->
20
+
21
+ If you run into trouble, please look at the included example code to see how
22
+ to best implement this
@@ -0,0 +1,93 @@
1
+ <!--
2
+ Copyright 2006 Google Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ -->
16
+ <html>
17
+ <head>
18
+ <title>ExplorerCanvas Example 1</title>
19
+ <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
20
+ <script type="text/javascript">
21
+ var canvas, ctx;
22
+ var particles = [];
23
+ var NUM_PARTICLES = 20;
24
+
25
+ function Particle() {
26
+ this.x = Math.random() * canvas.width;
27
+ this.y = Math.random() * canvas.height;
28
+
29
+ this.xvel = Math.random() * 5 - 2.5;
30
+ this.yvel = Math.random() * 5 - 2.5;
31
+ }
32
+
33
+ Particle.prototype.update = function() {
34
+ this.x += this.xvel;
35
+ this.y += this.yvel;
36
+
37
+ this.yvel += 0.1;
38
+
39
+ if (this.x > canvas.width || this.x < 0) {
40
+ this.xvel = -this.xvel;
41
+ }
42
+
43
+ if (this.y > canvas.height || this.y < 0) {
44
+ this.yvel = -this.yvel;
45
+ }
46
+ }
47
+
48
+ function loop() {
49
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
50
+
51
+ for(var i = 0; i < NUM_PARTICLES; i++) {
52
+ particles[i].update();
53
+
54
+ ctx.beginPath();
55
+ ctx.moveTo(particles[i].x, particles[i].y);
56
+ ctx.lineTo(particles[i].x - particles[i].xvel,
57
+ particles[i].y - particles[i].yvel);
58
+ ctx.stroke();
59
+ ctx.closePath();
60
+ }
61
+
62
+ setTimeout(loop, 10);
63
+ }
64
+
65
+ function load() {
66
+ canvas = document.getElementById("cv");
67
+ ctx = canvas.getContext("2d");
68
+
69
+ for(var i = 0; i < NUM_PARTICLES; i++) {
70
+ particles[i] = new Particle();
71
+ }
72
+
73
+ ctx.lineWidth = "2";
74
+ ctx.strokeStyle = "rgb(255, 255, 255)";
75
+ loop();
76
+ }
77
+ </script>
78
+ <style>
79
+ body {
80
+ background-color:black;
81
+ margin:50px;
82
+ text-align:center;
83
+ }
84
+
85
+ canvas {
86
+ border:1px solid #444;
87
+ }
88
+ </style>
89
+ </head>
90
+ <body onload="load();">
91
+ <canvas id="cv" width="400" height="300"></canvas>
92
+ </body>
93
+ </html>
@@ -0,0 +1,513 @@
1
+ <!--
2
+ Copyright 2006 Google Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ -->
16
+ <html>
17
+ <head>
18
+ <title>ExplorerCanvas Example 1</title>
19
+ <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
20
+ <script type="text/javascript">
21
+ /* -------------------------------------------------------------------- */
22
+
23
+ var canvas, ctx;
24
+ var canvasWidth, halfCanvasWidth;
25
+ var canvasHeight, halfCanvasHeight;
26
+
27
+ var space; // 3D Engine
28
+ var scene; // 3D Scene
29
+
30
+ /* -------------------------------------------------------------------- */
31
+
32
+ /**
33
+ * Space is a simple 3D system.
34
+ *
35
+ * Y+ = up
36
+ * Z+ = into screen
37
+ * X+ = right
38
+ */
39
+ function Space() {
40
+ this.m = this.createMatrixIdentity();
41
+ this.mStack = [];
42
+ }
43
+
44
+ Space.prototype.createMatrixIdentity = function() {
45
+ return [
46
+ [1, 0, 0, 0],
47
+ [0, 1, 0, 0],
48
+ [0, 0, 1, 0],
49
+ [0, 0, 0, 1]
50
+ ];
51
+ }
52
+
53
+ /**
54
+ * Multiplies two 4x4 matricies together.
55
+ */
56
+ Space.prototype.matrixMultiply = function(m1, m2) {
57
+ var result = this.createMatrixIdentity();
58
+
59
+ var width = m1[0].length;
60
+ var height = m1.length;
61
+
62
+ if (width != m2.length) {
63
+ // error
64
+ }
65
+
66
+ for (var x = 0; x < width; x++) {
67
+ for (var y = 0; y < height; y++) {
68
+ var sum = 0;
69
+
70
+ for (var z = 0; z < width; z++) {
71
+ sum += m1[y][z] * m2[z][x];
72
+ }
73
+
74
+ result[y][x] = sum;
75
+ }
76
+ }
77
+
78
+ return result;
79
+ }
80
+
81
+ /**
82
+ * Transforms a coordinate using the current transformation
83
+ * matrix, then flattens it using the projection matrix.
84
+ */
85
+ Space.prototype.flatten = function(point) {
86
+ var p = [[point.x, point.y, point.z, 1]];
87
+ var pm = this.matrixMultiply(p, this.m);
88
+
89
+ point.tx = pm[0][0];
90
+ point.ty = pm[0][1];
91
+ point.tz = pm[0][2];
92
+
93
+ // lazy projection
94
+ point.fx = halfCanvasWidth + (canvasWidth * point.tx / point.tz);
95
+ point.fy = halfCanvasHeight -(canvasWidth * point.ty / point.tz);
96
+ }
97
+
98
+ /**
99
+ * Translate (move) the current transformation matrix
100
+ */
101
+ Space.prototype.translate = function(x, y, z) {
102
+ var m = [
103
+ [1, 0, 0, 0],
104
+ [0, 1, 0, 0],
105
+ [0, 0, 1, 0],
106
+ [x, y, z, 1]
107
+ ];
108
+
109
+ this.m = this.matrixMultiply(m, this.m);
110
+ }
111
+
112
+ /**
113
+ * Rotate the current transformation matrix. Rotations are
114
+ * world-oriented, and occur in y,x,z order.
115
+ */
116
+ Space.prototype.rotate = function(x, y, z) {
117
+ if (y) {
118
+ var cosY = Math.cos(y);
119
+ var sinY = Math.sin(y);
120
+ var rotY = [
121
+ [cosY, 0, sinY, 0],
122
+ [0, 1, 0, 0],
123
+ [-sinY, 0, cosY, 0],
124
+ [0, 0, 0, 1]
125
+ ];
126
+
127
+ this.m = this.matrixMultiply(this.m, rotY);
128
+ }
129
+
130
+ if (x) {
131
+ var cosX = Math.cos(x);
132
+ var sinX = Math.sin(x);
133
+ var rotX = [
134
+ [1, 0, 0, 0],
135
+ [0, cosX, -sinX, 0],
136
+ [0, sinX, cosX,0],
137
+ [0, 0, 0, 1]
138
+ ];
139
+ this.m = this.matrixMultiply(this.m, rotX);
140
+ }
141
+
142
+ if (z) {
143
+ var cosZ = Math.cos(z);
144
+ var sinZ = Math.sin(z);
145
+ var rotZ = [
146
+ [cosZ, -sinZ, 0, 0],
147
+ [sinZ, cosZ, 0, 0],
148
+ [0, 0, 1, 0],
149
+ [0, 0, 0, 1]
150
+ ];
151
+
152
+ this.m = this.matrixMultiply(this.m, rotZ);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Pushes the current transformation onto the stack
158
+ */
159
+ Space.prototype.push = function() {
160
+ this.mStack.push(this.m);
161
+ this.m = [
162
+ [this.m[0][0], this.m[0][1], this.m[0][2], this.m[0][3]],
163
+ [this.m[1][0], this.m[1][1], this.m[1][2], this.m[1][3]],
164
+ [this.m[2][0], this.m[2][1], this.m[2][2], this.m[2][3]],
165
+ [this.m[3][0], this.m[3][1], this.m[3][2], this.m[3][3]]
166
+ ];
167
+ }
168
+
169
+ /**
170
+ * Pops the end off the transformation stack
171
+ */
172
+ Space.prototype.pop = function() {
173
+ this.m = this.mStack.pop();
174
+ }
175
+
176
+ /* -------------------------------------------------------------------- */
177
+
178
+ /**
179
+ * A 3d coordinate
180
+ */
181
+ function Point(x, y, z) {
182
+ this.x = x;
183
+ this.y = y;
184
+ this.z = z;
185
+
186
+ // Relative to camera coordinates
187
+ this.tx;
188
+ this.ty;
189
+ this.tz;
190
+
191
+ // Flattened coordinates
192
+ this.fx;
193
+ this.fy;
194
+ }
195
+
196
+ /**
197
+ * A Shape is made up of polygons
198
+ */
199
+ function Shape() {
200
+ this.points = [];
201
+ this.polygons = [];
202
+ }
203
+
204
+ /**
205
+ * Draws the shape
206
+ */
207
+ Shape.prototype.draw = function(drawlist) {
208
+ for (var i = 0; i< this.points.length; i++) {
209
+ space.flatten(this.points[i]);
210
+ }
211
+
212
+ for (var i = 0; i< this.polygons.length; i++) {
213
+ var poly = this.polygons[i]; // convenience
214
+
215
+ space.flatten(poly.origin);
216
+
217
+ // lazy backface culling
218
+ if (poly.normal && this.backface) {
219
+ space.flatten(poly.normal);
220
+
221
+ var originDist = Math.pow(poly.origin.tx, 2)
222
+ + Math.pow(poly.origin.ty, 2)
223
+ + Math.pow(poly.origin.tz, 2);
224
+
225
+ var normalDist = Math.pow(poly.normal.tx, 2)
226
+ + Math.pow(poly.normal.ty, 2)
227
+ + Math.pow(poly.normal.tz, 2);
228
+
229
+ if(originDist > normalDist) {
230
+ drawlist.push(poly);
231
+ }
232
+ } else {
233
+ drawlist.push(poly);
234
+ }
235
+ }
236
+ }
237
+
238
+ /**
239
+ * A polygon is a connection of points in the shape object. You
240
+ * should probably try to make them coplanar.
241
+ */
242
+ function Polygon(points, normal, backface, type, color) {
243
+ this.points = points;
244
+
245
+ this.origin = new Point(0, 0, 0);
246
+ for(var i = 0; i < this.points.length; i++) {
247
+ this.origin.x += this.points[i].x;
248
+ this.origin.y += this.points[i].y;
249
+ this.origin.z += this.points[i].z;
250
+ }
251
+
252
+ this.origin.x /= this.points.length;
253
+ this.origin.y /= this.points.length;
254
+ this.origin.z /= this.points.length;
255
+
256
+ if (normal) {
257
+ this.normal = new Point(this.origin.x + normal.x,
258
+ this.origin.y + normal.y,
259
+ this.origin.z + normal.z);
260
+ } else {
261
+ this.normal = null;
262
+ }
263
+
264
+ this.backface = backface;
265
+ this.type = type;
266
+ this.color = color;
267
+ }
268
+
269
+ Polygon.SOLID = 0;
270
+ Polygon.WIRE = 1;
271
+
272
+ /**
273
+ * Draws the polygon. Assumes that the points have already been
274
+ * flattened.
275
+ */
276
+ Polygon.prototype.draw = function() {
277
+ ctx.beginPath();
278
+ ctx.moveTo(this.points[0].fx, this.points[0].fy);
279
+
280
+ for(var i = 0; i < this.points.length; i++) {
281
+ ctx.lineTo(this.points[i].fx, this.points[i].fy);
282
+ }
283
+
284
+ ctx.closePath();
285
+
286
+ var color = this.color;
287
+
288
+ /*
289
+ // Do lighting here
290
+ lightvector = Math.abs(this.normal.x + this.normal.y);
291
+ if(lightvector > 1) {
292
+ lightvector = 1;
293
+ }
294
+
295
+ color[0] = (color[0] * lightvector).toString();
296
+ color[1] = (color[1] * lightvector).toString();
297
+ color[2] = (color[2] * lightvector).toString();
298
+ */
299
+
300
+ if (color.length > 3) {
301
+ var style = ["rgba(",
302
+ color[0], ",",
303
+ color[1], ",",
304
+ color[2], ",",
305
+ color[3], ")"].join("");
306
+ } else {
307
+ var style = ["rgb(",
308
+ color[0], ",",
309
+ color[1], ",",
310
+ color[2], ")"].join("");
311
+ }
312
+
313
+ if (this.type == Polygon.SOLID) {
314
+ ctx.fillStyle = style;
315
+ ctx.fill();
316
+ } else if (this.type == Polygon.WIRE) {
317
+ ctx.strokeStyle = style;
318
+ ctx.stroke();
319
+ }
320
+ }
321
+
322
+ /* -------------------------------------------------------------------- */
323
+
324
+ /**
325
+ * Scene describes the 3D environment
326
+ */
327
+ function Scene() {
328
+ this.shapes = {};
329
+ this.camera = new Point(0, 0, 0);
330
+ this.cameraTarget = new Point(0, 0, 0);
331
+ this.cameraRotation = 0;
332
+
333
+ this.drawlist = [];
334
+ }
335
+
336
+ /**
337
+ * Draw the world
338
+ */
339
+ Scene.prototype.draw = function() {
340
+ space.push();
341
+
342
+ // Camera transformation
343
+ space.translate(
344
+ -this.camera.x,
345
+ -this.camera.y,
346
+ -this.camera.z
347
+ );
348
+
349
+ // Camera rotation
350
+ var xdiff = this.cameraTarget.x - this.camera.x;
351
+ var ydiff = this.cameraTarget.y - this.camera.y;
352
+ var zdiff = this.cameraTarget.z - this.camera.z;
353
+
354
+ var xzdist = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(zdiff, 2));
355
+
356
+ var xrot = -Math.atan2(ydiff, xzdist); // up/down rotation
357
+ var yrot = Math.atan2(xdiff, zdiff); // left/right rotation
358
+
359
+ space.rotate(xrot, yrot, this.cameraRotation);
360
+
361
+ // Drawing
362
+ this.drawlist = [];
363
+
364
+ for(var i in this.shapes) {
365
+ this.shapes[i].draw(this.drawlist);
366
+ }
367
+
368
+ // Depth sorting (warning: this is only enough to drive this demo - feel
369
+ // free to contribute a better system).
370
+ this.drawlist.sort(function (poly1, poly2) {
371
+ return poly2.origin.tz - poly1.origin.tz;
372
+ });
373
+
374
+ for (var i = 0; i < this.drawlist.length; i++) {
375
+ this.drawlist[i].draw();
376
+ }
377
+
378
+ space.pop();
379
+ }
380
+
381
+ /* -------------------------------------------------------------------- */
382
+
383
+ var count = 0;
384
+
385
+ function loop() {
386
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
387
+
388
+ scene.camera.x = 70*Math.sin(count);
389
+ scene.camera.y = 70;
390
+ scene.camera.z = 70*Math.cos(count);
391
+ scene.cameraRotation = count / 10;
392
+
393
+ count += 0.01;
394
+ scene.draw();
395
+ }
396
+
397
+ function load() {
398
+ // Init drawing system
399
+ canvas = document.getElementById("cv");
400
+ ctx = canvas.getContext("2d");
401
+
402
+ canvasWidth = canvas.width;
403
+ canvasHeight = canvas.height;
404
+ halfCanvasWidth = canvasWidth * 0.5;
405
+ halfCanvasHeight = canvasHeight * 0.5;
406
+
407
+ // Init 3D components
408
+ space = new Space();
409
+ scene = new Scene();
410
+
411
+ // Create a box shape and add it to the scene
412
+ scene.shapes['box'] = new Shape();
413
+ var p = scene.shapes['box'].points; // for convenience
414
+
415
+ p[0] = new Point(-10, -10, -10); // left bottom front
416
+ p[1] = new Point(10, -10, -10); // right bottom front
417
+ p[2] = new Point(10, 10, -10); // right top front
418
+ p[3] = new Point(-10, 10, -10); // left top front
419
+
420
+ p[4] = new Point(-10, -10, 10); // left bottom back
421
+ p[5] = new Point(10, -10, 10); // right bottom back
422
+ p[6] = new Point(10, 10, 10); // right top back
423
+ p[7] = new Point(-10, 10, 10); // left top back
424
+
425
+ // Back
426
+ scene.shapes['box'].polygons.push(new Polygon(
427
+ [ p[0], p[1], p[2], p[3] ],
428
+ new Point(0, 0, -1),
429
+ true /* double-sided */,
430
+ Polygon.SOLID,
431
+ [255, 0, 0]
432
+ ));
433
+
434
+ // Front
435
+ scene.shapes['box'].polygons.push(new Polygon(
436
+ [ p[4], p[5], p[6], p[7] ],
437
+ new Point(0, 0, 1),
438
+ true /* double-sided */,
439
+ Polygon.SOLID,
440
+ [0, 0, 255]
441
+ ));
442
+
443
+ // Top
444
+ scene.shapes['box'].polygons.push(new Polygon(
445
+ [ p[2], p[3], p[7], p[6] ],
446
+ new Point(0, 1, 0),
447
+ false /* single-sided */,
448
+ Polygon.WIRE,
449
+ [0, 255, 0]
450
+ ));
451
+
452
+ // Transparent Top
453
+ scene.shapes['box'].polygons.push(new Polygon(
454
+ [ p[2], p[3], p[7], p[6] ],
455
+ new Point(0, 1, 0),
456
+ false /* single-sided */,
457
+ Polygon.SOLID,
458
+ [0, 255, 0, 0.4]
459
+ ));
460
+
461
+ // Left
462
+ scene.shapes['box'].polygons.push(new Polygon(
463
+ [ p[0], p[4], p[7], p[3] ],
464
+ new Point(-1, 0, 0),
465
+ true /* double-sided */,
466
+ Polygon.SOLID,
467
+ [255, 255, 0]
468
+ ));
469
+
470
+ // Right
471
+ scene.shapes['box'].polygons.push(new Polygon(
472
+ [ p[1], p[5], p[6], p[2] ],
473
+ new Point(1, 0, 0),
474
+ true /* double-sided */,
475
+ Polygon.SOLID,
476
+ [0, 255, 255]
477
+ ));
478
+
479
+ // Create a floor shape and add it to the scene
480
+ scene.shapes['floor'] = new Shape();
481
+ var p = scene.shapes['floor'].points; // for convenience
482
+
483
+ p[0] = new Point(-40, -10, -40);
484
+ p[1] = new Point(-40, -10, 40);
485
+ p[2] = new Point( 40, -10, 40);
486
+ p[3] = new Point( 40, -10, -40);
487
+
488
+ // Floor
489
+ scene.shapes['floor'].polygons.push(new Polygon(
490
+ [ p[0], p[1], p[2], p[3] ],
491
+ new Point(0, 1, 0),
492
+ false /* single-sided */,
493
+ Polygon.SOLID,
494
+ [45, 45, 45]
495
+ ));
496
+
497
+ setInterval('loop()', 20);
498
+ }
499
+
500
+ /* -------------------------------------------------------------------- */
501
+ </script>
502
+ <style>
503
+ body {
504
+ background-color:black;
505
+ margin:50px;
506
+ text-align:center;
507
+ }
508
+ </style>
509
+ </head>
510
+ <body onload="load();">
511
+ <canvas id="cv" width="400" height="300"></canvas>
512
+ </body>
513
+ </html>