Programming dojox.chart (3) - Prototype

Web September 17th, 2007

The prototype of dojox.chart is supposed to be done two weeks ago, I am far behind the schedule due to the underestimation of the design complexity. It is really a torture to take too many pieces into account into the early design. Anyway, I am going to release a very rough prototype for the community review.

First, the screenshot, dojox.chart.Column:
dojox.chart.Column
and dojox.chart.Bar:
dojox.chart.Bar

Using dojox.chart.Chart, it is like a breeze to implement Column and Bar. I am lazy, I would like to reuse as much as possible. Some highlights:

  • No need to calculate the range of the data, the chart would take care and figure it out the resolution to maximize the resolution
  • Very flexible architecture to make it easy to implement other kinds of chart. Well, maybe the constraint is too loose, I may refactor it later for error-proof.

And we also face some challenges, for example, current layout is hard-coded, a better approach is either to use a theme which covers the color scheme as well, or some better ways to describe the position and dimension of the plotArea, legend etc. using JSON, XML or whatever.

You can check out the snapshot of the implementation, this implementation depends on dojox.gfx.

Programming dojox.chart (2) - Divide and Conquer

Web September 12th, 2007

Divide and Conquer is our best friend, we would decompose a chart into different pieces and evaluate the possibilities to reuse components.

A chart can be described as:

  • dimension: width and length
  • type: how the data is visualized
  • title: states the purpose of the chart

and it can be decomposed as the following components:

  • Axes: XAxis, YAxis and ZAxis if necessary
  • Series: the raw data
  • Legend
  • Grid: used to improve readability in the background
  • Background/Wall/Floor: the background of chart, Wall and Floor are specialized in 3D chart
  • Marker: optional to mark the value in the data series

Here is a typical chart example:
Chart decomposition

Generally, we can categorize charts into different types : Lines, Area, Column, Bar, Pie, XY-Chart, Stack; and 3D variants if applicable. Type determines how the data is visualized.

Axis

Axis is another essential factor, which determines the resolution of the data. A “smart” axis needs to calibrate the length of unit and upper-bound, low-bound to maximize the resolution. On the other hand, users may override the default range. The attributes for Axis objects include: range: {floor, ceil}, lineStyle(color, dotted, slash …), labelStyle(font, position, …), arrowStyle.
Linear axis is commonly used in business world, while some scientists prefer logarithmic axis. A derived class LogarithmicAxis aims to support this feature.

Series

Series holds the raw data, which could be an array(in Column and Bar), single value(used in Pie), or array of (x, y) tuples(in XY-Chart). Series also needs a Label to identify itself and Style to differentiate itself with others.

Most user may not care about the Style, or even the Label, the chart would attach a default Style and Label if they are missing.

Grid

Grid is seldom used in most cases, but if the axis is logarithmic, the grid would improve readability dramatically.

Label

Label is the characterized by the font style and position.

Expected pseudo-code

Here is the code snippet of dojox.chart in action:

var chart = new dojox.chart.bar3d(“test”, 500, 500, “Chart Test”)
                .addSeries( new dojox.chart.Series(
                                        [10, 20, 15, 15, 22, 7]))
                .addSeries( new dojox.chart.Series(
                                        [5, 42, 35, 12, 18, 21], “T1″))
                .addSeries( new dojox.chart.Series(
                                        [50, 32, 25, 7, 15, 12], “S2″,
                                        {type: “plastic”, finish: dojox.gfx3d.lighting.finish.dull, color: “blue”}))
                .calibrate()
                .render();

The above code snippet demonstrates a typical chart operation. Once the series are added, calibrate would calculate the dimension of plotting area, resolution, floor and ceiling of values, add missing label and styles of series if necessary; then render the chart. These operations are supposed to be portable, aka the type of chart is interchangeable.

Programming dojox.chart (1) - Design Philosophy

Web September 8th, 2007

It is a big challenge to design a flexible and easy-to-use library.Developers need to balance between the complexity of API and the simplicity of ease of use. A good approach is to give the users options, but hide them with a default implementation that would fit for daily use. Advanced users may explore the API and fine tune the parameters.

From the perspective of developers, chart objects share lots of common attributes, most likely they all have axes, legend; some may have wall, grid. It would make sense to reuse the components. Furthermore, we should reuse some algorithms, for example, how to normalize the range of data.

In this series, we are going to discuss the chart design/development. This prototype is not merged into dojo trunk, so is one of its dependencies, dojox.gfx3d. You need to fetch the snapshot of dojox.gfx3d in my previous posts for testing.

NOTE I really enjoy series writing recently, it does not intimidate my readers to go though some complicated technical details, and helps me to organize the topic using divide-n-conquer approach.

Learning Django by Example(3): Just works

Web September 7th, 2007

In the last post, we succeeded to post the Javascript objects via JSON to the server, the data is to be serialized to the database in this section.

Before we move on, let’s remove some bumps in our way, the database scheme is modified in this way:

