Section 22.7. Graphics with Java


22.7. Graphics with Java

The Java plug-in from Sun Microsystems is not as widely deployed as the Flash plug-in is, but it is becoming more and more common, and some computer manufacturers are preinstalling it on their hardware. The Java2D API is a powerful vector-drawing API. It has been around since Java 1.2, is higher-level and easier to use than the Flash drawing API, and is more extensive (supporting text, for example) than the <canvas> API. In terms of features, Java2D is most similar to SVG. This section demonstrates two useful ways to use the Java2D API from client-side JavaScript.

22.7.1. Pie Charts with Java

You can take the same approach with Java as with Flash: create a "Canvas" applet that has no behavior of its own but exists simply to export the Java2D API so that it can be invoked by client-side JavaScript. (See Chapter 23 for more on scripting Java from JavaScript.) Such an applet might look like the one shown in Example 22-14. Note that this applet only scratches the surface of the Java2D API and exposes a bare-minimum number of methods. The Flash drawing API consisted of eight simple methods, and it was easy to export them all. Java2D has many more methods, and while it is not technically difficult to make them all available through a Canvas applet, such an applet would be too long to list here. The code in Example 22-14 shows a basic approach and provides a rich-enough API for the pie chart in Figure 22-5.

Example 22-14. A Java canvas applet for client-side drawing

 import java.applet.*; import java.awt.*; import java.awt.geom.*; import java.awt.image.*; /**  * This simple applet does nothing by itself: it simply exports an API  * for the use of client-side JavaScript code.  */ public class Canvas extends Applet {     BufferedImage image;  // We draw into this offscreen image     Graphics2D g;         // using this graphics context     // The browser calls this method to initialize the applet     public void init( ) {         // Find out how big the applet is and create an offscreen image         // that size.         int w = getWidth( );         int h = getHeight( );         image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);         // Get a graphics context for drawing into the image         g = image.createGraphics( );         // Start with a pure white background         g.setPaint(Color.WHITE);         g.fillRect(0, 0, w, h);         // Turn on antialiasing         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,                            RenderingHints.VALUE_ANTIALIAS_ON);     }     // The browser automatically calls this method when the applet needs     // to be redrawn. We copy the offscreen image onscreen.     // JavaScript code drawing to this applet must call the inherited     // repaint( ) method to request a redraw.     public void paint(Graphics g) { g.drawImage(image, 0, 0, this); }     // These methods set basic drawing parameters     // This is just a subset: the Java2D API supports many others     public void setLineWidth(float w) { g.setStroke(new BasicStroke(w)); }     public void setColor(int color) { g.setPaint(new Color(color)); }     public void setFont(String fontfamily, int pointsize) {         g.setFont(new Font(fontfamily, Font.PLAIN, pointsize));     }     // These are simple drawing primitives     public void fillRect(int x, int y, int w, int h) { g.fillRect(x,y,w,h); }     public void drawRect(int x, int y, int w, int h) { g.drawRect(x,y,w,h); }     public void drawString(String s, int x, int y) { g.drawString(s, x, y); }     // These methods fill and draw arbitrary shapes     public void fill(Shape shape) { g.fill(shape); }     public void draw(Shape shape) { g.draw(shape); }    // These methods return simple Shape objects     // This is just a sampler. The Java2D API supports many others.     public Shape createRectangle(double x, double y, double w, double h) {         return new Rectangle2D.Double(x, y, w, h);     }     public Shape createEllipse(double x, double y, double w, double h) {         return new Ellipse2D.Double(x, y, w, h);     }     public Shape createWedge(double x, double y, double w, double h,                              double start, double extent) {         return new Arc2D.Double(x, y, w, h, start, extent, Arc2D.PIE);     } } 

This applet is compiled with the javac compiler to produce a file named Canvas.class:

 % javac Canvas.java 

The compiled Canvas.class can then be embedded in an HTML file and scripted like this:

 <head> <script> window.onload = function( ) {      var canvas = document.getElementById('square');      canvas.setColor(0x0000ff);  // Note integer color      canvas.fillRect(0,0,10,10);      canvas.repaint( );      canvas = document.getElementById('circle');      canvas.setColor(0xff0000);      canvas.fill(canvas.createEllipse(0,0,10,10));      canvas.repaint( ); }; </script> </head> <body> This is a blue square: <applet  code="Canvas.class" width=10 height=10></applet> This is a red circle: <applet  code="Canvas.class" width=10 height=10></applet> </body> 

This code relies on the onload event handler not being called until the applet is fully loaded and ready. With older browsers and versions of the Java plug-in prior to Java 5, the onload handler is often called before applets are initialized, which causes code like this to fail. If you do your drawing in response to user events instead of the onload event, this is typically not a problem.

Example 22-15 is the JavaScript code for drawing a pie chart using the Java canvas applet. This example omits the makeCanvas( ) function defined in other examples. Because of the onload problem described earlier, this example also draws the chart in response to a button click instead of doing so automatically when the document loads.

Example 22-15. Drawing a pie chart with JavaScript and Java

 <head> <script> // Draw a pie chart using the Java Canvas applet function pieChart(canvas, data, cx, cy, r, colors, labels, lx, ly) {     // Locate canvas by name if needed     if (typeof canvas == "string") canvas = document.getElementById(canvas);     // All lines are 2 units thick. All text is 16pt bold sans-serif.     canvas.setLineWidth(2);     canvas.setFont("SansSerif", 16);     // Add up the data     var total = 0;     for(var i = 0; i < data.length; i++) total += data[i];     // Compute wedge angles in degrees     var angles = []     for(var i = 0; i < data.length; i++) angles[i] = data[i]/total*360;     startangle = 90;  // Start at 12 o'clock instead of 3 o'clock     // Loop through the wedges     for(var i = 0; i < data.length; i++) {         // This object describes one wedge of the pie         var arc = canvas.createWedge(cx-r, cy-r, r*2, r*2,                                      startangle, -angles[i]);         canvas.setColor(colors[i]);             // Set this color         canvas.fill(arc);                       // Fill the wedge         canvas.setColor(0x000000);              // Switch to black         canvas.draw(arc);                       // Outline the wedge         startangle -= angles[i]; // for next time         // Now draw the box in the legend         canvas.setColor(colors[i]);             // Back to wedge color         canvas.fillRect(lx, ly+30*i, 20, 20);   // Fill the box         canvas.setColor(0x000000);              // Back to black again         canvas.drawRect(lx, ly+30*i, 20, 20);   // Outline the box         // And draw the label for each wedge         // Remember that we set the font earlier         canvas.drawString(labels[i], lx+30, ly+30*i+18);     }     // Tell the applet to display itself     canvas.repaint( );  // Don't forget to call this } // This function is invoked when the Draw! button is clicked function draw( ) {     pieChart("canvas", [12, 23, 34, 45], 200, 200, 150,              [0xff0000, 0x0000ff, 0xffff00, 0x00ff00], // Colors are integers              ["North", "South", "East", "West"],              400, 100); } </script> </head> <body> <applet  code="Canvas.class" width=600 height=400></applet> <button onclick="draw( )">Draw!</button> </body> 

22.7.2. Client-Side Sparkline Images with Java

This section uses the Java2D API to draw graphics, but instead of displaying those graphics directly in an applet, the rendered graphic is written as a PNG byte stream and then that byte stream is encoded as a data: URL. In this way, client-side JavaScript can create its own inline images. Although this can be done using an applet, the approach used here scripts Java directly using LiveConnect (see Chapter 12), which is possible in Firefox and related browsers.

The application for this image-generation technique is the display of sparklines. A sparkline is a small data-display graphic that's included within the flow of text. Here is an example:Server load

The term sparkline was coined by author Edward Tufte who describes them as "small, high-resolution graphics embedded in a context of words, numbers, images. Sparklines are data-intense, design-simple, word-sized graphics." (Learn more about sparklines in Tufte's book Beautiful Evidence [Graphics Press].)

Example 22-16 shows the code used to produce the server-load sparkline shown earlier. The JavaScript function makeSparkline( ) uses LiveConnect extensively to script the Java2D API without relying on an applet.

Example 22-16. Creating a sparkline image with Javascript and Java

 <head> <script> /**  * data is an array of numbers to be plotted as a time-series  * dx is the number of x pixels between data points  * config is an object that holds values that are likely to  * be the same for multiple invocations:  *   height: the height, in pixels of the generated image  *   ymin, ymax: the range, or Y axis bounds in user-space  *   backgroundColor: the background color as a numeric HTML color.  *   lineWidth: the width of the line to draw  *   lineColor: the color of the line to draw as an HTML # color spec  *   dotColor: if specified, a dot of this color will be placed on  *             the last data point  *   bandColor: if specified, a band of this color will be drawn between  *      the bandMin and bandMax values to represent a "normal" range of  *      data values, and emphasize the values that exceed that range  */ function makeSparkline(data, dx, config) {     var width = data.length * dx + 1; // overall image width     var yscale = config.height/(config.ymax - config.ymin); // For scaling data     // Convert data point number to a pixel value     function x(i) { return i * dx; }     // Convert a Y coordinate from user space to pixel space     function y(y) { return config.height - (y - config.ymin)*yscale; }     // Convert an HTML color spec to a java.awt.Color object     function color(c) {         c = c.substring(1);  // Remove leading #         if (c.length == (3)) {  // convert to 6-char rep, if needed             c = c.charAt(0) + c.charAt(0) + c.charAt(1) + c.charAt(1) +                 c.charAt(2) + c.charAt(2);         }         var red = parseInt(c.substring(0,2), 16);         var green = parseInt(c.substring(2,4), 16);         var blue  = parseInt(c.substring(4,6), 16);         return new java.awt.Color(red/255, green/255, blue/255);     }     // Create an offscreen image for the sparkline     var image = new java.awt.image.BufferedImage(width, config.height,                                     java.awt.image.BufferedImage.TYPE_INT_RGB);     // Get a Graphics object that lets us draw into that image     var g = image.createGraphics( );     // Antialias everything. Tradeoff: makes the line smoother but fuzzier     g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,                        java.awt.RenderingHints.VALUE_ANTIALIAS_ON);     // Fill the image with the background color     g.setPaint(color(config.backgroundColor));     g.fillRect(0, 0, width, config.height);     // If a bandColor was specified, draw the band     if (config.bandColor) {         g.setPaint(color(config.bandColor));         g.fillRect(0, y(config.bandMax),                    width, y(config.bandMin)-y(config.bandMax));     }     // Now build the line     var line = new java.awt.geom.GeneralPath( );     line.moveTo(x(0), y(data[0]));     for(var i = 1; i < data.length; i++) line.lineTo(x(i), y(data[i]));     // Set the line color and width, then draw the line     g.setPaint(color(config.lineColor));                     // Set line color     g.setStroke(new java.awt.BasicStroke(config.lineWidth)); // Set width     g.draw(line);                                            // Draw!     // If the dotColor was set, draw the dot     if (config.dotColor) {         g.setPaint(color(config.dotColor));         var dot=new java.awt.geom.Ellipse2D$Double(x(data.length-1)-.75,                                                    y(data[data.length-1])-.75,                                                    1.5, 1.5)         g.draw(dot);     }     // Write the image out as a byte array in PNG format     var stream = new java.io.ByteArrayOutputStream( );     Packages.javax.imageio.ImageIO.write(image, "png", stream);     var imageData = stream.toByteArray( );     // Convert the data to a URL-encoded string     var rawString = new java.lang.String(imageData, "iso8859-1");     var encodedString = java.net.URLEncoder.encode(rawString, "iso8859-1");     encodedString = encodedString.replaceAll("\\+", "%20");     // And return it all as a data: URL     return "data:image/png," + encodedString; } // Here is an example that uses the makeSparkline( ) function window.onload = function( ) {     // Create the img tag for the sparkline     var img = document.createElement("img");     img.align = "center";     img.hspace = 1;     // Set its src attribute to the data: URL of the sparkline     img.src = makeSparkline([3, 4, 5, 6, 7, 8, 8, 9, 10, 10, 12,                              16, 11, 10, 11, 10, 10, 10, 11, 12,                              16, 11, 10, 11, 10, 10, 10, 11, 12,                              14, 16, 18, 18, 19, 18, 17, 17, 16,                              14, 16, 18, 18, 19, 18, 17, 17, 16],                             2, { height: 20, ymin: 0, ymax: 20,                                  backgroundColor: "#fff",                                  lineWidth: 1, lineColor: "#000",                                  dotColor: "#f00", bandColor: "#ddd",                                  bandMin: 6, bandMax: 14                             });     // Find the placeholder element for the sparkline     var placeholder = document.getElementById("placeholder");     // And replace it with the image.     placeholder.parentNode.replaceChild(img, placeholder); } </script> </head> <body> Server load: <span ></span><span style="color:#f00">16</SPAN> >/body> 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net