Visualizing the Enterprise:

How to Graphically Show Complex Data in Your Web Application

George Belotsky

Open Light Software Inc.

questions@openlight.com

Left-click for next slide. Move the mouse cursor to the bottom right of the page; additional controls will appear. This slide is based on the S5 presentation system.

What We'll Be Covering

High-Level Choices

Server-Based vs Client-Based

Server Client
Many diverse tools available, lots of architecture choices. Fewer choices (less developed area, browser limitations).
Can develop a single codebase to serve all clients. Browser compatibility complicates development.
Easier to secure. Harder to secure; client side code is also easy to copy.
Dynamic image data generation can strain resources. Client-side programs reduce server load.
Loading large images can rapidly consume bandwidth. Loading large client programs can rapidly consume bandwidth (but a well-designed network protocol can conserve bandwidth).
Low interactivity, simple solution. Can be highly interactive, but more complex.
Your application's requirements and your design determines the best mix of client and server tools.

Server-Based Tools

Here are the server-side tools that we will cover.

Client-Based Tools

Here are the client-based tools that we will cover.

JFreeChart

http://www.jfree.org/jfreechart/
  • JFreeChart generates many standard graphs and charts.
  • Attractive effects, including translucency and 3D.
  • JFreeChart is written in Java (although software written in another language can readily use a smaller Java program, especially under Unix-like OSes).
  • There are alternatives to JFreeChart, such as GDcharts
    • Take a look especially at Gnuplot, which is a very powerful graphing tool.

Use a charting package like JFreeChart to automate the presentation of common data that is found in any organization.

Example 1: Survey Results using JFreeChart

Here is a simulated survey, illustrated using JFreeChart.

Survey Example

Source Code for Example 1

1. Initialize. Remember to run the program with the -Djava.awt.headless=true option if X is not installed on your server.

  import java.io.*;
  import java.awt.image.BufferedImage;

  import org.jfree.chart.*;
  import org.jfree.data.general.DefaultPieDataset;

      ...

Source Code for Example 1 -- Cont.

2. Insert the data.

    ...

  DefaultPieDataset data = new DefaultPieDataset();
  for (int ii = 0; ii < values.length; ++ii) {
      data.setValue(labels[ii], values[ii]); 
  }	

Source Code for Example 1 -- Cont.

3. Finish by generating the chart, and outputting the data in PNG format. You can also write the data to a stream, or save it as a file.
  JFreeChart chart = ChartFactory.createPieChart3D
      (new String(name), data, true, true, true);
	
  //Make the chart translucent.	
  chart.getPlot().setForegroundAlpha(this._opacity);
	
  BufferedImage buffer = chart.createBufferedImage(this._width, 
                                                   this._height);

  return ChartUtilities.encodeAsPNG(buffer);
  //ChartUtilities.writeBufferedImageAsPNG(ostream, buffer);
  //ChartUtilities.saveChartAsPNG(new File("survey.png"), chart, 
  //                              this._width, this._height);
    ...

GD

http://www.boutell.com/gd/manual2.0.33.html
  • This is a fast library for image generation.
  • Provides low-level 2D tools (lines, arcs, polygons, etc.).
  • Bindings for many languages (including Python, Perl, PHP and Ruby) are available.
  • There are alternatives to GD, such as ImageMagick and Python PIL.

Not all graphics fit into typical categories (such as pie or bar charts). Low-level libraries like GD are very useful for creating non-standard visual representations.

Example 2: Item Finder using GD

Suppose you want to show the location of various items (products in a warehouse, trucks on the road, etc.) on a map. This is easy to implement with GD. Here is an example, written in Python.

Item Location Example

Source Code for Example 2

1. Initialize.

  import gd
  import cStringIO

  image = gd.image('map.png')

  tag_color = image.colorAllocate((255, 0, 0))
  txt_color = image.colorAllocate((0, 0, 0))

