22.6. Graphics with Flash Each vector-graphics technology discussed so far in this chapter has limited availability: the <canvas> tag is only in Safari 1.3, Firefox 1.5, and Opera 9, VML is (and will always be) IE-only, and SVG is supported natively only in Firefox 1.5. Plug-in support for SVG is available, but the plug-in is not widely installed. One powerful vector-graphics plug-in is widely (almost universally) installed, however: the Flash player from Adobe (formerly Macromedia). The Flash player has its own scripting language, called ActionScript (actually a dialect of JavaScript). Since Flash 6, the player has exposed a simple but powerful drawing API to ActionScript code. Flash 6 and 7 also provide limited communication channels between client-side JavaScript and ActionScript, making it possible for client-side JavaScript to send drawing commands to the Flash plug-in to be executed by the plug-in's ActionScript interpreter. This chapter relies on Flash 8, which is brand new at the time of this writing. Flash 8 includes an ExternalInterface API that makes it trivial to export ActionScript methods so that they can be transparently invoked by client-side JavaScript. Chapter 23 illustrates how Flash drawing methods can be invoked in Flash 6 and 7. To create a Flash-based drawing canvas, you need a .swf file that does no drawing of its own but just exports a drawing API to client-side JavaScript.[*] Let's begin with the ActionScript file shown in Example 22-12. [*] The Flash-based pie chart code in Example 22-13 uses this Flash drawing API, but I don't document it here. You can find documentation at the Adobe web site. Example 22-12. Canvas.as import flash.external.ExternalInterface; class Canvas { // The open source mtasc ActionScript compiler automatically invokes // this main( ) method in the compiled .swf file it produces. If you use // the Flash IDE to create a Canvas.swf file, you'll need to call // Canvas.main( ) from the first frame of the movie instead. static function main( ) { var canvas = new Canvas( ); } // This constructor contains initialization code for our Flash Canvas function Canvas( ) { // Specify resize behavior for the canvas Stage.scaleMode = "noScale"; Stage.align = "TL"; // Now simply export the functions of the Flash drawing API ExternalInterface.addCallback("beginFill", _root, _root.beginFill); ExternalInterface.addCallback("beginGradientFill", _root, _root.beginGradientFill); ExternalInterface.addCallback("clear", _root, _root.clear); ExternalInterface.addCallback("curveTo", _root, _root.curveTo); ExternalInterface.addCallback("endFill", _root, _root.endFill); ExternalInterface.addCallback("lineTo", _root, _root.lineTo); ExternalInterface.addCallback("lineStyle", _root, _root.lineStyle); ExternalInterface.addCallback("moveTo", _root, _root.moveTo); // Also export the addText( ) function below ExternalInterface.addCallback("addText", null, addText); } static function addText(text, x, y, w, h, depth, font, size) { // Create a TextField object to display text at the specified location var tf = _root.createTextField("tf", depth, x, y, w, h); // Tell it what text to display tf.text = text; // Set the font family and point size for the text var format = new TextFormat( ); format.font = font; format.size = size; tf.setTextFormat(format); } } | The ActionScript code shown in the Canvas.as file of Example 22-12 must be compiled into a Canvas.swf file before it can be used with the Flash player. Details on doing this are beyond the scope of this book, but you can use a commercial Flash IDE from Adobe or an open source ActionScript compiler.[*] [*] I used the open source mtasc compiler (http://www.mtasc.org) and compiled the code with this command:
mtasc -swf Canvas.swf -main -version 8 -header 500:500:1 Canvas.as After compilation, the resulting Canvas.swf file is a mere 578 bytes longsmaller than most bitmap images. Unfortunately, Flash offers only a low-level API. In particular, the only function for drawing curves is curveTo( ), which draws a quadratic Bezier curve. All circles, ellipses, and cubic Bezier curves must be approximated with simpler quadratic curves. This low-level API is well-suited to the compiled SWF Flash format: all the computation required for more complex curves can be done at compilation time, and the Flash player need only know how to draw simpler curves. A higher-level drawing API can be built on top of the primitives provided by the Flash player, and it's possible to do this in ActionScript or in JavaScript (Example 22-13 is in JavaScript). Example 22-13 begins with a utility function for embedding the Canvas.swf file into the HTML document. This is done differently in different browsers, and the insertCanvas( ) utility simplifies things. Next comes a wedge( ) function, which uses the simple Flash drawing API to draw a wedge of the pie chart. The pieChart( ) function follows and calls wedge( ) to draw its slices of pie. Finally, the example defines an onload handler to insert the Flash canvas into the document and draw into it. Example 22-13. Drawing a pie chart with JavaScript and Flash <html> <head> <script> // Embed a Flash canvas of the specified size, inserting it as the sole // child of the specified container element. For portability, this function // uses an <embed> tag in Netscape-style browsers and an <object> tag in others // Inspired by FlashObject from Geoff Stearns. // See http://blog.deconcept.com/flashobject/ function insertCanvas(containerid, canvasid, width, height) { var container = document.getElementById(containerid); if (navigator.plugins && navigator.mimeTypes&&navigator.mimeTypes.length){ container.innerHTML = "<embed src='/books/2/427/1/html/2/Canvas.swf' type='application/x-shockwave-flash' " + "width='" + width + "' height='" + height + "' bgcolor='#ffffff' " + "id='" + canvasid + "' name='" + canvasid + "'>"; } else { container.innerHTML = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' "+ "width='" + width + "' height='" + height + "' id='"+ canvasid + "'>" + " <param name='movie' value='Canvas.swf'>" + " <param name='bgcolor' value='#ffffff'>" + "</object>"; } } // The Flash drawing API is lower-level than others, with only a simple // bezier-curve primitive. This method draws a pie wedge using that API. // Note that angles must be specified in radians. function wedge(canvas, cx, cy, r, startangle, endangle, color) { // Figure out the starting point of the wedge var x1 = cx + r*Math.sin(startangle); var y1 = cy - r*Math.cos(startangle); canvas.beginFill(color, 100); // Fill with specified color, fully opaque canvas.moveTo(cx, cy); // Move to center of circle canvas.lineTo(x1, y1); // Draw a line to the edge of the circle // Now break the arc into pieces < 45 degrees and draw each // with a separate call to the nested arc( ) method while(startangle < endangle) { var theta; if (endangle-startangle > Math.PI/4) theta = startangle+Math.PI/4; else theta = endangle; arc(canvas,cx,cy,r,startangle,theta); startangle += Math.PI/4; } canvas.lineTo(cx, cy); // Finish with a line back to the center canvas.endFill( ); // Fill the wedge we've outlined // This nested function draws a portion of a circle using a Bezier curve. // endangle-startangle must be <= 45 degrees. // The current point must already be at the startangle point. // You can take this on faith if you don't understand the math. function arc(canvas, cx, cy, r, startangle, endangle) { // Compute end point of the curve var x2 = cx + r*Math.sin(endangle); var y2 = cy - r*Math.cos(endangle); var theta = (endangle - startangle)/2; // This is the distance from the center to the control point var l = r/Math.cos(theta); // angle from center to control point is: var alpha = (startangle + endangle)/2; // Compute the control point for the curve var controlX = cx + l * Math.sin(alpha); var controlY = cy - l * Math.cos(alpha); // Now call the Flash drawing API to draw the arc as a Bezier curve. canvas.curveTo(controlX, controlY, x2, y2); } } /** * Draw a pie chart in the Flash canvas specified by element or id. * data is an array of numbers: each number corresponds to a wedge of the chart. * The pie chart is centered at (cx, cy) and has radius r. * The colors of the wedges are Flash color values in the colors[] array. * A legend appears at (lx,ly) to associate the labels in the labels[] * array with each of the colors. */ function pieChart(canvas, data, cx, cy, r, colors, labels, lx, ly) { // Get the canvas if specified by id if (typeof canvas == "string") canvas = document.getElementById(canvas); // All the lines we draw are 2 pixels wide, black, and 100% opaque. canvas.lineStyle(2, 0x000000, 100); // Figure out the total of the data values var total = 0; for(var i = 0; i < data.length; i++) total += data[i]; // And compute the angle (in radians) for each one. var angles = [] for(var i = 0; i < data.length; i++) angles[i] = data[i]/total*Math.PI*2; // Now, loop through the wedges of the pie startangle = 0; for(var i = 0; i < data.length; i++) { // This is the angle where the wedge ends var endangle = startangle + angles[i]; // Draw a wedge: this function is defined earlier wedge(canvas, cx, cy, r, startangle, endangle, colors[i]); // The next wedge starts where this one ends. startangle = endangle; // Draw a box for the legend canvas.beginFill(colors[i], 100); canvas.moveTo(lx, ly+30*i); canvas.lineTo(lx+20, ly+30*i); canvas.lineTo(lx+20, ly+30*i+20); canvas.lineTo(lx, ly+30*i+20); canvas.lineTo(lx, ly+30*i); canvas.endFill( ); // Add text next to the box canvas.addText(labels[i], lx+30, ly+i*30, 100, 20, // Text and position i, // each text field must have a different depth "Helvetica", 16); // Font info } } // When the document loads, insert a Flash canvas and draw on it // Note that colors in Flash are integers instead of strings window.onload = function( ) { insertCanvas("placeholder", "canvas", 600, 400); pieChart("canvas", [12, 23, 34, 45], 200, 200, 150, [0xff0000, 0x0000ff, 0xffff00, 0x00ff00], ["North", "South", "East", "West"], 400, 100); } </script> </head> <body> <div ></div> </body> </html> | |