class Book(models.Model):
isbn = models.CharField(maxlength=10, primary_key=True)
pub_date = models.DateField(’data published’)

The reasoning lies in that AWS returns ISBN-10 as ASIN, we may take advantage of this. Another fix is to replace DateTimeField by DateField for pub_date. Django does not support seamless database scheme refactory so far. So we just dropped the database and re-generate it as described in the tutorial. The username/password for new database is set as admin/gelman as common practice.

It is quite annoying that a 500 error returned without any reason, Django server ate the exception due to the nature of Web server. We could wrap the suspicious buggy code with try/except block like this:

try :
    // database access
except :
    print sys.exc_info()[0]

Well, InterfaceError or IntegrityError pinned the bug in database insertion, but where? Interactive shells are our best friends, you can either use pdb or python manage.py shell.

Oh, my fault, I forgot the return value of get_or_create is a tuple, instead of the object; and for many to many relation, we could not simply set a list as its value, we need to use add method after the object is constructed:

for item in simplejson.loads(request.POST[‘items’]):
        publisher, created = Publisher.objects.get_or_create(name=item[‘publisher’])
        authors = [Author.objects.get_or_create(name=au) for au in item[‘authors’]]
        authors = [author for (author, created) in authors];
        y,m,d = [int(x) for x in item[‘pub_date’].split(‘-’)]
        pub_date = date(y, m, d)
        book, created = Book.objects.get_or_create(isbn=item[‘isbn’], name=item[‘title’],
            pages=300, pub_date=pub_date, publisher=publisher)
        book.authors.add(*authors)
        #print sys.exc_info()[0]
    return HttpResponseRedirect(‘/admin/library/book/’)

At the exit, we just redirect the browser to a new URL.

It seems the database is updated, but the page is not redirected. Since the POST operation is launched in an AJAX request, we need to send JSON object as the response. In server side, simplejson.dump is used:

xhr = {’succeed’: [], ‘failed’: []};
       for item in simplejson.loads(request.POST[‘items’]):
               # … ….
               try :
                       book, created = Book.objects.get_or_create(isbn=item[‘isbn’], name=item[‘title’],
                               pages=300, pub_date=pub_date, publisher=publisher)
                       book.authors.add(*authors)
               except :
                       xhr[‘failed’].append(item[‘title’])
               xhr[’succeed’].append(item[‘title’])

       return HttpResponse(simplejson.dumps(xhr), mimetype=‘text/javascript’);

In client side, we evaluate the JSON string then populate the DOM nodes:

dojo.xhrPost({
                url: dojo.byId(“book_form”).action,
                content: { items: dojo.toJson(formdata) },
            }).addCallback(function(response) {
                var result = dojo.fromJson(response);
                var messages = dojo.query(“ul[@class=messagelist]“)[0]
                dojo.forEach(result.failed, function(item) {
                    var msg = document.createElement(“li”);
                    msg.innerHTML = “<li><em>” + item + “</em> <strong>failed</strong> to add.</li>”
                    messages.appendChild(msg);
                });
                dojo.forEach(result.succeed, function(item) {
                    var msg = document.createElement(“li”);
                    msg.innerHTML = “<em>” + item + “</em> successfully added.”
                    messages.appendChild(msg);
                });
            });

The final result looks like this:

Add books with feedback



Check r19 for details.

Learning Django by Example(2): Show me your data

Web September 5th, 2007

In last post, a quick-and-dirty prototype is developed to make Gelman running. In this post, I would tail the XSLT for our needs.

If you have not tried XSLT, consider the XSLT (for dummies and official reference). It is a powerful tool, if you only take interest data extraction, You could compare the efforts to parse XML, for example, PyAWS. The xsl is not in the repository since we need a URI to access.

Let’s keep it simpler and more stupid. Since AWS support both ISBN query and keyword search, we could combine the two input box as one and send different requests to AWS based on the knowledge of ISBN validation. Check r12 for the implementation, here is the screenshot:

Add book by search



In r14, we decided to take a try of dojo.query, an alternative of jQuery; it is really cool to reference the object using CSS syntax.

dojo.forEach(dojo.query(“#book_form input[@class=incoming]“), function(item) {
                               if (item.value != “”) {
                                       formdata.push(cache[i]);
                               }
                               i++;
                       });

In the above code snippet, cache holds the JSON objects parsed from AWS request, if an eBook file is attached to incoming(upload does not work now, just use it as the mark), the corresponding meta data is collected in formdata. The last question is how to POST the it to the server?

JSON is supported in both client and server sides, i.e dojo.json and django.utils.simplejson. In client side, we could serialize the JavaScript to JSON string:

dojo.xhrPost({
                url: dojo.byId(“book_form”).action,
                content: { items: dojo.toJson(formdata) },
            }).addCallback(function(response) {

And in server side, just loads the JSON string to Python objects. Check r16 for implementation.

items = simplejson.loads(request.POST[‘items’])

Next section, we would discuss how to manipulate the database using Django’s database API.