Source Code for Example 2 -- Cont.

2. Mark item's location with a circular pattern.

  if (itemname):
      for ii in range(30, 5, -5):
          image.arc((item_x, item_y),
                    (ii, ii), 0, 360, tag_color)

      image.fillToBorder((item_x, item_y),
                         tag_color, tag_color)
        
      image.string(gd.gdFontGiant, (0, 0),
                   'Location of '+itemname, txt_color)

Source Code for Example 2 -- Cont.

3. Generate the generated image in memory, and return it.

 capture = cStringIO.StringIO()
    try:
        image.writeGif(capture)
        return ("image/gif", capture.getvalue())
    finally:
        capture.close()

Graphviz

http://www.graphviz.org/

With Graphviz, you can regularly regenerate "Visio-style" diagrams (including very large ones) to reflect current data.

Example 3: Orgchart Using Graphviz

Here is an organizational chart generator, using Graphviz. This is a Python program, which utilizes the Python bindings supplied by Graphviz 2.8.

Orgchart Example

Source Code for Example 3

1. Initialize.
    import os
    import time
    import sys

    #Replace with your own location, or append to PYTHONPATH
    sys.path.append('/usr/local/lib/graphviz/python')
    import gv

    g = gv.graph('Orgchart')

    #Set default node properties
    proto = gv.protonode(g)
    gv.setv(proto, 'shape', 'box')
    gv.setv(proto, 'fillcolor', 'blue')
    gv.setv(proto, 'style', 'filled')
    gv.setv(proto, 'fontcolor', 'white')

Source Code for Example 3 -- Cont.

2. Specify all superior subordinate relationships. Set employee titles, and mark all the important people.

   for boss, underling in bossof:
       rel = gv.edge(g, boss, underling)

   for someone in important:
       gv.setv(gv.findnode(g, someone), 'fillcolor', 'red')

   for lord in title:
       gv.setv(gv.findnode(g, lord), 'label', 
               lord+'\n('+title[lord]+')')

Source Code for Example 3 -- Cont.

3. Specify the layout, then render the graph. Currently, the Graphviz Python bindings will only render to a named file. As a workaround, you can create temporary filenames (make sure to do so securely, and to clean up the file after you are done). There are also alternative Python bindings, which are listed on the Graphviz website.

   gv.layout(g, 'dot')
   gv.render(g, 'gif', 'orgchart.gif')

Integrating the Server-Based Tools

The last point (scripting) typically applies to command-line tools, which read the standard input and write to standard output. With scripting, performance suffers greatly, but sometimes this is the simplest approach, and can save programming time.

More Scripting Options

Turn Dynamic Content Into Static Content

Dynamic web content has evolved greatly since the early days. Instead of inefficient CGIs, a powerful high-level language can now run directly in the web server's context (e.g. Apache with Python/mod_python, Perl/mod_perl or Java/Tomcat). Sophisticated frameworks such as Zope and JBoss exist to support complex applications.

Nevertheless, dynamic content remains a difficult problem. Scalability is always an issue. You also need to configure your web server appropriately. This often means coordinating between departments and limiting the variety of tools. Too many tools results in a complex server environment, which can harm stability and performance.

On the other hand, serving static content is a far easier. You should always consider solutions which convert dynamic content into static content. Fortunately, visualization applications can often use this approach.

The "Relative Static" Solution

It is important to realize that you do not need to update your diagrams until the underlying data changes. Between such changes, the diagram -- which is just an ordinary image file -- can be used by the web server many times. From the web server's point of view, the application is static. Optionally, you can still use dynamic techniques on the web server to control access to the static images.

On to Client-Based Tools

wz_jsgraphics

http://www.walterzorn.com/jsgraphics/jsgraphics_e.htm

With wz_jsgraphics you can develop cross-browser, interactive graphical applications now.

Example 4: ItemFinder with wz_jsgraphics and Ajax

This is a variation on example 2. The new code uses an image from the server, but draws the item marker on the client, using wz_jsgraphics. Enter items "cubevan11" or "flatbed28" in the demo's "Item" textbox.

Ajax-based Item Finder

Example 4 illustrates a hybrid design -- some of the rendering (the map) is done on the server, and some (the marker) on the client. A real-world hybrid would likely update the server-side image periodically.

Example 4: Supporting Tools

Source Code for Example 4

1a. The ItemFinder class, constructor.

var ItemFinder = Class.create();

ItemFinder.prototype = {
    initialize: function(url, markf) {
        this._url = url;
        this._markf = markf;
    },

    ...

Source Code for Example 4 -- Cont.

1b. The ItemFinder class, Ajax request.
    ...
    getitem: function(name) {
        markf = this._markf;

        new Ajax.Request(this._url+name, {
	    method: 'get',
	    onSuccess: function(r) {
		coords = r.responseText.split(',',2);
		markf(Number(coords[0]), Number(coords[1]));
	    },

	    onFailure: function(e) {
              alert('Error (make sure item exists)');
	    }});
    }   
}

ItemFinder retrieves the item's location from the server without redrawing the page. After receiving the item's coordinates, ItemFinder passes them on to a callback function for actual drawing. Note that the network protocol is very simple -- just the two coordinates of the item, separated by a comma.

See also the pure JavaScript animation at http://www.openlight.com/visualizing.html

Source Code for Example 4 -- Cont.

2. Load the necessary libraries Note the <div> at the bottom. The drawing will take place there.

  ...
<script src="/s/prototype.js" type="text/javascript"></script>
<script src="/s/wz_jsgraphics.js" type="text/javascript"></script>
<script src="/s/itemfinder.js" type="text/javascript"></script>
</head>

<body>

<div id="map">
<img src="map.png" alt="Item Location" 
width="283" height="320">
</div>
  ...
You could also use a JavaScript compression tool (such as the one from Dojo toolkit) to reduce the size of the javascript libraries. When using compressed libraries, do not forget to make sure that your scripts still work correctly.

Source Code for Example 4 -- Cont.

3. The controls which send user-generated events to the application.

  ...
<div>
<label>Item: </label><input id="item" type="text" size="15"/>
<button onclick="finder.getitem($F('item'))">Get Location</button>
</div>
  ...

Source Code for Example 4 -- Cont.

4. Initialize wz_jsgraphics and the ItemFinder class.
  ...
<script type="text/javascript">
   var canvas = new jsGraphics("map");
   canvas.setColor("#00ff00");
   canvas.setStroke(2);  
   
   var finder = new ItemFinder('items/', marksquares);
   
   function marksquares(x, y)  {
     canvas.clear();
     for (var ii = 32; ii > 0; ii -= 8) {
       canvas.drawRect(x-ii/2, y-ii/2, ii, ii);
     }
     canvas.paint();
   }
</script>
 ...
Note the function at the bottom of this slide; it renders the "nested squares" marker.

Client-Based Designs

The most important thing to remember is that you must design a network protocol as part of a client-side approach.

Generic Clients vs Specialized Clients

Generic Specialized
Harder to design, extensible Easier to design, but less extensible
Difficult for competitors to copy Easier to copy -- most functionality is on the client
Require lots of interaction with the server Can operate with little server input

Limiting the Client

A browser-based client works like an independent program. For proper operation, it requires controls that limit certain actions.

MTASC

http://www.mtasc.org/

Flash is not just for dumb movies -- it is an advanced programming environment. Flash-based clients can be very sophisticated, and browser support is excellent.

Compiling a Flash Client

Replace all paths to where MTASC resides on your system. The header option creates a Flash "movie" from scratch. Specify the file where your main resides; the compiler will find the other source code files automatically.

/opt/bin/mtasc -cp /opt/lib/mtasc/ -version 6 -strict \
 -swf canvas.swf -header 800:600:20 -main FdUI.as

Example 5: Monitoring Application Using Flash

We finish with a more complex example, which embodies a lot of the concepts covered in this talk. Here is a complete Open Source monitoring application (written by the presenter). You can download all the source code and view the demo at http://www.openlight.com/fdui

Flightdeck-UI Online Screenshot

Source Code for Example 5a -- Rendering

An ActionScript program renders to a movie clip. Movie clips are really more like canvasses -- completely under your program's control.
 class Canvas {      
    ...
 public function draw_poly(moviename:String, points:Array,
                           color:Number):Void {
        var movie:MovieClip = this.parent[moviename];
        with(movie) {
            beginFill(color,100);
            lineStyle(0,color,100);
            moveTo(points[0][0],points[0][1]);
            for (var ii:Number = 1; ii < points.length; ++ii) {
                lineTo(points[ii][0],points[ii][1]);
            }
            lineTo(points[0][0],points[0][1]);
            endFill();
        }
    }
    ...
You can probably tell from this example that Flightdeck-UI Online uses a generic client.

Source Code for Example 5a -- Cont.

You can create movie clips as your ActionScript program runs. Movie clips layer on top of one another. If you set one clip to be partially transparent, the one underneath will show through.

 class Canvas {     
       ...
    public function clear(moviename:String):Void {
        this.parent.createEmptyMovieClip(moviename,
                                         Number(this.names[moviename]));
    }
       ...
  }

Basic ActionScript Networking

Source Code for Example 5b -- Network Protocol

ActionScript supports interfaces, similar to Java. Here is the Protocol interface from Flightdeck-UI Online. Making the protocol replaceable is very important for extensibility, and greatly eases security patching.

   interface Protocol {
     public function init(hostdata:LoadVars, id:String):Void;
     public function input(hostdata:LoadVars):Object;
     public function output(hostdata:LoadVars):Void;
     public function remote_log(msg:String, level:String):Void;
   }

Source Code for Example 5b -- Cont.

Even with client-based visualization, the server usually supplies the underlying data. Hence, the "Relative Static" approach also works here. Flightdeck-UI Online uses a separate daemon process to periodically regenerate the data, and save it to a file.

$ cat demo.txt
reset\needles\2|poly\needles\697.0\501.5\691.0\498.5\ ...

Here, you can see the server's instructions to draw polygons into the "needles" movie clip of the Flash client. This updates the gauges.

Source Code for Example 5b -- Cont.

Protocol version check.

class Proto_X1 implements Protocol{
  ...
 public function input(hostdata:LoadVars):Object {
	if (!Number(hostdata.proto_x1)) {
	    this.remote_log("Wrong protocol version; expected x1")
	    return null;
  ...

Source Code for Example 5c -- Limiting the Client

In addition to the dedicated data generator daemon, Flightdeck-UI Online uses Apache with a mod_python script to control the client. The code below shuts off outdated clients.

def handler(req):
       ...
   ver = fields.getfirst('ver','').strip()
       ...
   if not versions.has_key(ver): #!!! Length-limit this?
      req.write('command='+Client.shutoff_panel('Version Expired ...
       ...

Source Code for Example 5c -- Cont.

Of course, the client must allow the server to control it. During initialization, the Flightdeck-UI Online client registers a "stop" command ("stop_cmd", below) for this purpose.

public function FdUI() {
       ...
   var protocol:Protocol = new Proto_X1({test:[this,"test_cmd"],
				       rclr:[this,"regclr_cmd"],
				       rsnd:[this,"sndreg_cmd"],
				       snd:[this,"sndplay_cmd"],
				       arc:[this,"arc_cmd"],
				       poly:[this,"poly_cmd"],
				       label:[this,"label_cmd"],
		                               ...
				       stop:[this,"stop_cmd"]},
				       this.version,
				       this.max_command_len);
       ...

Conclusion