[Jifty-commit] jifty-plugin-chart branch, plack, created. 8f654e620172dc2b67df803ea3bf644c5229c68a

Jifty commits jifty-commit at lists.jifty.org
Mon Feb 8 00:47:58 EST 2010


The branch, plack has been created
        at  8f654e620172dc2b67df803ea3bf644c5229c68a (commit)

- Log -----------------------------------------------------------------
commit 1d935941c772a2224262f5b239730d1b579a2873
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Jul 27 19:54:54 2007 +0000

    Adding a plugin for rendering charts of data.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3724 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
new file mode 100644
index 0000000..6594a9a
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -0,0 +1,30 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart;
+use base qw/ Jifty::Plugin Class::Accessor::Fast /;
+
+use Jifty::Plugin::Chart::Web;
+
+__PACKAGE__->mk_accessors(qw/ renderer /);
+
+sub init {
+    my $self = shift;
+    my %args = (
+        renderer => __PACKAGE__.'::Renderer::Chart',
+        @_,
+    );
+
+    eval "use $args{renderer}";
+    warn $@ if $@;
+    $self->renderer( $args{renderer} );
+
+    push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
+}
+
+sub render {
+    my $self = shift;
+    $self->renderer->render(@_);
+}
+
+1;
diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
new file mode 100644
index 0000000..d27dc4e
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Dispatcher;
+use Jifty::Dispatcher -base;
+
+use Jifty::YAML;
+
+on 'chart/*' => run {
+    my $session_id = 'chart_' . $1;
+
+    my $args = Jifty::YAML::Load( Jifty->web->session->get( $session_id ) );
+    Jifty->web->session->remove( $session_id );
+
+    last_rule unless defined $args;
+
+    set 'args' => $args;
+    show 'chart';
+};
+
+1;
diff --git a/lib/Jifty/Plugin/Chart/Renderer.pm b/lib/Jifty/Plugin/Chart/Renderer.pm
new file mode 100644
index 0000000..f7eec67
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer.pm
@@ -0,0 +1,6 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer;
+
+1;
diff --git a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
new file mode 100644
index 0000000..c2f6fb1
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
@@ -0,0 +1,32 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::Chart;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+use Jifty::YAML;
+
+sub render {
+    my $self = shift;
+    my %args = (
+        type   => 'points',
+        width  => 400,
+        height => 300,
+        data   => [],
+        @_,
+    );
+
+    for my $key (keys %args) {
+        $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
+    }
+
+    my $chart_id   = Jifty->web->serial;
+    my $session_id = 'chart_' . $chart_id;
+    Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
+
+    Jifty->web->out(qq{<img src="/chart/$chart_id" width="$args{width}" height="$args{height}"/>});
+
+    return;
+}
+
+1;
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
new file mode 100644
index 0000000..2a9a8a6
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -0,0 +1,25 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::View;
+use Jifty::View::Declare -base;
+
+use IO::String;
+
+template 'chart' => sub {
+    my $args = get 'args';
+
+    my $class = 'Chart::' . $args->{type};
+
+    eval "use $class";
+    die $@ if $@;
+
+    Jifty->handler->apache->content_type('image/png');
+
+    my $fh = IO::String->new;
+    my $chart = $class->new( $args->{width}, $args->{height} );
+    $chart->png($fh, $args->{data});
+    outs_raw( ${ $fh->string_ref } )
+};
+
+1;
diff --git a/lib/Jifty/Plugin/Chart/Web.pm b/lib/Jifty/Plugin/Chart/Web.pm
new file mode 100644
index 0000000..013b5d6
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Web.pm
@@ -0,0 +1,12 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Web;
+
+sub chart {
+    my $self = shift;
+    my ($plugin) = Jifty->find_plugin('Jifty::Plugin::Chart');
+    return $plugin->render(@_);
+}
+
+1;

commit 02fbffadede149e4d6d1c3570141a93c7a252086
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sun Jul 29 20:15:59 2007 +0000

    Added documentation to the experimental Chart plugin.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3731 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 6594a9a..cf94feb 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -6,6 +6,56 @@ use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 
 use Jifty::Plugin::Chart::Web;
 
+=head1 NAME
+
+Jifty::Plugin::Chart - A charting API for Jifty
+
+=head1 SYNOPSIS
+
+In your F<config.yml>:
+
+  Plugins:
+    - Chart: {}
+
+In your Mason templates:
+
+  <% Jifty->web->chart(
+      type   => 'Bar',
+      width  => 400,
+      height => 300,
+      data   => [
+          [ '2004', '2005', '2006', '2007' ], # labels
+          [ 14,     15,     17,     22     ], # first data set
+          [ 22,     25,     20,     21     ], # second data set
+      ],
+  ) %>
+
+=head1 DESCRIPTION
+
+B<CAUTION:> This plugin is experimental. The API I<will> change.
+
+This plugin provides a charting API that can be used by Jifty applications to build data visualizations without regard to the underlying rendering mechanism.
+
+As of this writing, the API is a barely veiled interface over L<Chart>. However, I intend to expand the interface to apply to something like Maani's XML/SWF Charts or Imprise Javascript charts or even something like OpenLaszlo (or something Open Source and Perl if I can find or build such a thing in time).
+
+=head1 INTERFACE
+
+By adding this method to the plugin configuration for your Jifty application, you will cause L<Jifty::Web> to inherit a new method, C<chart>, which is the cornerstone of this API.
+
+This method is described in L<Jifty::Plugin::Chart::Web> and an example is shown in the L</SYNOPSIS> above.
+
+=head1 CONFIGURATION
+
+The plugin takes a single configuration option called C<renderer>. This may be set to a chart renderer class, which is just an implementation of L<Jifty::Plugin::Chart::Renderer>. The default, L<Jifty::Plugin::Chart::Renderer::Chart>, uses L<Chart> to render charts as PNG files which are then included in your pages for you.
+
+Here is an example configuration for F<config.yml>:
+
+  Plugins:
+    - Chart:
+        renderer: Jifty::Plugin::Chart::Renderer::Chart
+
+=cut
+
 __PACKAGE__->mk_accessors(qw/ renderer /);
 
 sub init {
@@ -27,4 +77,20 @@ sub render {
     $self->renderer->render(@_);
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Plugin>, L<Jifty::Web>, L<Jifty::Plugin::Chart::Renderer>, L<Jifty::Plugin::Chart::Renderer::Chart>, L<Jifty::Plugin::Chart::View>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp E<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and redistributed under the same terms as Perl itself.
+
+=cut
+
 1;
diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
index d27dc4e..85982b2 100644
--- a/lib/Jifty/Plugin/Chart/Dispatcher.pm
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -6,6 +6,18 @@ use Jifty::Dispatcher -base;
 
 use Jifty::YAML;
 
+=head1 NAME
+
+Jifty::Plugin::Chart::Dispatcher - Dispatcher for the chart API plugin
+
+=head1 RULES
+
+=head2 chart/*
+
+Grabs the chart configuration stored in the key indicated in C<$1> and unpacks it using L<YAML>. It then passes it to the L<Jifty::Plugin::Chart::View/chart> template.
+
+=cut
+
 on 'chart/*' => run {
     my $session_id = 'chart_' . $1;
 
@@ -18,4 +30,20 @@ on 'chart/*' => run {
     show 'chart';
 };
 
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart::View>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp E<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and redistributed under the same terms as Perl itself.
+
+=cut
+
 1;
diff --git a/lib/Jifty/Plugin/Chart/Renderer.pm b/lib/Jifty/Plugin/Chart/Renderer.pm
index f7eec67..1386fa6 100644
--- a/lib/Jifty/Plugin/Chart/Renderer.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer.pm
@@ -3,4 +3,66 @@ use warnings;
 
 package Jifty::Plugin::Chart::Renderer;
 
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer - Base class for chart rendering classes
+
+=head1 SYNOPSIS
+
+In your F<config.yml>:
+
+  Plugins:
+    - Chart:
+        renderer: MyApp::Renderer;
+
+In F<lib/MyApp/Renderer.pm>:
+
+  package MyApp::Renderer;
+  use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+  sub render {
+      my $self = shift;
+      my %args = (
+          type   => 'points',
+          width  => 400,
+          height => 300,
+          data   => [],
+          @_,
+      );
+
+      # Output your chart
+      Jifty->web->out( #{ Output your chart here... } );
+
+      # You could also return it as a string...
+      return;
+  }
+
+=head1 METHODS
+
+Your renderer implementation must subclass this package and implement the following methods:
+
+=head2 render
+
+  Jifty->web->out($renderer->render(%args));
+
+See L<Jifty::Plugin::Chart::Web> for the arguments. It must (at least) accept the arguments given to the L<Jifty::Plugin::Chart::Web/chart> method.
+
+The C<render> method may either return it's output or print it out using L<Jifty::Web::out>.
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart::Web>, L<Jifty::Plugin::Chart::Renderer::Chart>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
 1;
diff --git a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
index c2f6fb1..50271a8 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
@@ -6,27 +6,60 @@ use base qw/ Jifty::Plugin::Chart::Renderer /;
 
 use Jifty::YAML;
 
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::Chart - A chart renderer using PNG charts
+
+=head1 DESCRIPTION
+
+This is the default chart renderer used by the L<Jifty::Plugin::Chart> plugin. It works by rendering an IMG tag in the HTML output, which then points to a URL which, when dispatched, retrieves the stored configuration and renders the chart using the L<Chart> package.
+
+=head1 METHODS
+
+=head2 render
+
+Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
+
+=cut
+
 sub render {
     my $self = shift;
-    my %args = (
-        type   => 'points',
-        width  => 400,
-        height => 300,
-        data   => [],
-        @_,
-    );
+    my %args = @_;
 
+    # Turn any subs into values returned
     for my $key (keys %args) {
         $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
     }
 
+    # Make sure the type is ready to be used as a class name
+    $args{type} = ucfirst lc $args{type};
+
+    # Save the data for retrieval from the session later
     my $chart_id   = Jifty->web->serial;
     my $session_id = 'chart_' . $chart_id;
     Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
 
+    # Output the <img> tag and include the chart's configuration key
     Jifty->web->out(qq{<img src="/chart/$chart_id" width="$args{width}" height="$args{height}"/>});
 
+    # Make sure we don't return anything that will get output
     return;
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
 1;
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 2a9a8a6..91edcfb 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -6,20 +6,55 @@ use Jifty::View::Declare -base;
 
 use IO::String;
 
+=head1 NAME
+
+Jifty::Plugin::Chart::View - Views for the renderers built into the Chart plugin
+
+=head1 TEMPLATES
+
+=head2 chart
+
+This shows a chart using L<Chart>. It expects to find the arguments in the C<args> parameter, which is setup for it in L<Jifty::Plugin::Chart::Dispatcher>.
+
+This will output a PNG file unless there is an error building the chart.
+
+=cut
+
 template 'chart' => sub {
+    # Load the arguments
     my $args = get 'args';
 
+    # Use the "type" to determine which class to use
     my $class = 'Chart::' . $args->{type};
 
+    # Load that class or die if it does not exist
     eval "use $class";
     die $@ if $@;
 
+    # Set the output type to the PNG file type
     Jifty->handler->apache->content_type('image/png');
 
+    # Render the chart and output the PNG file generated
     my $fh = IO::String->new;
     my $chart = $class->new( $args->{width}, $args->{height} );
     $chart->png($fh, $args->{data});
     outs_raw( ${ $fh->string_ref } )
 };
 
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart::Dispatcher>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
 1;
diff --git a/lib/Jifty/Plugin/Chart/Web.pm b/lib/Jifty/Plugin/Chart/Web.pm
index 013b5d6..f7140b9 100644
--- a/lib/Jifty/Plugin/Chart/Web.pm
+++ b/lib/Jifty/Plugin/Chart/Web.pm
@@ -3,10 +3,122 @@ use warnings;
 
 package Jifty::Plugin::Chart::Web;
 
+=head1 NAME
+
+Jifty::Plugin::Chart::Web - Base class to add to Jifty::Web's ISA
+
+=head1 DESCRIPTION
+
+When the L<Jifty::Plugin::Chart> is loaded, this class is added as a base class for L<Jifty::Web> to add the L</chart> method to that class.
+
+=head1 METHODS
+
+=head2 chart
+
+  Jifty->web->out(Jifty->web->chart(%args));
+
+The arguments passed in C<%args> may include:
+
+=over
+
+=item type
+
+This will be one of the following scalar values indicating the kind of chart:
+
+=over
+
+=item Points
+
+This is the default value. A scatter plot with each dataset represented using differnet dot styles.
+
+=item Lines
+
+A line plot with each dataset presented as separate line.
+
+=item Bars
+
+A bar chart with each dataset set side-by-side.
+
+=item StackedBars
+
+A bar chart with each dataset stacked on top of each other.
+
+=item Pie
+
+A pie chart with a single dataset representing the values for different pieces of the pie.
+
+=item HorizontalBars
+
+A bar chart turned sideways.
+
+=back
+
+=item width
+
+The width, in pixels, the chart should take on the page. Defaults to 400.
+
+=item height
+
+The height, in pixels, the chart should take on the page. Defaults to 300.
+
+=item data
+
+An array of arrays containing the data. The first array in the parent array is a list of labels. Each following array is the set of data points matching each label in the first array.
+
+Defaults to no data (i.e., it must be given if anything useful is to happen).
+
+=back
+
+Here's an example:
+
+  <% Jifty->web->chart(
+      type   => 'Pie',
+      width  => 400,
+      height => 300,
+      data   => sub {
+          [
+              [ 2004, 2005, 2006, 2007 ],
+              [ 26, 37, 12, 42 ]
+          ];
+      },
+  ) %>
+
+Be sure to output anything returned by the method (unless it returns undef).
+
+=cut
+
 sub chart {
     my $self = shift;
     my ($plugin) = Jifty->find_plugin('Jifty::Plugin::Chart');
+
+    # TODO It might be a good idea to make this config.yml-able
+    # Setup the defaults
+    my %args = (
+        type   => 'points',
+        width  => 400,
+        height => 300,
+        data   => [],
+        @_,
+    );
+
+    # Call the rendering plugin's render method
     return $plugin->render(@_);
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
 1;

commit 17e81b0aacb0bcacc424bff9a12a463f8cde78a4
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Mon Jul 30 02:16:37 2007 +0000

    Regarding Jifty::Plugin::Chart: Added better comments. Fixed some error handling. Switched to using scalar_png(). Switched to using ->require rather than an eval to load Chart classes. Eliminated the need for IO::String. Moved some processing out of View and into Dispatcher.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3737 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
index 85982b2..05b45d0 100644
--- a/lib/Jifty/Plugin/Chart/Dispatcher.pm
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -19,13 +19,26 @@ Grabs the chart configuration stored in the key indicated in C<$1> and unpacks i
 =cut
 
 on 'chart/*' => run {
+    # Create a session ID to lookup the chart configuration
     my $session_id = 'chart_' . $1;
 
+    # Unpack the data and then clear it from the session
     my $args = Jifty::YAML::Load( Jifty->web->session->get( $session_id ) );
     Jifty->web->session->remove( $session_id );
 
+    # No data? Act like a 404
     last_rule unless defined $args;
 
+    # Use the "type" to determine which class to use
+    my $class = 'Chart::' . $args->{type};
+
+    # Load that class or die if it does not exist
+    $class->require;
+
+    # Remember the class name for the view
+    $args->{class} = $class;
+
+    # Send them on to chart the chart
     set 'args' => $args;
     show 'chart';
 };
@@ -36,7 +49,7 @@ L<Jifty::Plugin::Chart::View>
 
 =head1 AUTHOR
 
-Andrew Sterling Hanenkamp E<< <andrew.hanenkamp at boomer.com> >>
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
 
 =head1 COPYRIGHT AND LICENSE
 
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 91edcfb..f967bb4 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -4,8 +4,6 @@ use warnings;
 package Jifty::Plugin::Chart::View;
 use Jifty::View::Declare -base;
 
-use IO::String;
-
 =head1 NAME
 
 Jifty::Plugin::Chart::View - Views for the renderers built into the Chart plugin
@@ -24,21 +22,22 @@ template 'chart' => sub {
     # Load the arguments
     my $args = get 'args';
 
-    # Use the "type" to determine which class to use
-    my $class = 'Chart::' . $args->{type};
-
-    # Load that class or die if it does not exist
-    eval "use $class";
-    die $@ if $@;
 
     # Set the output type to the PNG file type
     Jifty->handler->apache->content_type('image/png');
 
     # Render the chart and output the PNG file generated
-    my $fh = IO::String->new;
-    my $chart = $class->new( $args->{width}, $args->{height} );
-    $chart->png($fh, $args->{data});
-    outs_raw( ${ $fh->string_ref } )
+    eval {
+        my $chart = $args->{class}->new( $args->{width}, $args->{height} );
+        # XXX scalar_png() is undocumented!!! Might bad to rely upon.
+        outs_raw($chart->scalar_png($args->{data}));
+    };
+
+    # Should have thrown an error if bad stuff happened, handle that
+    if ($@) {
+        Jifty->log->error("Failed to render chart: $@");
+        die $@;
+    }
 };
 
 =head1 SEE ALSO

commit 5bd11e70ef40282501e56cdd3cb61faa9487cd52
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Wed Aug 1 02:33:01 2007 +0000

    Fixed the way arguments are passed to the render() method in Jifty::Plugin::Chart::Web.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3749 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Web.pm b/lib/Jifty/Plugin/Chart/Web.pm
index f7140b9..ed3fa62 100644
--- a/lib/Jifty/Plugin/Chart/Web.pm
+++ b/lib/Jifty/Plugin/Chart/Web.pm
@@ -101,8 +101,13 @@ sub chart {
         @_,
     );
 
+    # Turn any subs into values returned
+    for my $key (keys %args) {
+        $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
+    }
+
     # Call the rendering plugin's render method
-    return $plugin->render(@_);
+    return $plugin->render(%args);
 }
 
 =head1 SEE ALSO

commit dc55eb3b07a4b10d15036b78e9b695dab4d5838d
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Wed Aug 1 02:33:09 2007 +0000

    Moved the chart/* dispatch to chart/chart/* to make room for alternate charting mechanisms.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3750 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
index 05b45d0..9d54517 100644
--- a/lib/Jifty/Plugin/Chart/Dispatcher.pm
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -12,13 +12,13 @@ Jifty::Plugin::Chart::Dispatcher - Dispatcher for the chart API plugin
 
 =head1 RULES
 
-=head2 chart/*
+=head2 chart/chart/*
 
 Grabs the chart configuration stored in the key indicated in C<$1> and unpacks it using L<YAML>. It then passes it to the L<Jifty::Plugin::Chart::View/chart> template.
 
 =cut
 
-on 'chart/*' => run {
+on 'chart/chart/*' => run {
     # Create a session ID to lookup the chart configuration
     my $session_id = 'chart_' . $1;
 
@@ -40,7 +40,38 @@ on 'chart/*' => run {
 
     # Send them on to chart the chart
     set 'args' => $args;
-    show 'chart';
+    show 'chart/chart'
+};
+
+=head2 chart/gd_graph/*
+
+Grabs the chart configuration stored in the key indicated in C<$1> and unpacks it using L<YAML>. It then passes it to the L<Jifty::Plugin::Chart::View/chart> template.
+
+=cut
+
+on 'chart/gd_graph/*' => run {
+    # Create a session ID to lookup the chart configuration
+    my $session_id = 'chart_' . $1;
+
+    # Unpack the data and then clear it from the session
+    my $args = Jifty::YAML::Load( Jifty->web->session->get( $session_id ) );
+    Jifty->web->session->remove( $session_id );
+
+    # No data? Act like a 404
+    last_rule unless defined $args;
+
+    # Use the "type" to determine which class to use
+    my $class = 'GD::Graph::' . $args->{type};
+
+    # Load that class or die if it does not exist
+    #$class->require;
+
+    # Remember the class name for the view
+    $args->{class} = $class;
+
+    # Send them on to chart the chart
+    set 'args' => $args;
+    show 'chart/gd_graph'
 };
 
 =head1 SEE ALSO
diff --git a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
index 50271a8..4f79ed5 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
@@ -26,11 +26,6 @@ sub render {
     my $self = shift;
     my %args = @_;
 
-    # Turn any subs into values returned
-    for my $key (keys %args) {
-        $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
-    }
-
     # Make sure the type is ready to be used as a class name
     $args{type} = ucfirst lc $args{type};
 
@@ -40,7 +35,7 @@ sub render {
     Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
 
     # Output the <img> tag and include the chart's configuration key
-    Jifty->web->out(qq{<img src="/chart/$chart_id" width="$args{width}" height="$args{height}"/>});
+    Jifty->web->out(qq{<img src="/chart/chart/$chart_id" width="$args{width}" height="$args{height}"/>});
 
     # Make sure we don't return anything that will get output
     return;
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index f967bb4..d1e0959 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -10,7 +10,7 @@ Jifty::Plugin::Chart::View - Views for the renderers built into the Chart plugin
 
 =head1 TEMPLATES
 
-=head2 chart
+=head2 chart/chart
 
 This shows a chart using L<Chart>. It expects to find the arguments in the C<args> parameter, which is setup for it in L<Jifty::Plugin::Chart::Dispatcher>.
 
@@ -18,11 +18,10 @@ This will output a PNG file unless there is an error building the chart.
 
 =cut
 
-template 'chart' => sub {
+template 'chart/chart' => sub {
     # Load the arguments
     my $args = get 'args';
 
-
     # Set the output type to the PNG file type
     Jifty->handler->apache->content_type('image/png');
 
@@ -40,6 +39,36 @@ template 'chart' => sub {
     }
 };
 
+=head2 chart/gd_graph
+
+This shows a chart using L<GD::Graph>. It expects to find the arguments in the C<args> parameter, which is setup for it in L<Jifty::Plugin::Chart::Dispatcher>.
+
+This will output a PNG file unless there is an error building the chart.
+
+=cut
+
+template 'chart/gd_graph' => sub {
+    # Load the arguments
+    my $args = get 'args';
+
+    # Set the output type to the PNG file type
+    Jifty->handler->apache->content_type('image/png');
+
+    # Render the chart and output the PNG file generated
+    eval {
+        my $graph = $args->{class}->new( $args->{width}, $args->{height} );
+        my $gd    = $graph->plot($args->{data})
+            or die $graph->error;
+        outs_raw($gd->png);
+    };
+
+    # Should have thrown an error if bad stuff happened, handle that
+    if ($@) {
+        Jifty->log->error("Failed to render chart: $@");
+        die $@;
+    }
+};
+
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart::Dispatcher>

commit dc022e7b1aa1e2200c95e31e393f4ce339ab9d0e
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Wed Aug 1 02:33:17 2007 +0000

    Added a renderer for GD::Graph
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3751 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
new file mode 100644
index 0000000..aba3387
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
@@ -0,0 +1,56 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::GD::Graph;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::GD::Graph - A chart renderer using GD::Graph
+
+=head1 SYNOPSIS
+
+In F<config.yml>:
+
+  Plugins:
+    - Chart:
+        renderer: Jifty::Plugin::Chart::Renderer::GD::Graph
+
+=head1 DESCRIPTION
+
+This is a chart renderer that uses L<GD::Graph> to build charts.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = @_;
+
+    # Convert the type to lowercase
+    $args{type} = lc $args{type};
+
+    # Save the data for retrieval from the session later
+    my $chart_id   = Jifty->web->serial;
+    my $session_id = 'chart_' . $chart_id;
+    Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
+
+    # Output the <img> tag and include the chart's configuration key
+    Jifty->web->out(qq{<img src="/chart/gd_graph/$chart_id" width="$args{width}" height="$args{height}"/>});
+
+    # Make sure we don't return anything that will get output
+    return;
+}
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

commit 64af10c579a4bb4d8c37ce014787b7d44eaf16b2
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Wed Aug 1 02:47:45 2007 +0000

    Updated POD and removed an unnecessary extra subroutine call.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3753 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index cf94feb..1438c63 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -6,6 +6,8 @@ use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 
 use Jifty::Plugin::Chart::Web;
 
+__PACKAGE__->mk_accessors(qw/ renderer /);
+
 =head1 NAME
 
 Jifty::Plugin::Chart - A charting API for Jifty
@@ -54,9 +56,13 @@ Here is an example configuration for F<config.yml>:
     - Chart:
         renderer: Jifty::Plugin::Chart::Renderer::Chart
 
-=cut
+=head1 METHODS
 
-__PACKAGE__->mk_accessors(qw/ renderer /);
+=head2 init
+
+Adds the L<Jifty::Plugin::Chart::Web/chart> method to L<Jifty::Web>.
+
+=cut
 
 sub init {
     my $self = shift;
@@ -72,11 +78,6 @@ sub init {
     push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
 }
 
-sub render {
-    my $self = shift;
-    $self->renderer->render(@_);
-}
-
 =head1 SEE ALSO
 
 L<Jifty::Plugin>, L<Jifty::Web>, L<Jifty::Plugin::Chart::Renderer>, L<Jifty::Plugin::Chart::Renderer::Chart>, L<Jifty::Plugin::Chart::View>
diff --git a/lib/Jifty/Plugin/Chart/Web.pm b/lib/Jifty/Plugin/Chart/Web.pm
index ed3fa62..ac10e55 100644
--- a/lib/Jifty/Plugin/Chart/Web.pm
+++ b/lib/Jifty/Plugin/Chart/Web.pm
@@ -107,7 +107,7 @@ sub chart {
     }
 
     # Call the rendering plugin's render method
-    return $plugin->render(%args);
+    return $plugin->renderer->render(%args);
 }
 
 =head1 SEE ALSO

commit 91856c4fbb1b1363e010910d8dc0c2325920e418
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Wed Aug 1 02:47:56 2007 +0000

    Fixed POD coverage issue.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3754 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
index aba3387..34d3599 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
@@ -20,6 +20,12 @@ In F<config.yml>:
 
 This is a chart renderer that uses L<GD::Graph> to build charts.
 
+=head1 METHODS
+
+=head2 render
+
+Renders an IMG tag referring to the L<GD::Graph> image view.
+
 =cut
 
 sub render {

commit 33c7f8d4160599f752cf10aff13a99c7c8567773
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Wed Aug 1 02:48:02 2007 +0000

    Fixed an eensy POD bug.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3755 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 1438c63..c83ac24 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -84,7 +84,7 @@ L<Jifty::Plugin>, L<Jifty::Web>, L<Jifty::Plugin::Chart::Renderer>, L<Jifty::Plu
 
 =head1 AUTHOR
 
-Andrew Sterling Hanenkamp E<< <andrew.hanenkamp at boomer.com> >>
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
 
 =head1 COPYRIGHT AND LICENSE
 

commit c6201e093bb6d3c855d37a5bcc785bff6481a635
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Aug 1 03:41:50 2007 +0000

    Basic PlotKit renderer for Chart plugin
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3757 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index c83ac24..cdeec5e 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -54,7 +54,7 @@ Here is an example configuration for F<config.yml>:
 
   Plugins:
     - Chart:
-        renderer: Jifty::Plugin::Chart::Renderer::Chart
+        renderer: Chart
 
 =head1 METHODS
 
@@ -67,14 +67,27 @@ Adds the L<Jifty::Plugin::Chart::Web/chart> method to L<Jifty::Web>.
 sub init {
     my $self = shift;
     my %args = (
-        renderer => __PACKAGE__.'::Renderer::Chart',
+        renderer => 'Chart',
         @_,
     );
 
+    if ( $args{renderer} !~ /::/ ) {
+        $args{renderer} = __PACKAGE__.'::Renderer::'.$args{renderer};
+    }
+
     eval "use $args{renderer}";
     warn $@ if $@;
     $self->renderer( $args{renderer} );
 
+    if ( $self->renderer =~ 'PlotKit' ) {
+        # XXX TODO: Why does MochiKit need to be loaded before everything else?
+        Jifty->web->javascript_libs([
+            'MochiKit/MochiKit.js',
+            @{ Jifty->web->javascript_libs },
+            'PlotKit/PlotKit_Packed.js'
+        ]);
+    }
+
     push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
 }
 
diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
new file mode 100644
index 0000000..4723ab8
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -0,0 +1,112 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::PlotKit;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+use Jifty::YAML;
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::PlotKit - A chart renderer using PlotKit
+
+=head1 DESCRIPTION
+
+This is an alternate chart renderer used by the L<Jifty::Plugin::Chart> plugin. It works by rendering a <div> tag in the HTML output and some JavaScript in a <script> tag.
+
+=head1 METHODS
+
+=head2 render
+
+Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = ( options => {}, @_ );
+
+    # Turn any subs into values returned
+    for my $key (keys %args) {
+        $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
+    }
+
+    my %types = (
+        Lines   => 'line',
+        Bars    => 'bar',
+        Pie     => 'pie',
+    );
+
+    # Make sure the type is ready to be used
+    $args{type} = $types{ ucfirst lc $args{type} } || undef;
+
+    if ( not defined $args{type} ) {
+        Jifty->log->warn("Unsupported chart type: $args{type}!");
+        return;
+    }
+
+    $self->_transform_data( \%args );
+
+    # Save the data for retrieval from the session later
+    my $chart_id   = 'chart_' . Jifty->web->serial;
+
+    # Output the <canvas> tag and include the chart's JS
+    Jifty->web->out(<<"    END_OF_HTML");
+<div id="$chart_id" height="$args{height}" width="$args{width}"></div>
+
+<script type="text/javascript">
+var plot = function() {
+    var plotter = PlotKit.EasyPlot(
+        "$args{type}",
+        @{[Jifty::JSON::objToJson( $args{options} )]},
+        \$("$chart_id"),
+        @{[Jifty::JSON::objToJson( $args{data} )]}
+    );
+};
+YAHOO.util.Event.addListener( window, "load", plot );
+</script>
+    END_OF_HTML
+
+    # Make sure we don't return anything that will get output
+    return;
+}
+
+sub _transform_data {
+    my $self = shift;
+    my $args = shift;
+
+    my @data;
+    my $labels = shift @{ $args->{data} };
+
+    for ( my $i = 0; $i < @$labels; $i++ ) {
+        push @{$args->{options}{xTicks}}, { v => $i, label => $labels->[$i] };
+    }
+    
+    for my $dataset ( @{ $args->{data} } ) {
+        my @ds;
+        for ( my $i = 0; $i < @$dataset; $i++ ) {
+            push @ds, [ $i, $dataset->[$i] ];
+        }
+        push @data, \@ds;
+    }
+
+    $args->{data} = \@data;
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>
+
+=head1 AUTHOR
+
+Thomas Sibley
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
diff --git a/share/web/static/js/MochiKit/MochiKit.js b/share/web/static/js/MochiKit/MochiKit.js
new file mode 100644
index 0000000..37c0d72
--- /dev/null
+++ b/share/web/static/js/MochiKit/MochiKit.js
@@ -0,0 +1,4802 @@
+/***
+
+    MochiKit.MochiKit 1.3.1 : PACKED VERSION
+
+    THIS FILE IS AUTOMATICALLY GENERATED.  If creating patches, please
+    diff against the source tree, not this file.
+
+    See <http://mochikit.com/> for documentation, downloads, license, etc.
+
+    (c) 2005 Bob Ippolito.  All rights Reserved.
+
+***/
+
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Base");
+}
+if(typeof (MochiKit)=="undefined"){
+MochiKit={};
+}
+if(typeof (MochiKit.Base)=="undefined"){
+MochiKit.Base={};
+}
+MochiKit.Base.VERSION="1.3.1";
+MochiKit.Base.NAME="MochiKit.Base";
+MochiKit.Base.update=function(_1,_2){
+if(_1===null){
+_1={};
+}
+for(var i=1;i<arguments.length;i++){
+var o=arguments[i];
+if(typeof (o)!="undefined"&&o!==null){
+for(var k in o){
+_1[k]=o[k];
+}
+}
+}
+return _1;
+};
+MochiKit.Base.update(MochiKit.Base,{__repr__:function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+},toString:function(){
+return this.__repr__();
+},counter:function(n){
+if(arguments.length===0){
+n=1;
+}
+return function(){
+return n++;
+};
+},clone:function(_7){
+var me=arguments.callee;
+if(arguments.length==1){
+me.prototype=_7;
+return new me();
+}
+},flattenArguments:function(_9){
+var res=[];
+var m=MochiKit.Base;
+var _12=m.extend(null,arguments);
+while(_12.length){
+var o=_12.shift();
+if(o&&typeof (o)=="object"&&typeof (o.length)=="number"){
+for(var i=o.length-1;i>=0;i--){
+_12.unshift(o[i]);
+}
+}else{
+res.push(o);
+}
+}
+return res;
+},extend:function(_13,obj,_15){
+if(!_15){
+_15=0;
+}
+if(obj){
+var l=obj.length;
+if(typeof (l)!="number"){
+if(typeof (MochiKit.Iter)!="undefined"){
+obj=MochiKit.Iter.list(obj);
+l=obj.length;
+}else{
+throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
+}
+}
+if(!_13){
+_13=[];
+}
+for(var i=_15;i<l;i++){
+_13.push(obj[i]);
+}
+}
+return _13;
+},updatetree:function(_17,obj){
+if(_17===null){
+_17={};
+}
+for(var i=1;i<arguments.length;i++){
+var o=arguments[i];
+if(typeof (o)!="undefined"&&o!==null){
+for(var k in o){
+var v=o[k];
+if(typeof (_17[k])=="object"&&typeof (v)=="object"){
+arguments.callee(_17[k],v);
+}else{
+_17[k]=v;
+}
+}
+}
+}
+return _17;
+},setdefault:function(_19,obj){
+if(_19===null){
+_19={};
+}
+for(var i=1;i<arguments.length;i++){
+var o=arguments[i];
+for(var k in o){
+if(!(k in _19)){
+_19[k]=o[k];
+}
+}
+}
+return _19;
+},keys:function(obj){
+var _20=[];
+for(var _21 in obj){
+_20.push(_21);
+}
+return _20;
+},items:function(obj){
+var _22=[];
+var e;
+for(var _24 in obj){
+var v;
+try{
+v=obj[_24];
+}
+catch(e){
+continue;
+}
+_22.push([_24,v]);
+}
+return _22;
+},_newNamedError:function(_25,_26,_27){
+_27.prototype=new MochiKit.Base.NamedError(_25.NAME+"."+_26);
+_25[_26]=_27;
+},operator:{truth:function(a){
+return !!a;
+},lognot:function(a){
+return !a;
+},identity:function(a){
+return a;
+},not:function(a){
+return ~a;
+},neg:function(a){
+return -a;
+},add:function(a,b){
+return a+b;
+},sub:function(a,b){
+return a-b;
+},div:function(a,b){
+return a/b;
+},mod:function(a,b){
+return a%b;
+},mul:function(a,b){
+return a*b;
+},and:function(a,b){
+return a&b;
+},or:function(a,b){
+return a|b;
+},xor:function(a,b){
+return a^b;
+},lshift:function(a,b){
+return a<<b;
+},rshift:function(a,b){
+return a>>b;
+},zrshift:function(a,b){
+return a>>>b;
+},eq:function(a,b){
+return a==b;
+},ne:function(a,b){
+return a!=b;
+},gt:function(a,b){
+return a>b;
+},ge:function(a,b){
+return a>=b;
+},lt:function(a,b){
+return a<b;
+},le:function(a,b){
+return a<=b;
+},ceq:function(a,b){
+return MochiKit.Base.compare(a,b)===0;
+},cne:function(a,b){
+return MochiKit.Base.compare(a,b)!==0;
+},cgt:function(a,b){
+return MochiKit.Base.compare(a,b)==1;
+},cge:function(a,b){
+return MochiKit.Base.compare(a,b)!=-1;
+},clt:function(a,b){
+return MochiKit.Base.compare(a,b)==-1;
+},cle:function(a,b){
+return MochiKit.Base.compare(a,b)!=1;
+},logand:function(a,b){
+return a&&b;
+},logor:function(a,b){
+return a||b;
+},contains:function(a,b){
+return b in a;
+}},forwardCall:function(_30){
+return function(){
+return this[_30].apply(this,arguments);
+};
+},itemgetter:function(_31){
+return function(arg){
+return arg[_31];
+};
+},typeMatcher:function(){
+var _33={};
+for(var i=0;i<arguments.length;i++){
+var typ=arguments[i];
+_33[typ]=typ;
+}
+return function(){
+for(var i=0;i<arguments.length;i++){
+if(!(typeof (arguments[i]) in _33)){
+return false;
+}
+}
+return true;
+};
+},isNull:function(){
+for(var i=0;i<arguments.length;i++){
+if(arguments[i]!==null){
+return false;
+}
+}
+return true;
+},isUndefinedOrNull:function(){
+for(var i=0;i<arguments.length;i++){
+var o=arguments[i];
+if(!(typeof (o)=="undefined"||o===null)){
+return false;
+}
+}
+return true;
+},isEmpty:function(obj){
+return !MochiKit.Base.isNotEmpty.apply(this,arguments);
+},isNotEmpty:function(obj){
+for(var i=0;i<arguments.length;i++){
+var o=arguments[i];
+if(!(o&&o.length)){
+return false;
+}
+}
+return true;
+},isArrayLike:function(){
+for(var i=0;i<arguments.length;i++){
+var o=arguments[i];
+var typ=typeof (o);
+if((typ!="object"&&!(typ=="function"&&typeof (o.item)=="function"))||o===null||typeof (o.length)!="number"){
+return false;
+}
+}
+return true;
+},isDateLike:function(){
+for(var i=0;i<arguments.length;i++){
+var o=arguments[i];
+if(typeof (o)!="object"||o===null||typeof (o.getTime)!="function"){
+return false;
+}
+}
+return true;
+},xmap:function(fn){
+if(fn===null){
+return MochiKit.Base.extend(null,arguments,1);
+}
+var _36=[];
+for(var i=1;i<arguments.length;i++){
+_36.push(fn(arguments[i]));
+}
+return _36;
+},map:function(fn,lst){
+var m=MochiKit.Base;
+var itr=MochiKit.Iter;
+var _39=m.isArrayLike;
+if(arguments.length<=2){
+if(!_39(lst)){
+if(itr){
+lst=itr.list(lst);
+if(fn===null){
+return lst;
+}
+}else{
+throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
+}
+}
+if(fn===null){
+return m.extend(null,lst);
+}
+var _40=[];
+for(var i=0;i<lst.length;i++){
+_40.push(fn(lst[i]));
+}
+return _40;
+}else{
+if(fn===null){
+fn=Array;
+}
+var _41=null;
+for(i=1;i<arguments.length;i++){
+if(!_39(arguments[i])){
+if(itr){
+return itr.list(itr.imap.apply(null,arguments));
+}else{
+throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
+}
+}
+var l=arguments[i].length;
+if(_41===null||_41>l){
+_41=l;
+}
+}
+_40=[];
+for(i=0;i<_41;i++){
+var _42=[];
+for(var j=1;j<arguments.length;j++){
+_42.push(arguments[j][i]);
+}
+_40.push(fn.apply(this,_42));
+}
+return _40;
+}
+},xfilter:function(fn){
+var _44=[];
+if(fn===null){
+fn=MochiKit.Base.operator.truth;
+}
+for(var i=1;i<arguments.length;i++){
+var o=arguments[i];
+if(fn(o)){
+_44.push(o);
+}
+}
+return _44;
+},filter:function(fn,lst,_45){
+var _46=[];
+var m=MochiKit.Base;
+if(!m.isArrayLike(lst)){
+if(MochiKit.Iter){
+lst=MochiKit.Iter.list(lst);
+}else{
+throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
+}
+}
+if(fn===null){
+fn=m.operator.truth;
+}
+if(typeof (Array.prototype.filter)=="function"){
+return Array.prototype.filter.call(lst,fn,_45);
+}else{
+if(typeof (_45)=="undefined"||_45===null){
+for(var i=0;i<lst.length;i++){
+var o=lst[i];
+if(fn(o)){
+_46.push(o);
+}
+}
+}else{
+for(i=0;i<lst.length;i++){
+o=lst[i];
+if(fn.call(_45,o)){
+_46.push(o);
+}
+}
+}
+}
+return _46;
+},_wrapDumbFunction:function(_47){
+return function(){
+switch(arguments.length){
+case 0:
+return _47();
+case 1:
+return _47(arguments[0]);
+case 2:
+return _47(arguments[0],arguments[1]);
+case 3:
+return _47(arguments[0],arguments[1],arguments[2]);
+}
+var _48=[];
+for(var i=0;i<arguments.length;i++){
+_48.push("arguments["+i+"]");
+}
+return eval("(func("+_48.join(",")+"))");
+};
+},method:function(_49,_50){
+var m=MochiKit.Base;
+return m.bind.apply(this,m.extend([_50,_49],arguments,2));
+},bind:function(_51,_52){
+if(typeof (_51)=="string"){
+_51=_52[_51];
+}
+var _53=_51.im_func;
+var _54=_51.im_preargs;
+var _55=_51.im_self;
+var m=MochiKit.Base;
+if(typeof (_51)=="function"&&typeof (_51.apply)=="undefined"){
+_51=m._wrapDumbFunction(_51);
+}
+if(typeof (_53)!="function"){
+_53=_51;
+}
+if(typeof (_52)!="undefined"){
+_55=_52;
+}
+if(typeof (_54)=="undefined"){
+_54=[];
+}else{
+_54=_54.slice();
+}
+m.extend(_54,arguments,2);
+var _56=function(){
+var _57=arguments;
+var me=arguments.callee;
+if(me.im_preargs.length>0){
+_57=m.concat(me.im_preargs,_57);
+}
+var _52=me.im_self;
+if(!_52){
+_52=this;
+}
+return me.im_func.apply(_52,_57);
+};
+_56.im_self=_55;
+_56.im_func=_53;
+_56.im_preargs=_54;
+return _56;
+},bindMethods:function(_58){
+var _59=MochiKit.Base.bind;
+for(var k in _58){
+var _60=_58[k];
+if(typeof (_60)=="function"){
+_58[k]=_59(_60,_58);
+}
+}
+},registerComparator:function(_61,_62,_63,_64){
+MochiKit.Base.comparatorRegistry.register(_61,_62,_63,_64);
+},_primitives:{"boolean":true,"string":true,"number":true},compare:function(a,b){
+if(a==b){
+return 0;
+}
+var _65=(typeof (a)=="undefined"||a===null);
+var _66=(typeof (b)=="undefined"||b===null);
+if(_65&&_66){
+return 0;
+}else{
+if(_65){
+return -1;
+}else{
+if(_66){
+return 1;
+}
+}
+}
+var m=MochiKit.Base;
+var _67=m._primitives;
+if(!(typeof (a) in _67&&typeof (b) in _67)){
+try{
+return m.comparatorRegistry.match(a,b);
+}
+catch(e){
+if(e!=m.NotFound){
+throw e;
+}
+}
+}
+if(a<b){
+return -1;
+}else{
+if(a>b){
+return 1;
+}
+}
+var _68=m.repr;
+throw new TypeError(_68(a)+" and "+_68(b)+" can not be compared");
+},compareDateLike:function(a,b){
+return MochiKit.Base.compare(a.getTime(),b.getTime());
+},compareArrayLike:function(a,b){
+var _69=MochiKit.Base.compare;
+var _70=a.length;
+var _71=0;
+if(_70>b.length){
+_71=1;
+_70=b.length;
+}else{
+if(_70<b.length){
+_71=-1;
+}
+}
+for(var i=0;i<_70;i++){
+var cmp=_69(a[i],b[i]);
+if(cmp){
+return cmp;
+}
+}
+return _71;
+},registerRepr:function(_73,_74,_75,_76){
+MochiKit.Base.reprRegistry.register(_73,_74,_75,_76);
+},repr:function(o){
+if(typeof (o)=="undefined"){
+return "undefined";
+}else{
+if(o===null){
+return "null";
+}
+}
+try{
+if(typeof (o.__repr__)=="function"){
+return o.__repr__();
+}else{
+if(typeof (o.repr)=="function"&&o.repr!=arguments.callee){
+return o.repr();
+}
+}
+return MochiKit.Base.reprRegistry.match(o);
+}
+catch(e){
+if(typeof (o.NAME)=="string"&&(o.toString==Function.prototype.toString||o.toString==Object.prototype.toString)){
+return o.NAME;
+}
+}
+try{
+var _77=(o+"");
+}
+catch(e){
+return "["+typeof (o)+"]";
+}
+if(typeof (o)=="function"){
+o=_77.replace(/^\s+/,"");
+var idx=o.indexOf("{");
+if(idx!=-1){
+o=o.substr(0,idx)+"{...}";
+}
+}
+return _77;
+},reprArrayLike:function(o){
+var m=MochiKit.Base;
+return "["+m.map(m.repr,o).join(", ")+"]";
+},reprString:function(o){
+return ("\""+o.replace(/(["\\])/g,"\\$1")+"\"").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r");
+},reprNumber:function(o){
+return o+"";
+},registerJSON:function(_79,_80,_81,_82){
+MochiKit.Base.jsonRegistry.register(_79,_80,_81,_82);
+},evalJSON:function(){
+return eval("("+arguments[0]+")");
+},serializeJSON:function(o){
+var _83=typeof (o);
+if(_83=="undefined"){
+return "undefined";
+}else{
+if(_83=="number"||_83=="boolean"){
+return o+"";
+}else{
+if(o===null){
+return "null";
+}
+}
+}
+var m=MochiKit.Base;
+var _84=m.reprString;
+if(_83=="string"){
+return _84(o);
+}
+var me=arguments.callee;
+var _85;
+if(typeof (o.__json__)=="function"){
+_85=o.__json__();
+if(o!==_85){
+return me(_85);
+}
+}
+if(typeof (o.json)=="function"){
+_85=o.json();
+if(o!==_85){
+return me(_85);
+}
+}
+if(_83!="function"&&typeof (o.length)=="number"){
+var res=[];
+for(var i=0;i<o.length;i++){
+var val=me(o[i]);
+if(typeof (val)!="string"){
+val="undefined";
+}
+res.push(val);
+}
+return "["+res.join(", ")+"]";
+}
+try{
+_85=m.jsonRegistry.match(o);
+return me(_85);
+}
+catch(e){
+if(e!=m.NotFound){
+throw e;
+}
+}
+if(_83=="function"){
+return null;
+}
+res=[];
+for(var k in o){
+var _87;
+if(typeof (k)=="number"){
+_87="\""+k+"\"";
+}else{
+if(typeof (k)=="string"){
+_87=_84(k);
+}else{
+continue;
+}
+}
+val=me(o[k]);
+if(typeof (val)!="string"){
+continue;
+}
+res.push(_87+":"+val);
+}
+return "{"+res.join(", ")+"}";
+},objEqual:function(a,b){
+return (MochiKit.Base.compare(a,b)===0);
+},arrayEqual:function(_88,arr){
+if(_88.length!=arr.length){
+return false;
+}
+return (MochiKit.Base.compare(_88,arr)===0);
+},concat:function(){
+var _90=[];
+var _91=MochiKit.Base.extend;
+for(var i=0;i<arguments.length;i++){
+_91(_90,arguments[i]);
+}
+return _90;
+},keyComparator:function(key){
+var m=MochiKit.Base;
+var _93=m.compare;
+if(arguments.length==1){
+return function(a,b){
+return _93(a[key],b[key]);
+};
+}
+var _94=m.extend(null,arguments);
+return function(a,b){
+var _95=0;
+for(var i=0;(_95===0)&&(i<_94.length);i++){
+var key=_94[i];
+_95=_93(a[key],b[key]);
+}
+return _95;
+};
+},reverseKeyComparator:function(key){
+var _96=MochiKit.Base.keyComparator.apply(this,arguments);
+return function(a,b){
+return _96(b,a);
+};
+},partial:function(_97){
+var m=MochiKit.Base;
+return m.bind.apply(this,m.extend([_97,undefined],arguments,1));
+},listMinMax:function(_98,lst){
+if(lst.length===0){
+return null;
+}
+var cur=lst[0];
+var _100=MochiKit.Base.compare;
+for(var i=1;i<lst.length;i++){
+var o=lst[i];
+if(_100(o,cur)==_98){
+cur=o;
+}
+}
+return cur;
+},objMax:function(){
+return MochiKit.Base.listMinMax(1,arguments);
+},objMin:function(){
+return MochiKit.Base.listMinMax(-1,arguments);
+},findIdentical:function(lst,_101,_102,end){
+if(typeof (end)=="undefined"||end===null){
+end=lst.length;
+}
+for(var i=(_102||0);i<end;i++){
+if(lst[i]===_101){
+return i;
+}
+}
+return -1;
+},findValue:function(lst,_104,_105,end){
+if(typeof (end)=="undefined"||end===null){
+end=lst.length;
+}
+var cmp=MochiKit.Base.compare;
+for(var i=(_105||0);i<end;i++){
+if(cmp(lst[i],_104)===0){
+return i;
+}
+}
+return -1;
+},nodeWalk:function(node,_107){
+var _108=[node];
+var _109=MochiKit.Base.extend;
+while(_108.length){
+var res=_107(_108.shift());
+if(res){
+_109(_108,res);
+}
+}
+},nameFunctions:function(_110){
+var base=_110.NAME;
+if(typeof (base)=="undefined"){
+base="";
+}else{
+base=base+".";
+}
+for(var name in _110){
+var o=_110[name];
+if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
+try{
+o.NAME=base+name;
+}
+catch(e){
+}
+}
+}
+},queryString:function(_113,_114){
+if(typeof (MochiKit.DOM)!="undefined"&&arguments.length==1&&(typeof (_113)=="string"||(typeof (_113.nodeType)!="undefined"&&_113.nodeType>0))){
+var kv=MochiKit.DOM.formContents(_113);
+_113=kv[0];
+_114=kv[1];
+}else{
+if(arguments.length==1){
+var o=_113;
+_113=[];
+_114=[];
+for(var k in o){
+var v=o[k];
+if(typeof (v)!="function"){
+_113.push(k);
+_114.push(v);
+}
+}
+}
+}
+var rval=[];
+var len=Math.min(_113.length,_114.length);
+var _118=MochiKit.Base.urlEncode;
+for(var i=0;i<len;i++){
+v=_114[i];
+if(typeof (v)!="undefined"&&v!==null){
+rval.push(_118(_113[i])+"="+_118(v));
+}
+}
+return rval.join("&");
+},parseQueryString:function(_119,_120){
+var _121=_119.replace(/\+/g,"%20").split("&");
+var o={};
+var _122;
+if(typeof (decodeURIComponent)!="undefined"){
+_122=decodeURIComponent;
+}else{
+_122=unescape;
+}
+if(_120){
+for(var i=0;i<_121.length;i++){
+var pair=_121[i].split("=");
+var name=_122(pair[0]);
+var arr=o[name];
+if(!(arr instanceof Array)){
+arr=[];
+o[name]=arr;
+}
+arr.push(_122(pair[1]));
+}
+}else{
+for(i=0;i<_121.length;i++){
+pair=_121[i].split("=");
+o[_122(pair[0])]=_122(pair[1]);
+}
+}
+return o;
+}});
+MochiKit.Base.AdapterRegistry=function(){
+this.pairs=[];
+};
+MochiKit.Base.AdapterRegistry.prototype={register:function(name,_124,wrap,_126){
+if(_126){
+this.pairs.unshift([name,_124,wrap]);
+}else{
+this.pairs.push([name,_124,wrap]);
+}
+},match:function(){
+for(var i=0;i<this.pairs.length;i++){
+var pair=this.pairs[i];
+if(pair[1].apply(this,arguments)){
+return pair[2].apply(this,arguments);
+}
+}
+throw MochiKit.Base.NotFound;
+},unregister:function(name){
+for(var i=0;i<this.pairs.length;i++){
+var pair=this.pairs[i];
+if(pair[0]==name){
+this.pairs.splice(i,1);
+return true;
+}
+}
+return false;
+}};
+MochiKit.Base.EXPORT=["counter","clone","extend","update","updatetree","setdefault","keys","items","NamedError","operator","forwardCall","itemgetter","typeMatcher","isCallable","isUndefined","isUndefinedOrNull","isNull","isEmpty","isNotEmpty","isArrayLike","isDateLike","xmap","map","xfilter","filter","bind","bindMethods","NotFound","AdapterRegistry","registerComparator","compare","registerRepr","repr","objEqual","arrayEqual","concat","keyComparator","reverseKeyComparator","partial","merge","listMinMax","listMax","listMin","objMax","objMin","nodeWalk","zip","urlEncode","queryString","serializeJSON","registerJSON","evalJSON","parseQueryString","findValue","findIdentical","flattenArguments","method"];
+MochiKit.Base.EXPORT_OK=["nameFunctions","comparatorRegistry","reprRegistry","jsonRegistry","compareDateLike","compareArrayLike","reprArrayLike","reprString","reprNumber"];
+MochiKit.Base._exportSymbols=function(_127,_128){
+if(typeof (MochiKit.__export__)=="undefined"){
+MochiKit.__export__=(MochiKit.__compat__||(typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined"));
+}
+if(!MochiKit.__export__){
+return;
+}
+var all=_128.EXPORT_TAGS[":all"];
+for(var i=0;i<all.length;i++){
+_127[all[i]]=_128[all[i]];
+}
+};
+MochiKit.Base.__new__=function(){
+var m=this;
+m.forward=m.forwardCall;
+m.find=m.findValue;
+if(typeof (encodeURIComponent)!="undefined"){
+m.urlEncode=function(_130){
+return encodeURIComponent(_130).replace(/\'/g,"%27");
+};
+}else{
+m.urlEncode=function(_131){
+return escape(_131).replace(/\+/g,"%2B").replace(/\"/g,"%22").rval.replace(/\'/g,"%27");
+};
+}
+m.NamedError=function(name){
+this.message=name;
+this.name=name;
+};
+m.NamedError.prototype=new Error();
+m.update(m.NamedError.prototype,{repr:function(){
+if(this.message&&this.message!=this.name){
+return this.name+"("+m.repr(this.message)+")";
+}else{
+return this.name+"()";
+}
+},toString:m.forwardCall("repr")});
+m.NotFound=new m.NamedError("MochiKit.Base.NotFound");
+m.listMax=m.partial(m.listMinMax,1);
+m.listMin=m.partial(m.listMinMax,-1);
+m.isCallable=m.typeMatcher("function");
+m.isUndefined=m.typeMatcher("undefined");
+m.merge=m.partial(m.update,null);
+m.zip=m.partial(m.map,null);
+m.comparatorRegistry=new m.AdapterRegistry();
+m.registerComparator("dateLike",m.isDateLike,m.compareDateLike);
+m.registerComparator("arrayLike",m.isArrayLike,m.compareArrayLike);
+m.reprRegistry=new m.AdapterRegistry();
+m.registerRepr("arrayLike",m.isArrayLike,m.reprArrayLike);
+m.registerRepr("string",m.typeMatcher("string"),m.reprString);
+m.registerRepr("numbers",m.typeMatcher("number","boolean"),m.reprNumber);
+m.jsonRegistry=new m.AdapterRegistry();
+var all=m.concat(m.EXPORT,m.EXPORT_OK);
+m.EXPORT_TAGS={":common":m.concat(m.EXPORT_OK),":all":all};
+m.nameFunctions(this);
+};
+MochiKit.Base.__new__();
+if(!MochiKit.__compat__){
+compare=MochiKit.Base.compare;
+}
+MochiKit.Base._exportSymbols(this,MochiKit.Base);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Iter");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Iter depends on MochiKit.Base!";
+}
+if(typeof (MochiKit.Iter)=="undefined"){
+MochiKit.Iter={};
+}
+MochiKit.Iter.NAME="MochiKit.Iter";
+MochiKit.Iter.VERSION="1.3.1";
+MochiKit.Base.update(MochiKit.Iter,{__repr__:function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+},toString:function(){
+return this.__repr__();
+},registerIteratorFactory:function(name,_132,_133,_134){
+MochiKit.Iter.iteratorRegistry.register(name,_132,_133,_134);
+},iter:function(_135,_136){
+var self=MochiKit.Iter;
+if(arguments.length==2){
+return self.takewhile(function(a){
+return a!=_136;
+},_135);
+}
+if(typeof (_135.next)=="function"){
+return _135;
+}else{
+if(typeof (_135.iter)=="function"){
+return _135.iter();
+}
+}
+try{
+return self.iteratorRegistry.match(_135);
+}
+catch(e){
+var m=MochiKit.Base;
+if(e==m.NotFound){
+e=new TypeError(typeof (_135)+": "+m.repr(_135)+" is not iterable");
+}
+throw e;
+}
+},count:function(n){
+if(!n){
+n=0;
+}
+var m=MochiKit.Base;
+return {repr:function(){
+return "count("+n+")";
+},toString:m.forwardCall("repr"),next:m.counter(n)};
+},cycle:function(p){
+var self=MochiKit.Iter;
+var m=MochiKit.Base;
+var lst=[];
+var _139=self.iter(p);
+return {repr:function(){
+return "cycle(...)";
+},toString:m.forwardCall("repr"),next:function(){
+try{
+var rval=_139.next();
+lst.push(rval);
+return rval;
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+if(lst.length===0){
+this.next=function(){
+throw self.StopIteration;
+};
+}else{
+var i=-1;
+this.next=function(){
+i=(i+1)%lst.length;
+return lst[i];
+};
+}
+return this.next();
+}
+}};
+},repeat:function(elem,n){
+var m=MochiKit.Base;
+if(typeof (n)=="undefined"){
+return {repr:function(){
+return "repeat("+m.repr(elem)+")";
+},toString:m.forwardCall("repr"),next:function(){
+return elem;
+}};
+}
+return {repr:function(){
+return "repeat("+m.repr(elem)+", "+n+")";
+},toString:m.forwardCall("repr"),next:function(){
+if(n<=0){
+throw MochiKit.Iter.StopIteration;
+}
+n-=1;
+return elem;
+}};
+},next:function(_141){
+return _141.next();
+},izip:function(p,q){
+var m=MochiKit.Base;
+var next=MochiKit.Iter.next;
+var _144=m.map(iter,arguments);
+return {repr:function(){
+return "izip(...)";
+},toString:m.forwardCall("repr"),next:function(){
+return m.map(next,_144);
+}};
+},ifilter:function(pred,seq){
+var m=MochiKit.Base;
+seq=MochiKit.Iter.iter(seq);
+if(pred===null){
+pred=m.operator.truth;
+}
+return {repr:function(){
+return "ifilter(...)";
+},toString:m.forwardCall("repr"),next:function(){
+while(true){
+var rval=seq.next();
+if(pred(rval)){
+return rval;
+}
+}
+return undefined;
+}};
+},ifilterfalse:function(pred,seq){
+var m=MochiKit.Base;
+seq=MochiKit.Iter.iter(seq);
+if(pred===null){
+pred=m.operator.truth;
+}
+return {repr:function(){
+return "ifilterfalse(...)";
+},toString:m.forwardCall("repr"),next:function(){
+while(true){
+var rval=seq.next();
+if(!pred(rval)){
+return rval;
+}
+}
+return undefined;
+}};
+},islice:function(seq){
+var self=MochiKit.Iter;
+var m=MochiKit.Base;
+seq=self.iter(seq);
+var _147=0;
+var stop=0;
+var step=1;
+var i=-1;
+if(arguments.length==2){
+stop=arguments[1];
+}else{
+if(arguments.length==3){
+_147=arguments[1];
+stop=arguments[2];
+}else{
+_147=arguments[1];
+stop=arguments[2];
+step=arguments[3];
+}
+}
+return {repr:function(){
+return "islice("+["...",_147,stop,step].join(", ")+")";
+},toString:m.forwardCall("repr"),next:function(){
+var rval;
+while(i<_147){
+rval=seq.next();
+i++;
+}
+if(_147>=stop){
+throw self.StopIteration;
+}
+_147+=step;
+return rval;
+}};
+},imap:function(fun,p,q){
+var m=MochiKit.Base;
+var self=MochiKit.Iter;
+var _151=m.map(self.iter,m.extend(null,arguments,1));
+var map=m.map;
+var next=self.next;
+return {repr:function(){
+return "imap(...)";
+},toString:m.forwardCall("repr"),next:function(){
+return fun.apply(this,map(next,_151));
+}};
+},applymap:function(fun,seq,self){
+seq=MochiKit.Iter.iter(seq);
+var m=MochiKit.Base;
+return {repr:function(){
+return "applymap(...)";
+},toString:m.forwardCall("repr"),next:function(){
+return fun.apply(self,seq.next());
+}};
+},chain:function(p,q){
+var self=MochiKit.Iter;
+var m=MochiKit.Base;
+if(arguments.length==1){
+return self.iter(arguments[0]);
+}
+var _153=m.map(self.iter,arguments);
+return {repr:function(){
+return "chain(...)";
+},toString:m.forwardCall("repr"),next:function(){
+while(_153.length>1){
+try{
+return _153[0].next();
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+_153.shift();
+}
+}
+if(_153.length==1){
+var arg=_153.shift();
+this.next=m.bind("next",arg);
+return this.next();
+}
+throw self.StopIteration;
+}};
+},takewhile:function(pred,seq){
+var self=MochiKit.Iter;
+seq=self.iter(seq);
+return {repr:function(){
+return "takewhile(...)";
+},toString:MochiKit.Base.forwardCall("repr"),next:function(){
+var rval=seq.next();
+if(!pred(rval)){
+this.next=function(){
+throw self.StopIteration;
+};
+this.next();
+}
+return rval;
+}};
+},dropwhile:function(pred,seq){
+seq=MochiKit.Iter.iter(seq);
+var m=MochiKit.Base;
+var bind=m.bind;
+return {"repr":function(){
+return "dropwhile(...)";
+},"toString":m.forwardCall("repr"),"next":function(){
+while(true){
+var rval=seq.next();
+if(!pred(rval)){
+break;
+}
+}
+this.next=bind("next",seq);
+return rval;
+}};
+},_tee:function(_155,sync,_157){
+sync.pos[_155]=-1;
+var m=MochiKit.Base;
+var _158=m.listMin;
+return {repr:function(){
+return "tee("+_155+", ...)";
+},toString:m.forwardCall("repr"),next:function(){
+var rval;
+var i=sync.pos[_155];
+if(i==sync.max){
+rval=_157.next();
+sync.deque.push(rval);
+sync.max+=1;
+sync.pos[_155]+=1;
+}else{
+rval=sync.deque[i-sync.min];
+sync.pos[_155]+=1;
+if(i==sync.min&&_158(sync.pos)!=sync.min){
+sync.min+=1;
+sync.deque.shift();
+}
+}
+return rval;
+}};
+},tee:function(_159,n){
+var rval=[];
+var sync={"pos":[],"deque":[],"max":-1,"min":-1};
+if(arguments.length==1){
+n=2;
+}
+var self=MochiKit.Iter;
+_159=self.iter(_159);
+var _tee=self._tee;
+for(var i=0;i<n;i++){
+rval.push(_tee(i,sync,_159));
+}
+return rval;
+},list:function(_161){
+var m=MochiKit.Base;
+if(typeof (_161.slice)=="function"){
+return _161.slice();
+}else{
+if(m.isArrayLike(_161)){
+return m.concat(_161);
+}
+}
+var self=MochiKit.Iter;
+_161=self.iter(_161);
+var rval=[];
+try{
+while(true){
+rval.push(_161.next());
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+return rval;
+}
+return undefined;
+},reduce:function(fn,_162,_163){
+var i=0;
+var x=_163;
+var self=MochiKit.Iter;
+_162=self.iter(_162);
+if(arguments.length<3){
+try{
+x=_162.next();
+}
+catch(e){
+if(e==self.StopIteration){
+e=new TypeError("reduce() of empty sequence with no initial value");
+}
+throw e;
+}
+i++;
+}
+try{
+while(true){
+x=fn(x,_162.next());
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+}
+return x;
+},range:function(){
+var _165=0;
+var stop=0;
+var step=1;
+if(arguments.length==1){
+stop=arguments[0];
+}else{
+if(arguments.length==2){
+_165=arguments[0];
+stop=arguments[1];
+}else{
+if(arguments.length==3){
+_165=arguments[0];
+stop=arguments[1];
+step=arguments[2];
+}else{
+throw new TypeError("range() takes 1, 2, or 3 arguments!");
+}
+}
+}
+if(step===0){
+throw new TypeError("range() step must not be 0");
+}
+return {next:function(){
+if((step>0&&_165>=stop)||(step<0&&_165<=stop)){
+throw MochiKit.Iter.StopIteration;
+}
+var rval=_165;
+_165+=step;
+return rval;
+},repr:function(){
+return "range("+[_165,stop,step].join(", ")+")";
+},toString:MochiKit.Base.forwardCall("repr")};
+},sum:function(_166,_167){
+var x=_167||0;
+var self=MochiKit.Iter;
+_166=self.iter(_166);
+try{
+while(true){
+x+=_166.next();
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+}
+return x;
+},exhaust:function(_168){
+var self=MochiKit.Iter;
+_168=self.iter(_168);
+try{
+while(true){
+_168.next();
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+}
+},forEach:function(_169,func,self){
+var m=MochiKit.Base;
+if(arguments.length>2){
+func=m.bind(func,self);
+}
+if(m.isArrayLike(_169)){
+try{
+for(var i=0;i<_169.length;i++){
+func(_169[i]);
+}
+}
+catch(e){
+if(e!=MochiKit.Iter.StopIteration){
+throw e;
+}
+}
+}else{
+self=MochiKit.Iter;
+self.exhaust(self.imap(func,_169));
+}
+},every:function(_171,func){
+var self=MochiKit.Iter;
+try{
+self.ifilterfalse(func,_171).next();
+return false;
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+return true;
+}
+},sorted:function(_172,cmp){
+var rval=MochiKit.Iter.list(_172);
+if(arguments.length==1){
+cmp=MochiKit.Base.compare;
+}
+rval.sort(cmp);
+return rval;
+},reversed:function(_173){
+var rval=MochiKit.Iter.list(_173);
+rval.reverse();
+return rval;
+},some:function(_174,func){
+var self=MochiKit.Iter;
+try{
+self.ifilter(func,_174).next();
+return true;
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+return false;
+}
+},iextend:function(lst,_175){
+if(MochiKit.Base.isArrayLike(_175)){
+for(var i=0;i<_175.length;i++){
+lst.push(_175[i]);
+}
+}else{
+var self=MochiKit.Iter;
+_175=self.iter(_175);
+try{
+while(true){
+lst.push(_175.next());
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+}
+}
+return lst;
+},groupby:function(_176,_177){
+var m=MochiKit.Base;
+var self=MochiKit.Iter;
+if(arguments.length<2){
+_177=m.operator.identity;
+}
+_176=self.iter(_176);
+var pk=undefined;
+var k=undefined;
+var v;
+function fetch(){
+v=_176.next();
+k=_177(v);
+}
+function eat(){
+var ret=v;
+v=undefined;
+return ret;
+}
+var _180=true;
+return {repr:function(){
+return "groupby(...)";
+},next:function(){
+while(k==pk){
+fetch();
+if(_180){
+_180=false;
+break;
+}
+}
+pk=k;
+return [k,{next:function(){
+if(v==undefined){
+fetch();
+}
+if(k!=pk){
+throw self.StopIteration;
+}
+return eat();
+}}];
+}};
+},groupby_as_array:function(_181,_182){
+var m=MochiKit.Base;
+var self=MochiKit.Iter;
+if(arguments.length<2){
+_182=m.operator.identity;
+}
+_181=self.iter(_181);
+var _183=[];
+var _184=true;
+var _185;
+while(true){
+try{
+var _186=_181.next();
+var key=_182(_186);
+}
+catch(e){
+if(e==self.StopIteration){
+break;
+}
+throw e;
+}
+if(_184||key!=_185){
+var _187=[];
+_183.push([key,_187]);
+}
+_187.push(_186);
+_184=false;
+_185=key;
+}
+return _183;
+},arrayLikeIter:function(_188){
+var i=0;
+return {repr:function(){
+return "arrayLikeIter(...)";
+},toString:MochiKit.Base.forwardCall("repr"),next:function(){
+if(i>=_188.length){
+throw MochiKit.Iter.StopIteration;
+}
+return _188[i++];
+}};
+},hasIterateNext:function(_189){
+return (_189&&typeof (_189.iterateNext)=="function");
+},iterateNextIter:function(_190){
+return {repr:function(){
+return "iterateNextIter(...)";
+},toString:MochiKit.Base.forwardCall("repr"),next:function(){
+var rval=_190.iterateNext();
+if(rval===null||rval===undefined){
+throw MochiKit.Iter.StopIteration;
+}
+return rval;
+}};
+}});
+MochiKit.Iter.EXPORT_OK=["iteratorRegistry","arrayLikeIter","hasIterateNext","iterateNextIter",];
+MochiKit.Iter.EXPORT=["StopIteration","registerIteratorFactory","iter","count","cycle","repeat","next","izip","ifilter","ifilterfalse","islice","imap","applymap","chain","takewhile","dropwhile","tee","list","reduce","range","sum","exhaust","forEach","every","sorted","reversed","some","iextend","groupby","groupby_as_array"];
+MochiKit.Iter.__new__=function(){
+var m=MochiKit.Base;
+this.StopIteration=new m.NamedError("StopIteration");
+this.iteratorRegistry=new m.AdapterRegistry();
+this.registerIteratorFactory("arrayLike",m.isArrayLike,this.arrayLikeIter);
+this.registerIteratorFactory("iterateNext",this.hasIterateNext,this.iterateNextIter);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+};
+MochiKit.Iter.__new__();
+if(!MochiKit.__compat__){
+reduce=MochiKit.Iter.reduce;
+}
+MochiKit.Base._exportSymbols(this,MochiKit.Iter);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Logging");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Logging depends on MochiKit.Base!";
+}
+if(typeof (MochiKit.Logging)=="undefined"){
+MochiKit.Logging={};
+}
+MochiKit.Logging.NAME="MochiKit.Logging";
+MochiKit.Logging.VERSION="1.3.1";
+MochiKit.Logging.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Logging.toString=function(){
+return this.__repr__();
+};
+MochiKit.Logging.EXPORT=["LogLevel","LogMessage","Logger","alertListener","logger","log","logError","logDebug","logFatal","logWarning"];
+MochiKit.Logging.EXPORT_OK=["logLevelAtLeast","isLogMessage","compareLogMessage"];
+MochiKit.Logging.LogMessage=function(num,_192,info){
+this.num=num;
+this.level=_192;
+this.info=info;
+this.timestamp=new Date();
+};
+MochiKit.Logging.LogMessage.prototype={repr:function(){
+var m=MochiKit.Base;
+return "LogMessage("+m.map(m.repr,[this.num,this.level,this.info]).join(", ")+")";
+},toString:MochiKit.Base.forwardCall("repr")};
+MochiKit.Base.update(MochiKit.Logging,{logLevelAtLeast:function(_194){
+var self=MochiKit.Logging;
+if(typeof (_194)=="string"){
+_194=self.LogLevel[_194];
+}
+return function(msg){
+var _196=msg.level;
+if(typeof (_196)=="string"){
+_196=self.LogLevel[_196];
+}
+return _196>=_194;
+};
+},isLogMessage:function(){
+var _197=MochiKit.Logging.LogMessage;
+for(var i=0;i<arguments.length;i++){
+if(!(arguments[i] instanceof _197)){
+return false;
+}
+}
+return true;
+},compareLogMessage:function(a,b){
+return MochiKit.Base.compare([a.level,a.info],[b.level,b.info]);
+},alertListener:function(msg){
+alert("num: "+msg.num+"\nlevel: "+msg.level+"\ninfo: "+msg.info.join(" "));
+}});
+MochiKit.Logging.Logger=function(_198){
+this.counter=0;
+if(typeof (_198)=="undefined"||_198===null){
+_198=-1;
+}
+this.maxSize=_198;
+this._messages=[];
+this.listeners={};
+this.useNativeConsole=false;
+};
+MochiKit.Logging.Logger.prototype={clear:function(){
+this._messages.splice(0,this._messages.length);
+},logToConsole:function(msg){
+if(typeof (window)!="undefined"&&window.console&&window.console.log){
+window.console.log(msg);
+}else{
+if(typeof (opera)!="undefined"&&opera.postError){
+opera.postError(msg);
+}else{
+if(typeof (printfire)=="function"){
+printfire(msg);
+}
+}
+}
+},dispatchListeners:function(msg){
+for(var k in this.listeners){
+var pair=this.listeners[k];
+if(pair.ident!=k||(pair[0]&&!pair[0](msg))){
+continue;
+}
+pair[1](msg);
+}
+},addListener:function(_199,_200,_201){
+if(typeof (_200)=="string"){
+_200=MochiKit.Logging.logLevelAtLeast(_200);
+}
+var _202=[_200,_201];
+_202.ident=_199;
+this.listeners[_199]=_202;
+},removeListener:function(_203){
+delete this.listeners[_203];
+},baseLog:function(_204,_205){
+var msg=new MochiKit.Logging.LogMessage(this.counter,_204,MochiKit.Base.extend(null,arguments,1));
+this._messages.push(msg);
+this.dispatchListeners(msg);
+if(this.useNativeConsole){
+this.logToConsole(msg.level+": "+msg.info.join(" "));
+}
+this.counter+=1;
+while(this.maxSize>=0&&this._messages.length>this.maxSize){
+this._messages.shift();
+}
+},getMessages:function(_206){
+var _207=0;
+if(!(typeof (_206)=="undefined"||_206===null)){
+_207=Math.max(0,this._messages.length-_206);
+}
+return this._messages.slice(_207);
+},getMessageText:function(_208){
+if(typeof (_208)=="undefined"||_208===null){
+_208=30;
+}
+var _209=this.getMessages(_208);
+if(_209.length){
+var lst=map(function(m){
+return "\n  ["+m.num+"] "+m.level+": "+m.info.join(" ");
+},_209);
+lst.unshift("LAST "+_209.length+" MESSAGES:");
+return lst.join("");
+}
+return "";
+},debuggingBookmarklet:function(_210){
+if(typeof (MochiKit.LoggingPane)=="undefined"){
+alert(this.getMessageText());
+}else{
+MochiKit.LoggingPane.createLoggingPane(_210||false);
+}
+}};
+MochiKit.Logging.__new__=function(){
+this.LogLevel={ERROR:40,FATAL:50,WARNING:30,INFO:20,DEBUG:10};
+var m=MochiKit.Base;
+m.registerComparator("LogMessage",this.isLogMessage,this.compareLogMessage);
+var _211=m.partial;
+var _212=this.Logger;
+var _213=_212.prototype.baseLog;
+m.update(this.Logger.prototype,{debug:_211(_213,"DEBUG"),log:_211(_213,"INFO"),error:_211(_213,"ERROR"),fatal:_211(_213,"FATAL"),warning:_211(_213,"WARNING")});
+var self=this;
+var _214=function(name){
+return function(){
+self.logger[name].apply(self.logger,arguments);
+};
+};
+this.log=_214("log");
+this.logError=_214("error");
+this.logDebug=_214("debug");
+this.logFatal=_214("fatal");
+this.logWarning=_214("warning");
+this.logger=new _212();
+this.logger.useNativeConsole=true;
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+};
+if(typeof (printfire)=="undefined"&&typeof (document)!="undefined"&&document.createEvent&&typeof (dispatchEvent)!="undefined"){
+printfire=function(){
+printfire.args=arguments;
+var ev=document.createEvent("Events");
+ev.initEvent("printfire",false,true);
+dispatchEvent(ev);
+};
+}
+MochiKit.Logging.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.Logging);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.DateTime");
+}
+if(typeof (MochiKit)=="undefined"){
+MochiKit={};
+}
+if(typeof (MochiKit.DateTime)=="undefined"){
+MochiKit.DateTime={};
+}
+MochiKit.DateTime.NAME="MochiKit.DateTime";
+MochiKit.DateTime.VERSION="1.3.1";
+MochiKit.DateTime.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.DateTime.toString=function(){
+return this.__repr__();
+};
+MochiKit.DateTime.isoDate=function(str){
+str=str+"";
+if(typeof (str)!="string"||str.length===0){
+return null;
+}
+var iso=str.split("-");
+if(iso.length===0){
+return null;
+}
+return new Date(iso[0],iso[1]-1,iso[2]);
+};
+MochiKit.DateTime._isoRegexp=/(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/;
+MochiKit.DateTime.isoTimestamp=function(str){
+str=str+"";
+if(typeof (str)!="string"||str.length===0){
+return null;
+}
+var res=str.match(MochiKit.DateTime._isoRegexp);
+if(typeof (res)=="undefined"||res===null){
+return null;
+}
+var year,month,day,hour,min,sec,msec;
+year=parseInt(res[1],10);
+if(typeof (res[2])=="undefined"||res[2]===""){
+return new Date(year);
+}
+month=parseInt(res[2],10)-1;
+day=parseInt(res[3],10);
+if(typeof (res[4])=="undefined"||res[4]===""){
+return new Date(year,month,day);
+}
+hour=parseInt(res[4],10);
+min=parseInt(res[5],10);
+sec=(typeof (res[6])!="undefined"&&res[6]!=="")?parseInt(res[6],10):0;
+if(typeof (res[7])!="undefined"&&res[7]!==""){
+msec=Math.round(1000*parseFloat("0."+res[7]));
+}else{
+msec=0;
+}
+if((typeof (res[8])=="undefined"||res[8]==="")&&(typeof (res[9])=="undefined"||res[9]==="")){
+return new Date(year,month,day,hour,min,sec,msec);
+}
+var ofs;
+if(typeof (res[9])!="undefined"&&res[9]!==""){
+ofs=parseInt(res[10],10)*3600000;
+if(typeof (res[11])!="undefined"&&res[11]!==""){
+ofs+=parseInt(res[11],10)*60000;
+}
+if(res[9]=="-"){
+ofs=-ofs;
+}
+}else{
+ofs=0;
+}
+return new Date(Date.UTC(year,month,day,hour,min,sec,msec)-ofs);
+};
+MochiKit.DateTime.toISOTime=function(date,_221){
+if(typeof (date)=="undefined"||date===null){
+return null;
+}
+var hh=date.getHours();
+var mm=date.getMinutes();
+var ss=date.getSeconds();
+var lst=[((_221&&(hh<10))?"0"+hh:hh),((mm<10)?"0"+mm:mm),((ss<10)?"0"+ss:ss)];
+return lst.join(":");
+};
+MochiKit.DateTime.toISOTimestamp=function(date,_225){
+if(typeof (date)=="undefined"||date===null){
+return null;
+}
+var sep=_225?"T":" ";
+var foot=_225?"Z":"";
+if(_225){
+date=new Date(date.getTime()+(date.getTimezoneOffset()*60000));
+}
+return MochiKit.DateTime.toISODate(date)+sep+MochiKit.DateTime.toISOTime(date,_225)+foot;
+};
+MochiKit.DateTime.toISODate=function(date){
+if(typeof (date)=="undefined"||date===null){
+return null;
+}
+var _228=MochiKit.DateTime._padTwo;
+return [date.getFullYear(),_228(date.getMonth()+1),_228(date.getDate())].join("-");
+};
+MochiKit.DateTime.americanDate=function(d){
+d=d+"";
+if(typeof (d)!="string"||d.length===0){
+return null;
+}
+var a=d.split("/");
+return new Date(a[2],a[0]-1,a[1]);
+};
+MochiKit.DateTime._padTwo=function(n){
+return (n>9)?n:"0"+n;
+};
+MochiKit.DateTime.toPaddedAmericanDate=function(d){
+if(typeof (d)=="undefined"||d===null){
+return null;
+}
+var _230=MochiKit.DateTime._padTwo;
+return [_230(d.getMonth()+1),_230(d.getDate()),d.getFullYear()].join("/");
+};
+MochiKit.DateTime.toAmericanDate=function(d){
+if(typeof (d)=="undefined"||d===null){
+return null;
+}
+return [d.getMonth()+1,d.getDate(),d.getFullYear()].join("/");
+};
+MochiKit.DateTime.EXPORT=["isoDate","isoTimestamp","toISOTime","toISOTimestamp","toISODate","americanDate","toPaddedAmericanDate","toAmericanDate"];
+MochiKit.DateTime.EXPORT_OK=[];
+MochiKit.DateTime.EXPORT_TAGS={":common":MochiKit.DateTime.EXPORT,":all":MochiKit.DateTime.EXPORT};
+MochiKit.DateTime.__new__=function(){
+var base=this.NAME+".";
+for(var k in this){
+var o=this[k];
+if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
+try{
+o.NAME=base+k;
+}
+catch(e){
+}
+}
+}
+};
+MochiKit.DateTime.__new__();
+if(typeof (MochiKit.Base)!="undefined"){
+MochiKit.Base._exportSymbols(this,MochiKit.DateTime);
+}else{
+(function(_231,_232){
+if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){
+var all=_232.EXPORT_TAGS[":all"];
+for(var i=0;i<all.length;i++){
+_231[all[i]]=_232[all[i]];
+}
+}
+})(this,MochiKit.DateTime);
+}
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Format");
+}
+if(typeof (MochiKit)=="undefined"){
+MochiKit={};
+}
+if(typeof (MochiKit.Format)=="undefined"){
+MochiKit.Format={};
+}
+MochiKit.Format.NAME="MochiKit.Format";
+MochiKit.Format.VERSION="1.3.1";
+MochiKit.Format.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Format.toString=function(){
+return this.__repr__();
+};
+MochiKit.Format._numberFormatter=function(_233,_234,_235,_236,_237,_238,_239,_240,_241){
+return function(num){
+num=parseFloat(num);
+if(typeof (num)=="undefined"||num===null||isNaN(num)){
+return _233;
+}
+var _242=_234;
+var _243=_235;
+if(num<0){
+num=-num;
+}else{
+_242=_242.replace(/-/,"");
+}
+var me=arguments.callee;
+var fmt=MochiKit.Format.formatLocale(_236);
+if(_237){
+num=num*100;
+_243=fmt.percent+_243;
+}
+num=MochiKit.Format.roundToFixed(num,_238);
+var _245=num.split(/\./);
+var _246=_245[0];
+var frac=(_245.length==1)?"":_245[1];
+var res="";
+while(_246.length<_239){
+_246="0"+_246;
+}
+if(_240){
+while(_246.length>_240){
+var i=_246.length-_240;
+res=fmt.separator+_246.substring(i,_246.length)+res;
+_246=_246.substring(0,i);
+}
+}
+res=_246+res;
+if(_238>0){
+while(frac.length<_241){
+frac=frac+"0";
+}
+res=res+fmt.decimal+frac;
+}
+return _242+res+_243;
+};
+};
+MochiKit.Format.numberFormatter=function(_248,_249,_250){
+if(typeof (_249)=="undefined"){
+_249="";
+}
+var _251=_248.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/);
+if(!_251){
+throw TypeError("Invalid pattern");
+}
+var _252=_248.substr(0,_251.index);
+var _253=_248.substr(_251.index+_251[0].length);
+if(_252.search(/-/)==-1){
+_252=_252+"-";
+}
+var _254=_251[1];
+var frac=(typeof (_251[2])=="string"&&_251[2]!="")?_251[2]:"";
+var _255=(typeof (_251[3])=="string"&&_251[3]!="");
+var tmp=_254.split(/,/);
+var _257;
+if(typeof (_250)=="undefined"){
+_250="default";
+}
+if(tmp.length==1){
+_257=null;
+}else{
+_257=tmp[1].length;
+}
+var _258=_254.length-_254.replace(/0/g,"").length;
+var _259=frac.length-frac.replace(/0/g,"").length;
+var _260=frac.length;
+var rval=MochiKit.Format._numberFormatter(_249,_252,_253,_250,_255,_260,_258,_257,_259);
+var m=MochiKit.Base;
+if(m){
+var fn=arguments.callee;
+var args=m.concat(arguments);
+rval.repr=function(){
+return [self.NAME,"(",map(m.repr,args).join(", "),")"].join("");
+};
+}
+return rval;
+};
+MochiKit.Format.formatLocale=function(_262){
+if(typeof (_262)=="undefined"||_262===null){
+_262="default";
+}
+if(typeof (_262)=="string"){
+var rval=MochiKit.Format.LOCALE[_262];
+if(typeof (rval)=="string"){
+rval=arguments.callee(rval);
+MochiKit.Format.LOCALE[_262]=rval;
+}
+return rval;
+}else{
+return _262;
+}
+};
+MochiKit.Format.twoDigitAverage=function(_263,_264){
+if(_264){
+var res=_263/_264;
+if(!isNaN(res)){
+return MochiKit.Format.twoDigitFloat(_263/_264);
+}
+}
+return "0";
+};
+MochiKit.Format.twoDigitFloat=function(_265){
+var sign=(_265<0?"-":"");
+var s=Math.floor(Math.abs(_265)*100).toString();
+if(s=="0"){
+return s;
+}
+if(s.length<3){
+while(s.charAt(s.length-1)=="0"){
+s=s.substring(0,s.length-1);
+}
+return sign+"0."+s;
+}
+var head=sign+s.substring(0,s.length-2);
+var tail=s.substring(s.length-2,s.length);
+if(tail=="00"){
+return head;
+}else{
+if(tail.charAt(1)=="0"){
+return head+"."+tail.charAt(0);
+}else{
+return head+"."+tail;
+}
+}
+};
+MochiKit.Format.lstrip=function(str,_270){
+str=str+"";
+if(typeof (str)!="string"){
+return null;
+}
+if(!_270){
+return str.replace(/^\s+/,"");
+}else{
+return str.replace(new RegExp("^["+_270+"]+"),"");
+}
+};
+MochiKit.Format.rstrip=function(str,_271){
+str=str+"";
+if(typeof (str)!="string"){
+return null;
+}
+if(!_271){
+return str.replace(/\s+$/,"");
+}else{
+return str.replace(new RegExp("["+_271+"]+$"),"");
+}
+};
+MochiKit.Format.strip=function(str,_272){
+var self=MochiKit.Format;
+return self.rstrip(self.lstrip(str,_272),_272);
+};
+MochiKit.Format.truncToFixed=function(_273,_274){
+_273=Math.floor(_273*Math.pow(10,_274));
+var res=(_273*Math.pow(10,-_274)).toFixed(_274);
+if(res.charAt(0)=="."){
+res="0"+res;
+}
+return res;
+};
+MochiKit.Format.roundToFixed=function(_275,_276){
+return MochiKit.Format.truncToFixed(_275+0.5*Math.pow(10,-_276),_276);
+};
+MochiKit.Format.percentFormat=function(_277){
+return MochiKit.Format.twoDigitFloat(100*_277)+"%";
+};
+MochiKit.Format.EXPORT=["truncToFixed","roundToFixed","numberFormatter","formatLocale","twoDigitAverage","twoDigitFloat","percentFormat","lstrip","rstrip","strip"];
+MochiKit.Format.LOCALE={en_US:{separator:",",decimal:".",percent:"%"},de_DE:{separator:".",decimal:",",percent:"%"},fr_FR:{separator:" ",decimal:",",percent:"%"},"default":"en_US"};
+MochiKit.Format.EXPORT_OK=[];
+MochiKit.Format.EXPORT_TAGS={":all":MochiKit.Format.EXPORT,":common":MochiKit.Format.EXPORT};
+MochiKit.Format.__new__=function(){
+var base=this.NAME+".";
+var k,v,o;
+for(k in this.LOCALE){
+o=this.LOCALE[k];
+if(typeof (o)=="object"){
+o.repr=function(){
+return this.NAME;
+};
+o.NAME=base+"LOCALE."+k;
+}
+}
+for(k in this){
+o=this[k];
+if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
+try{
+o.NAME=base+k;
+}
+catch(e){
+}
+}
+}
+};
+MochiKit.Format.__new__();
+if(typeof (MochiKit.Base)!="undefined"){
+MochiKit.Base._exportSymbols(this,MochiKit.Format);
+}else{
+(function(_278,_279){
+if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){
+var all=_279.EXPORT_TAGS[":all"];
+for(var i=0;i<all.length;i++){
+_278[all[i]]=_279[all[i]];
+}
+}
+})(this,MochiKit.Format);
+}
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Async");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Async depends on MochiKit.Base!";
+}
+if(typeof (MochiKit.Async)=="undefined"){
+MochiKit.Async={};
+}
+MochiKit.Async.NAME="MochiKit.Async";
+MochiKit.Async.VERSION="1.3.1";
+MochiKit.Async.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Async.toString=function(){
+return this.__repr__();
+};
+MochiKit.Async.Deferred=function(_280){
+this.chain=[];
+this.id=this._nextId();
+this.fired=-1;
+this.paused=0;
+this.results=[null,null];
+this.canceller=_280;
+this.silentlyCancelled=false;
+this.chained=false;
+};
+MochiKit.Async.Deferred.prototype={repr:function(){
+var _281;
+if(this.fired==-1){
+_281="unfired";
+}else{
+if(this.fired===0){
+_281="success";
+}else{
+_281="error";
+}
+}
+return "Deferred("+this.id+", "+_281+")";
+},toString:MochiKit.Base.forwardCall("repr"),_nextId:MochiKit.Base.counter(),cancel:function(){
+var self=MochiKit.Async;
+if(this.fired==-1){
+if(this.canceller){
+this.canceller(this);
+}else{
+this.silentlyCancelled=true;
+}
+if(this.fired==-1){
+this.errback(new self.CancelledError(this));
+}
+}else{
+if((this.fired===0)&&(this.results[0] instanceof self.Deferred)){
+this.results[0].cancel();
+}
+}
+},_pause:function(){
+this.paused++;
+},_unpause:function(){
+this.paused--;
+if((this.paused===0)&&(this.fired>=0)){
+this._fire();
+}
+},_continue:function(res){
+this._resback(res);
+this._unpause();
+},_resback:function(res){
+this.fired=((res instanceof Error)?1:0);
+this.results[this.fired]=res;
+this._fire();
+},_check:function(){
+if(this.fired!=-1){
+if(!this.silentlyCancelled){
+throw new MochiKit.Async.AlreadyCalledError(this);
+}
+this.silentlyCancelled=false;
+return;
+}
+},callback:function(res){
+this._check();
+if(res instanceof MochiKit.Async.Deferred){
+throw new Error("Deferred instances can only be chained if they are the result of a callback");
+}
+this._resback(res);
+},errback:function(res){
+this._check();
+var self=MochiKit.Async;
+if(res instanceof self.Deferred){
+throw new Error("Deferred instances can only be chained if they are the result of a callback");
+}
+if(!(res instanceof Error)){
+res=new self.GenericError(res);
+}
+this._resback(res);
+},addBoth:function(fn){
+if(arguments.length>1){
+fn=MochiKit.Base.partial.apply(null,arguments);
+}
+return this.addCallbacks(fn,fn);
+},addCallback:function(fn){
+if(arguments.length>1){
+fn=MochiKit.Base.partial.apply(null,arguments);
+}
+return this.addCallbacks(fn,null);
+},addErrback:function(fn){
+if(arguments.length>1){
+fn=MochiKit.Base.partial.apply(null,arguments);
+}
+return this.addCallbacks(null,fn);
+},addCallbacks:function(cb,eb){
+if(this.chained){
+throw new Error("Chained Deferreds can not be re-used");
+}
+this.chain.push([cb,eb]);
+if(this.fired>=0){
+this._fire();
+}
+return this;
+},_fire:function(){
+var _284=this.chain;
+var _285=this.fired;
+var res=this.results[_285];
+var self=this;
+var cb=null;
+while(_284.length>0&&this.paused===0){
+var pair=_284.shift();
+var f=pair[_285];
+if(f===null){
+continue;
+}
+try{
+res=f(res);
+_285=((res instanceof Error)?1:0);
+if(res instanceof MochiKit.Async.Deferred){
+cb=function(res){
+self._continue(res);
+};
+this._pause();
+}
+}
+catch(err){
+_285=1;
+if(!(err instanceof Error)){
+err=new MochiKit.Async.GenericError(err);
+}
+res=err;
+}
+}
+this.fired=_285;
+this.results[_285]=res;
+if(cb&&this.paused){
+res.addBoth(cb);
+res.chained=true;
+}
+}};
+MochiKit.Base.update(MochiKit.Async,{evalJSONRequest:function(){
+return eval("("+arguments[0].responseText+")");
+},succeed:function(_287){
+var d=new MochiKit.Async.Deferred();
+d.callback.apply(d,arguments);
+return d;
+},fail:function(_288){
+var d=new MochiKit.Async.Deferred();
+d.errback.apply(d,arguments);
+return d;
+},getXMLHttpRequest:function(){
+var self=arguments.callee;
+if(!self.XMLHttpRequest){
+var _289=[function(){
+return new XMLHttpRequest();
+},function(){
+return new ActiveXObject("Msxml2.XMLHTTP");
+},function(){
+return new ActiveXObject("Microsoft.XMLHTTP");
+},function(){
+return new ActiveXObject("Msxml2.XMLHTTP.4.0");
+},function(){
+throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
+}];
+for(var i=0;i<_289.length;i++){
+var func=_289[i];
+try{
+self.XMLHttpRequest=func;
+return func();
+}
+catch(e){
+}
+}
+}
+return self.XMLHttpRequest();
+},_nothing:function(){
+},_xhr_onreadystatechange:function(d){
+if(this.readyState==4){
+try{
+this.onreadystatechange=null;
+}
+catch(e){
+try{
+this.onreadystatechange=MochiKit.Async._nothing;
+}
+catch(e){
+}
+}
+var _290=null;
+try{
+_290=this.status;
+if(!_290&&MochiKit.Base.isNotEmpty(this.responseText)){
+_290=304;
+}
+}
+catch(e){
+}
+if(_290==200||_290==304){
+d.callback(this);
+}else{
+var err=new MochiKit.Async.XMLHttpRequestError(this,"Request failed");
+if(err.number){
+d.errback(err);
+}else{
+d.errback(err);
+}
+}
+}
+},_xhr_canceller:function(req){
+try{
+req.onreadystatechange=null;
+}
+catch(e){
+try{
+req.onreadystatechange=MochiKit.Async._nothing;
+}
+catch(e){
+}
+}
+req.abort();
+},sendXMLHttpRequest:function(req,_293){
+if(typeof (_293)=="undefined"||_293===null){
+_293="";
+}
+var m=MochiKit.Base;
+var self=MochiKit.Async;
+var d=new self.Deferred(m.partial(self._xhr_canceller,req));
+try{
+req.onreadystatechange=m.bind(self._xhr_onreadystatechange,req,d);
+req.send(_293);
+}
+catch(e){
+try{
+req.onreadystatechange=null;
+}
+catch(ignore){
+}
+d.errback(e);
+}
+return d;
+},doSimpleXMLHttpRequest:function(url){
+var self=MochiKit.Async;
+var req=self.getXMLHttpRequest();
+if(arguments.length>1){
+var m=MochiKit.Base;
+var qs=m.queryString.apply(null,m.extend(null,arguments,1));
+if(qs){
+url+="?"+qs;
+}
+}
+req.open("GET",url,true);
+return self.sendXMLHttpRequest(req);
+},loadJSONDoc:function(url){
+var self=MochiKit.Async;
+var d=self.doSimpleXMLHttpRequest.apply(self,arguments);
+d=d.addCallback(self.evalJSONRequest);
+return d;
+},wait:function(_296,_297){
+var d=new MochiKit.Async.Deferred();
+var m=MochiKit.Base;
+if(typeof (_297)!="undefined"){
+d.addCallback(function(){
+return _297;
+});
+}
+var _298=setTimeout(m.bind("callback",d),Math.floor(_296*1000));
+d.canceller=function(){
+try{
+clearTimeout(_298);
+}
+catch(e){
+}
+};
+return d;
+},callLater:function(_299,func){
+var m=MochiKit.Base;
+var _300=m.partial.apply(m,m.extend(null,arguments,1));
+return MochiKit.Async.wait(_299).addCallback(function(res){
+return _300();
+});
+}});
+MochiKit.Async.DeferredLock=function(){
+this.waiting=[];
+this.locked=false;
+this.id=this._nextId();
+};
+MochiKit.Async.DeferredLock.prototype={__class__:MochiKit.Async.DeferredLock,acquire:function(){
+d=new MochiKit.Async.Deferred();
+if(this.locked){
+this.waiting.push(d);
+}else{
+this.locked=true;
+d.callback(this);
+}
+return d;
+},release:function(){
+if(!this.locked){
+throw TypeError("Tried to release an unlocked DeferredLock");
+}
+this.locked=false;
+if(this.waiting.length>0){
+this.locked=true;
+this.waiting.shift().callback(this);
+}
+},_nextId:MochiKit.Base.counter(),repr:function(){
+var _301;
+if(this.locked){
+_301="locked, "+this.waiting.length+" waiting";
+}else{
+_301="unlocked";
+}
+return "DeferredLock("+this.id+", "+_301+")";
+},toString:MochiKit.Base.forwardCall("repr")};
+MochiKit.Async.DeferredList=function(list,_303,_304,_305,_306){
+this.list=list;
+this.resultList=new Array(this.list.length);
+this.chain=[];
+this.id=this._nextId();
+this.fired=-1;
+this.paused=0;
+this.results=[null,null];
+this.canceller=_306;
+this.silentlyCancelled=false;
+if(this.list.length===0&&!_303){
+this.callback(this.resultList);
+}
+this.finishedCount=0;
+this.fireOnOneCallback=_303;
+this.fireOnOneErrback=_304;
+this.consumeErrors=_305;
+var _307=0;
+MochiKit.Base.map(MochiKit.Base.bind(function(d){
+d.addCallback(MochiKit.Base.bind(this._cbDeferred,this),_307,true);
+d.addErrback(MochiKit.Base.bind(this._cbDeferred,this),_307,false);
+_307+=1;
+},this),this.list);
+};
+MochiKit.Base.update(MochiKit.Async.DeferredList.prototype,MochiKit.Async.Deferred.prototype);
+MochiKit.Base.update(MochiKit.Async.DeferredList.prototype,{_cbDeferred:function(_308,_309,_310){
+this.resultList[_308]=[_309,_310];
+this.finishedCount+=1;
+if(this.fired!==0){
+if(_309&&this.fireOnOneCallback){
+this.callback([_308,_310]);
+}else{
+if(!_309&&this.fireOnOneErrback){
+this.errback(_310);
+}else{
+if(this.finishedCount==this.list.length){
+this.callback(this.resultList);
+}
+}
+}
+}
+if(!_309&&this.consumeErrors){
+_310=null;
+}
+return _310;
+}});
+MochiKit.Async.gatherResults=function(_311){
+var d=new MochiKit.Async.DeferredList(_311,false,true,false);
+d.addCallback(function(_312){
+var ret=[];
+for(var i=0;i<_312.length;i++){
+ret.push(_312[i][1]);
+}
+return ret;
+});
+return d;
+};
+MochiKit.Async.maybeDeferred=function(func){
+var self=MochiKit.Async;
+var _313;
+try{
+var r=func.apply(null,MochiKit.Base.extend([],arguments,1));
+if(r instanceof self.Deferred){
+_313=r;
+}else{
+if(r instanceof Error){
+_313=self.fail(r);
+}else{
+_313=self.succeed(r);
+}
+}
+}
+catch(e){
+_313=self.fail(e);
+}
+return _313;
+};
+MochiKit.Async.EXPORT=["AlreadyCalledError","CancelledError","BrowserComplianceError","GenericError","XMLHttpRequestError","Deferred","succeed","fail","getXMLHttpRequest","doSimpleXMLHttpRequest","loadJSONDoc","wait","callLater","sendXMLHttpRequest","DeferredLock","DeferredList","gatherResults","maybeDeferred"];
+MochiKit.Async.EXPORT_OK=["evalJSONRequest"];
+MochiKit.Async.__new__=function(){
+var m=MochiKit.Base;
+var ne=m.partial(m._newNamedError,this);
+ne("AlreadyCalledError",function(_316){
+this.deferred=_316;
+});
+ne("CancelledError",function(_317){
+this.deferred=_317;
+});
+ne("BrowserComplianceError",function(msg){
+this.message=msg;
+});
+ne("GenericError",function(msg){
+this.message=msg;
+});
+ne("XMLHttpRequestError",function(req,msg){
+this.req=req;
+this.message=msg;
+try{
+this.number=req.status;
+}
+catch(e){
+}
+});
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+};
+MochiKit.Async.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.Async);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.DOM");
+dojo.require("MochiKit.Iter");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Iter",[]);
+}
+try{
+if(typeof (MochiKit.Iter)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.DOM depends on MochiKit.Iter!";
+}
+if(typeof (MochiKit.DOM)=="undefined"){
+MochiKit.DOM={};
+}
+MochiKit.DOM.NAME="MochiKit.DOM";
+MochiKit.DOM.VERSION="1.3.1";
+MochiKit.DOM.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.DOM.toString=function(){
+return this.__repr__();
+};
+MochiKit.DOM.EXPORT=["formContents","currentWindow","currentDocument","withWindow","withDocument","registerDOMConverter","coerceToDOM","createDOM","createDOMFunc","getNodeAttribute","setNodeAttribute","updateNodeAttributes","appendChildNodes","replaceChildNodes","removeElement","swapDOM","BUTTON","TT","PRE","H1","H2","H3","BR","CANVAS","HR","LABEL","TEXTAREA","FORM","STRONG","SELECT","OPTION","OPTGROUP","LEGEND","FIELDSET","P","UL","OL","LI","TD","TR","THEAD","TBODY","TFOOT","TABLE","TH","INPUT","SPAN","A","DIV","IMG","getElement","$","computedStyle","getElementsByTagAndClassName","addToCallStack","addLoadEvent","focusOnLoad","setElementClass","toggleElementClass","addElementClass","removeElementClass","swapElementClass","hasElementClass","escapeHTML","toHTML","emitHTML","setDisplayForElement","hideElement","showElement","scrapeText","elementDimensions","elementPosition","setElementDimensions","setElementPosition","getViewportDimensions","setOpacity"];
+MochiKit.DOM.EXPORT_OK=["domConverters"];
+MochiKit.DOM.Dimensions=function(w,h){
+this.w=w;
+this.h=h;
+};
+MochiKit.DOM.Dimensions.prototype.repr=function(){
+var repr=MochiKit.Base.repr;
+return "{w: "+repr(this.w)+", h: "+repr(this.h)+"}";
+};
+MochiKit.DOM.Coordinates=function(x,y){
+this.x=x;
+this.y=y;
+};
+MochiKit.DOM.Coordinates.prototype.repr=function(){
+var repr=MochiKit.Base.repr;
+return "{x: "+repr(this.x)+", y: "+repr(this.y)+"}";
+};
+MochiKit.DOM.Coordinates.prototype.toString=function(){
+return this.repr();
+};
+MochiKit.Base.update(MochiKit.DOM,{setOpacity:function(elem,o){
+elem=MochiKit.DOM.getElement(elem);
+MochiKit.DOM.updateNodeAttributes(elem,{"style":{"opacity":o,"-moz-opacity":o,"-khtml-opacity":o,"filter":" alpha(opacity="+(o*100)+")"}});
+},getViewportDimensions:function(){
+var d=new MochiKit.DOM.Dimensions();
+var w=MochiKit.DOM._window;
+var b=MochiKit.DOM._document.body;
+if(w.innerWidth){
+d.w=w.innerWidth;
+d.h=w.innerHeight;
+}else{
+if(b.parentElement.clientWidth){
+d.w=b.parentElement.clientWidth;
+d.h=b.parentElement.clientHeight;
+}else{
+if(b&&b.clientWidth){
+d.w=b.clientWidth;
+d.h=b.clientHeight;
+}
+}
+}
+return d;
+},elementDimensions:function(elem){
+var self=MochiKit.DOM;
+if(typeof (elem.w)=="number"||typeof (elem.h)=="number"){
+return new self.Dimensions(elem.w||0,elem.h||0);
+}
+elem=self.getElement(elem);
+if(!elem){
+return undefined;
+}
+if(self.computedStyle(elem,"display")!="none"){
+return new self.Dimensions(elem.offsetWidth||0,elem.offsetHeight||0);
+}
+var s=elem.style;
+var _322=s.visibility;
+var _323=s.position;
+s.visibility="hidden";
+s.position="absolute";
+s.display="";
+var _324=elem.offsetWidth;
+var _325=elem.offsetHeight;
+s.display="none";
+s.position=_323;
+s.visibility=_322;
+return new self.Dimensions(_324,_325);
+},elementPosition:function(elem,_326){
+var self=MochiKit.DOM;
+elem=self.getElement(elem);
+if(!elem){
+return undefined;
+}
+var c=new self.Coordinates(0,0);
+if(elem.x&&elem.y){
+c.x+=elem.x||0;
+c.y+=elem.y||0;
+return c;
+}else{
+if(elem.parentNode===null||self.computedStyle(elem,"display")=="none"){
+return undefined;
+}
+}
+var box=null;
+var _329=null;
+var d=MochiKit.DOM._document;
+var de=d.documentElement;
+var b=d.body;
+if(elem.getBoundingClientRect){
+box=elem.getBoundingClientRect();
+c.x+=box.left+(de.scrollLeft||b.scrollLeft)-(de.clientLeft||b.clientLeft);
+c.y+=box.top+(de.scrollTop||b.scrollTop)-(de.clientTop||b.clientTop);
+}else{
+if(d.getBoxObjectFor){
+box=d.getBoxObjectFor(elem);
+c.x+=box.x;
+c.y+=box.y;
+}else{
+if(elem.offsetParent){
+c.x+=elem.offsetLeft;
+c.y+=elem.offsetTop;
+_329=elem.offsetParent;
+if(_329!=elem){
+while(_329){
+c.x+=_329.offsetLeft;
+c.y+=_329.offsetTop;
+_329=_329.offsetParent;
+}
+}
+var ua=navigator.userAgent.toLowerCase();
+if((typeof (opera)!="undefined"&&parseFloat(opera.version())<9)||(ua.indexOf("safari")!=-1&&self.computedStyle(elem,"position")=="absolute")){
+c.x-=b.offsetLeft;
+c.y-=b.offsetTop;
+}
+}
+}
+}
+if(typeof (_326)!="undefined"){
+_326=arguments.callee(_326);
+if(_326){
+c.x-=(_326.x||0);
+c.y-=(_326.y||0);
+}
+}
+if(elem.parentNode){
+_329=elem.parentNode;
+}else{
+_329=null;
+}
+while(_329&&_329.tagName!="BODY"&&_329.tagName!="HTML"){
+c.x-=_329.scrollLeft;
+c.y-=_329.scrollTop;
+if(_329.parentNode){
+_329=_329.parentNode;
+}else{
+_329=null;
+}
+}
+return c;
+},setElementDimensions:function(elem,_332,_333){
+elem=MochiKit.DOM.getElement(elem);
+if(typeof (_333)=="undefined"){
+_333="px";
+}
+MochiKit.DOM.updateNodeAttributes(elem,{"style":{"width":_332.w+_333,"height":_332.h+_333}});
+},setElementPosition:function(elem,_334,_335){
+elem=MochiKit.DOM.getElement(elem);
+if(typeof (_335)=="undefined"){
+_335="px";
+}
+MochiKit.DOM.updateNodeAttributes(elem,{"style":{"left":_334.x+_335,"top":_334.y+_335}});
+},currentWindow:function(){
+return MochiKit.DOM._window;
+},currentDocument:function(){
+return MochiKit.DOM._document;
+},withWindow:function(win,func){
+var self=MochiKit.DOM;
+var _337=self._document;
+var _338=self._win;
+var rval;
+try{
+self._window=win;
+self._document=win.document;
+rval=func();
+}
+catch(e){
+self._window=_338;
+self._document=_337;
+throw e;
+}
+self._window=_338;
+self._document=_337;
+return rval;
+},formContents:function(elem){
+var _339=[];
+var _340=[];
+var m=MochiKit.Base;
+var self=MochiKit.DOM;
+if(typeof (elem)=="undefined"||elem===null){
+elem=self._document;
+}else{
+elem=self.getElement(elem);
+}
+m.nodeWalk(elem,function(elem){
+var name=elem.name;
+if(m.isNotEmpty(name)){
+var _341=elem.nodeName;
+if(_341=="INPUT"&&(elem.type=="radio"||elem.type=="checkbox")&&!elem.checked){
+return null;
+}
+if(_341=="SELECT"){
+if(elem.selectedIndex>=0){
+var opt=elem.options[elem.selectedIndex];
+_339.push(name);
+_340.push((opt.value)?opt.value:opt.text);
+return null;
+}
+_339.push(name);
+_340.push("");
+return null;
+}
+if(_341=="FORM"||_341=="P"||_341=="SPAN"||_341=="DIV"){
+return elem.childNodes;
+}
+_339.push(name);
+_340.push(elem.value||"");
+return null;
+}
+return elem.childNodes;
+});
+return [_339,_340];
+},withDocument:function(doc,func){
+var self=MochiKit.DOM;
+var _344=self._document;
+var rval;
+try{
+self._document=doc;
+rval=func();
+}
+catch(e){
+self._document=_344;
+throw e;
+}
+self._document=_344;
+return rval;
+},registerDOMConverter:function(name,_345,wrap,_346){
+MochiKit.DOM.domConverters.register(name,_345,wrap,_346);
+},coerceToDOM:function(node,ctx){
+var im=MochiKit.Iter;
+var self=MochiKit.DOM;
+var iter=im.iter;
+var _350=im.repeat;
+var imap=im.imap;
+var _352=self.domConverters;
+var _353=self.coerceToDOM;
+var _354=MochiKit.Base.NotFound;
+while(true){
+if(typeof (node)=="undefined"||node===null){
+return null;
+}
+if(typeof (node.nodeType)!="undefined"&&node.nodeType>0){
+return node;
+}
+if(typeof (node)=="number"||typeof (node)=="boolean"){
+node=node.toString();
+}
+if(typeof (node)=="string"){
+return self._document.createTextNode(node);
+}
+if(typeof (node.toDOM)=="function"){
+node=node.toDOM(ctx);
+continue;
+}
+if(typeof (node)=="function"){
+node=node(ctx);
+continue;
+}
+var _355=null;
+try{
+_355=iter(node);
+}
+catch(e){
+}
+if(_355){
+return imap(_353,_355,_350(ctx));
+}
+try{
+node=_352.match(node,ctx);
+continue;
+}
+catch(e){
+if(e!=_354){
+throw e;
+}
+}
+return self._document.createTextNode(node.toString());
+}
+return undefined;
+},setNodeAttribute:function(node,attr,_357){
+var o={};
+o[attr]=_357;
+try{
+return MochiKit.DOM.updateNodeAttributes(node,o);
+}
+catch(e){
+}
+return null;
+},getNodeAttribute:function(node,attr){
+var self=MochiKit.DOM;
+var _358=self.attributeArray.renames[attr];
+node=self.getElement(node);
+try{
+if(_358){
+return node[_358];
+}
+return node.getAttribute(attr);
+}
+catch(e){
+}
+return null;
+},updateNodeAttributes:function(node,_359){
+var elem=node;
+var self=MochiKit.DOM;
+if(typeof (node)=="string"){
+elem=self.getElement(node);
+}
+if(_359){
+var _360=MochiKit.Base.updatetree;
+if(self.attributeArray.compliant){
+for(var k in _359){
+var v=_359[k];
+if(typeof (v)=="object"&&typeof (elem[k])=="object"){
+_360(elem[k],v);
+}else{
+if(k.substring(0,2)=="on"){
+if(typeof (v)=="string"){
+v=new Function(v);
+}
+elem[k]=v;
+}else{
+elem.setAttribute(k,v);
+}
+}
+}
+}else{
+var _361=self.attributeArray.renames;
+for(k in _359){
+v=_359[k];
+var _362=_361[k];
+if(k=="style"&&typeof (v)=="string"){
+elem.style.cssText=v;
+}else{
+if(typeof (_362)=="string"){
+elem[_362]=v;
+}else{
+if(typeof (elem[k])=="object"&&typeof (v)=="object"){
+_360(elem[k],v);
+}else{
+if(k.substring(0,2)=="on"){
+if(typeof (v)=="string"){
+v=new Function(v);
+}
+elem[k]=v;
+}else{
+elem.setAttribute(k,v);
+}
+}
+}
+}
+}
+}
+}
+return elem;
+},appendChildNodes:function(node){
+var elem=node;
+var self=MochiKit.DOM;
+if(typeof (node)=="string"){
+elem=self.getElement(node);
+}
+var _363=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)];
+var _364=MochiKit.Base.concat;
+while(_363.length){
+var n=_363.shift();
+if(typeof (n)=="undefined"||n===null){
+}else{
+if(typeof (n.nodeType)=="number"){
+elem.appendChild(n);
+}else{
+_363=_364(n,_363);
+}
+}
+}
+return elem;
+},replaceChildNodes:function(node){
+var elem=node;
+var self=MochiKit.DOM;
+if(typeof (node)=="string"){
+elem=self.getElement(node);
+arguments[0]=elem;
+}
+var _365;
+while((_365=elem.firstChild)){
+elem.removeChild(_365);
+}
+if(arguments.length<2){
+return elem;
+}else{
+return self.appendChildNodes.apply(this,arguments);
+}
+},createDOM:function(name,_366){
+var elem;
+var self=MochiKit.DOM;
+var m=MochiKit.Base;
+if(typeof (_366)=="string"||typeof (_366)=="number"){
+var args=m.extend([name,null],arguments,1);
+return arguments.callee.apply(this,args);
+}
+if(typeof (name)=="string"){
+if(_366&&"name" in _366&&!self.attributeArray.compliant){
+name=("<"+name+" name=\""+self.escapeHTML(_366.name)+"\">");
+}
+elem=self._document.createElement(name);
+}else{
+elem=name;
+}
+if(_366){
+self.updateNodeAttributes(elem,_366);
+}
+if(arguments.length<=2){
+return elem;
+}else{
+var args=m.extend([elem],arguments,2);
+return self.appendChildNodes.apply(this,args);
+}
+},createDOMFunc:function(){
+var m=MochiKit.Base;
+return m.partial.apply(this,m.extend([MochiKit.DOM.createDOM],arguments));
+},swapDOM:function(dest,src){
+var self=MochiKit.DOM;
+dest=self.getElement(dest);
+var _369=dest.parentNode;
+if(src){
+src=self.getElement(src);
+_369.replaceChild(src,dest);
+}else{
+_369.removeChild(dest);
+}
+return src;
+},getElement:function(id){
+var self=MochiKit.DOM;
+if(arguments.length==1){
+return ((typeof (id)=="string")?self._document.getElementById(id):id);
+}else{
+return MochiKit.Base.map(self.getElement,arguments);
+}
+},computedStyle:function(_371,_372,_373){
+if(arguments.length==2){
+_373=_372;
+}
+var self=MochiKit.DOM;
+var el=self.getElement(_371);
+var _375=self._document;
+if(!el||el==_375){
+return undefined;
+}
+if(el.currentStyle){
+return el.currentStyle[_372];
+}
+if(typeof (_375.defaultView)=="undefined"){
+return undefined;
+}
+if(_375.defaultView===null){
+return undefined;
+}
+var _376=_375.defaultView.getComputedStyle(el,null);
+if(typeof (_376)=="undefined"||_376===null){
+return undefined;
+}
+return _376.getPropertyValue(_373);
+},getElementsByTagAndClassName:function(_377,_378,_379){
+var self=MochiKit.DOM;
+if(typeof (_377)=="undefined"||_377===null){
+_377="*";
+}
+if(typeof (_379)=="undefined"||_379===null){
+_379=self._document;
+}
+_379=self.getElement(_379);
+var _380=(_379.getElementsByTagName(_377)||self._document.all);
+if(typeof (_378)=="undefined"||_378===null){
+return MochiKit.Base.extend(null,_380);
+}
+var _381=[];
+for(var i=0;i<_380.length;i++){
+var _382=_380[i];
+var _383=_382.className.split(" ");
+for(var j=0;j<_383.length;j++){
+if(_383[j]==_378){
+_381.push(_382);
+break;
+}
+}
+}
+return _381;
+},_newCallStack:function(path,once){
+var rval=function(){
+var _386=arguments.callee.callStack;
+for(var i=0;i<_386.length;i++){
+if(_386[i].apply(this,arguments)===false){
+break;
+}
+}
+if(once){
+try{
+this[path]=null;
+}
+catch(e){
+}
+}
+};
+rval.callStack=[];
+return rval;
+},addToCallStack:function(_387,path,func,once){
+var self=MochiKit.DOM;
+var _388=_387[path];
+var _389=_388;
+if(!(typeof (_388)=="function"&&typeof (_388.callStack)=="object"&&_388.callStack!==null)){
+_389=self._newCallStack(path,once);
+if(typeof (_388)=="function"){
+_389.callStack.push(_388);
+}
+_387[path]=_389;
+}
+_389.callStack.push(func);
+},addLoadEvent:function(func){
+var self=MochiKit.DOM;
+self.addToCallStack(self._window,"onload",func,true);
+},focusOnLoad:function(_390){
+var self=MochiKit.DOM;
+self.addLoadEvent(function(){
+_390=self.getElement(_390);
+if(_390){
+_390.focus();
+}
+});
+},setElementClass:function(_391,_392){
+var self=MochiKit.DOM;
+var obj=self.getElement(_391);
+if(self.attributeArray.compliant){
+obj.setAttribute("class",_392);
+}else{
+obj.setAttribute("className",_392);
+}
+},toggleElementClass:function(_393){
+var self=MochiKit.DOM;
+for(var i=1;i<arguments.length;i++){
+var obj=self.getElement(arguments[i]);
+if(!self.addElementClass(obj,_393)){
+self.removeElementClass(obj,_393);
+}
+}
+},addElementClass:function(_394,_395){
+var self=MochiKit.DOM;
+var obj=self.getElement(_394);
+var cls=obj.className;
+if(cls.length===0){
+self.setElementClass(obj,_395);
+return true;
+}
+if(cls==_395){
+return false;
+}
+var _397=obj.className.split(" ");
+for(var i=0;i<_397.length;i++){
+if(_397[i]==_395){
+return false;
+}
+}
+self.setElementClass(obj,cls+" "+_395);
+return true;
+},removeElementClass:function(_398,_399){
+var self=MochiKit.DOM;
+var obj=self.getElement(_398);
+var cls=obj.className;
+if(cls.length===0){
+return false;
+}
+if(cls==_399){
+self.setElementClass(obj,"");
+return true;
+}
+var _400=obj.className.split(" ");
+for(var i=0;i<_400.length;i++){
+if(_400[i]==_399){
+_400.splice(i,1);
+self.setElementClass(obj,_400.join(" "));
+return true;
+}
+}
+return false;
+},swapElementClass:function(_401,_402,_403){
+var obj=MochiKit.DOM.getElement(_401);
+var res=MochiKit.DOM.removeElementClass(obj,_402);
+if(res){
+MochiKit.DOM.addElementClass(obj,_403);
+}
+return res;
+},hasElementClass:function(_404,_405){
+var obj=MochiKit.DOM.getElement(_404);
+var _406=obj.className.split(" ");
+for(var i=1;i<arguments.length;i++){
+var good=false;
+for(var j=0;j<_406.length;j++){
+if(_406[j]==arguments[i]){
+good=true;
+break;
+}
+}
+if(!good){
+return false;
+}
+}
+return true;
+},escapeHTML:function(s){
+return s.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
+},toHTML:function(dom){
+return MochiKit.DOM.emitHTML(dom).join("");
+},emitHTML:function(dom,lst){
+if(typeof (lst)=="undefined"||lst===null){
+lst=[];
+}
+var _409=[dom];
+var self=MochiKit.DOM;
+var _410=self.escapeHTML;
+var _411=self.attributeArray;
+while(_409.length){
+dom=_409.pop();
+if(typeof (dom)=="string"){
+lst.push(dom);
+}else{
+if(dom.nodeType==1){
+lst.push("<"+dom.nodeName.toLowerCase());
+var _412=[];
+var _413=_411(dom);
+for(var i=0;i<_413.length;i++){
+var a=_413[i];
+_412.push([" ",a.name,"=\"",_410(a.value),"\""]);
+}
+_412.sort();
+for(i=0;i<_412.length;i++){
+var _414=_412[i];
+for(var j=0;j<_414.length;j++){
+lst.push(_414[j]);
+}
+}
+if(dom.hasChildNodes()){
+lst.push(">");
+_409.push("</"+dom.nodeName.toLowerCase()+">");
+var _415=dom.childNodes;
+for(i=_415.length-1;i>=0;i--){
+_409.push(_415[i]);
+}
+}else{
+lst.push("/>");
+}
+}else{
+if(dom.nodeType==3){
+lst.push(_410(dom.nodeValue));
+}
+}
+}
+}
+return lst;
+},setDisplayForElement:function(_416,_417){
+var m=MochiKit.Base;
+var _418=m.extend(null,arguments,1);
+MochiKit.Iter.forEach(m.filter(null,m.map(MochiKit.DOM.getElement,_418)),function(_417){
+_417.style.display=_416;
+});
+},scrapeText:function(node,_419){
+var rval=[];
+(function(node){
+var cn=node.childNodes;
+if(cn){
+for(var i=0;i<cn.length;i++){
+arguments.callee.call(this,cn[i]);
+}
+}
+var _421=node.nodeValue;
+if(typeof (_421)=="string"){
+rval.push(_421);
+}
+})(MochiKit.DOM.getElement(node));
+if(_419){
+return rval;
+}else{
+return rval.join("");
+}
+},__new__:function(win){
+var m=MochiKit.Base;
+this._document=document;
+this._window=win;
+this.domConverters=new m.AdapterRegistry();
+var _422=this._document.createElement("span");
+var _423;
+if(_422&&_422.attributes&&_422.attributes.length>0){
+var _424=m.filter;
+_423=function(node){
+return _424(_423.ignoreAttrFilter,node.attributes);
+};
+_423.ignoreAttr={};
+MochiKit.Iter.forEach(_422.attributes,function(a){
+_423.ignoreAttr[a.name]=a.value;
+});
+_423.ignoreAttrFilter=function(a){
+return (_423.ignoreAttr[a.name]!=a.value);
+};
+_423.compliant=false;
+_423.renames={"class":"className","checked":"defaultChecked","usemap":"useMap","for":"htmlFor"};
+}else{
+_423=function(node){
+return node.attributes;
+};
+_423.compliant=true;
+_423.renames={};
+}
+this.attributeArray=_423;
+var _425=this.createDOMFunc;
+this.UL=_425("ul");
+this.OL=_425("ol");
+this.LI=_425("li");
+this.TD=_425("td");
+this.TR=_425("tr");
+this.TBODY=_425("tbody");
+this.THEAD=_425("thead");
+this.TFOOT=_425("tfoot");
+this.TABLE=_425("table");
+this.TH=_425("th");
+this.INPUT=_425("input");
+this.SPAN=_425("span");
+this.A=_425("a");
+this.DIV=_425("div");
+this.IMG=_425("img");
+this.BUTTON=_425("button");
+this.TT=_425("tt");
+this.PRE=_425("pre");
+this.H1=_425("h1");
+this.H2=_425("h2");
+this.H3=_425("h3");
+this.BR=_425("br");
+this.HR=_425("hr");
+this.LABEL=_425("label");
+this.TEXTAREA=_425("textarea");
+this.FORM=_425("form");
+this.P=_425("p");
+this.SELECT=_425("select");
+this.OPTION=_425("option");
+this.OPTGROUP=_425("optgroup");
+this.LEGEND=_425("legend");
+this.FIELDSET=_425("fieldset");
+this.STRONG=_425("strong");
+this.CANVAS=_425("canvas");
+this.hideElement=m.partial(this.setDisplayForElement,"none");
+this.showElement=m.partial(this.setDisplayForElement,"block");
+this.removeElement=this.swapDOM;
+this.$=this.getElement;
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+}});
+MochiKit.DOM.__new__(((typeof (window)=="undefined")?this:window));
+if(!MochiKit.__compat__){
+withWindow=MochiKit.DOM.withWindow;
+withDocument=MochiKit.DOM.withDocument;
+}
+MochiKit.Base._exportSymbols(this,MochiKit.DOM);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.LoggingPane");
+dojo.require("MochiKit.Logging");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Logging",[]);
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.Logging)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.LoggingPane depends on MochiKit.Base and MochiKit.Logging!";
+}
+if(typeof (MochiKit.LoggingPane)=="undefined"){
+MochiKit.LoggingPane={};
+}
+MochiKit.LoggingPane.NAME="MochiKit.LoggingPane";
+MochiKit.LoggingPane.VERSION="1.3.1";
+MochiKit.LoggingPane.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.LoggingPane.toString=function(){
+return this.__repr__();
+};
+MochiKit.LoggingPane.createLoggingPane=function(_426){
+var m=MochiKit.LoggingPane;
+_426=!(!_426);
+if(m._loggingPane&&m._loggingPane.inline!=_426){
+m._loggingPane.closePane();
+m._loggingPane=null;
+}
+if(!m._loggingPane||m._loggingPane.closed){
+m._loggingPane=new m.LoggingPane(_426,MochiKit.Logging.logger);
+}
+return m._loggingPane;
+};
+MochiKit.LoggingPane.LoggingPane=function(_427,_428){
+if(typeof (_428)=="undefined"||_428===null){
+_428=MochiKit.Logging.logger;
+}
+this.logger=_428;
+var _429=MochiKit.Base.update;
+var _430=MochiKit.Base.updatetree;
+var bind=MochiKit.Base.bind;
+var _431=MochiKit.Base.clone;
+var win=window;
+var uid="_MochiKit_LoggingPane";
+if(typeof (MochiKit.DOM)!="undefined"){
+win=MochiKit.DOM.currentWindow();
+}
+if(!_427){
+var url=win.location.href.split("?")[0].replace(/[:\/.><&]/g,"_");
+var name=uid+"_"+url;
+var nwin=win.open("",name,"dependent,resizable,height=200");
+if(!nwin){
+alert("Not able to open debugging window due to pop-up blocking.");
+return undefined;
+}
+nwin.document.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" "+"\"http://www.w3.org/TR/html4/loose.dtd\">"+"<html><head><title>[MochiKit.LoggingPane]</title></head>"+"<body></body></html>");
+nwin.document.close();
+nwin.document.title+=" "+win.document.title;
+win=nwin;
+}
+var doc=win.document;
+this.doc=doc;
+var _434=doc.getElementById(uid);
+var _435=!!_434;
+if(_434&&typeof (_434.loggingPane)!="undefined"){
+_434.loggingPane.logger=this.logger;
+_434.loggingPane.buildAndApplyFilter();
+return _434.loggingPane;
+}
+if(_435){
+var _436;
+while((_436=_434.firstChild)){
+_434.removeChild(_436);
+}
+}else{
+_434=doc.createElement("div");
+_434.id=uid;
+}
+_434.loggingPane=this;
+var _437=doc.createElement("input");
+var _438=doc.createElement("input");
+var _439=doc.createElement("button");
+var _440=doc.createElement("button");
+var _441=doc.createElement("button");
+var _442=doc.createElement("button");
+var _443=doc.createElement("div");
+var _444=doc.createElement("div");
+var _445=uid+"_Listener";
+this.colorTable=_431(this.colorTable);
+var _446=[];
+var _447=null;
+var _448=function(msg){
+var _449=msg.level;
+if(typeof (_449)=="number"){
+_449=MochiKit.Logging.LogLevel[_449];
+}
+return _449;
+};
+var _450=function(msg){
+return msg.info.join(" ");
+};
+var _451=bind(function(msg){
+var _452=_448(msg);
+var text=_450(msg);
+var c=this.colorTable[_452];
+var p=doc.createElement("span");
+p.className="MochiKit-LogMessage MochiKit-LogLevel-"+_452;
+p.style.cssText="margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: "+c;
+p.appendChild(doc.createTextNode(_452+": "+text));
+_444.appendChild(p);
+_444.appendChild(doc.createElement("br"));
+if(_443.offsetHeight>_443.scrollHeight){
+_443.scrollTop=0;
+}else{
+_443.scrollTop=_443.scrollHeight;
+}
+},this);
+var _454=function(msg){
+_446[_446.length]=msg;
+_451(msg);
+};
+var _455=function(){
+var _456,infore;
+try{
+_456=new RegExp(_437.value);
+infore=new RegExp(_438.value);
+}
+catch(e){
+logDebug("Error in filter regex: "+e.message);
+return null;
+}
+return function(msg){
+return (_456.test(_448(msg))&&infore.test(_450(msg)));
+};
+};
+var _457=function(){
+while(_444.firstChild){
+_444.removeChild(_444.firstChild);
+}
+};
+var _458=function(){
+_446=[];
+_457();
+};
+var _459=bind(function(){
+if(this.closed){
+return;
+}
+this.closed=true;
+if(MochiKit.LoggingPane._loggingPane==this){
+MochiKit.LoggingPane._loggingPane=null;
+}
+this.logger.removeListener(_445);
+_434.loggingPane=null;
+if(_427){
+_434.parentNode.removeChild(_434);
+}else{
+this.win.close();
+}
+},this);
+var _460=function(){
+_457();
+for(var i=0;i<_446.length;i++){
+var msg=_446[i];
+if(_447===null||_447(msg)){
+_451(msg);
+}
+}
+};
+this.buildAndApplyFilter=function(){
+_447=_455();
+_460();
+this.logger.removeListener(_445);
+this.logger.addListener(_445,_447,_454);
+};
+var _461=bind(function(){
+_446=this.logger.getMessages();
+_460();
+},this);
+var _462=bind(function(_463){
+_463=_463||window.event;
+key=_463.which||_463.keyCode;
+if(key==13){
+this.buildAndApplyFilter();
+}
+},this);
+var _464="display: block; z-index: 1000; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: "+this.logFont;
+if(_427){
+_464+="; height: 10em; border-top: 2px solid black";
+}else{
+_464+="; height: 100%;";
+}
+_434.style.cssText=_464;
+if(!_435){
+doc.body.appendChild(_434);
+}
+_464={"cssText":"width: 33%; display: inline; font: "+this.logFont};
+_430(_437,{"value":"FATAL|ERROR|WARNING|INFO|DEBUG","onkeypress":_462,"style":_464});
+_434.appendChild(_437);
+_430(_438,{"value":".*","onkeypress":_462,"style":_464});
+_434.appendChild(_438);
+_464="width: 8%; display:inline; font: "+this.logFont;
+_439.appendChild(doc.createTextNode("Filter"));
+_439.onclick=bind("buildAndApplyFilter",this);
+_439.style.cssText=_464;
+_434.appendChild(_439);
+_440.appendChild(doc.createTextNode("Load"));
+_440.onclick=_461;
+_440.style.cssText=_464;
+_434.appendChild(_440);
+_441.appendChild(doc.createTextNode("Clear"));
+_441.onclick=_458;
+_441.style.cssText=_464;
+_434.appendChild(_441);
+_442.appendChild(doc.createTextNode("Close"));
+_442.onclick=_459;
+_442.style.cssText=_464;
+_434.appendChild(_442);
+_443.style.cssText="overflow: auto; width: 100%";
+_444.style.cssText="width: 100%; height: "+(_427?"8em":"100%");
+_443.appendChild(_444);
+_434.appendChild(_443);
+this.buildAndApplyFilter();
+_461();
+if(_427){
+this.win=undefined;
+}else{
+this.win=win;
+}
+this.inline=_427;
+this.closePane=_459;
+this.closed=false;
+return this;
+};
+MochiKit.LoggingPane.LoggingPane.prototype={"logFont":"8pt Verdana,sans-serif","colorTable":{"ERROR":"red","FATAL":"darkred","WARNING":"blue","INFO":"black","DEBUG":"green"}};
+MochiKit.LoggingPane.EXPORT_OK=["LoggingPane"];
+MochiKit.LoggingPane.EXPORT=["createLoggingPane"];
+MochiKit.LoggingPane.__new__=function(){
+this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)};
+MochiKit.Base.nameFunctions(this);
+MochiKit.LoggingPane._loggingPane=null;
+};
+MochiKit.LoggingPane.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.LoggingPane);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Color");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Color depends on MochiKit.Base";
+}
+if(typeof (MochiKit.Color)=="undefined"){
+MochiKit.Color={};
+}
+MochiKit.Color.NAME="MochiKit.Color";
+MochiKit.Color.VERSION="1.3.1";
+MochiKit.Color.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Color.toString=function(){
+return this.__repr__();
+};
+MochiKit.Color.Color=function(red,_466,blue,_468){
+if(typeof (_468)=="undefined"||_468===null){
+_468=1;
+}
+this.rgb={r:red,g:_466,b:blue,a:_468};
+};
+MochiKit.Color.Color.prototype={__class__:MochiKit.Color.Color,colorWithAlpha:function(_469){
+var rgb=this.rgb;
+var m=MochiKit.Color;
+return m.Color.fromRGB(rgb.r,rgb.g,rgb.b,_469);
+},colorWithHue:function(hue){
+var hsl=this.asHSL();
+hsl.h=hue;
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},colorWithSaturation:function(_473){
+var hsl=this.asHSL();
+hsl.s=_473;
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},colorWithLightness:function(_474){
+var hsl=this.asHSL();
+hsl.l=_474;
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},darkerColorWithLevel:function(_475){
+var hsl=this.asHSL();
+hsl.l=Math.max(hsl.l-_475,0);
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},lighterColorWithLevel:function(_476){
+var hsl=this.asHSL();
+hsl.l=Math.min(hsl.l+_476,1);
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},blendedColor:function(_477,_478){
+if(typeof (_478)=="undefined"||_478===null){
+_478=0.5;
+}
+var sf=1-_478;
+var s=this.rgb;
+var d=_477.rgb;
+var df=_478;
+return MochiKit.Color.Color.fromRGB((s.r*sf)+(d.r*df),(s.g*sf)+(d.g*df),(s.b*sf)+(d.b*df),(s.a*sf)+(d.a*df));
+},compareRGB:function(_481){
+var a=this.asRGB();
+var b=_481.asRGB();
+return MochiKit.Base.compare([a.r,a.g,a.b,a.a],[b.r,b.g,b.b,b.a]);
+},isLight:function(){
+return this.asHSL().b>0.5;
+},isDark:function(){
+return (!this.isLight());
+},toHSLString:function(){
+var c=this.asHSL();
+var ccc=MochiKit.Color.clampColorComponent;
+var rval=this._hslString;
+if(!rval){
+var mid=(ccc(c.h,360).toFixed(0)+","+ccc(c.s,100).toPrecision(4)+"%"+","+ccc(c.l,100).toPrecision(4)+"%");
+var a=c.a;
+if(a>=1){
+a=1;
+rval="hsl("+mid+")";
+}else{
+if(a<=0){
+a=0;
+}
+rval="hsla("+mid+","+a+")";
+}
+this._hslString=rval;
+}
+return rval;
+},toRGBString:function(){
+var c=this.rgb;
+var ccc=MochiKit.Color.clampColorComponent;
+var rval=this._rgbString;
+if(!rval){
+var mid=(ccc(c.r,255).toFixed(0)+","+ccc(c.g,255).toFixed(0)+","+ccc(c.b,255).toFixed(0));
+if(c.a!=1){
+rval="rgba("+mid+","+c.a+")";
+}else{
+rval="rgb("+mid+")";
+}
+this._rgbString=rval;
+}
+return rval;
+},asRGB:function(){
+return MochiKit.Base.clone(this.rgb);
+},toHexString:function(){
+var m=MochiKit.Color;
+var c=this.rgb;
+var ccc=MochiKit.Color.clampColorComponent;
+var rval=this._hexString;
+if(!rval){
+rval=("#"+m.toColorPart(ccc(c.r,255))+m.toColorPart(ccc(c.g,255))+m.toColorPart(ccc(c.b,255)));
+this._hexString=rval;
+}
+return rval;
+},asHSV:function(){
+var hsv=this.hsv;
+var c=this.rgb;
+if(typeof (hsv)=="undefined"||hsv===null){
+hsv=MochiKit.Color.rgbToHSV(this.rgb);
+this.hsv=hsv;
+}
+return MochiKit.Base.clone(hsv);
+},asHSL:function(){
+var hsl=this.hsl;
+var c=this.rgb;
+if(typeof (hsl)=="undefined"||hsl===null){
+hsl=MochiKit.Color.rgbToHSL(this.rgb);
+this.hsl=hsl;
+}
+return MochiKit.Base.clone(hsl);
+},toString:function(){
+return this.toRGBString();
+},repr:function(){
+var c=this.rgb;
+var col=[c.r,c.g,c.b,c.a];
+return this.__class__.NAME+"("+col.join(", ")+")";
+}};
+MochiKit.Base.update(MochiKit.Color.Color,{fromRGB:function(red,_486,blue,_487){
+var _488=MochiKit.Color.Color;
+if(arguments.length==1){
+var rgb=red;
+red=rgb.r;
+_486=rgb.g;
+blue=rgb.b;
+if(typeof (rgb.a)=="undefined"){
+_487=undefined;
+}else{
+_487=rgb.a;
+}
+}
+return new _488(red,_486,blue,_487);
+},fromHSL:function(hue,_489,_490,_491){
+var m=MochiKit.Color;
+return m.Color.fromRGB(m.hslToRGB.apply(m,arguments));
+},fromHSV:function(hue,_492,_493,_494){
+var m=MochiKit.Color;
+return m.Color.fromRGB(m.hsvToRGB.apply(m,arguments));
+},fromName:function(name){
+var _495=MochiKit.Color.Color;
+if(name.charAt(0)=="\""){
+name=name.substr(1,name.length-2);
+}
+var _496=_495._namedColors[name.toLowerCase()];
+if(typeof (_496)=="string"){
+return _495.fromHexString(_496);
+}else{
+if(name=="transparent"){
+return _495.transparentColor();
+}
+}
+return null;
+},fromString:function(_497){
+var self=MochiKit.Color.Color;
+var _498=_497.substr(0,3);
+if(_498=="rgb"){
+return self.fromRGBString(_497);
+}else{
+if(_498=="hsl"){
+return self.fromHSLString(_497);
+}else{
+if(_497.charAt(0)=="#"){
+return self.fromHexString(_497);
+}
+}
+}
+return self.fromName(_497);
+},fromHexString:function(_499){
+if(_499.charAt(0)=="#"){
+_499=_499.substring(1);
+}
+var _500=[];
+var i,hex;
+if(_499.length==3){
+for(i=0;i<3;i++){
+hex=_499.substr(i,1);
+_500.push(parseInt(hex+hex,16)/255);
+}
+}else{
+for(i=0;i<6;i+=2){
+hex=_499.substr(i,2);
+_500.push(parseInt(hex,16)/255);
+}
+}
+var _501=MochiKit.Color.Color;
+return _501.fromRGB.apply(_501,_500);
+},_fromColorString:function(pre,_503,_504,_505){
+if(_505.indexOf(pre)===0){
+_505=_505.substring(_505.indexOf("(",3)+1,_505.length-1);
+}
+var _506=_505.split(/\s*,\s*/);
+var _507=[];
+for(var i=0;i<_506.length;i++){
+var c=_506[i];
+var val;
+var _508=c.substring(c.length-3);
+if(c.charAt(c.length-1)=="%"){
+val=0.01*parseFloat(c.substring(0,c.length-1));
+}else{
+if(_508=="deg"){
+val=parseFloat(c)/360;
+}else{
+if(_508=="rad"){
+val=parseFloat(c)/(Math.PI*2);
+}else{
+val=_504[i]*parseFloat(c);
+}
+}
+}
+_507.push(val);
+}
+return this[_503].apply(this,_507);
+},fromComputedStyle:function(elem,_509,_510){
+var d=MochiKit.DOM;
+var cls=MochiKit.Color.Color;
+for(elem=d.getElement(elem);elem;elem=elem.parentNode){
+var _511=d.computedStyle.apply(d,arguments);
+if(!_511){
+continue;
+}
+var _512=cls.fromString(_511);
+if(!_512){
+break;
+}
+if(_512.asRGB().a>0){
+return _512;
+}
+}
+return null;
+},fromBackground:function(elem){
+var cls=MochiKit.Color.Color;
+return cls.fromComputedStyle(elem,"backgroundColor","background-color")||cls.whiteColor();
+},fromText:function(elem){
+var cls=MochiKit.Color.Color;
+return cls.fromComputedStyle(elem,"color","color")||cls.blackColor();
+},namedColors:function(){
+return MochiKit.Base.clone(MochiKit.Color.Color._namedColors);
+}});
+MochiKit.Base.update(MochiKit.Color,{clampColorComponent:function(v,_513){
+v*=_513;
+if(v<0){
+return 0;
+}else{
+if(v>_513){
+return _513;
+}else{
+return v;
+}
+}
+},_hslValue:function(n1,n2,hue){
+if(hue>6){
+hue-=6;
+}else{
+if(hue<0){
+hue+=6;
+}
+}
+var val;
+if(hue<1){
+val=n1+(n2-n1)*hue;
+}else{
+if(hue<3){
+val=n2;
+}else{
+if(hue<4){
+val=n1+(n2-n1)*(4-hue);
+}else{
+val=n1;
+}
+}
+}
+return val;
+},hsvToRGB:function(hue,_516,_517,_518){
+if(arguments.length==1){
+var hsv=hue;
+hue=hsv.h;
+_516=hsv.s;
+_517=hsv.v;
+_518=hsv.a;
+}
+var red;
+var _519;
+var blue;
+if(_516===0){
+red=0;
+_519=0;
+blue=0;
+}else{
+var i=Math.floor(hue*6);
+var f=(hue*6)-i;
+var p=_517*(1-_516);
+var q=_517*(1-(_516*f));
+var t=_517*(1-(_516*(1-f)));
+switch(i){
+case 1:
+red=q;
+_519=_517;
+blue=p;
+break;
+case 2:
+red=p;
+_519=_517;
+blue=t;
+break;
+case 3:
+red=p;
+_519=q;
+blue=_517;
+break;
+case 4:
+red=t;
+_519=p;
+blue=_517;
+break;
+case 5:
+red=_517;
+_519=p;
+blue=q;
+break;
+case 6:
+case 0:
+red=_517;
+_519=t;
+blue=p;
+break;
+}
+}
+return {r:red,g:_519,b:blue,a:_518};
+},hslToRGB:function(hue,_521,_522,_523){
+if(arguments.length==1){
+var hsl=hue;
+hue=hsl.h;
+_521=hsl.s;
+_522=hsl.l;
+_523=hsl.a;
+}
+var red;
+var _524;
+var blue;
+if(_521===0){
+red=_522;
+_524=_522;
+blue=_522;
+}else{
+var m2;
+if(_522<=0.5){
+m2=_522*(1+_521);
+}else{
+m2=_522+_521-(_522*_521);
+}
+var m1=(2*_522)-m2;
+var f=MochiKit.Color._hslValue;
+var h6=hue*6;
+red=f(m1,m2,h6+2);
+_524=f(m1,m2,h6);
+blue=f(m1,m2,h6-2);
+}
+return {r:red,g:_524,b:blue,a:_523};
+},rgbToHSV:function(red,_528,blue,_529){
+if(arguments.length==1){
+var rgb=red;
+red=rgb.r;
+_528=rgb.g;
+blue=rgb.b;
+_529=rgb.a;
+}
+var max=Math.max(Math.max(red,_528),blue);
+var min=Math.min(Math.min(red,_528),blue);
+var hue;
+var _532;
+var _533=max;
+if(min==max){
+hue=0;
+_532=0;
+}else{
+var _534=(max-min);
+_532=_534/max;
+if(red==max){
+hue=(_528-blue)/_534;
+}else{
+if(_528==max){
+hue=2+((blue-red)/_534);
+}else{
+hue=4+((red-_528)/_534);
+}
+}
+hue/=6;
+if(hue<0){
+hue+=1;
+}
+if(hue>1){
+hue-=1;
+}
+}
+return {h:hue,s:_532,v:_533,a:_529};
+},rgbToHSL:function(red,_535,blue,_536){
+if(arguments.length==1){
+var rgb=red;
+red=rgb.r;
+_535=rgb.g;
+blue=rgb.b;
+_536=rgb.a;
+}
+var max=Math.max(red,Math.max(_535,blue));
+var min=Math.min(red,Math.min(_535,blue));
+var hue;
+var _537;
+var _538=(max+min)/2;
+var _539=max-min;
+if(_539===0){
+hue=0;
+_537=0;
+}else{
+if(_538<=0.5){
+_537=_539/(max+min);
+}else{
+_537=_539/(2-max-min);
+}
+if(red==max){
+hue=(_535-blue)/_539;
+}else{
+if(_535==max){
+hue=2+((blue-red)/_539);
+}else{
+hue=4+((red-_535)/_539);
+}
+}
+hue/=6;
+if(hue<0){
+hue+=1;
+}
+if(hue>1){
+hue-=1;
+}
+}
+return {h:hue,s:_537,l:_538,a:_536};
+},toColorPart:function(num){
+num=Math.round(num);
+var _540=num.toString(16);
+if(num<16){
+return "0"+_540;
+}
+return _540;
+},__new__:function(){
+var m=MochiKit.Base;
+this.Color.fromRGBString=m.bind(this.Color._fromColorString,this.Color,"rgb","fromRGB",[1/255,1/255,1/255,1]);
+this.Color.fromHSLString=m.bind(this.Color._fromColorString,this.Color,"hsl","fromHSL",[1/360,0.01,0.01,1]);
+var _541=1/3;
+var _542={black:[0,0,0],blue:[0,0,1],brown:[0.6,0.4,0.2],cyan:[0,1,1],darkGray:[_541,_541,_541],gray:[0.5,0.5,0.5],green:[0,1,0],lightGray:[2*_541,2*_541,2*_541],magenta:[1,0,1],orange:[1,0.5,0],purple:[0.5,0,0.5],red:[1,0,0],transparent:[0,0,0,0],white:[1,1,1],yellow:[1,1,0]};
+var _543=function(name,r,g,b,a){
+var rval=this.fromRGB(r,g,b,a);
+this[name]=function(){
+return rval;
+};
+return rval;
+};
+for(var k in _542){
+var name=k+"Color";
+var _545=m.concat([_543,this.Color,name],_542[k]);
+this.Color[name]=m.bind.apply(null,_545);
+}
+var _546=function(){
+for(var i=0;i<arguments.length;i++){
+if(!(arguments[i] instanceof Color)){
+return false;
+}
+}
+return true;
+};
+var _547=function(a,b){
+return a.compareRGB(b);
+};
+m.nameFunctions(this);
+m.registerComparator(this.Color.NAME,_546,_547);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+}});
+MochiKit.Color.EXPORT=["Color"];
+MochiKit.Color.EXPORT_OK=["clampColorComponent","rgbToHSL","hslToRGB","rgbToHSV","hsvToRGB","toColorPart"];
+MochiKit.Color.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.Color);
+MochiKit.Color.Color._namedColors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Signal");
+dojo.require("MochiKit.Base");
+dojo.require("MochiKit.DOM");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+JSAN.use("MochiKit.DOM",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Signal depends on MochiKit.Base!";
+}
+try{
+if(typeof (MochiKit.DOM)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Signal depends on MochiKit.DOM!";
+}
+if(typeof (MochiKit.Signal)=="undefined"){
+MochiKit.Signal={};
+}
+MochiKit.Signal.NAME="MochiKit.Signal";
+MochiKit.Signal.VERSION="1.3.1";
+MochiKit.Signal._observers=[];
+MochiKit.Signal.Event=function(src,e){
+this._event=e||window.event;
+this._src=src;
+};
+MochiKit.Base.update(MochiKit.Signal.Event.prototype,{__repr__:function(){
+var repr=MochiKit.Base.repr;
+var str="{event(): "+repr(this.event())+", src(): "+repr(this.src())+", type(): "+repr(this.type())+", target(): "+repr(this.target())+", modifier(): "+"{alt: "+repr(this.modifier().alt)+", ctrl: "+repr(this.modifier().ctrl)+", meta: "+repr(this.modifier().meta)+", shift: "+repr(this.modifier().shift)+", any: "+repr(this.modifier().any)+"}";
+if(this.type()&&this.type().indexOf("key")===0){
+str+=", key(): {code: "+repr(this.key().code)+", string: "+repr(this.key().string)+"}";
+}
+if(this.type()&&(this.type().indexOf("mouse")===0||this.type().indexOf("click")!=-1||this.type()=="contextmenu")){
+str+=", mouse(): {page: "+repr(this.mouse().page)+", client: "+repr(this.mouse().client);
+if(this.type()!="mousemove"){
+str+=", button: {left: "+repr(this.mouse().button.left)+", middle: "+repr(this.mouse().button.middle)+", right: "+repr(this.mouse().button.right)+"}}";
+}else{
+str+="}";
+}
+}
+if(this.type()=="mouseover"||this.type()=="mouseout"){
+str+=", relatedTarget(): "+repr(this.relatedTarget());
+}
+str+="}";
+return str;
+},toString:function(){
+return this.__repr__();
+},src:function(){
+return this._src;
+},event:function(){
+return this._event;
+},type:function(){
+return this._event.type||undefined;
+},target:function(){
+return this._event.target||this._event.srcElement;
+},relatedTarget:function(){
+if(this.type()=="mouseover"){
+return (this._event.relatedTarget||this._event.fromElement);
+}else{
+if(this.type()=="mouseout"){
+return (this._event.relatedTarget||this._event.toElement);
+}
+}
+return undefined;
+},modifier:function(){
+var m={};
+m.alt=this._event.altKey;
+m.ctrl=this._event.ctrlKey;
+m.meta=this._event.metaKey||false;
+m.shift=this._event.shiftKey;
+m.any=m.alt||m.ctrl||m.shift||m.meta;
+return m;
+},key:function(){
+var k={};
+if(this.type()&&this.type().indexOf("key")===0){
+if(this.type()=="keydown"||this.type()=="keyup"){
+k.code=this._event.keyCode;
+k.string=(MochiKit.Signal._specialKeys[k.code]||"KEY_UNKNOWN");
+return k;
+}else{
+if(this.type()=="keypress"){
+k.code=0;
+k.string="";
+if(typeof (this._event.charCode)!="undefined"&&this._event.charCode!==0&&!MochiKit.Signal._specialMacKeys[this._event.charCode]){
+k.code=this._event.charCode;
+k.string=String.fromCharCode(k.code);
+}else{
+if(this._event.keyCode&&typeof (this._event.charCode)=="undefined"){
+k.code=this._event.keyCode;
+k.string=String.fromCharCode(k.code);
+}
+}
+return k;
+}
+}
+}
+return undefined;
+},mouse:function(){
+var m={};
+var e=this._event;
+if(this.type()&&(this.type().indexOf("mouse")===0||this.type().indexOf("click")!=-1||this.type()=="contextmenu")){
+m.client=new MochiKit.DOM.Coordinates(0,0);
+if(e.clientX||e.clientY){
+m.client.x=(!e.clientX||e.clientX<0)?0:e.clientX;
+m.client.y=(!e.clientY||e.clientY<0)?0:e.clientY;
+}
+m.page=new MochiKit.DOM.Coordinates(0,0);
+if(e.pageX||e.pageY){
+m.page.x=(!e.pageX||e.pageX<0)?0:e.pageX;
+m.page.y=(!e.pageY||e.pageY<0)?0:e.pageY;
+}else{
+var de=MochiKit.DOM._document.documentElement;
+var b=MochiKit.DOM._document.body;
+m.page.x=e.clientX+(de.scrollLeft||b.scrollLeft)-(de.clientLeft||b.clientLeft);
+m.page.y=e.clientY+(de.scrollTop||b.scrollTop)-(de.clientTop||b.clientTop);
+}
+if(this.type()!="mousemove"){
+m.button={};
+m.button.left=false;
+m.button.right=false;
+m.button.middle=false;
+if(e.which){
+m.button.left=(e.which==1);
+m.button.middle=(e.which==2);
+m.button.right=(e.which==3);
+}else{
+m.button.left=!!(e.button&1);
+m.button.right=!!(e.button&2);
+m.button.middle=!!(e.button&4);
+}
+}
+return m;
+}
+return undefined;
+},stop:function(){
+this.stopPropagation();
+this.preventDefault();
+},stopPropagation:function(){
+if(this._event.stopPropagation){
+this._event.stopPropagation();
+}else{
+this._event.cancelBubble=true;
+}
+},preventDefault:function(){
+if(this._event.preventDefault){
+this._event.preventDefault();
+}else{
+this._event.returnValue=false;
+}
+}});
+MochiKit.Signal._specialMacKeys={3:"KEY_ENTER",63289:"KEY_NUM_PAD_CLEAR",63276:"KEY_PAGE_UP",63277:"KEY_PAGE_DOWN",63275:"KEY_END",63273:"KEY_HOME",63234:"KEY_ARROW_LEFT",63232:"KEY_ARROW_UP",63235:"KEY_ARROW_RIGHT",63233:"KEY_ARROW_DOWN",63302:"KEY_INSERT",63272:"KEY_DELETE"};
+for(i=63236;i<=63242;i++){
+MochiKit.Signal._specialMacKeys[i]="KEY_F"+(i-63236+1);
+}
+MochiKit.Signal._specialKeys={8:"KEY_BACKSPACE",9:"KEY_TAB",12:"KEY_NUM_PAD_CLEAR",13:"KEY_ENTER",16:"KEY_SHIFT",17:"KEY_CTRL",18:"KEY_ALT",19:"KEY_PAUSE",20:"KEY_CAPS_LOCK",27:"KEY_ESCAPE",32:"KEY_SPACEBAR",33:"KEY_PAGE_UP",34:"KEY_PAGE_DOWN",35:"KEY_END",36:"KEY_HOME",37:"KEY_ARROW_LEFT",38:"KEY_ARROW_UP",39:"KEY_ARROW_RIGHT",40:"KEY_ARROW_DOWN",44:"KEY_PRINT_SCREEN",45:"KEY_INSERT",46:"KEY_DELETE",59:"KEY_SEMICOLON",91:"KEY_WINDOWS_LEFT",92:"KEY_WINDOWS_RIGHT",93:"KEY_SELECT",106:"KEY_NUM_PAD_ASTERISK",107:"KEY_NUM_PAD_PLUS_SIGN",109:"KEY_NUM_PAD_HYPHEN-MINUS",110:"KEY_NUM_PAD_FULL_STOP",111:"KEY_NUM_PAD_SOLIDUS",144:"KEY_NUM_LOCK",145:"KEY_SCROLL_LOCK",186:"KEY_SEMICOLON",187:"KEY_EQUALS_SIGN",188:"KEY_COMMA",189:"KEY_HYPHEN-MINUS",190:"KEY_FULL_STOP",191:"KEY_SOLIDUS",192:"KEY_GRAVE_ACCENT",219:"KEY_LEFT_SQUARE_BRACKET",220:"KEY_REVERSE_SOLIDUS",221:"KEY_RIGHT_SQUARE_BRACKET",222:"KEY_APOSTROPHE"};
+for(var i=48;i<=57;i++){
+MochiKit.Signal._specialKeys[i]="KEY_"+(i-48);
+}
+for(i=65;i<=90;i++){
+MochiKit.Signal._specialKeys[i]="KEY_"+String.fromCharCode(i);
+}
+for(i=96;i<=105;i++){
+MochiKit.Signal._specialKeys[i]="KEY_NUM_PAD_"+(i-96);
+}
+for(i=112;i<=123;i++){
+MochiKit.Signal._specialKeys[i]="KEY_F"+(i-112+1);
+}
+MochiKit.Base.update(MochiKit.Signal,{__repr__:function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+},toString:function(){
+return this.__repr__();
+},_unloadCache:function(){
+var self=MochiKit.Signal;
+var _548=self._observers;
+for(var i=0;i<_548.length;i++){
+self._disconnect(_548[i]);
+}
+delete self._observers;
+try{
+window.onload=undefined;
+}
+catch(e){
+}
+try{
+window.onunload=undefined;
+}
+catch(e){
+}
+},_listener:function(src,func,obj,_549){
+var E=MochiKit.Signal.Event;
+if(!_549){
+return MochiKit.Base.bind(func,obj);
+}
+obj=obj||src;
+if(typeof (func)=="string"){
+return function(_551){
+obj[func].apply(obj,[new E(src,_551)]);
+};
+}else{
+return function(_552){
+func.apply(obj,[new E(src,_552)]);
+};
+}
+},connect:function(src,sig,_554,_555){
+src=MochiKit.DOM.getElement(src);
+var self=MochiKit.Signal;
+if(typeof (sig)!="string"){
+throw new Error("'sig' must be a string");
+}
+var obj=null;
+var func=null;
+if(typeof (_555)!="undefined"){
+obj=_554;
+func=_555;
+if(typeof (_555)=="string"){
+if(typeof (_554[_555])!="function"){
+throw new Error("'funcOrStr' must be a function on 'objOrFunc'");
+}
+}else{
+if(typeof (_555)!="function"){
+throw new Error("'funcOrStr' must be a function or string");
+}
+}
+}else{
+if(typeof (_554)!="function"){
+throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given");
+}else{
+func=_554;
+}
+}
+if(typeof (obj)=="undefined"||obj===null){
+obj=src;
+}
+var _556=!!(src.addEventListener||src.attachEvent);
+var _557=self._listener(src,func,obj,_556);
+if(src.addEventListener){
+src.addEventListener(sig.substr(2),_557,false);
+}else{
+if(src.attachEvent){
+src.attachEvent(sig,_557);
+}
+}
+var _558=[src,sig,_557,_556,_554,_555];
+self._observers.push(_558);
+return _558;
+},_disconnect:function(_559){
+if(!_559[3]){
+return;
+}
+var src=_559[0];
+var sig=_559[1];
+var _560=_559[2];
+if(src.removeEventListener){
+src.removeEventListener(sig.substr(2),_560,false);
+}else{
+if(src.detachEvent){
+src.detachEvent(sig,_560);
+}else{
+throw new Error("'src' must be a DOM element");
+}
+}
+},disconnect:function(_561){
+var self=MochiKit.Signal;
+var _562=self._observers;
+var m=MochiKit.Base;
+if(arguments.length>1){
+var src=MochiKit.DOM.getElement(arguments[0]);
+var sig=arguments[1];
+var obj=arguments[2];
+var func=arguments[3];
+for(var i=_562.length-1;i>=0;i--){
+var o=_562[i];
+if(o[0]===src&&o[1]===sig&&o[4]===obj&&o[5]===func){
+self._disconnect(o);
+_562.splice(i,1);
+return true;
+}
+}
+}else{
+var idx=m.findIdentical(_562,_561);
+if(idx>=0){
+self._disconnect(_561);
+_562.splice(idx,1);
+return true;
+}
+}
+return false;
+},disconnectAll:function(src,sig){
+src=MochiKit.DOM.getElement(src);
+var m=MochiKit.Base;
+var _563=m.flattenArguments(m.extend(null,arguments,1));
+var self=MochiKit.Signal;
+var _564=self._disconnect;
+var _565=self._observers;
+if(_563.length===0){
+for(var i=_565.length-1;i>=0;i--){
+var _566=_565[i];
+if(_566[0]===src){
+_564(_566);
+_565.splice(i,1);
+}
+}
+}else{
+var sigs={};
+for(var i=0;i<_563.length;i++){
+sigs[_563[i]]=true;
+}
+for(var i=_565.length-1;i>=0;i--){
+var _566=_565[i];
+if(_566[0]===src&&_566[1] in sigs){
+_564(_566);
+_565.splice(i,1);
+}
+}
+}
+},signal:function(src,sig){
+var _568=MochiKit.Signal._observers;
+src=MochiKit.DOM.getElement(src);
+var args=MochiKit.Base.extend(null,arguments,2);
+var _569=[];
+for(var i=0;i<_568.length;i++){
+var _570=_568[i];
+if(_570[0]===src&&_570[1]===sig){
+try{
+_570[2].apply(src,args);
+}
+catch(e){
+_569.push(e);
+}
+}
+}
+if(_569.length==1){
+throw _569[0];
+}else{
+if(_569.length>1){
+var e=new Error("Multiple errors thrown in handling 'sig', see errors property");
+e.errors=_569;
+throw e;
+}
+}
+}});
+MochiKit.Signal.EXPORT_OK=[];
+MochiKit.Signal.EXPORT=["connect","disconnect","signal","disconnectAll"];
+MochiKit.Signal.__new__=function(win){
+var m=MochiKit.Base;
+this._document=document;
+this._window=win;
+try{
+this.connect(window,"onunload",this._unloadCache);
+}
+catch(e){
+}
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+};
+MochiKit.Signal.__new__(this);
+if(!MochiKit.__compat__){
+connect=MochiKit.Signal.connect;
+disconnect=MochiKit.Signal.disconnect;
+disconnectAll=MochiKit.Signal.disconnectAll;
+signal=MochiKit.Signal.signal;
+}
+MochiKit.Base._exportSymbols(this,MochiKit.Signal);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Visual");
+dojo.require("MochiKit.Base");
+dojo.require("MochiKit.DOM");
+dojo.require("MochiKit.Color");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+JSAN.use("MochiKit.DOM",[]);
+JSAN.use("MochiKit.Color",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.DOM)=="undefined"||typeof (MochiKit.Color)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Visual depends on MochiKit.Base, MochiKit.DOM and MochiKit.Color!";
+}
+if(typeof (MochiKit.Visual)=="undefined"){
+MochiKit.Visual={};
+}
+MochiKit.Visual.NAME="MochiKit.Visual";
+MochiKit.Visual.VERSION="1.3.1";
+MochiKit.Visual.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Visual.toString=function(){
+return this.__repr__();
+};
+MochiKit.Visual._RoundCorners=function(e,_571){
+e=MochiKit.DOM.getElement(e);
+this._setOptions(_571);
+if(this.options.__unstable__wrapElement){
+e=this._doWrap(e);
+}
+var _572=this.options.color;
+var C=MochiKit.Color.Color;
+if(this.options.color=="fromElement"){
+_572=C.fromBackground(e);
+}else{
+if(!(_572 instanceof C)){
+_572=C.fromString(_572);
+}
+}
+this.isTransparent=(_572.asRGB().a<=0);
+var _574=this.options.bgColor;
+if(this.options.bgColor=="fromParent"){
+_574=C.fromBackground(e.offsetParent);
+}else{
+if(!(_574 instanceof C)){
+_574=C.fromString(_574);
+}
+}
+this._roundCornersImpl(e,_572,_574);
+};
+MochiKit.Visual._RoundCorners.prototype={_doWrap:function(e){
+var _575=e.parentNode;
+var doc=MochiKit.DOM.currentDocument();
+if(typeof (doc.defaultView)=="undefined"||doc.defaultView===null){
+return e;
+}
+var _576=doc.defaultView.getComputedStyle(e,null);
+if(typeof (_576)=="undefined"||_576===null){
+return e;
+}
+var _577=MochiKit.DOM.DIV({"style":{display:"block",marginTop:_576.getPropertyValue("padding-top"),marginRight:_576.getPropertyValue("padding-right"),marginBottom:_576.getPropertyValue("padding-bottom"),marginLeft:_576.getPropertyValue("padding-left"),padding:"0px"}});
+_577.innerHTML=e.innerHTML;
+e.innerHTML="";
+e.appendChild(_577);
+return e;
+},_roundCornersImpl:function(e,_578,_579){
+if(this.options.border){
+this._renderBorder(e,_579);
+}
+if(this._isTopRounded()){
+this._roundTopCorners(e,_578,_579);
+}
+if(this._isBottomRounded()){
+this._roundBottomCorners(e,_578,_579);
+}
+},_renderBorder:function(el,_580){
+var _581="1px solid "+this._borderColor(_580);
+var _582="border-left: "+_581;
+var _583="border-right: "+_581;
+var _584="style='"+_582+";"+_583+"'";
+el.innerHTML="<div "+_584+">"+el.innerHTML+"</div>";
+},_roundTopCorners:function(el,_585,_586){
+var _587=this._createCorner(_586);
+for(var i=0;i<this.options.numSlices;i++){
+_587.appendChild(this._createCornerSlice(_585,_586,i,"top"));
+}
+el.style.paddingTop=0;
+el.insertBefore(_587,el.firstChild);
+},_roundBottomCorners:function(el,_588,_589){
+var _590=this._createCorner(_589);
+for(var i=(this.options.numSlices-1);i>=0;i--){
+_590.appendChild(this._createCornerSlice(_588,_589,i,"bottom"));
+}
+el.style.paddingBottom=0;
+el.appendChild(_590);
+},_createCorner:function(_591){
+var dom=MochiKit.DOM;
+return dom.DIV({style:{backgroundColor:_591.toString()}});
+},_createCornerSlice:function(_592,_593,n,_594){
+var _595=MochiKit.DOM.SPAN();
+var _596=_595.style;
+_596.backgroundColor=_592.toString();
+_596.display="block";
+_596.height="1px";
+_596.overflow="hidden";
+_596.fontSize="1px";
+var _597=this._borderColor(_592,_593);
+if(this.options.border&&n===0){
+_596.borderTopStyle="solid";
+_596.borderTopWidth="1px";
+_596.borderLeftWidth="0px";
+_596.borderRightWidth="0px";
+_596.borderBottomWidth="0px";
+_596.height="0px";
+_596.borderColor=_597.toString();
+}else{
+if(_597){
+_596.borderColor=_597.toString();
+_596.borderStyle="solid";
+_596.borderWidth="0px 1px";
+}
+}
+if(!this.options.compact&&(n==(this.options.numSlices-1))){
+_596.height="2px";
+}
+this._setMargin(_595,n,_594);
+this._setBorder(_595,n,_594);
+return _595;
+},_setOptions:function(_598){
+this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false,__unstable__wrapElement:false};
+MochiKit.Base.update(this.options,_598);
+this.options.numSlices=(this.options.compact?2:4);
+},_whichSideTop:function(){
+var _599=this.options.corners;
+if(this._hasString(_599,"all","top")){
+return "";
+}
+var _600=(_599.indexOf("tl")!=-1);
+var _601=(_599.indexOf("tr")!=-1);
+if(_600&&_601){
+return "";
+}
+if(_600){
+return "left";
+}
+if(_601){
+return "right";
+}
+return "";
+},_whichSideBottom:function(){
+var _602=this.options.corners;
+if(this._hasString(_602,"all","bottom")){
+return "";
+}
+var _603=(_602.indexOf("bl")!=-1);
+var _604=(_602.indexOf("br")!=-1);
+if(_603&&_604){
+return "";
+}
+if(_603){
+return "left";
+}
+if(_604){
+return "right";
+}
+return "";
+},_borderColor:function(_605,_606){
+if(_605=="transparent"){
+return _606;
+}else{
+if(this.options.border){
+return this.options.border;
+}else{
+if(this.options.blend){
+return _606.blendedColor(_605);
+}
+}
+}
+return "";
+},_setMargin:function(el,n,_607){
+var _608=this._marginSize(n)+"px";
+var _609=(_607=="top"?this._whichSideTop():this._whichSideBottom());
+var _610=el.style;
+if(_609=="left"){
+_610.marginLeft=_608;
+_610.marginRight="0px";
+}else{
+if(_609=="right"){
+_610.marginRight=_608;
+_610.marginLeft="0px";
+}else{
+_610.marginLeft=_608;
+_610.marginRight=_608;
+}
+}
+},_setBorder:function(el,n,_611){
+var _612=this._borderSize(n)+"px";
+var _613=(_611=="top"?this._whichSideTop():this._whichSideBottom());
+var _614=el.style;
+if(_613=="left"){
+_614.borderLeftWidth=_612;
+_614.borderRightWidth="0px";
+}else{
+if(_613=="right"){
+_614.borderRightWidth=_612;
+_614.borderLeftWidth="0px";
+}else{
+_614.borderLeftWidth=_612;
+_614.borderRightWidth=_612;
+}
+}
+},_marginSize:function(n){
+if(this.isTransparent){
+return 0;
+}
+var o=this.options;
+if(o.compact&&o.blend){
+var _615=[1,0];
+return _615[n];
+}else{
+if(o.compact){
+var _616=[2,1];
+return _616[n];
+}else{
+if(o.blend){
+var _617=[3,2,1,0];
+return _617[n];
+}else{
+var _618=[5,3,2,1];
+return _618[n];
+}
+}
+}
+},_borderSize:function(n){
+var o=this.options;
+var _619;
+if(o.compact&&(o.blend||this.isTransparent)){
+return 1;
+}else{
+if(o.compact){
+_619=[1,0];
+}else{
+if(o.blend){
+_619=[2,1,1,1];
+}else{
+if(o.border){
+_619=[0,2,0,0];
+}else{
+if(this.isTransparent){
+_619=[5,3,2,1];
+}else{
+return 0;
+}
+}
+}
+}
+}
+return _619[n];
+},_hasString:function(str){
+for(var i=1;i<arguments.length;i++){
+if(str.indexOf(arguments[i])!=-1){
+return true;
+}
+}
+return false;
+},_isTopRounded:function(){
+return this._hasString(this.options.corners,"all","top","tl","tr");
+},_isBottomRounded:function(){
+return this._hasString(this.options.corners,"all","bottom","bl","br");
+},_hasSingleTextChild:function(el){
+return (el.childNodes.length==1&&el.childNodes[0].nodeType==3);
+}};
+MochiKit.Visual.roundElement=function(e,_620){
+new MochiKit.Visual._RoundCorners(e,_620);
+};
+MochiKit.Visual.roundClass=function(_621,_622,_623){
+var _624=MochiKit.DOM.getElementsByTagAndClassName(_621,_622);
+for(var i=0;i<_624.length;i++){
+MochiKit.Visual.roundElement(_624[i],_623);
+}
+};
+MochiKit.Visual.Color=MochiKit.Color.Color;
+MochiKit.Visual.getElementsComputedStyle=MochiKit.DOM.computedStyle;
+MochiKit.Visual.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+MochiKit.Visual.EXPORT=["roundElement","roundClass"];
+MochiKit.Visual.EXPORT_OK=[];
+MochiKit.Visual.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.Visual);
+if(typeof (MochiKit)=="undefined"){
+MochiKit={};
+}
+if(typeof (MochiKit.MochiKit)=="undefined"){
+MochiKit.MochiKit={};
+}
+MochiKit.MochiKit.NAME="MochiKit.MochiKit";
+MochiKit.MochiKit.VERSION="1.3.1";
+MochiKit.MochiKit.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.MochiKit.toString=function(){
+return this.__repr__();
+};
+MochiKit.MochiKit.SUBMODULES=["Base","Iter","Logging","DateTime","Format","Async","DOM","LoggingPane","Color","Signal","Visual"];
+if(typeof (JSAN)!="undefined"||typeof (dojo)!="undefined"){
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.MochiKit");
+dojo.require("MochiKit.*");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+JSAN.use("MochiKit.Iter",[]);
+JSAN.use("MochiKit.Logging",[]);
+JSAN.use("MochiKit.DateTime",[]);
+JSAN.use("MochiKit.Format",[]);
+JSAN.use("MochiKit.Async",[]);
+JSAN.use("MochiKit.DOM",[]);
+JSAN.use("MochiKit.LoggingPane",[]);
+JSAN.use("MochiKit.Color",[]);
+JSAN.use("MochiKit.Signal",[]);
+JSAN.use("MochiKit.Visual",[]);
+}
+(function(){
+var _625=MochiKit.Base.extend;
+var self=MochiKit.MochiKit;
+var _626=self.SUBMODULES;
+var _627=[];
+var _628=[];
+var _629={};
+var i,k,m,all;
+for(i=0;i<_626.length;i++){
+m=MochiKit[_626[i]];
+_625(_627,m.EXPORT);
+_625(_628,m.EXPORT_OK);
+for(k in m.EXPORT_TAGS){
+_629[k]=_625(_629[k],m.EXPORT_TAGS[k]);
+}
+all=m.EXPORT_TAGS[":all"];
+if(!all){
+all=_625(null,m.EXPORT,m.EXPORT_OK);
+}
+var j;
+for(j=0;j<all.length;j++){
+k=all[j];
+self[k]=m[k];
+}
+}
+self.EXPORT=_627;
+self.EXPORT_OK=_628;
+self.EXPORT_TAGS=_629;
+}());
+}else{
+if(typeof (MochiKit.__compat__)=="undefined"){
+MochiKit.__compat__=true;
+}
+(function(){
+var _630=document.getElementsByTagName("script");
+var _631="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+var base=null;
+var _632=null;
+var _633={};
+var i;
+for(i=0;i<_630.length;i++){
+var src=_630[i].getAttribute("src");
+if(!src){
+continue;
+}
+_633[src]=true;
+if(src.match(/MochiKit.js$/)){
+base=src.substring(0,src.lastIndexOf("MochiKit.js"));
+_632=_630[i];
+}
+}
+if(base===null){
+return;
+}
+var _634=MochiKit.MochiKit.SUBMODULES;
+for(var i=0;i<_634.length;i++){
+if(MochiKit[_634[i]]){
+continue;
+}
+var uri=base+_634[i]+".js";
+if(uri in _633){
+continue;
+}
+if(document.documentElement&&document.documentElement.namespaceURI==_631){
+var s=document.createElementNS(_631,"script");
+s.setAttribute("id","MochiKit_"+base+_634[i]);
+s.setAttribute("src",uri);
+s.setAttribute("type","application/x-javascript");
+_632.parentNode.appendChild(s);
+}else{
+document.write("<script src=\""+uri+"\" type=\"text/javascript\"></script>");
+}
+}
+})();
+}
+
+
diff --git a/share/web/static/js/MochiKit/__package__.js b/share/web/static/js/MochiKit/__package__.js
new file mode 100644
index 0000000..2f5be0d
--- /dev/null
+++ b/share/web/static/js/MochiKit/__package__.js
@@ -0,0 +1,2 @@
+dojo.hostenv.conditionalLoadModule({"common": ["MochiKit.MochiKit"]});
+dojo.hostenv.moduleLoaded("MochiKit.*");
diff --git a/share/web/static/js/PlotKit/Base.js b/share/web/static/js/PlotKit/Base.js
new file mode 100644
index 0000000..49988c9
--- /dev/null
+++ b/share/web/static/js/PlotKit/Base.js
@@ -0,0 +1,406 @@
+/*
+    PlotKit
+    =======
+    PlotKit is a collection of Javascript classes that allows
+    you to quickly visualise data using different types of charts.
+
+    For license/info/documentation: http://www.liquidx.net/plotkit/
+
+    Copyright
+    ---------
+    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
+    For use under the BSD license. <http://www.liquidx.net/plotkit>
+*/
+
+// --------------------------------------------------------------------
+// Check required components
+// --------------------------------------------------------------------
+
+try {    
+    if (typeof(MochiKit.Base) == 'undefined'   ||
+        typeof(MochiKit.DOM) == 'undefined'    ||
+        typeof(MochiKit.Color) == 'undefined'  ||
+        typeof(MochiKit.Format) == 'undefined')
+    {
+        throw "";    
+    }
+} 
+catch (e) {    
+    throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format}"
+}
+
+// -------------------------------------------------------------------
+// Inject Common Shortcuts we use into MochiKit.Color.Color
+// -------------------------------------------------------------------
+
+MochiKit.Base.update(MochiKit.Color.Color.prototype, {
+    asFillColor: function() {
+        return this.lighterColorWithLevel(0.3);
+    },
+        
+    asStrokeColor: function() {
+        return this.darkerColorWithLevel(0.1);
+    },
+
+    asPointColor: function() {
+        return this.lighterColorWithLevel(0.1);
+    }
+});
+
+
+// -------------------------------------------------------------------
+// Define our own PlotKit namespace
+// -------------------------------------------------------------------
+
+if (typeof(PlotKit) == 'undefined') {
+    PlotKit = {};
+}
+
+PlotKit.NAME = "PlotKit";
+PlotKit.VERSION = "0.8";
+PlotKit.__repr__ = function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.toString = function() {
+    return this.__repr__();
+}
+
+// -------------------------------------------------------------------
+//  Encapsulate all our utility function into it's own namespace.
+// -------------------------------------------------------------------
+
+if (typeof(PlotKit.Base) == 'undefined') {
+    PlotKit.Base = {};
+}
+
+PlotKit.Base.NAME = 'PlotKit.Base';
+PlotKit.Base.VERSION = PlotKit.VERSION;
+
+PlotKit.Base.__repr__ = function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.Base.toString = function() {
+    return this.__repr__();
+}
+
+
+// Detect whether we are using prototype.js
+PlotKit.Base.usingPrototype =  function() {
+    try {
+        return (typeof(Object.extend) == 'function');
+    }
+    catch (e) {
+        return false;
+    }
+}
+
+
+MochiKit.Base.update(PlotKit.Base, {
+    roundInterval: function(range, intervals, precision) {
+        // We want to make the interval look regular,
+        var trunc = MochiKit.Format.roundToFixed;
+        var sep = range/intervals;
+        return parseFloat(trunc(sep, precision));
+    },
+
+    collapse: function(lst) {
+        var m = MochiKit.Base;
+        var biggerList = new Array();
+        for (var i = 0; i < lst.length; i++) {
+            biggerList = m.concat(biggerList, lst[i]);
+        }
+        if (PlotKit.Base.usingPrototype()) {
+            delete biggerList.extend;
+            delete biggerList.from;
+            delete biggerList.inspect;
+        }
+        
+        return biggerList;
+    },
+    
+    uniq: function(sortedList) {
+        // get unique elements in list, exactly the same as unix shell's uniq.
+        var m = MochiKit.Base;
+        
+        if (!m.isArrayLike(sortedList) || (sortedList.length < 1))
+            return new Array();
+
+        var uniq = new Array();
+        var lastElem = sortedList[0];    
+        uniq.push(sortedList[0]);
+        for (var i = 1; i < sortedList.length; i++) {
+            if (m.compare(sortedList[i], lastElem) != 0) {
+                lastElem = sortedList[i];
+                uniq.push(sortedList[i]);            
+            }
+        }
+        return uniq;
+    },
+    
+    colorScheme: function() {
+        var mb = MochiKit.Base;
+        var mc = MochiKit.Color
+        var scheme = ["red", "orange", "yellow", "green", "cyan", "blue", "purple", "magenta"];
+        
+        var makeColor = function(name) {
+            return mc.Color[name + "Color"]()
+        };
+        
+        return mb.map(makeColor, scheme);
+    },
+
+    baseDarkPrimaryColors: function () {
+        var hexColor = MochiKit.Color.Color.fromHexString;
+        return [hexColor("#ad3f40"),
+                hexColor("#ddac2c"),
+                hexColor("#dfdd0c"),
+                hexColor("#5276c4"),
+                hexColor("#739c5a")];
+    },
+
+    basePrimaryColors: function () {
+        var hexColor = MochiKit.Color.Color.fromHexString;
+        return [hexColor("#d24c4d"),
+                hexColor("#f2b32f"),
+                hexColor("#ece90e"),
+                hexColor("#5d83da"),
+                hexColor("#78a15d")];
+    },
+    
+    baseBlueColors: function () {
+         var hexColor = MochiKit.Color.Color.fromHexString;
+         return [hexColor("#4b6b94"), hexColor("#5d81b4"), hexColor("#acbad2")];
+    },
+
+    palette: function(baseColor, fromLevel, toLevel, increment) {
+        var isNil = MochiKit.Base.isUndefinedOrNull;
+        var fractions = new Array();
+        if (isNil(increment))
+            increment = 0.1;
+        if (isNil(toLevel))
+            toLevel = 0.4;
+        if (isNil(fromLevel))
+            fromLevel = -0.2;
+
+        var level = fromLevel;
+        while (level <= toLevel) {
+            fractions.push(level);
+            level += increment;
+        }
+            
+        var makeColor = function(color, fraction) {
+            return color.lighterColorWithLevel(fraction);
+        };
+        return MochiKit.Base.map(partial(makeColor, baseColor), fractions);
+    },
+    
+    excanvasSupported: function() {
+         if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+             return true;
+         }
+         return false;
+    },
+
+    // The following functions are from quirksmode.org
+    // http://www.quirksmode.org/js/findpos.html
+
+    findPosX: function(obj) {
+        var curleft = 0;
+        if (obj.offsetParent) {
+            while (obj.offsetParent) {
+                    curleft += obj.offsetLeft
+                        obj = obj.offsetParent;
+            }
+        }
+        else if (obj.x)
+            curleft += obj.x;
+        return curleft;
+    },
+                       
+   findPosY: function(obj) {
+       var curtop = 0;
+       if (obj.offsetParent) {
+           while (obj.offsetParent) {
+               curtop += obj.offsetTop
+               obj = obj.offsetParent;
+           }
+       }
+       else if (obj.y)
+           curtop += obj.y;
+       return curtop;
+   },
+   
+   isFuncLike: function(obj) {
+       return (typeof(obj) == 'function');
+   }
+});    
+
+//
+// Prototype.js aware (crippled) versions of map and items.
+//
+
+PlotKit.Base.map = function(fn, lst) {
+    if (PlotKit.Base.usingPrototype()) {
+        var rval = [];
+        for (var x in lst) {
+            if (typeof(lst[x]) == 'function') continue;
+            rval.push(fn(lst[x]));
+        }
+        return rval;
+    }
+    else {
+        return MochiKit.Base.map(fn, lst);
+    }
+};
+
+PlotKit.Base.items = function(lst) {
+    if (PlotKit.Base.usingPrototype()) {
+        var rval = [];
+         for (var x in lst) {
+             if (typeof(lst[x]) == 'function') continue;
+             rval.push([x, lst[x]]);
+         }
+         return rval;
+    }
+    else {
+        return MochiKit.Base.items(lst);
+    }
+};
+
+PlotKit.Base.keys = function(lst) {
+    if (PlotKit.Base.usingPrototype()) {
+        var rval = [];
+         for (var x in lst) {
+             if (typeof(lst[x]) == 'function') continue;
+             rval.push(x);
+         }
+         return rval;
+    }
+    else {
+        return MochiKit.Base.keys(lst);
+    }
+};
+
+// 
+// colour schemes
+//
+
+PlotKit.Base.baseColors = function () {
+   var hexColor = MochiKit.Color.Color.fromHexString;
+   return [hexColor("#476fb2"),
+           hexColor("#be2c2b"),
+           hexColor("#85b730"),
+           hexColor("#734a99"),
+           hexColor("#26a1c5"),
+           hexColor("#fb8707"),
+           hexColor("#000000")];
+};
+
+PlotKit.Base.officeBaseStyle = {
+    "axisLineWidth": 2.0,
+    "axisLabelColor": Color.grayColor(),
+    "axisLineColor": Color.whiteColor(),
+    "padding": {top: 5, bottom: 10, left: 30, right: 30}
+};    
+
+MochiKit.Base.update(PlotKit.Base,{
+    officeBlue: function() {
+        var r = {
+        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),
+        "backgroundColor": PlotKit.Base.baseColors()[0].lighterColorWithLevel(0.45)
+        };
+        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
+        return r;
+    },
+    officeRed: function() {
+        var r = {
+        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),
+        "backgroundColor": PlotKit.Base.baseColors()[1].lighterColorWithLevel(0.5)
+        };
+        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
+        return r;
+    },
+    officeGreen: function() {
+        var r = {
+        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[2]),
+        "backgroundColor": PlotKit.Base.baseColors()[2].lighterColorWithLevel(0.5)
+        };
+        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
+        return r;
+    },
+    officePurple: function() {
+        var r = {
+        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[3]),
+        "backgroundColor": PlotKit.Base.baseColors()[3].lighterColorWithLevel(0.5)
+        };
+        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
+        return r;
+    },
+    
+    officeCyan: function() {
+        var r = {
+            "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[4]),
+            "backgroundColor": PlotKit.Base.baseColors()[4].lighterColorWithLevel(0.5)
+            };
+        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
+        return r;
+    },
+    
+    officeOrange: function() {
+        var r = {
+            "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[5]),
+            "backgroundColor": PlotKit.Base.baseColors()[5].lighterColorWithLevel(0.4)
+            };
+        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
+        return r;
+    },
+    
+    officeBlack: function() {
+        var r = {
+        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[6], 0.0, 0.6),
+        "backgroundColor": PlotKit.Base.baseColors()[6].lighterColorWithLevel(0.9)
+        };
+        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
+        return r;
+    }
+});
+
+
+PlotKit.Base.EXPORT = [
+   "baseColors",
+   "collapse",
+   "colorScheme",
+   "findPosX",
+   "findPosY",
+   "officeBaseStyle",
+   "officeBlue",
+   "officeRed",
+   "officeGreen",
+   "officePurple",
+   "officeCyan",
+   "officeOrange",
+   "officeBlack",
+   "roundInterval",
+   "uniq",
+   "isFuncLike",
+   "excanvasSupported"
+];
+
+PlotKit.Base.EXPORT_OK = [];
+
+PlotKit.Base.__new__ = function() {
+    var m = MochiKit.Base;
+    
+    m.nameFunctions(this);
+    
+    this.EXPORT_TAGS = {
+        ":common": this.EXPORT,
+        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
+    };
+};
+
+PlotKit.Base.__new__();
+MochiKit.Base._exportSymbols(this, PlotKit.Base);
+
diff --git a/share/web/static/js/PlotKit/Canvas.js b/share/web/static/js/PlotKit/Canvas.js
new file mode 100644
index 0000000..1e88e3c
--- /dev/null
+++ b/share/web/static/js/PlotKit/Canvas.js
@@ -0,0 +1,683 @@
+/* 
+    PlotKit Canvas
+    ==============
+    
+    Provides HTML Canvas Renderer. This is supported under:
+    
+    - Safari 2.0
+    - Mozilla Firefox 1.5
+    - Opera 9.0 preview 2
+    - IE 6 (via VML Emulation)
+    
+    It uses DIVs for labels.
+    
+    Copyright
+    ---------
+    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
+    For use under the BSD license. <http://www.liquidx.net/plotkit>
+    
+*/
+// --------------------------------------------------------------------
+// Check required components
+// --------------------------------------------------------------------
+
+try {    
+    if ((typeof(PlotKit.Base) == 'undefined') ||
+        (typeof(PlotKit.Layout) == 'undefined'))
+    {
+        throw "";    
+    }
+} 
+catch (e) {    
+    throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Base,Layout}"
+}
+
+
+// ------------------------------------------------------------------------
+//  Defines the renderer class
+// ------------------------------------------------------------------------
+
+if (typeof(PlotKit.CanvasRenderer) == 'undefined') {
+    PlotKit.CanvasRenderer = {};
+}
+
+PlotKit.CanvasRenderer.NAME = "PlotKit.CanvasRenderer";
+PlotKit.CanvasRenderer.VERSION = PlotKit.VERSION;
+
+PlotKit.CanvasRenderer.__repr__ = function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.CanvasRenderer.toString = function() {
+    return this.__repr__();
+}
+
+PlotKit.CanvasRenderer = function(element, layout, options) {
+    if (arguments.length  > 0)
+        this.__init__(element, layout, options);
+};
+
+PlotKit.CanvasRenderer.prototype.__init__ = function(element, layout, options) {
+    var isNil = MochiKit.Base.isUndefinedOrNull;
+    var Color = MochiKit.Color.Color;
+    
+    // default options
+    this.options = {
+        "drawBackground": true,
+        "backgroundColor": Color.whiteColor(),
+        "padding": {left: 30, right: 30, top: 5, bottom: 10},
+        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),
+        "strokeColor": Color.whiteColor(),
+        "strokeColorTransform": "asStrokeColor",
+        "strokeWidth": 0.5,
+        "shouldFill": true,
+        "shouldStroke": true,
+        "drawXAxis": true,
+        "drawYAxis": true,
+        "axisLineColor": Color.blackColor(),
+        "axisLineWidth": 0.5,
+        "axisTickSize": 3,
+        "axisLabelColor": Color.blackColor(),
+        "axisLabelFont": "Arial",
+        "axisLabelFontSize": 9,
+		"axisLabelWidth": 50,
+		"pieRadius": 0.4,
+        "enableEvents": true
+    };
+    MochiKit.Base.update(this.options, options ? options : {});
+
+    this.layout = layout;
+    this.element = MochiKit.DOM.getElement(element);
+    this.container = this.element.parentNode;
+
+    // Stuff relating to Canvas on IE support    
+    this.isIE = PlotKit.Base.excanvasSupported();
+
+    if (this.isIE && !isNil(G_vmlCanvasManager)) {
+        this.IEDelay = 0.5;
+        this.maxTries = 5;
+        this.renderDelay = null;
+        this.clearDelay = null;
+        this.element = G_vmlCanvasManager.initElement(this.element);
+    }
+
+    this.height = this.element.height;
+    this.width = this.element.width;
+
+    // --- check whether everything is ok before we return
+
+    if (isNil(this.element))
+        throw "CanvasRenderer() - passed canvas is not found";
+
+    if (!this.isIE && !(PlotKit.CanvasRenderer.isSupported(this.element)))
+        throw "CanvasRenderer() - Canvas is not supported.";
+
+    if (isNil(this.container) || (this.container.nodeName.toLowerCase() != "div"))
+        throw "CanvasRenderer() - <canvas> needs to be enclosed in <div>";
+
+    // internal state
+    this.xlabels = new Array();
+    this.ylabels = new Array();
+    this.isFirstRender = true;
+
+    this.area = {
+        x: this.options.padding.left,
+        y: this.options.padding.top,
+        w: this.width - this.options.padding.left - this.options.padding.right,
+        h: this.height - this.options.padding.top - this.options.padding.bottom
+    };
+
+    MochiKit.DOM.updateNodeAttributes(this.container, 
+    {"style":{ "position": "relative", "width": this.width + "px"}});
+
+    // load event system if we have Signals
+    /* Disabled until we have a proper implementation
+    try {
+        this.event_isinside = null;
+        if (MochiKit.Signal && this.options.enableEvents) {
+            this._initialiseEvents();
+        }
+    }
+    catch (e) {
+        // still experimental
+    }
+    */
+};
+
+PlotKit.CanvasRenderer.prototype.render = function() {
+    if (this.isIE) {
+        // VML takes a while to start up, so we just poll every this.IEDelay
+        try {
+            if (this.renderDelay) {
+                this.renderDelay.cancel();
+                this.renderDelay = null;
+            }
+            var context = this.element.getContext("2d");
+        }
+        catch (e) {
+            this.isFirstRender = false;
+            if (this.maxTries-- > 0) {
+                this.renderDelay = MochiKit.Async.wait(this.IEDelay);
+                this.renderDelay.addCallback(bind(this.render, this));
+            }
+            return;
+        }
+    }
+
+    if (this.options.drawBackground)
+        this._renderBackground();
+
+    if (this.layout.style == "bar") {
+        this._renderBarChart();
+		this._renderBarAxis(); 
+	}
+    else if (this.layout.style == "pie") {
+        this._renderPieChart();
+		this._renderPieAxis();
+	}
+    else if (this.layout.style == "line") {
+        this._renderLineChart();
+		this._renderLineAxis();
+	}
+};
+
+PlotKit.CanvasRenderer.prototype._renderBarChartWrap = function(data, plotFunc) {
+    var context = this.element.getContext("2d");
+    var colorCount = this.options.colorScheme.length;
+    var colorScheme = this.options.colorScheme;
+    var setNames = MochiKit.Base.keys(this.layout.datasets);
+    var setCount = setNames.length;
+
+    for (var i = 0; i < setCount; i++) {
+        var setName = setNames[i];
+        var color = colorScheme[i%colorCount];
+        context.save();
+        context.fillStyle = color.toRGBString();
+        if (this.options.strokeColor)
+            context.strokeStyle = this.options.strokeColor.toRGBString();
+        else if (this.options.strokeColorTransform) 
+            context.strokeStyle = color[this.options.strokeColorTransform]().toRGBString();
+        
+        context.lineWidth = this.options.strokeWidth;
+        var forEachFunc = function(obj) {
+            if (obj.name == setName)
+                plotFunc(context, obj);
+        };                
+
+        MochiKit.Iter.forEach(data, bind(forEachFunc, this));
+        context.restore();
+    }
+};
+
+PlotKit.CanvasRenderer.prototype._renderBarChart = function() {
+    var bind = MochiKit.Base.bind;
+
+    var drawRect = function(context, bar) {
+        var x = this.area.w * bar.x + this.area.x;
+        var y = this.area.h * bar.y + this.area.y;
+        var w = this.area.w * bar.w;
+        var h = this.area.h * bar.h;       
+        if ((w < 1) || (h < 1))
+            return;
+        if (this.options.shouldFill)
+            context.fillRect(x, y, w, h);
+        if (this.options.shouldStroke)
+            context.strokeRect(x, y, w, h);                
+    };
+    this._renderBarChartWrap(this.layout.bars, bind(drawRect, this));
+};
+
+PlotKit.CanvasRenderer.prototype._renderLineChart = function() {
+    var context = this.element.getContext("2d");
+    var colorCount = this.options.colorScheme.length;
+    var colorScheme = this.options.colorScheme;
+    var setNames = MochiKit.Base.keys(this.layout.datasets);
+    var setCount = setNames.length;
+    var bind = MochiKit.Base.bind;
+    var partial = MochiKit.Base.partial;
+
+    for (var i = 0; i < setCount; i++) {
+        var setName = setNames[i];
+        var color = colorScheme[i%colorCount];
+        var strokeX = this.options.strokeColorTransform;
+
+        // setup graphics context
+        context.save();
+        context.fillStyle = color.toRGBString();
+        if (this.options.strokeColor)
+            context.strokeStyle = this.options.strokeColor.toRGBString();
+        else if (this.options.strokeColorTransform) 
+            context.strokeStyle = color[strokeX]().toRGBString();
+        
+        context.lineWidth = this.options.strokeWidth;
+        
+        // create paths
+        var makePath = function(ctx) {
+            ctx.beginPath();
+            ctx.moveTo(this.area.x, this.area.y + this.area.h);
+            var addPoint = function(ctx_, point) {
+                if (point.name == setName)
+                    ctx_.lineTo(this.area.w * point.x + this.area.x,
+                                this.area.h * point.y + this.area.y);
+            };
+            MochiKit.Iter.forEach(this.layout.points, partial(addPoint, ctx), this);
+            ctx.lineTo(this.area.w + this.area.x,
+                           this.area.h + this.area.y);
+            ctx.lineTo(this.area.x, this.area.y + this.area.h);
+            ctx.closePath();
+        };
+
+        if (this.options.shouldFill) {
+            bind(makePath, this)(context);
+            context.fill();
+        }
+        if (this.options.shouldStroke) {
+            bind(makePath, this)(context);
+            context.stroke();
+        }
+
+        context.restore();
+    }
+};
+
+PlotKit.CanvasRenderer.prototype._renderPieChart = function() {
+    var context = this.element.getContext("2d");
+    var colorCount = this.options.colorScheme.length;
+    var slices = this.layout.slices;
+
+    var centerx = this.area.x + this.area.w * 0.5;
+    var centery = this.area.y + this.area.h * 0.5;
+    var radius = Math.min(this.area.w * this.options.pieRadius, 
+                          this.area.h * this.options.pieRadius);
+
+    if (this.isIE) {
+        centerx = parseInt(centerx);
+        centery = parseInt(centery);
+        radius = parseInt(radius);
+    }
+
+
+	// NOTE NOTE!! Canvas Tag draws the circle clockwise from the y = 0, x = 1
+	// so we have to subtract 90 degrees to make it start at y = 1, x = 0
+
+    for (var i = 0; i < slices.length; i++) {
+        var color = this.options.colorScheme[i%colorCount];
+        context.save();
+        context.fillStyle = color.toRGBString();
+
+        var makePath = function() {
+            context.beginPath();
+            context.moveTo(centerx, centery);
+            context.arc(centerx, centery, radius, 
+                        slices[i].startAngle - Math.PI/2,
+                        slices[i].endAngle - Math.PI/2,
+                        false);
+            context.lineTo(centerx, centery);
+            context.closePath();
+        };
+
+        if (Math.abs(slices[i].startAngle - slices[i].endAngle) > 0.001) {
+            if (this.options.shouldFill) {
+                makePath();
+                context.fill();
+            }
+            
+            if (this.options.shouldStroke) {
+                makePath();
+                context.lineWidth = this.options.strokeWidth;
+                if (this.options.strokeColor)
+                    context.strokeStyle = this.options.strokeColor.toRGBString();
+                else if (this.options.strokeColorTransform)
+                    context.strokeStyle = color[this.options.strokeColorTransform]().toRGBString();
+                context.stroke();
+            }
+        }
+        context.restore();
+    }
+};
+
+PlotKit.CanvasRenderer.prototype._renderBarAxis = function() {
+	this._renderAxis();
+}
+
+PlotKit.CanvasRenderer.prototype._renderLineAxis = function() {
+	this._renderAxis();
+};
+
+
+PlotKit.CanvasRenderer.prototype._renderAxis = function() {
+    if (!this.options.drawXAxis && !this.options.drawYAxis)
+        return;
+
+    var context = this.element.getContext("2d");
+
+    var labelStyle = {"style":
+         {"position": "absolute",
+          "fontSize": this.options.axisLabelFontSize + "px",
+          "zIndex": 10,
+          "color": this.options.axisLabelColor.toRGBString(),
+          "width": this.options.axisLabelWidth + "px",
+          "overflow": "hidden"
+         }
+    };
+
+    // axis lines
+    context.save();
+    context.strokeStyle = this.options.axisLineColor.toRGBString();
+    context.lineWidth = this.options.axisLineWidth;
+
+
+    if (this.options.drawYAxis) {
+        if (this.layout.yticks) {
+            var drawTick = function(tick) {
+                if (typeof(tick) == "function") return;
+                var x = this.area.x;
+                var y = this.area.y + tick[0] * this.area.h;
+                context.beginPath();
+                context.moveTo(x, y);
+                context.lineTo(x - this.options.axisTickSize, y);
+                context.closePath();
+                context.stroke();
+
+                var label = DIV(labelStyle, tick[1]);
+                label.style.top = (y - this.options.axisLabelFontSize) + "px";
+                label.style.left = (x - this.options.padding.left - this.options.axisTickSize) + "px";
+                label.style.textAlign = "right";
+                label.style.width = (this.options.padding.left - this.options.axisTickSize * 2) + "px";
+                MochiKit.DOM.appendChildNodes(this.container, label);
+                this.ylabels.push(label);
+            };
+            
+            MochiKit.Iter.forEach(this.layout.yticks, bind(drawTick, this));
+        }
+
+        context.beginPath();
+        context.moveTo(this.area.x, this.area.y);
+        context.lineTo(this.area.x, this.area.y + this.area.h);
+        context.closePath();
+        context.stroke();
+    }
+
+    if (this.options.drawXAxis) {
+        if (this.layout.xticks) {
+            var drawTick = function(tick) {
+                if (typeof(dataset) == "function") return;
+                
+                var x = this.area.x + tick[0] * this.area.w;
+                var y = this.area.y + this.area.h;
+                context.beginPath();
+                context.moveTo(x, y);
+                context.lineTo(x, y + this.options.axisTickSize);
+                context.closePath();
+                context.stroke();
+
+                var label = DIV(labelStyle, tick[1]);
+                label.style.top = (y + this.options.axisTickSize) + "px";
+                label.style.left = (x - this.options.axisLabelWidth/2) + "px";
+                label.style.textAlign = "center";
+                label.style.width = this.options.axisLabelWidth + "px";
+                MochiKit.DOM.appendChildNodes(this.container, label);
+                this.xlabels.push(label);
+            };
+            
+            MochiKit.Iter.forEach(this.layout.xticks, bind(drawTick, this));
+        }
+
+        context.beginPath();
+        context.moveTo(this.area.x, this.area.y + this.area.h);
+        context.lineTo(this.area.x + this.area.w, this.area.y + this.area.h);
+        context.closePath();
+        context.stroke();
+    }
+
+    context.restore();
+
+};
+
+PlotKit.CanvasRenderer.prototype._renderPieAxis = function() {
+    if (!this.options.drawXAxis)
+        return;
+
+	if (this.layout.xticks) {
+		// make a lookup dict for x->slice values
+		var lookup = new Array();
+		for (var i = 0; i < this.layout.slices.length; i++) {
+			lookup[this.layout.slices[i].xval] = this.layout.slices[i];
+		}
+		
+		var centerx = this.area.x + this.area.w * 0.5;
+	    var centery = this.area.y + this.area.h * 0.5;
+	    var radius = Math.min(this.area.w * this.options.pieRadius,
+	                          this.area.h * this.options.pieRadius);
+		var labelWidth = this.options.axisLabelWidth;
+		
+		for (var i = 0; i < this.layout.xticks.length; i++) {
+			var slice = lookup[this.layout.xticks[i][0]];
+			if (MochiKit.Base.isUndefinedOrNull(slice))
+				continue;
+				
+				
+			var angle = (slice.startAngle + slice.endAngle)/2;
+			// normalize the angle
+			var normalisedAngle = angle;
+			if (normalisedAngle > Math.PI * 2)
+				normalisedAngle = normalisedAngle - Math.PI * 2;
+			else if (normalisedAngle < 0)
+				normalisedAngle = normalisedAngle + Math.PI * 2;
+				
+			var labelx = centerx + Math.sin(normalisedAngle) * (radius + 10);
+	        var labely = centery - Math.cos(normalisedAngle) * (radius + 10);
+
+			var attrib = {"position": "absolute",
+	                      "zIndex": 11,
+	                      "width": labelWidth + "px",
+	                      "fontSize": this.options.axisLabelFontSize + "px",
+	                      "overflow": "hidden",
+						  "color": this.options.axisLabelColor.toHexString()
+						};
+
+			if (normalisedAngle <= Math.PI * 0.5) {
+	            // text on top and align left
+	            attrib["textAlign"] = "left";
+	            attrib["verticalAlign"] = "top";
+	            attrib["left"] = labelx + "px";
+	            attrib["top"] = (labely - this.options.axisLabelFontSize) + "px";
+	        }
+	        else if ((normalisedAngle > Math.PI * 0.5) && (normalisedAngle <= Math.PI)) {
+	            // text on bottom and align left
+	            attrib["textAlign"] = "left";
+	            attrib["verticalAlign"] = "bottom";     
+	            attrib["left"] = labelx + "px";
+	            attrib["top"] = labely + "px";
+
+	        }
+	        else if ((normalisedAngle > Math.PI) && (normalisedAngle <= Math.PI*1.5)) {
+	            // text on bottom and align right
+	            attrib["textAlign"] = "right";
+	            attrib["verticalAlign"] = "bottom"; 
+	            attrib["left"] = (labelx  - labelWidth) + "px";
+	            attrib["top"] = labely + "px";
+	        }
+	        else {
+	            // text on top and align right
+	            attrib["textAlign"] = "right";
+	            attrib["verticalAlign"] = "bottom";  
+	            attrib["left"] = (labelx  - labelWidth) + "px";
+	            attrib["top"] = (labely - this.options.axisLabelFontSize) + "px";
+	        }
+	
+			var label = DIV({'style': attrib}, this.layout.xticks[i][1]);
+			this.xlabels.push(label);
+			MochiKit.DOM.appendChildNodes(this.container, label);
+	  }
+		
+	}
+};
+
+PlotKit.CanvasRenderer.prototype._renderBackground = function() {
+    var context = this.element.getContext("2d");
+    context.save();
+    context.fillStyle = this.options.backgroundColor.toRGBString();
+    context.fillRect(0, 0, this.width, this.height);
+    context.restore();
+};
+
+PlotKit.CanvasRenderer.prototype.clear = function() {
+    if (this.isIE) {
+        // VML takes a while to start up, so we just poll every this.IEDelay
+        try {
+            if (this.clearDelay) {
+                this.clearDelay.cancel();
+                this.clearDelay = null;
+            }
+            var context = this.element.getContext("2d");
+        }
+        catch (e) {
+            this.isFirstRender = false;
+            this.clearDelay = MochiKit.Async.wait(this.IEDelay);
+            this.clearDelay.addCallback(bind(this.clear, this));
+            return;
+        }
+    }
+
+    var context = this.element.getContext("2d");
+    context.clearRect(0, 0, this.width, this.height);
+
+    MochiKit.Iter.forEach(this.xlabels, MochiKit.DOM.removeElement);
+    MochiKit.Iter.forEach(this.ylabels, MochiKit.DOM.removeElement);
+    this.xlabels = new Array();
+    this.ylabels = new Array();
+};
+
+// ----------------------------------------------------------------
+//  Everything below here is experimental and undocumented.
+// ----------------------------------------------------------------
+
+PlotKit.CanvasRenderer.prototype._initialiseEvents = function() {
+    var connect = MochiKit.Signal.connect;
+    var bind = MochiKit.Base.bind;
+    //MochiKit.Signal.registerSignals(this, ['onmouseover', 'onclick', 'onmouseout', 'onmousemove']);
+    //connect(this.element, 'onmouseover', bind(this.onmouseover, this));
+    //connect(this.element, 'onmouseout', bind(this.onmouseout, this));
+    //connect(this.element, 'onmousemove', bind(this.onmousemove, this));
+    connect(this.element, 'onclick', bind(this.onclick, this));
+};
+
+PlotKit.CanvasRenderer.prototype._resolveObject = function(e) {
+    // does not work in firefox
+	//var x = (e.event().offsetX - this.area.x) / this.area.w;
+	//var y = (e.event().offsetY - this.area.y) / this.area.h;
+
+    var x = (e.mouse().page.x - PlotKit.Base.findPosX(this.element) - this.area.x) / this.area.w;
+    var y = (e.mouse().page.y - PlotKit.Base.findPosY(this.element) - this.area.y) / this.area.h;
+	
+    //log(x, y);
+
+    var isHit = this.layout.hitTest(x, y);
+    if (isHit)
+        return isHit;
+    return null;
+};
+
+PlotKit.CanvasRenderer.prototype._createEventObject = function(layoutObj, e) {
+    if (layoutObj == null) {
+        return null;
+    }
+
+    e.chart = layoutObj
+    return e;
+};
+
+
+PlotKit.CanvasRenderer.prototype.onclick = function(e) {
+    var layoutObject = this._resolveObject(e);
+    var eventObject = this._createEventObject(layoutObject, e);
+    if (eventObject != null)
+        MochiKit.Signal.signal(this, "onclick", eventObject);
+};
+
+PlotKit.CanvasRenderer.prototype.onmouseover = function(e) {
+    var layoutObject = this._resolveObject(e);
+    var eventObject = this._createEventObject(layoutObject, e);
+    if (eventObject != null) 
+        signal(this, "onmouseover", eventObject);
+};
+
+PlotKit.CanvasRenderer.prototype.onmouseout = function(e) {
+    var layoutObject = this._resolveObject(e);
+    var eventObject = this._createEventObject(layoutObject, e);
+    if (eventObject == null)
+        signal(this, "onmouseout", e);
+    else 
+        signal(this, "onmouseout", eventObject);
+
+};
+
+PlotKit.CanvasRenderer.prototype.onmousemove = function(e) {
+    var layoutObject = this._resolveObject(e);
+    var eventObject = this._createEventObject(layoutObject, e);
+
+    if ((layoutObject == null) && (this.event_isinside == null)) {
+        // TODO: should we emit an event anyway?
+        return;
+    }
+
+    if ((layoutObject != null) && (this.event_isinside == null))
+        signal(this, "onmouseover", eventObject);
+
+    if ((layoutObject == null) && (this.event_isinside != null))
+        signal(this, "onmouseout", eventObject);
+
+    if ((layoutObject != null) && (this.event_isinside != null))
+        signal(this, "onmousemove", eventObject);
+
+    this.event_isinside = layoutObject;
+    //log("move", x, y);    
+};
+
+PlotKit.CanvasRenderer.isSupported = function(canvasName) {
+    var canvas = null;
+    try {
+        if (MochiKit.Base.isUndefinedOrNull(canvasName)) 
+            canvas = MochiKit.DOM.CANVAS({});
+        else
+            canvas = MochiKit.DOM.getElement(canvasName);
+        var context = canvas.getContext("2d");
+    }
+    catch (e) {
+        var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
+        var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
+        if ((!ie) || (ie[1] < 6) || (opera))
+            return false;
+        return true;
+    }
+    return true;
+};
+
+// Namespace Iniitialisation
+
+PlotKit.Canvas = {}
+PlotKit.Canvas.CanvasRenderer = PlotKit.CanvasRenderer;
+
+PlotKit.Canvas.EXPORT = [
+    "CanvasRenderer"
+];
+
+PlotKit.Canvas.EXPORT_OK = [
+    "CanvasRenderer"
+];
+
+PlotKit.Canvas.__new__ = function() {
+    var m = MochiKit.Base;
+    
+    m.nameFunctions(this);
+    
+    this.EXPORT_TAGS = {
+        ":common": this.EXPORT,
+        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
+    };
+};
+
+PlotKit.Canvas.__new__();
+MochiKit.Base._exportSymbols(this, PlotKit.Canvas);
+
diff --git a/share/web/static/js/PlotKit/EasyPlot.js b/share/web/static/js/PlotKit/EasyPlot.js
new file mode 100644
index 0000000..7607731
--- /dev/null
+++ b/share/web/static/js/PlotKit/EasyPlot.js
@@ -0,0 +1,161 @@
+/* 
+    PlotKit EasyPlot
+    ================
+
+    User friendly wrapper around the common plotting functions.
+
+    Copyright
+    ---------
+    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
+    For use under the BSD license. <http://www.liquidx.net/plotkit>
+    
+*/
+
+try {    
+    if (typeof(PlotKit.CanvasRenderer) == 'undefined')
+    {
+        throw ""
+    }
+} 
+catch (e) {    
+    throw "PlotKit.EasyPlot depends on all of PlotKit's components";
+}
+
+// --------------------------------------------------------------------
+// Start of EasyPlot definition
+// --------------------------------------------------------------------
+
+if (typeof(PlotKit.EasyPlot) == 'undefined') {
+    PlotKit.EasyPlot = {};
+}
+
+PlotKit.EasyPlot.NAME = "PlotKit.EasyPlot";
+PlotKit.EasyPlot.VERSION = PlotKit.VERSION;
+
+PlotKit.EasyPlot.__repr__ = function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.EasyPlot.toString = function() {
+    return this.__repr__();
+}
+
+// --------------------------------------------------------------------
+// Start of EasyPlot definition
+// --------------------------------------------------------------------
+
+PlotKit.EasyPlot = function(style, options, divElem, datasources) {
+    this.layout = new Layout(style, options);
+    this.divElem = divElem;
+    this.width = parseInt(divElem.getAttribute('width'));
+    this.height = parseInt(divElem.getAttribute('height'));
+    this.deferredCount = 0;
+
+    // make sure we have non-zero width
+    if (this.width < 1) {
+        this.width = this.divElem.width ? this.divElem.width : 300;
+    }
+    
+    if (this.height < 1) {
+        this.height = this.divElem.height ? this.divElem.height : 300;
+    }
+    
+    // load data sources
+    if (isArrayLike(datasources)) {
+        for (var i = 0; i < datasources.length; i++) {
+            if (typeof(datasources[i]) == "string") {
+                this.deferredCount++;
+                // load CSV via ajax
+                var d = MochiKit.Async.doSimpleXMLHttpRequest(datasources[i]);
+                d.addCallback(MochiKit.Base.bind(PlotKit.EasyPlot.onDataLoaded, this));
+            }
+            else if (isArrayLike(datasources[i])) {
+                this.layout.addDataset("data-" + i, datasources[i]);
+            }
+        }
+    }
+    else if (!isUndefinedOrNull(datasources)) {
+        throw "Passed datasources are not Array like";
+    }
+    
+    // setup canvas to render
+    
+    if (CanvasRenderer.isSupported()) {
+        this.element = CANVAS({"id": this.divElem.getAttribute("id") + "-canvas",
+                               "width": this.width,
+                               "height": this.height}, "");
+        this.divElem.appendChild(this.element);
+        this.renderer = new SweetCanvasRenderer(this.element, this.layout, options);
+    }
+    else if (SVGRenderer.isSupported()) {
+        this.element = SVGRenderer.SVG({"id": this.divElem.getAttribute("id") + "-svg",
+                                        "width": this.width,
+                                        "height": this.height,
+                                        "version": "1.1",
+                                        "baseProfile": "full"}, "");
+        this.divElem.appendChild(this.element);
+        this.renderer = new SweetSVGRenderer(this.element, this.layout, options);
+    }
+    
+    if ((this.deferredCount == 0) && (PlotKit.Base.keys(this.layout.datasets).length > 0)) {
+        this.layout.evaluate();
+        this.renderer.clear();
+        this.renderer.render();    
+    }
+    
+};
+
+PlotKit.EasyPlot.onDataLoaded = function(request) {
+    
+    // very primitive CSV parser, should fix to make it more compliant.
+    var table = new Array();
+    var lines = request.responseText.split('\n');
+    for (var i = 0; i < lines.length; i++) {
+        var stripped = MochiKit.Format.strip(lines[i]);
+        if ((stripped.length > 1) && (stripped.charAt(0) != '#')) {
+            table.push(stripped.split(','));
+        }
+    }
+  
+    this.layout.addDataset("data-ajax-" + this.deferredCount, table);
+    this.deferredCount--;
+    
+    if ((this.deferredCount == 0) && (PlotKit.Base.keys(this.layout.datasets).length > 0)) {
+        this.layout.evaluate();
+        this.renderer.clear();
+        this.renderer.render();
+    }
+};
+
+PlotKit.EasyPlot.prototype.reload = function() {
+    this.layout.evaluate();
+    this.renderer.clear();
+    this.renderer.render();
+};
+
+// Namespace Iniitialisation
+
+PlotKit.EasyPlotModule = {};
+PlotKit.EasyPlotModule.EasyPlot = PlotKit.EasyPlot;
+
+PlotKit.EasyPlotModule.EXPORT = [
+    "EasyPlot"
+];
+
+PlotKit.EasyPlotModule.EXPORT_OK = [];
+
+PlotKit.EasyPlotModule.__new__ = function() {
+    var m = MochiKit.Base;
+    
+    m.nameFunctions(this);
+    
+    this.EXPORT_TAGS = {
+        ":common": this.EXPORT,
+        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
+    };
+};
+
+PlotKit.EasyPlotModule.__new__();
+MochiKit.Base._exportSymbols(this, PlotKit.EasyPlotModule);
+
+
diff --git a/share/web/static/js/PlotKit/Layout.js b/share/web/static/js/PlotKit/Layout.js
new file mode 100644
index 0000000..ff1c9db
--- /dev/null
+++ b/share/web/static/js/PlotKit/Layout.js
@@ -0,0 +1,756 @@
+/* 
+    PlotKit Layout
+    ==============
+    
+    Handles laying out data on to a virtual canvas square canvas between 0.0 
+    and 1.0. If you want to add new chart/plot types such as point plots,
+    you need to add them here.
+    
+    Copyright
+    ---------
+    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
+    For use under the BSD license. <http://www.liquidx.net/plotkit>
+    
+*/
+
+try {    
+    if (typeof(PlotKit.Base) == 'undefined')
+    {
+        throw ""
+    }
+} 
+catch (e) {    
+    throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base"
+}
+
+// --------------------------------------------------------------------
+// Start of Layout definition
+// --------------------------------------------------------------------
+
+if (typeof(PlotKit.Layout) == 'undefined') {
+    PlotKit.Layout = {};
+}
+
+PlotKit.Layout.NAME = "PlotKit.Layout";
+PlotKit.Layout.VERSION = PlotKit.VERSION;
+
+PlotKit.Layout.__repr__ = function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.Layout.toString = function() {
+    return this.__repr__();
+}
+
+PlotKit.Layout.valid_styles = ["bar", "line", "pie", "point"];
+
+// --------------------------------------------------------------------
+// Start of Layout definition
+// --------------------------------------------------------------------
+
+PlotKit.Layout = function(style, options) {
+  
+    this.options = {
+        "barWidthFillFraction": 0.75,
+        "barOrientation": "vertical",
+        "xOriginIsZero": true,
+        "yOriginIsZero": true,
+        "xAxis": null, // [xmin, xmax]
+        "yAxis": null, // [ymin, ymax]
+        "xTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.)
+        "yTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.)
+        "xNumberOfTicks": 10,
+        "yNumberOfTicks": 5,
+        "xTickPrecision": 1,
+        "yTickPrecision": 1,
+        "pieRadius": 0.4
+    };
+
+    // valid external options : TODO: input verification
+    this.style = style; 
+    MochiKit.Base.update(this.options, options ? options : {});
+
+    // externally visible states
+    // overriden if xAxis and yAxis are set in options
+    if (!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)) {
+        this.minxval = this.options.xAxis[0];
+        this.maxxval = this.options.xAxis[1];
+        this.xscale = this.maxxval - this.minxval; 
+    }
+    else {
+        this.minxval = 0;
+        this.maxxval = null;
+        this.xscale = null; // val -> pos factor (eg, xval * xscale = xpos)
+    }
+
+    if (!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)) {
+        this.minyval = this.options.yAxis[0];
+        this.maxyval = this.options.yAxis[1];
+        this.yscale = this.maxyval - this.minyval;
+    }
+    else {
+        this.minyval = 0;
+        this.maxyval = null;
+        this.yscale = null;
+    }
+
+    this.bars = new Array();   // array of bars to plot for bar charts
+    this.points = new Array(); // array of points to plot for line plots
+    this.slices = new Array(); // array of slices to draw for pie charts
+
+    this.xticks = new Array();
+    this.yticks = new Array();
+
+    // internal states
+    this.datasets = new Array();
+    this.minxdelta = 0;
+    this.xrange = 1;
+    this.yrange = 1;
+
+    this.hitTestCache = {x2maxy: null};
+    
+};
+
+// --------------------------------------------------------------------
+// Dataset Manipulation
+// --------------------------------------------------------------------
+
+
+PlotKit.Layout.prototype.addDataset = function(setname, set_xy) {
+    this.datasets[setname] = set_xy;
+};
+
+PlotKit.Layout.prototype.removeDataset = function(setname, set_xy) {
+    delete this.datasets[setname];
+};
+
+PlotKit.Layout.prototype.addDatasetFromTable = function(name, tableElement, xcol, ycol,  lcol) {
+	var isNil = MochiKit.Base.isUndefinedOrNull;
+	var scrapeText = MochiKit.DOM.scrapeText;
+	var strip = MochiKit.Format.strip;
+	
+	if (isNil(xcol))
+		xcol = 0;
+	if (isNil(ycol))
+		ycol = 1;
+	if (isNil(lcol))
+	    lcol = -1;
+        
+    var rows = tableElement.tBodies[0].rows;
+    var data = new Array();
+    var labels = new Array();
+    
+    if (!isNil(rows)) {
+        for (var i = 0; i < rows.length; i++) {
+            data.push([parseFloat(strip(scrapeText(rows[i].cells[xcol]))),
+                       parseFloat(strip(scrapeText(rows[i].cells[ycol])))]);
+            if (lcol >= 0){
+               labels.push({v: parseFloat(strip(scrapeText(rows[i].cells[xcol]))),
+                            label:  strip(scrapeText(rows[i].cells[lcol]))});
+            }
+        }
+        this.addDataset(name, data);
+        if (lcol >= 0) {
+            this.options.xTicks = labels;
+        }
+        return true;
+    }
+    return false;
+};
+
+// --------------------------------------------------------------------
+// Evaluates the layout for the current data and style.
+// --------------------------------------------------------------------
+
+PlotKit.Layout.prototype.evaluate = function() {
+    this._evaluateLimits();
+    this._evaluateScales();
+    if (this.style == "bar") {
+        if (this.options.barOrientation == "horizontal") {
+            this._evaluateHorizBarCharts();
+        }
+        else {
+            this._evaluateBarCharts();
+        }
+        this._evaluateBarTicks();
+    }
+    else if (this.style == "line") {
+        this._evaluateLineCharts();
+        this._evaluateLineTicks();
+    }
+    else if (this.style == "pie") {
+        this._evaluatePieCharts();
+        this._evaluatePieTicks();
+    }
+};
+
+
+
+// Given the fractional x, y positions, report the corresponding
+// x, y values.
+PlotKit.Layout.prototype.hitTest = function(x, y) {
+    // TODO: make this more efficient with better datastructures
+    //       for this.bars, this.points and this.slices
+
+    var f = MochiKit.Format.twoDigitFloat;
+
+    if ((this.style == "bar") && this.bars && (this.bars.length > 0)) {
+        for (var i = 0; i < this.bars.length; i++) {
+            var bar = this.bars[i];
+            if ((x >= bar.x) && (x <= bar.x + bar.w) 
+                && (y >= bar.y) && (y - bar.y <= bar.h))
+                return bar;
+        }
+    }
+
+    else if (this.style == "line") {
+        if (this.hitTestCache.x2maxy == null) {
+            this._regenerateHitTestCache();
+        }
+
+        // 1. find the xvalues that equal or closest to the give x
+        var xval = x / this.xscale;
+        var xvalues = this.hitTestCache.xvalues;
+        var xbefore = null;
+        var xafter = null;
+
+        for (var i = 1; i < xvalues.length; i++) {
+            if (xvalues[i] > xval) {
+                xbefore = xvalues[i-1];
+                xafter = xvalues[i];
+                break;
+            }
+        }
+
+        if ((xbefore != null)) {
+            var ybefore = this.hitTestCache.x2maxy[xbefore];
+            var yafter = this.hitTestCache.x2maxy[xafter];
+            var yval = (1.0 - y)/this.yscale;
+
+            // interpolate whether we will fall inside or outside
+            var gradient = (yafter - ybefore) / (xafter - xbefore);
+            var projmaxy = ybefore + gradient * (xval - xbefore);
+            if (projmaxy >= yval) {
+                // inside the highest curve (roughly)
+                var obj = {xval: xval, yval: yval,
+                           xafter: xafter, yafter: yafter,
+                           xbefore: xbefore, ybefore: ybefore,
+                           yprojected: projmaxy
+                };
+                return obj;
+            }
+        }
+    }
+
+    else if (this.style == "pie") {
+        var dist = Math.sqrt((y-0.5)*(y-0.5) + (x-0.5)*(x-0.5));
+        if (dist > this.options.pieRadius)
+            return null;
+
+        // TODO: actually doesn't work if we don't know how the Canvas
+        //       lays it out, need to fix!
+        var angle = Math.atan2(y - 0.5, x - 0.5) - Math.PI/2;
+        for (var i = 0; i < this.slices.length; i++) {
+            var slice = this.slices[i];
+            if (slice.startAngle < angle && slice.endAngle >= angle)
+                return slice;
+        }
+    }
+
+    return null;
+};
+
+// Reports valid position rectangle for X value (only valid for bar charts)
+PlotKit.Layout.prototype.rectForX = function(x) {
+    return null;
+};
+
+// Reports valid angles through which X value encloses (only valid for pie charts)
+PlotKit.Layout.prototype.angleRangeForX = function(x) {
+    return null;
+};
+
+// --------------------------------------------------------------------
+// START Internal Functions
+// --------------------------------------------------------------------
+
+PlotKit.Layout.prototype._evaluateLimits = function() {
+    // take all values from all datasets and find max and min
+    var map = PlotKit.Base.map;
+    var items = PlotKit.Base.items;
+    var itemgetter = MochiKit.Base.itemgetter;
+    var collapse = PlotKit.Base.collapse;
+    var listMin = MochiKit.Base.listMin;
+    var listMax = MochiKit.Base.listMax;
+    var isNil = MochiKit.Base.isUndefinedOrNull;
+
+
+    var all = collapse(map(itemgetter(1), items(this.datasets)));
+    if (isNil(this.options.xAxis)) {
+        if (this.options.xOriginIsZero)
+            this.minxval = 0;
+        else
+            this.minxval = listMin(map(parseFloat, map(itemgetter(0), all)));
+
+        this.maxxval = listMax(map(parseFloat, map(itemgetter(0), all)));
+    }
+    else {
+        this.minxval = this.options.xAxis[0];
+        this.maxxval = this.options.xAxis[1];
+        this.xscale = this.maxval - this.minxval;
+    }
+    
+    if (isNil(this.options.yAxis)) {
+        if (this.options.yOriginIsZero)
+            this.minyval = 0;
+        else
+            this.minyval = listMin(map(parseFloat, map(itemgetter(1), all)));
+
+        this.maxyval = listMax(map(parseFloat, map(itemgetter(1), all)));
+    }
+    else {
+        this.minyval = this.options.yAxis[0];
+        this.maxyval = this.options.yAxis[1];
+        this.yscale = this.maxyval - this.minyval;
+    }
+
+};
+
+PlotKit.Layout.prototype._evaluateScales = function() {
+    var isNil = MochiKit.Base.isUndefinedOrNull;
+
+    this.xrange = this.maxxval - this.minxval;
+    if (this.xrange == 0)
+        this.xscale = 1.0;
+    else
+        this.xscale = 1/this.xrange;
+
+    this.yrange = this.maxyval - this.minyval;
+    if (this.yrange == 0)
+        this.yscale = 1.0;
+    else
+        this.yscale = 1/this.yrange;
+};
+
+PlotKit.Layout.prototype._uniqueXValues = function() {
+    var collapse = PlotKit.Base.collapse;
+    var map = PlotKit.Base.map;
+    var uniq = PlotKit.Base.uniq;
+    var getter = MochiKit.Base.itemgetter;
+    var items = PlotKit.Base.items;
+    
+    var xvalues = map(parseFloat, map(getter(0), collapse(map(getter(1), items(this.datasets)))));
+    xvalues.sort(MochiKit.Base.compare);
+    return uniq(xvalues);
+};
+
+// Create the bars
+PlotKit.Layout.prototype._evaluateBarCharts = function() {
+    var items = PlotKit.Base.items;
+
+    var setCount = items(this.datasets).length;
+
+    // work out how far separated values are
+    var xdelta = 10000000;
+    var xvalues = this._uniqueXValues();
+    for (var i = 1; i < xvalues.length; i++) {
+        xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta);
+    }
+
+    var barWidth = 0;
+    var barWidthForSet = 0;
+    var barMargin = 0;
+    if (xvalues.length == 1) {
+        // note we have to do something smarter if we only plot one value
+        xdelta = 1.0;
+        this.xscale = 1.0;
+        this.minxval = xvalues[0];
+        barWidth = 1.0 * this.options.barWidthFillFraction;
+        barWidthForSet = barWidth/setCount;
+        barMargin = (1.0 - this.options.barWidthFillFraction)/2;
+    }
+    else {
+        // readjust xscale to fix with bar charts
+        if (this.xrange == 1) {
+            this.xscale = 0.5;
+        }
+        else if (this.xrange == 2) {
+            this.xscale = 1/3.0;
+        }
+        else {
+            this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
+        }
+        barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
+        barWidthForSet = barWidth / setCount;
+        barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
+    }
+    
+    this.minxdelta = xdelta; // need this for tick positions
+
+    // add all the rects
+    this.bars = new Array();
+    var i = 0;
+    for (var setName in this.datasets) {
+        var dataset = this.datasets[setName];
+        if (PlotKit.Base.isFuncLike(dataset)) continue;
+        for (var j = 0; j < dataset.length; j++) {
+            var item = dataset[j];
+            var rect = {
+                x: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
+                y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
+                w: barWidthForSet,
+                h: ((parseFloat(item[1]) - this.minyval) * this.yscale),
+                xval: parseFloat(item[0]),
+                yval: parseFloat(item[1]),
+                name: setName
+            };
+            if ((rect.x >= 0.0) && (rect.x <= 1.0) && 
+                (rect.y >= 0.0) && (rect.y <= 1.0)) {
+                this.bars.push(rect);
+            }
+        }
+        i++;
+    }
+};
+
+// Create the horizontal bars
+PlotKit.Layout.prototype._evaluateHorizBarCharts = function() {
+    var items = PlotKit.Base.items;
+
+    var setCount = items(this.datasets).length;
+
+    // work out how far separated values are
+    var xdelta = 10000000;
+    var xvalues = this._uniqueXValues();
+    for (var i = 1; i < xvalues.length; i++) {
+        xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta);
+    }
+
+    var barWidth = 0;
+    var barWidthForSet = 0;
+    var barMargin = 0;
+    
+    // work out how far each far each bar is separated
+    if (xvalues.length == 1) {
+        // do something smarter if we only plot one value
+        xdelta = 1.0;
+        this.xscale = 1.0;
+        this.minxval = xvalues[0];
+        barWidth = 1.0 * this.options.barWidthFillFraction;
+        barWidthForSet = barWidth/setCount;
+        barMargin = (1.0 - this.options.barWidthFillFraction)/2;
+    }
+    else {
+        // readjust yscale to fix with bar charts
+        this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
+        barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
+        barWidthForSet = barWidth / setCount;
+        barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
+    }
+
+    this.minxdelta = xdelta; // need this for tick positions
+
+    // add all the rects
+    this.bars = new Array();
+    var i = 0;
+    for (var setName in this.datasets) {
+        var dataset = this.datasets[setName];
+        if (PlotKit.Base.isFuncLike(dataset)) continue;
+        for (var j = 0; j < dataset.length; j++) {
+            var item = dataset[j];
+            var rect = {
+                y: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
+                x: 0.0,
+                h: barWidthForSet,
+                w: ((parseFloat(item[1]) - this.minyval) * this.yscale),
+                xval: parseFloat(item[0]),
+                yval: parseFloat(item[1]),
+                name: setName
+            };
+
+            // limit the x, y values so they do not overdraw
+            if (rect.y <= 0.0) {
+                rect.y = 0.0;
+            }
+            if (rect.y >= 1.0) {
+                rect.y = 1.0;
+            }
+            if ((rect.x >= 0.0) && (rect.x <= 1.0)) {
+                this.bars.push(rect);
+            }
+        }
+        i++;
+    }
+};
+
+
+// Create the line charts
+PlotKit.Layout.prototype._evaluateLineCharts = function() {
+    var items = PlotKit.Base.items;
+
+    var setCount = items(this.datasets).length;
+
+    // add all the rects
+    this.points = new Array();
+    var i = 0;
+    for (var setName in this.datasets) {
+        var dataset = this.datasets[setName];
+        if (PlotKit.Base.isFuncLike(dataset)) continue;
+        dataset.sort(function(a, b) { return compare(parseFloat(a[0]), parseFloat(b[0])); });
+        for (var j = 0; j < dataset.length; j++) {
+            var item = dataset[j];
+            var point = {
+                x: ((parseFloat(item[0]) - this.minxval) * this.xscale),
+                y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
+                xval: parseFloat(item[0]),
+                yval: parseFloat(item[1]),
+                name: setName
+            };
+
+            // limit the x, y values so they do not overdraw
+            if (point.y <= 0.0) {
+                point.y = 0.0;
+            }
+            if (point.y >= 1.0) {
+                point.y = 1.0;
+            }
+            if ((point.x >= 0.0) && (point.x <= 1.0)) {
+                this.points.push(point);
+            }
+        }
+        i++;
+    }
+};
+
+// Create the pie charts
+PlotKit.Layout.prototype._evaluatePieCharts = function() {
+    var items = PlotKit.Base.items;
+    var sum = MochiKit.Iter.sum;
+    var getter = MochiKit.Base.itemgetter;
+
+    var setCount = items(this.datasets).length;
+
+    // we plot the y values of the first dataset
+    var dataset = items(this.datasets)[0][1];
+    var total = sum(map(getter(1), dataset));
+
+    this.slices = new Array();
+    var currentAngle = 0.0;
+    for (var i = 0; i < dataset.length; i++) {
+        var fraction = dataset[i][1] / total;
+		var startAngle = currentAngle * Math.PI * 2;
+		var endAngle = (currentAngle + fraction) * Math.PI * 2;
+			
+        var slice = {fraction: fraction,
+                     xval: dataset[i][0],
+                     yval: dataset[i][1],
+                     startAngle: startAngle,
+                     endAngle: endAngle
+        };
+        if (dataset[i][1] != 0) {
+            this.slices.push(slice);
+        }
+        currentAngle += fraction;
+    }
+};
+
+PlotKit.Layout.prototype._evaluateLineTicksForXAxis = function() {
+    var isNil = MochiKit.Base.isUndefinedOrNull;
+    
+    if (this.options.xTicks) {
+        // we use use specified ticks with optional labels
+
+        this.xticks = new Array();
+        var makeTicks = function(tick) {
+            var label = tick.label;
+            if (isNil(label))
+                label = tick.v.toString();
+            var pos = this.xscale * (tick.v - this.minxval);
+            if ((pos >= 0.0) && (pos <= 1.0)) {
+                this.xticks.push([pos, label]);
+            }
+        };
+        MochiKit.Iter.forEach(this.options.xTicks, bind(makeTicks, this));
+    }
+    else if (this.options.xNumberOfTicks) {
+        // we use defined number of ticks as hint to auto generate
+        var xvalues = this._uniqueXValues();
+        var roughSeparation = this.xrange / this.options.xNumberOfTicks;
+        var tickCount = 0;
+
+        this.xticks = new Array();
+        for (var i = 0; i <= xvalues.length; i++) {
+            if ((xvalues[i] - this.minxval) >= (tickCount * roughSeparation)) {
+                var pos = this.xscale * (xvalues[i] - this.minxval);
+                if ((pos > 1.0) || (pos < 0.0))
+                    continue;
+                this.xticks.push([pos, xvalues[i]]);
+                tickCount++;
+            }
+            if (tickCount > this.options.xNumberOfTicks)
+                break;
+        }
+    }
+};
+
+PlotKit.Layout.prototype._evaluateLineTicksForYAxis = function() {
+    var isNil = MochiKit.Base.isUndefinedOrNull;
+
+
+    if (this.options.yTicks) {
+        this.yticks = new Array();
+        var makeTicks = function(tick) {
+            var label = tick.label;
+            if (isNil(label))
+                label = tick.v.toString();
+            var pos = 1.0 - (this.yscale * (tick.v - this.minyval));
+            if ((pos >= 0.0) && (pos <= 1.0)) {
+                this.yticks.push([pos, label]);
+            }
+        };
+        MochiKit.Iter.forEach(this.options.yTicks, bind(makeTicks, this));
+    }
+    else if (this.options.yNumberOfTicks) {
+        // We use the optionally defined number of ticks as a guide        
+        this.yticks = new Array();
+
+        // if we get this separation right, we'll have good looking graphs
+        var roundInt = PlotKit.Base.roundInterval;
+        var prec = this.options.yTickPrecision;
+        var roughSeparation = roundInt(this.yrange, 
+                                       this.options.yNumberOfTicks, prec);
+
+        // round off each value of the y-axis to the precision
+        // eg. 1.3333 at precision 1 -> 1.3
+        for (var i = 0; i <= this.options.yNumberOfTicks; i++) {
+            var yval = this.minyval + (i * roughSeparation);
+            var pos = 1.0 - ((yval - this.minyval) * this.yscale);
+            if ((pos > 1.0) || (pos < 0.0))
+                continue;
+            this.yticks.push([pos, MochiKit.Format.roundToFixed(yval, prec)]);
+        }
+    }
+};
+
+PlotKit.Layout.prototype._evaluateLineTicks = function() {
+    this._evaluateLineTicksForXAxis();
+    this._evaluateLineTicksForYAxis();
+};
+
+PlotKit.Layout.prototype._evaluateBarTicks = function() {
+    this._evaluateLineTicks();
+    var centerInBar = function(tick) {
+        return [tick[0] + (this.minxdelta * this.xscale)/2, tick[1]];
+    };
+    this.xticks = MochiKit.Base.map(bind(centerInBar, this), this.xticks);
+    
+    if (this.options.barOrientation == "horizontal") {
+        // swap scales
+        var tempticks = this.xticks;
+        this.xticks = this.yticks;
+        this.yticks = tempticks;
+
+        // we need to invert the "yaxis" (which is now the xaxis when drawn)
+        var invert = function(tick) {
+            return [1.0 - tick[0], tick[1]];
+        }
+        this.xticks = MochiKit.Base.map(invert, this.xticks);
+    }
+};
+
+PlotKit.Layout.prototype._evaluatePieTicks = function() {
+    var isNil = MochiKit.Base.isUndefinedOrNull;
+	var formatter = MochiKit.Format.numberFormatter("#%");
+
+    this.xticks = new Array();
+	if (this.options.xTicks) {
+		// make a lookup dict for x->slice values
+		var lookup = new Array();
+		for (var i = 0; i < this.slices.length; i++) {
+			lookup[this.slices[i].xval] = this.slices[i];
+		}
+		
+		for (var i =0; i < this.options.xTicks.length; i++) {
+			var tick = this.options.xTicks[i];
+			var slice = lookup[tick.v]; 
+            var label = tick.label;
+			if (slice) {
+                if (isNil(label))
+                    label = tick.v.toString();
+				label += " (" + formatter(slice.fraction) + ")";
+				this.xticks.push([tick.v, label]);
+			}
+		}
+	}
+	else {
+		// we make our own labels from all the slices
+		for (var i =0; i < this.slices.length; i++) {
+			var slice = this.slices[i];
+			var label = slice.xval + " (" + formatter(slice.fraction) + ")";
+			this.xticks.push([slice.xval, label]);
+		}
+	}
+};
+
+PlotKit.Layout.prototype._regenerateHitTestCache = function() {
+    this.hitTestCache.xvalues = this._uniqueXValues();
+    this.hitTestCache.xlookup = new Array();
+    this.hitTestCache.x2maxy = new Array();
+
+    var listMax = MochiKit.Base.listMax;
+    var itemgetter = MochiKit.Base.itemgetter;
+    var map = MochiKit.Base.map;
+
+    // generate a lookup table for x values to y values
+    var setNames = keys(this.datasets);
+    for (var i = 0; i < setNames.length; i++) {
+        var dataset = this.datasets[setNames[i]];
+        for (var j = 0; j < dataset.length; j++) {
+            var xval = dataset[j][0];
+            var yval = dataset[j][1];
+            if (this.hitTestCache.xlookup[xval])
+                this.hitTestCache.xlookup[xval].push([yval, setNames[i]]);
+            else 
+                this.hitTestCache.xlookup[xval] = [[yval, setNames[i]]];
+        }
+    }
+
+    for (var x in this.hitTestCache.xlookup) {
+        var yvals = this.hitTestCache.xlookup[x];
+        this.hitTestCache.x2maxy[x] = listMax(map(itemgetter(0), yvals));
+    }
+
+
+};
+
+// --------------------------------------------------------------------
+// END Internal Functions
+// --------------------------------------------------------------------
+
+
+// Namespace Iniitialisation
+
+PlotKit.LayoutModule = {};
+PlotKit.LayoutModule.Layout = PlotKit.Layout;
+
+PlotKit.LayoutModule.EXPORT = [
+    "Layout"
+];
+
+PlotKit.LayoutModule.EXPORT_OK = [];
+
+PlotKit.LayoutModule.__new__ = function() {
+    var m = MochiKit.Base;
+    
+    m.nameFunctions(this);
+    
+    this.EXPORT_TAGS = {
+        ":common": this.EXPORT,
+        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
+    };
+};
+
+PlotKit.LayoutModule.__new__();
+MochiKit.Base._exportSymbols(this, PlotKit.LayoutModule);
+
+
diff --git a/share/web/static/js/PlotKit/PlotKit.js b/share/web/static/js/PlotKit/PlotKit.js
new file mode 100644
index 0000000..e79abf7
--- /dev/null
+++ b/share/web/static/js/PlotKit/PlotKit.js
@@ -0,0 +1,151 @@
+/***
+
+PlotKit Autoload Javascript Module.
+
+This file was adapted from MochiKit.
+See <http://mochikit.com/> for documentation, downloads, license, etc.
+(c) 2005 Bob Ippolito.  All rights Reserved.
+
+Modified by Alastair Tse, 2006, for PlotKit.
+
+***/
+
+if (typeof(PlotKit) == 'undefined') {
+    PlotKit = {};
+}
+
+if (typeof(PlotKit.PlotKit) == 'undefined') {
+    PlotKit.PlotKit = {};
+}
+
+PlotKit.PlotKit.NAME = "PlotKit.PlotKit";
+PlotKit.PlotKit.VERSION = "0.9.1";
+PlotKit.PlotKit.__repr__ = function () {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.PlotKit.toString = function () {
+    return this.__repr__();
+};
+
+PlotKit.PlotKit.SUBMODULES = [
+    "Base",
+    "Layout",
+    "Canvas",
+    "SVG",
+    "SweetCanvas",
+    "SweetSVG",
+    "EasyPlot"
+];
+
+if (typeof(JSAN) != 'undefined' || typeof(dojo) != 'undefined') {
+    if (typeof(dojo) != 'undefined') {
+        dojo.provide('PlotKit.PlotKit');
+        dojo.require("PlotKit.*");
+    }
+    if (typeof(JSAN) != 'undefined') {
+        // hopefully this makes it easier for static analysis?
+        JSAN.use("PlotKit.Base", []);
+        JSAN.use("PlotKit.Layout", []);
+        JSAN.use("PlotKit.Canvas", []);
+        JSAN.use("PlotKit.SweetCanvas", []);
+        JSAN.use("PlotKit.SVG", []);
+        JSAN.use("PlotKit.SweetSVG", []);
+    }
+    (function () {
+        var extend = MochiKit.Base.extend;
+        var self = PlotKit.PlotKit;
+        var modules = self.SUBMODULES;
+        var EXPORT = [];
+        var EXPORT_OK = [];
+        var EXPORT_TAGS = {};
+        var i, k, m, all;
+        for (i = 0; i < modules.length; i++) {
+            m = PlotKit[modules[i]];
+            extend(EXPORT, m.EXPORT);
+            extend(EXPORT_OK, m.EXPORT_OK);
+            for (k in m.EXPORT_TAGS) {
+                EXPORT_TAGS[k] = extend(EXPORT_TAGS[k], m.EXPORT_TAGS[k]);
+            }
+            all = m.EXPORT_TAGS[":all"];
+            if (!all) {
+                all = extend(null, m.EXPORT, m.EXPORT_OK);
+            }
+            var j;
+            for (j = 0; j < all.length; j++) {
+                k = all[j];
+                self[k] = m[k];
+            }
+        }
+        self.EXPORT = EXPORT;
+        self.EXPORT_OK = EXPORT_OK;
+        self.EXPORT_TAGS = EXPORT_TAGS;
+    }());
+    
+} else {
+    if (typeof(PlotKit.__compat__) == 'undefined') {
+        PlotKit.__compat__ = true;
+    }
+    (function () {
+        if (typeof(document) == "undefined") {
+              return;
+        }
+        
+        var scripts = document.getElementsByTagName("script");
+        var kXULNSURI = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+        var base = null;
+        var baseElem = null;
+        var allScripts = {};
+        var i;
+        for (i = 0; i < scripts.length; i++) {
+            var src = scripts[i].getAttribute("src");
+            if (!src) {
+                continue;
+            }
+            allScripts[src] = true;
+            if (src.match(/PlotKit.js$/)) {
+                base = src.substring(0, src.lastIndexOf('PlotKit.js'));
+                baseElem = scripts[i];
+            }
+
+        }
+
+        if (base === null) {
+            return;
+        }
+        var modules = PlotKit.PlotKit.SUBMODULES;
+        for (var i = 0; i < modules.length; i++) {
+            if (PlotKit[modules[i]]) {
+                continue;
+            }
+            var uri = base + modules[i] + '.js';
+            if (uri in allScripts) {
+                continue;
+            }
+            if (document.documentElement &&
+                document.documentElement.namespaceURI == kXULNSURI) {
+                // XUL
+                var s = document.createElementNS(kXULNSURI, 'script');
+                s.setAttribute("id", "PlotKit_" + base + modules[i]);
+                s.setAttribute("src", uri);
+                s.setAttribute("type", "application/x-javascript");
+                baseElem.parentNode.appendChild(s);
+            } else {
+                // HTML
+                /*
+                    DOM can not be used here because Safari does
+                    deferred loading of scripts unless they are
+                    in the document or inserted with document.write
+
+                    This is not XHTML compliant.  If you want XHTML
+                    compliance then you must use the packed version of MochiKit
+                    or include each script individually (basically unroll
+                    these document.write calls into your XHTML source)
+
+                */
+                document.write('<script src="' + uri +
+                    '" type="text/javascript"></script>');
+            }
+        };
+    })();
+}
diff --git a/share/web/static/js/PlotKit/PlotKit_Packed.js b/share/web/static/js/PlotKit/PlotKit_Packed.js
new file mode 100644
index 0000000..363042a
--- /dev/null
+++ b/share/web/static/js/PlotKit/PlotKit_Packed.js
@@ -0,0 +1,2177 @@
+/***
+
+    PlotKit.PlotKit 0.9.1 : PACKED VERSION
+
+    THIS FILE IS AUTOMATICALLY GENERATED.  If creating patches, please
+    diff against the source tree, not this file.
+
+    For more information, <http://www.liquidx.net/plotkit/>.
+    
+    Copyright (c) 2006. Alastair Tse.
+
+***/
+
+try{
+if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.DOM)=="undefined"||typeof (MochiKit.Color)=="undefined"||typeof (MochiKit.Format)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format}";
+}
+MochiKit.Base.update(MochiKit.Color.Color.prototype,{asFillColor:function(){
+return this.lighterColorWithLevel(0.3);
+},asStrokeColor:function(){
+return this.darkerColorWithLevel(0.1);
+},asPointColor:function(){
+return this.lighterColorWithLevel(0.1);
+}});
+if(typeof (PlotKit)=="undefined"){
+PlotKit={};
+}
+PlotKit.NAME="PlotKit";
+PlotKit.VERSION="0.8";
+PlotKit.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.toString=function(){
+return this.__repr__();
+};
+if(typeof (PlotKit.Base)=="undefined"){
+PlotKit.Base={};
+}
+PlotKit.Base.NAME="PlotKit.Base";
+PlotKit.Base.VERSION=PlotKit.VERSION;
+PlotKit.Base.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.Base.toString=function(){
+return this.__repr__();
+};
+PlotKit.Base.usingPrototype=function(){
+try{
+return (typeof (Object.extend)=="function");
+}
+catch(e){
+return false;
+}
+};
+MochiKit.Base.update(PlotKit.Base,{roundInterval:function(_1,_2,_3){
+var _4=MochiKit.Format.roundToFixed;
+var _5=_1/_2;
+return parseFloat(_4(_5,_3));
+},collapse:function(_6){
+var m=MochiKit.Base;
+var _8=new Array();
+for(var i=0;i<_6.length;i++){
+_8=m.concat(_8,_6[i]);
+}
+if(PlotKit.Base.usingPrototype()){
+delete _8.extend;
+delete _8.from;
+delete _8.inspect;
+}
+return _8;
+},uniq:function(_10){
+var m=MochiKit.Base;
+if(!m.isArrayLike(_10)||(_10.length<1)){
+return new Array();
+}
+var _11=new Array();
+var _12=_10[0];
+_11.push(_10[0]);
+for(var i=1;i<_10.length;i++){
+if(m.compare(_10[i],_12)!=0){
+_12=_10[i];
+_11.push(_10[i]);
+}
+}
+return _11;
+},colorScheme:function(){
+var mb=MochiKit.Base;
+var mc=MochiKit.Color;
+var _15=["red","orange","yellow","green","cyan","blue","purple","magenta"];
+var _16=function(_17){
+return mc.Color[_17+"Color"]();
+};
+return mb.map(_16,_15);
+},baseDarkPrimaryColors:function(){
+var _18=MochiKit.Color.Color.fromHexString;
+return [_18("#ad3f40"),_18("#ddac2c"),_18("#dfdd0c"),_18("#5276c4"),_18("#739c5a")];
+},basePrimaryColors:function(){
+var _19=MochiKit.Color.Color.fromHexString;
+return [_19("#d24c4d"),_19("#f2b32f"),_19("#ece90e"),_19("#5d83da"),_19("#78a15d")];
+},baseBlueColors:function(){
+var _20=MochiKit.Color.Color.fromHexString;
+return [_20("#4b6b94"),_20("#5d81b4"),_20("#acbad2")];
+},palette:function(_21,_22,_23,_24){
+var _25=MochiKit.Base.isUndefinedOrNull;
+var _26=new Array();
+if(_25(_24)){
+_24=0.1;
+}
+if(_25(_23)){
+_23=0.4;
+}
+if(_25(_22)){
+_22=-0.2;
+}
+var _27=_22;
+while(_27<=_23){
+_26.push(_27);
+_27+=_24;
+}
+var _28=function(_29,_30){
+return _29.lighterColorWithLevel(_30);
+};
+return MochiKit.Base.map(partial(_28,_21),_26);
+},excanvasSupported:function(){
+if(/MSIE/.test(navigator.userAgent)&&!window.opera){
+return true;
+}
+return false;
+},findPosX:function(obj){
+var _32=0;
+if(obj.offsetParent){
+while(obj.offsetParent){
+_32+=obj.offsetLeft;
+obj=obj.offsetParent;
+}
+}else{
+if(obj.x){
+_32+=obj.x;
+}
+}
+return _32;
+},findPosY:function(obj){
+var _33=0;
+if(obj.offsetParent){
+while(obj.offsetParent){
+_33+=obj.offsetTop;
+obj=obj.offsetParent;
+}
+}else{
+if(obj.y){
+_33+=obj.y;
+}
+}
+return _33;
+},isFuncLike:function(obj){
+return (typeof (obj)=="function");
+}});
+PlotKit.Base.map=function(fn,lst){
+if(PlotKit.Base.usingPrototype()){
+var _36=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_36.push(fn(lst[x]));
+}
+return _36;
+}else{
+return MochiKit.Base.map(fn,lst);
+}
+};
+PlotKit.Base.items=function(lst){
+if(PlotKit.Base.usingPrototype()){
+var _38=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_38.push([x,lst[x]]);
+}
+return _38;
+}else{
+return MochiKit.Base.items(lst);
+}
+};
+PlotKit.Base.keys=function(lst){
+if(PlotKit.Base.usingPrototype()){
+var _39=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_39.push(x);
+}
+return _39;
+}else{
+return MochiKit.Base.keys(lst);
+}
+};
+PlotKit.Base.baseColors=function(){
+var _40=MochiKit.Color.Color.fromHexString;
+return [_40("#476fb2"),_40("#be2c2b"),_40("#85b730"),_40("#734a99"),_40("#26a1c5"),_40("#fb8707"),_40("#000000")];
+};
+PlotKit.Base.officeBaseStyle={"axisLineWidth":2,"axisLabelColor":Color.grayColor(),"axisLineColor":Color.whiteColor(),"padding":{top:5,bottom:10,left:30,right:30}};
+MochiKit.Base.update(PlotKit.Base,{officeBlue:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),"backgroundColor":PlotKit.Base.baseColors()[0].lighterColorWithLevel(0.45)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeRed:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),"backgroundColor":PlotKit.Base.baseColors()[1].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeGreen:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[2]),"backgroundColor":PlotKit.Base.baseColors()[2].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officePurple:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[3]),"backgroundColor":PlotKit.Base.baseColors()[3].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeCyan:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[4]),"backgroundColor":PlotKit.Base.baseColors()[4].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeOrange:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[5]),"backgroundColor":PlotKit.Base.baseColors()[5].lighterColorWithLevel(0.4)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeBlack:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[6],0,0.6),"backgroundColor":PlotKit.Base.baseColors()[6].lighterColorWithLevel(0.9)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+}});
+PlotKit.Base.EXPORT=["baseColors","collapse","colorScheme","findPosX","findPosY","officeBaseStyle","officeBlue","officeRed","officeGreen","officePurple","officeCyan","officeOrange","officeBlack","roundInterval","uniq","isFuncLike","excanvasSupported"];
+PlotKit.Base.EXPORT_OK=[];
+PlotKit.Base.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.Base.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.Base);
+try{
+if(typeof (PlotKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base";
+}
+if(typeof (PlotKit.Layout)=="undefined"){
+PlotKit.Layout={};
+}
+PlotKit.Layout.NAME="PlotKit.Layout";
+PlotKit.Layout.VERSION=PlotKit.VERSION;
+PlotKit.Layout.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.Layout.toString=function(){
+return this.__repr__();
+};
+PlotKit.Layout.valid_styles=["bar","line","pie","point"];
+PlotKit.Layout=function(_42,_43){
+this.options={"barWidthFillFraction":0.75,"barOrientation":"vertical","xOriginIsZero":true,"yOriginIsZero":true,"xAxis":null,"yAxis":null,"xTicks":null,"yTicks":null,"xNumberOfTicks":10,"yNumberOfTicks":5,"xTickPrecision":1,"yTickPrecision":1,"pieRadius":0.4};
+this.style=_42;
+MochiKit.Base.update(this.options,_43?_43:{});
+if(!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)){
+this.minxval=this.options.xAxis[0];
+this.maxxval=this.options.xAxis[1];
+this.xscale=this.maxxval-this.minxval;
+}else{
+this.minxval=0;
+this.maxxval=null;
+this.xscale=null;
+}
+if(!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)){
+this.minyval=this.options.yAxis[0];
+this.maxyval=this.options.yAxis[1];
+this.yscale=this.maxyval-this.minyval;
+}else{
+this.minyval=0;
+this.maxyval=null;
+this.yscale=null;
+}
+this.bars=new Array();
+this.points=new Array();
+this.slices=new Array();
+this.xticks=new Array();
+this.yticks=new Array();
+this.datasets=new Array();
+this.minxdelta=0;
+this.xrange=1;
+this.yrange=1;
+this.hitTestCache={x2maxy:null};
+};
+PlotKit.Layout.prototype.addDataset=function(_44,_45){
+this.datasets[_44]=_45;
+};
+PlotKit.Layout.prototype.removeDataset=function(_46,_47){
+delete this.datasets[_46];
+};
+PlotKit.Layout.prototype.addDatasetFromTable=function(_48,_49,_50,_51,_52){
+var _53=MochiKit.Base.isUndefinedOrNull;
+var _54=MochiKit.DOM.scrapeText;
+var _55=MochiKit.Format.strip;
+if(_53(_50)){
+_50=0;
+}
+if(_53(_51)){
+_51=1;
+}
+if(_53(_52)){
+_52=-1;
+}
+var _56=_49.tBodies[0].rows;
+var _57=new Array();
+var _58=new Array();
+if(!_53(_56)){
+for(var i=0;i<_56.length;i++){
+_57.push([parseFloat(_55(_54(_56[i].cells[_50]))),parseFloat(_55(_54(_56[i].cells[_51])))]);
+if(_52>=0){
+_58.push({v:parseFloat(_55(_54(_56[i].cells[_50]))),label:_55(_54(_56[i].cells[_52]))});
+}
+}
+this.addDataset(_48,_57);
+if(_52>=0){
+this.options.xTicks=_58;
+}
+return true;
+}
+return false;
+};
+PlotKit.Layout.prototype.evaluate=function(){
+this._evaluateLimits();
+this._evaluateScales();
+if(this.style=="bar"){
+if(this.options.barOrientation=="horizontal"){
+this._evaluateHorizBarCharts();
+}else{
+this._evaluateBarCharts();
+}
+this._evaluateBarTicks();
+}else{
+if(this.style=="line"){
+this._evaluateLineCharts();
+this._evaluateLineTicks();
+}else{
+if(this.style=="pie"){
+this._evaluatePieCharts();
+this._evaluatePieTicks();
+}
+}
+}
+};
+PlotKit.Layout.prototype.hitTest=function(x,y){
+var f=MochiKit.Format.twoDigitFloat;
+if((this.style=="bar")&&this.bars&&(this.bars.length>0)){
+for(var i=0;i<this.bars.length;i++){
+var bar=this.bars[i];
+if((x>=bar.x)&&(x<=bar.x+bar.w)&&(y>=bar.y)&&(y-bar.y<=bar.h)){
+return bar;
+}
+}
+}else{
+if(this.style=="line"){
+if(this.hitTestCache.x2maxy==null){
+this._regenerateHitTestCache();
+}
+var _62=x/this.xscale;
+var _63=this.hitTestCache.xvalues;
+var _64=null;
+var _65=null;
+for(var i=1;i<_63.length;i++){
+if(_63[i]>_62){
+_64=_63[i-1];
+_65=_63[i];
+break;
+}
+}
+if((_64!=null)){
+var _66=this.hitTestCache.x2maxy[_64];
+var _67=this.hitTestCache.x2maxy[_65];
+var _68=(1-y)/this.yscale;
+var _69=(_67-_66)/(_65-_64);
+var _70=_66+_69*(_62-_64);
+if(_70>=_68){
+var obj={xval:_62,yval:_68,xafter:_65,yafter:_67,xbefore:_64,ybefore:_66,yprojected:_70};
+return obj;
+}
+}
+}else{
+if(this.style=="pie"){
+var _71=Math.sqrt((y-0.5)*(y-0.5)+(x-0.5)*(x-0.5));
+if(_71>this.options.pieRadius){
+return null;
+}
+var _72=Math.atan2(y-0.5,x-0.5)-Math.PI/2;
+for(var i=0;i<this.slices.length;i++){
+var _73=this.slices[i];
+if(_73.startAngle<_72&&_73.endAngle>=_72){
+return _73;
+}
+}
+}
+}
+}
+return null;
+};
+PlotKit.Layout.prototype.rectForX=function(x){
+return null;
+};
+PlotKit.Layout.prototype.angleRangeForX=function(x){
+return null;
+};
+PlotKit.Layout.prototype._evaluateLimits=function(){
+var map=PlotKit.Base.map;
+var _75=PlotKit.Base.items;
+var _76=MochiKit.Base.itemgetter;
+var _77=PlotKit.Base.collapse;
+var _78=MochiKit.Base.listMin;
+var _79=MochiKit.Base.listMax;
+var _80=MochiKit.Base.isUndefinedOrNull;
+var all=_77(map(_76(1),_75(this.datasets)));
+if(_80(this.options.xAxis)){
+if(this.options.xOriginIsZero){
+this.minxval=0;
+}else{
+this.minxval=_78(map(parseFloat,map(_76(0),all)));
+}
+this.maxxval=_79(map(parseFloat,map(_76(0),all)));
+}else{
+this.minxval=this.options.xAxis[0];
+this.maxxval=this.options.xAxis[1];
+this.xscale=this.maxval-this.minxval;
+}
+if(_80(this.options.yAxis)){
+if(this.options.yOriginIsZero){
+this.minyval=0;
+}else{
+this.minyval=_78(map(parseFloat,map(_76(1),all)));
+}
+this.maxyval=_79(map(parseFloat,map(_76(1),all)));
+}else{
+this.minyval=this.options.yAxis[0];
+this.maxyval=this.options.yAxis[1];
+this.yscale=this.maxyval-this.minyval;
+}
+};
+PlotKit.Layout.prototype._evaluateScales=function(){
+var _82=MochiKit.Base.isUndefinedOrNull;
+this.xrange=this.maxxval-this.minxval;
+if(this.xrange==0){
+this.xscale=1;
+}else{
+this.xscale=1/this.xrange;
+}
+this.yrange=this.maxyval-this.minyval;
+if(this.yrange==0){
+this.yscale=1;
+}else{
+this.yscale=1/this.yrange;
+}
+};
+PlotKit.Layout.prototype._uniqueXValues=function(){
+var _83=PlotKit.Base.collapse;
+var map=PlotKit.Base.map;
+var _84=PlotKit.Base.uniq;
+var _85=MochiKit.Base.itemgetter;
+var _86=PlotKit.Base.items;
+var _87=map(parseFloat,map(_85(0),_83(map(_85(1),_86(this.datasets)))));
+_87.sort(MochiKit.Base.compare);
+return _84(_87);
+};
+PlotKit.Layout.prototype._evaluateBarCharts=function(){
+var _88=PlotKit.Base.items;
+var _89=_88(this.datasets).length;
+var _90=10000000;
+var _91=this._uniqueXValues();
+for(var i=1;i<_91.length;i++){
+_90=Math.min(Math.abs(_91[i]-_91[i-1]),_90);
+}
+var _92=0;
+var _93=0;
+var _94=0;
+if(_91.length==1){
+_90=1;
+this.xscale=1;
+this.minxval=_91[0];
+_92=1*this.options.barWidthFillFraction;
+_93=_92/_89;
+_94=(1-this.options.barWidthFillFraction)/2;
+}else{
+if(this.xrange==1){
+this.xscale=0.5;
+}else{
+if(this.xrange==2){
+this.xscale=1/3;
+}else{
+this.xscale=(1-_90/this.xrange)/this.xrange;
+}
+}
+_92=_90*this.xscale*this.options.barWidthFillFraction;
+_93=_92/_89;
+_94=_90*this.xscale*(1-this.options.barWidthFillFraction)/2;
+}
+this.minxdelta=_90;
+this.bars=new Array();
+var i=0;
+for(var _95 in this.datasets){
+var _96=this.datasets[_95];
+if(PlotKit.Base.isFuncLike(_96)){
+continue;
+}
+for(var j=0;j<_96.length;j++){
+var _98=_96[j];
+var _99={x:((parseFloat(_98[0])-this.minxval)*this.xscale)+(i*_93)+_94,y:1-((parseFloat(_98[1])-this.minyval)*this.yscale),w:_93,h:((parseFloat(_98[1])-this.minyval)*this.yscale),xval:parseFloat(_98[0]),yval:parseFloat(_98[1]),name:_95};
+if((_99.x>=0)&&(_99.x<=1)&&(_99.y>=0)&&(_99.y<=1)){
+this.bars.push(_99);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluateHorizBarCharts=function(){
+var _100=PlotKit.Base.items;
+var _101=_100(this.datasets).length;
+var _102=10000000;
+var _103=this._uniqueXValues();
+for(var i=1;i<_103.length;i++){
+_102=Math.min(Math.abs(_103[i]-_103[i-1]),_102);
+}
+var _104=0;
+var _105=0;
+var _106=0;
+if(_103.length==1){
+_102=1;
+this.xscale=1;
+this.minxval=_103[0];
+_104=1*this.options.barWidthFillFraction;
+_105=_104/_101;
+_106=(1-this.options.barWidthFillFraction)/2;
+}else{
+this.xscale=(1-_102/this.xrange)/this.xrange;
+_104=_102*this.xscale*this.options.barWidthFillFraction;
+_105=_104/_101;
+_106=_102*this.xscale*(1-this.options.barWidthFillFraction)/2;
+}
+this.minxdelta=_102;
+this.bars=new Array();
+var i=0;
+for(var _107 in this.datasets){
+var _108=this.datasets[_107];
+if(PlotKit.Base.isFuncLike(_108)){
+continue;
+}
+for(var j=0;j<_108.length;j++){
+var item=_108[j];
+var rect={y:((parseFloat(item[0])-this.minxval)*this.xscale)+(i*_105)+_106,x:0,h:_105,w:((parseFloat(item[1])-this.minyval)*this.yscale),xval:parseFloat(item[0]),yval:parseFloat(item[1]),name:_107};
+if(rect.y<=0){
+rect.y=0;
+}
+if(rect.y>=1){
+rect.y=1;
+}
+if((rect.x>=0)&&(rect.x<=1)){
+this.bars.push(rect);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluateLineCharts=function(){
+var _111=PlotKit.Base.items;
+var _112=_111(this.datasets).length;
+this.points=new Array();
+var i=0;
+for(var _113 in this.datasets){
+var _114=this.datasets[_113];
+if(PlotKit.Base.isFuncLike(_114)){
+continue;
+}
+_114.sort(function(a,b){
+return compare(parseFloat(a[0]),parseFloat(b[0]));
+});
+for(var j=0;j<_114.length;j++){
+var item=_114[j];
+var _117={x:((parseFloat(item[0])-this.minxval)*this.xscale),y:1-((parseFloat(item[1])-this.minyval)*this.yscale),xval:parseFloat(item[0]),yval:parseFloat(item[1]),name:_113};
+if(_117.y<=0){
+_117.y=0;
+}
+if(_117.y>=1){
+_117.y=1;
+}
+if((_117.x>=0)&&(_117.x<=1)){
+this.points.push(_117);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluatePieCharts=function(){
+var _118=PlotKit.Base.items;
+var sum=MochiKit.Iter.sum;
+var _120=MochiKit.Base.itemgetter;
+var _121=_118(this.datasets).length;
+var _122=_118(this.datasets)[0][1];
+var _123=sum(map(_120(1),_122));
+this.slices=new Array();
+var _124=0;
+for(var i=0;i<_122.length;i++){
+var _125=_122[i][1]/_123;
+var _126=_124*Math.PI*2;
+var _127=(_124+_125)*Math.PI*2;
+var _128={fraction:_125,xval:_122[i][0],yval:_122[i][1],startAngle:_126,endAngle:_127};
+if(_122[i][1]!=0){
+this.slices.push(_128);
+}
+_124+=_125;
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicksForXAxis=function(){
+var _129=MochiKit.Base.isUndefinedOrNull;
+if(this.options.xTicks){
+this.xticks=new Array();
+var _130=function(tick){
+var _132=tick.label;
+if(_129(_132)){
+_132=tick.v.toString();
+}
+var pos=this.xscale*(tick.v-this.minxval);
+if((pos>=0)&&(pos<=1)){
+this.xticks.push([pos,_132]);
+}
+};
+MochiKit.Iter.forEach(this.options.xTicks,bind(_130,this));
+}else{
+if(this.options.xNumberOfTicks){
+var _134=this._uniqueXValues();
+var _135=this.xrange/this.options.xNumberOfTicks;
+var _136=0;
+this.xticks=new Array();
+for(var i=0;i<=_134.length;i++){
+if((_134[i]-this.minxval)>=(_136*_135)){
+var pos=this.xscale*(_134[i]-this.minxval);
+if((pos>1)||(pos<0)){
+continue;
+}
+this.xticks.push([pos,_134[i]]);
+_136++;
+}
+if(_136>this.options.xNumberOfTicks){
+break;
+}
+}
+}
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicksForYAxis=function(){
+var _137=MochiKit.Base.isUndefinedOrNull;
+if(this.options.yTicks){
+this.yticks=new Array();
+var _138=function(tick){
+var _139=tick.label;
+if(_137(_139)){
+_139=tick.v.toString();
+}
+var pos=1-(this.yscale*(tick.v-this.minyval));
+if((pos>=0)&&(pos<=1)){
+this.yticks.push([pos,_139]);
+}
+};
+MochiKit.Iter.forEach(this.options.yTicks,bind(_138,this));
+}else{
+if(this.options.yNumberOfTicks){
+this.yticks=new Array();
+var _140=PlotKit.Base.roundInterval;
+var prec=this.options.yTickPrecision;
+var _142=_140(this.yrange,this.options.yNumberOfTicks,prec);
+for(var i=0;i<=this.options.yNumberOfTicks;i++){
+var yval=this.minyval+(i*_142);
+var pos=1-((yval-this.minyval)*this.yscale);
+if((pos>1)||(pos<0)){
+continue;
+}
+this.yticks.push([pos,MochiKit.Format.roundToFixed(yval,prec)]);
+}
+}
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicks=function(){
+this._evaluateLineTicksForXAxis();
+this._evaluateLineTicksForYAxis();
+};
+PlotKit.Layout.prototype._evaluateBarTicks=function(){
+this._evaluateLineTicks();
+var _144=function(tick){
+return [tick[0]+(this.minxdelta*this.xscale)/2,tick[1]];
+};
+this.xticks=MochiKit.Base.map(bind(_144,this),this.xticks);
+if(this.options.barOrientation=="horizontal"){
+var _145=this.xticks;
+this.xticks=this.yticks;
+this.yticks=_145;
+var _146=function(tick){
+return [1-tick[0],tick[1]];
+};
+this.xticks=MochiKit.Base.map(_146,this.xticks);
+}
+};
+PlotKit.Layout.prototype._evaluatePieTicks=function(){
+var _147=MochiKit.Base.isUndefinedOrNull;
+var _148=MochiKit.Format.numberFormatter("#%");
+this.xticks=new Array();
+if(this.options.xTicks){
+var _149=new Array();
+for(var i=0;i<this.slices.length;i++){
+_149[this.slices[i].xval]=this.slices[i];
+}
+for(var i=0;i<this.options.xTicks.length;i++){
+var tick=this.options.xTicks[i];
+var _150=_149[tick.v];
+var _151=tick.label;
+if(_150){
+if(_147(_151)){
+_151=tick.v.toString();
+}
+_151+=" ("+_148(_150.fraction)+")";
+this.xticks.push([tick.v,_151]);
+}
+}
+}else{
+for(var i=0;i<this.slices.length;i++){
+var _150=this.slices[i];
+var _151=_150.xval+" ("+_148(_150.fraction)+")";
+this.xticks.push([_150.xval,_151]);
+}
+}
+};
+PlotKit.Layout.prototype._regenerateHitTestCache=function(){
+this.hitTestCache.xvalues=this._uniqueXValues();
+this.hitTestCache.xlookup=new Array();
+this.hitTestCache.x2maxy=new Array();
+var _152=MochiKit.Base.listMax;
+var _153=MochiKit.Base.itemgetter;
+var map=MochiKit.Base.map;
+var _154=keys(this.datasets);
+for(var i=0;i<_154.length;i++){
+var _155=this.datasets[_154[i]];
+for(var j=0;j<_155.length;j++){
+var xval=_155[j][0];
+var yval=_155[j][1];
+if(this.hitTestCache.xlookup[xval]){
+this.hitTestCache.xlookup[xval].push([yval,_154[i]]);
+}else{
+this.hitTestCache.xlookup[xval]=[[yval,_154[i]]];
+}
+}
+}
+for(var x in this.hitTestCache.xlookup){
+var _157=this.hitTestCache.xlookup[x];
+this.hitTestCache.x2maxy[x]=_152(map(_153(0),_157));
+}
+};
+PlotKit.LayoutModule={};
+PlotKit.LayoutModule.Layout=PlotKit.Layout;
+PlotKit.LayoutModule.EXPORT=["Layout"];
+PlotKit.LayoutModule.EXPORT_OK=[];
+PlotKit.LayoutModule.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.LayoutModule.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.LayoutModule);
+try{
+if((typeof (PlotKit.Base)=="undefined")||(typeof (PlotKit.Layout)=="undefined")){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Base,Layout}";
+}
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+PlotKit.CanvasRenderer={};
+}
+PlotKit.CanvasRenderer.NAME="PlotKit.CanvasRenderer";
+PlotKit.CanvasRenderer.VERSION=PlotKit.VERSION;
+PlotKit.CanvasRenderer.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.CanvasRenderer.toString=function(){
+return this.__repr__();
+};
+PlotKit.CanvasRenderer=function(_158,_159,_160){
+if(arguments.length>0){
+this.__init__(_158,_159,_160);
+}
+};
+PlotKit.CanvasRenderer.prototype.__init__=function(_161,_162,_163){
+var _164=MochiKit.Base.isUndefinedOrNull;
+var _165=MochiKit.Color.Color;
+this.options={"drawBackground":true,"backgroundColor":_165.whiteColor(),"padding":{left:30,right:30,top:5,bottom:10},"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),"strokeColor":_165.whiteColor(),"strokeColorTransform":"asStrokeColor","strokeWidth":0.5,"shouldFill":true,"shouldStroke":true,"drawXAxis":true,"drawYAxis":true,"axisLineColor":_165.blackColor(),"axisLineWidth":0.5,"axisTickSize":3,"axisLabelColor":_165.blackColor(),"axisLabelFont":"Arial","axisLabelFontSize":9,"axisLabelWidth":50,"pieRadius":0.4,"enableEvents":true};
+MochiKit.Base.update(this.options,_163?_163:{});
+this.layout=_162;
+this.element=MochiKit.DOM.getElement(_161);
+this.container=this.element.parentNode;
+this.isIE=PlotKit.Base.excanvasSupported();
+if(this.isIE&&!_164(G_vmlCanvasManager)){
+this.IEDelay=0.5;
+this.maxTries=5;
+this.renderDelay=null;
+this.clearDelay=null;
+this.element=G_vmlCanvasManager.initElement(this.element);
+}
+this.height=this.element.height;
+this.width=this.element.width;
+if(_164(this.element)){
+throw "CanvasRenderer() - passed canvas is not found";
+}
+if(!this.isIE&&!(PlotKit.CanvasRenderer.isSupported(this.element))){
+throw "CanvasRenderer() - Canvas is not supported.";
+}
+if(_164(this.container)||(this.container.nodeName.toLowerCase()!="div")){
+throw "CanvasRenderer() - <canvas> needs to be enclosed in <div>";
+}
+this.xlabels=new Array();
+this.ylabels=new Array();
+this.isFirstRender=true;
+this.area={x:this.options.padding.left,y:this.options.padding.top,w:this.width-this.options.padding.left-this.options.padding.right,h:this.height-this.options.padding.top-this.options.padding.bottom};
+MochiKit.DOM.updateNodeAttributes(this.container,{"style":{"position":"relative","width":this.width+"px"}});
+};
+PlotKit.CanvasRenderer.prototype.render=function(){
+if(this.isIE){
+try{
+if(this.renderDelay){
+this.renderDelay.cancel();
+this.renderDelay=null;
+}
+var _166=this.element.getContext("2d");
+}
+catch(e){
+this.isFirstRender=false;
+if(this.maxTries-->0){
+this.renderDelay=MochiKit.Async.wait(this.IEDelay);
+this.renderDelay.addCallback(bind(this.render,this));
+}
+return;
+}
+}
+if(this.options.drawBackground){
+this._renderBackground();
+}
+if(this.layout.style=="bar"){
+this._renderBarChart();
+this._renderBarAxis();
+}else{
+if(this.layout.style=="pie"){
+this._renderPieChart();
+this._renderPieAxis();
+}else{
+if(this.layout.style=="line"){
+this._renderLineChart();
+this._renderLineAxis();
+}
+}
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarChartWrap=function(data,_168){
+var _169=this.element.getContext("2d");
+var _170=this.options.colorScheme.length;
+var _171=this.options.colorScheme;
+var _172=MochiKit.Base.keys(this.layout.datasets);
+var _173=_172.length;
+for(var i=0;i<_173;i++){
+var _174=_172[i];
+var _175=_171[i%_170];
+_169.save();
+_169.fillStyle=_175.toRGBString();
+if(this.options.strokeColor){
+_169.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_169.strokeStyle=_175[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_169.lineWidth=this.options.strokeWidth;
+var _176=function(obj){
+if(obj.name==_174){
+_168(_169,obj);
+}
+};
+MochiKit.Iter.forEach(data,bind(_176,this));
+_169.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarChart=function(){
+var bind=MochiKit.Base.bind;
+var _178=function(_179,bar){
+var x=this.area.w*bar.x+this.area.x;
+var y=this.area.h*bar.y+this.area.y;
+var w=this.area.w*bar.w;
+var h=this.area.h*bar.h;
+if((w<1)||(h<1)){
+return;
+}
+if(this.options.shouldFill){
+_179.fillRect(x,y,w,h);
+}
+if(this.options.shouldStroke){
+_179.strokeRect(x,y,w,h);
+}
+};
+this._renderBarChartWrap(this.layout.bars,bind(_178,this));
+};
+PlotKit.CanvasRenderer.prototype._renderLineChart=function(){
+var _182=this.element.getContext("2d");
+var _183=this.options.colorScheme.length;
+var _184=this.options.colorScheme;
+var _185=MochiKit.Base.keys(this.layout.datasets);
+var _186=_185.length;
+var bind=MochiKit.Base.bind;
+var _187=MochiKit.Base.partial;
+for(var i=0;i<_186;i++){
+var _188=_185[i];
+var _189=_184[i%_183];
+var _190=this.options.strokeColorTransform;
+_182.save();
+_182.fillStyle=_189.toRGBString();
+if(this.options.strokeColor){
+_182.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_182.strokeStyle=_189[_190]().toRGBString();
+}
+}
+_182.lineWidth=this.options.strokeWidth;
+var _191=function(ctx){
+ctx.beginPath();
+ctx.moveTo(this.area.x,this.area.y+this.area.h);
+var _193=function(ctx_,_195){
+if(_195.name==_188){
+ctx_.lineTo(this.area.w*_195.x+this.area.x,this.area.h*_195.y+this.area.y);
+}
+};
+MochiKit.Iter.forEach(this.layout.points,_187(_193,ctx),this);
+ctx.lineTo(this.area.w+this.area.x,this.area.h+this.area.y);
+ctx.lineTo(this.area.x,this.area.y+this.area.h);
+ctx.closePath();
+};
+if(this.options.shouldFill){
+bind(_191,this)(_182);
+_182.fill();
+}
+if(this.options.shouldStroke){
+bind(_191,this)(_182);
+_182.stroke();
+}
+_182.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderPieChart=function(){
+var _196=this.element.getContext("2d");
+var _197=this.options.colorScheme.length;
+var _198=this.layout.slices;
+var _199=this.area.x+this.area.w*0.5;
+var _200=this.area.y+this.area.h*0.5;
+var _201=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+if(this.isIE){
+_199=parseInt(_199);
+_200=parseInt(_200);
+_201=parseInt(_201);
+}
+for(var i=0;i<_198.length;i++){
+var _202=this.options.colorScheme[i%_197];
+_196.save();
+_196.fillStyle=_202.toRGBString();
+var _203=function(){
+_196.beginPath();
+_196.moveTo(_199,_200);
+_196.arc(_199,_200,_201,_198[i].startAngle-Math.PI/2,_198[i].endAngle-Math.PI/2,false);
+_196.lineTo(_199,_200);
+_196.closePath();
+};
+if(Math.abs(_198[i].startAngle-_198[i].endAngle)>0.001){
+if(this.options.shouldFill){
+_203();
+_196.fill();
+}
+if(this.options.shouldStroke){
+_203();
+_196.lineWidth=this.options.strokeWidth;
+if(this.options.strokeColor){
+_196.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_196.strokeStyle=_202[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_196.stroke();
+}
+}
+_196.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarAxis=function(){
+this._renderAxis();
+};
+PlotKit.CanvasRenderer.prototype._renderLineAxis=function(){
+this._renderAxis();
+};
+PlotKit.CanvasRenderer.prototype._renderAxis=function(){
+if(!this.options.drawXAxis&&!this.options.drawYAxis){
+return;
+}
+var _204=this.element.getContext("2d");
+var _205={"style":{"position":"absolute","fontSize":this.options.axisLabelFontSize+"px","zIndex":10,"color":this.options.axisLabelColor.toRGBString(),"width":this.options.axisLabelWidth+"px","overflow":"hidden"}};
+_204.save();
+_204.strokeStyle=this.options.axisLineColor.toRGBString();
+_204.lineWidth=this.options.axisLineWidth;
+if(this.options.drawYAxis){
+if(this.layout.yticks){
+var _206=function(tick){
+if(typeof (tick)=="function"){
+return;
+}
+var x=this.area.x;
+var y=this.area.y+tick[0]*this.area.h;
+_204.beginPath();
+_204.moveTo(x,y);
+_204.lineTo(x-this.options.axisTickSize,y);
+_204.closePath();
+_204.stroke();
+var _207=DIV(_205,tick[1]);
+_207.style.top=(y-this.options.axisLabelFontSize)+"px";
+_207.style.left=(x-this.options.padding.left-this.options.axisTickSize)+"px";
+_207.style.textAlign="right";
+_207.style.width=(this.options.padding.left-this.options.axisTickSize*2)+"px";
+MochiKit.DOM.appendChildNodes(this.container,_207);
+this.ylabels.push(_207);
+};
+MochiKit.Iter.forEach(this.layout.yticks,bind(_206,this));
+}
+_204.beginPath();
+_204.moveTo(this.area.x,this.area.y);
+_204.lineTo(this.area.x,this.area.y+this.area.h);
+_204.closePath();
+_204.stroke();
+}
+if(this.options.drawXAxis){
+if(this.layout.xticks){
+var _206=function(tick){
+if(typeof (dataset)=="function"){
+return;
+}
+var x=this.area.x+tick[0]*this.area.w;
+var y=this.area.y+this.area.h;
+_204.beginPath();
+_204.moveTo(x,y);
+_204.lineTo(x,y+this.options.axisTickSize);
+_204.closePath();
+_204.stroke();
+var _208=DIV(_205,tick[1]);
+_208.style.top=(y+this.options.axisTickSize)+"px";
+_208.style.left=(x-this.options.axisLabelWidth/2)+"px";
+_208.style.textAlign="center";
+_208.style.width=this.options.axisLabelWidth+"px";
+MochiKit.DOM.appendChildNodes(this.container,_208);
+this.xlabels.push(_208);
+};
+MochiKit.Iter.forEach(this.layout.xticks,bind(_206,this));
+}
+_204.beginPath();
+_204.moveTo(this.area.x,this.area.y+this.area.h);
+_204.lineTo(this.area.x+this.area.w,this.area.y+this.area.h);
+_204.closePath();
+_204.stroke();
+}
+_204.restore();
+};
+PlotKit.CanvasRenderer.prototype._renderPieAxis=function(){
+if(!this.options.drawXAxis){
+return;
+}
+if(this.layout.xticks){
+var _209=new Array();
+for(var i=0;i<this.layout.slices.length;i++){
+_209[this.layout.slices[i].xval]=this.layout.slices[i];
+}
+var _210=this.area.x+this.area.w*0.5;
+var _211=this.area.y+this.area.h*0.5;
+var _212=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+var _213=this.options.axisLabelWidth;
+for(var i=0;i<this.layout.xticks.length;i++){
+var _214=_209[this.layout.xticks[i][0]];
+if(MochiKit.Base.isUndefinedOrNull(_214)){
+continue;
+}
+var _215=(_214.startAngle+_214.endAngle)/2;
+var _216=_215;
+if(_216>Math.PI*2){
+_216=_216-Math.PI*2;
+}else{
+if(_216<0){
+_216=_216+Math.PI*2;
+}
+}
+var _217=_210+Math.sin(_216)*(_212+10);
+var _218=_211-Math.cos(_216)*(_212+10);
+var _219={"position":"absolute","zIndex":11,"width":_213+"px","fontSize":this.options.axisLabelFontSize+"px","overflow":"hidden","color":this.options.axisLabelColor.toHexString()};
+if(_216<=Math.PI*0.5){
+_219["textAlign"]="left";
+_219["verticalAlign"]="top";
+_219["left"]=_217+"px";
+_219["top"]=(_218-this.options.axisLabelFontSize)+"px";
+}else{
+if((_216>Math.PI*0.5)&&(_216<=Math.PI)){
+_219["textAlign"]="left";
+_219["verticalAlign"]="bottom";
+_219["left"]=_217+"px";
+_219["top"]=_218+"px";
+}else{
+if((_216>Math.PI)&&(_216<=Math.PI*1.5)){
+_219["textAlign"]="right";
+_219["verticalAlign"]="bottom";
+_219["left"]=(_217-_213)+"px";
+_219["top"]=_218+"px";
+}else{
+_219["textAlign"]="right";
+_219["verticalAlign"]="bottom";
+_219["left"]=(_217-_213)+"px";
+_219["top"]=(_218-this.options.axisLabelFontSize)+"px";
+}
+}
+}
+var _220=DIV({"style":_219},this.layout.xticks[i][1]);
+this.xlabels.push(_220);
+MochiKit.DOM.appendChildNodes(this.container,_220);
+}
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBackground=function(){
+var _221=this.element.getContext("2d");
+_221.save();
+_221.fillStyle=this.options.backgroundColor.toRGBString();
+_221.fillRect(0,0,this.width,this.height);
+_221.restore();
+};
+PlotKit.CanvasRenderer.prototype.clear=function(){
+if(this.isIE){
+try{
+if(this.clearDelay){
+this.clearDelay.cancel();
+this.clearDelay=null;
+}
+var _222=this.element.getContext("2d");
+}
+catch(e){
+this.isFirstRender=false;
+this.clearDelay=MochiKit.Async.wait(this.IEDelay);
+this.clearDelay.addCallback(bind(this.clear,this));
+return;
+}
+}
+var _222=this.element.getContext("2d");
+_222.clearRect(0,0,this.width,this.height);
+MochiKit.Iter.forEach(this.xlabels,MochiKit.DOM.removeElement);
+MochiKit.Iter.forEach(this.ylabels,MochiKit.DOM.removeElement);
+this.xlabels=new Array();
+this.ylabels=new Array();
+};
+PlotKit.CanvasRenderer.prototype._initialiseEvents=function(){
+var _223=MochiKit.Signal.connect;
+var bind=MochiKit.Base.bind;
+_223(this.element,"onclick",bind(this.onclick,this));
+};
+PlotKit.CanvasRenderer.prototype._resolveObject=function(e){
+var x=(e.mouse().page.x-PlotKit.Base.findPosX(this.element)-this.area.x)/this.area.w;
+var y=(e.mouse().page.y-PlotKit.Base.findPosY(this.element)-this.area.y)/this.area.h;
+var _225=this.layout.hitTest(x,y);
+if(_225){
+return _225;
+}
+return null;
+};
+PlotKit.CanvasRenderer.prototype._createEventObject=function(_226,e){
+if(_226==null){
+return null;
+}
+e.chart=_226;
+return e;
+};
+PlotKit.CanvasRenderer.prototype.onclick=function(e){
+var _227=this._resolveObject(e);
+var _228=this._createEventObject(_227,e);
+if(_228!=null){
+MochiKit.Signal.signal(this,"onclick",_228);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmouseover=function(e){
+var _229=this._resolveObject(e);
+var _230=this._createEventObject(_229,e);
+if(_230!=null){
+signal(this,"onmouseover",_230);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmouseout=function(e){
+var _231=this._resolveObject(e);
+var _232=this._createEventObject(_231,e);
+if(_232==null){
+signal(this,"onmouseout",e);
+}else{
+signal(this,"onmouseout",_232);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmousemove=function(e){
+var _233=this._resolveObject(e);
+var _234=this._createEventObject(_233,e);
+if((_233==null)&&(this.event_isinside==null)){
+return;
+}
+if((_233!=null)&&(this.event_isinside==null)){
+signal(this,"onmouseover",_234);
+}
+if((_233==null)&&(this.event_isinside!=null)){
+signal(this,"onmouseout",_234);
+}
+if((_233!=null)&&(this.event_isinside!=null)){
+signal(this,"onmousemove",_234);
+}
+this.event_isinside=_233;
+};
+PlotKit.CanvasRenderer.isSupported=function(_235){
+var _236=null;
+try{
+if(MochiKit.Base.isUndefinedOrNull(_235)){
+_236=MochiKit.DOM.CANVAS({});
+}else{
+_236=MochiKit.DOM.getElement(_235);
+}
+var _237=_236.getContext("2d");
+}
+catch(e){
+var ie=navigator.appVersion.match(/MSIE (\d\.\d)/);
+var _239=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);
+if((!ie)||(ie[1]<6)||(_239)){
+return false;
+}
+return true;
+}
+return true;
+};
+PlotKit.Canvas={};
+PlotKit.Canvas.CanvasRenderer=PlotKit.CanvasRenderer;
+PlotKit.Canvas.EXPORT=["CanvasRenderer"];
+PlotKit.Canvas.EXPORT_OK=["CanvasRenderer"];
+PlotKit.Canvas.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.Canvas.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.Canvas);
+try{
+if(typeof (PlotKit.Layout)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Layout";
+}
+PlotKit.SVGRenderer=function(_240,_241,_242){
+if(arguments.length>0){
+this.__init__(_240,_241,_242);
+}
+};
+PlotKit.SVGRenderer.NAME="PlotKit.SVGRenderer";
+PlotKit.SVGRenderer.VERSION=PlotKit.VERSION;
+PlotKit.SVGRenderer.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.SVGRenderer.toString=function(){
+return this.__repr__();
+};
+PlotKit.SVGRenderer.SVGNS="http://www.w3.org/2000/svg";
+PlotKit.SVGRenderer.prototype.__init__=function(_243,_244,_245){
+var _246=MochiKit.Base.isUndefinedOrNull;
+this.options={"drawBackground":true,"backgroundColor":Color.whiteColor(),"padding":{left:30,right:30,top:5,bottom:10},"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),"strokeColor":Color.whiteColor(),"strokeColorTransform":"asStrokeColor","strokeWidth":0.5,"shouldFill":true,"shouldStroke":true,"drawXAxis":true,"drawYAxis":true,"axisLineColor":Color.blackColor(),"axisLineWidth":0.5,"axisTickSize":3,"axisLabelColor":Color.blackColor(),"axisLabelFont":"Arial","axisLabelFontSize":9,"axisLabelWidth":50,"axisLabelUseDiv":true,"pieRadius":0.4,"enableEvents":true};
+MochiKit.Base.update(this.options,_245?_245:{});
+this.layout=_244;
+this.element=MochiKit.DOM.getElement(_243);
+this.container=this.element.parentNode;
+this.height=parseInt(this.element.getAttribute("height"));
+this.width=parseInt(this.element.getAttribute("width"));
+this.document=document;
+this.root=this.element;
+try{
+this.document=this.element.getSVGDocument();
+this.root=_246(this.document.documentElement)?this.element:this.document.documentElement;
+}
+catch(e){
+}
+this.element.style.zIndex=1;
+if(_246(this.element)){
+throw "SVGRenderer() - passed SVG object is not found";
+}
+if(_246(this.container)||this.container.nodeName.toLowerCase()!="div"){
+throw "SVGRenderer() - No DIV's around the SVG.";
+}
+this.xlabels=new Array();
+this.ylabels=new Array();
+this.defs=this.createSVGElement("defs");
+this.area={x:this.options.padding.left,y:this.options.padding.top,w:this.width-this.options.padding.left-this.options.padding.right,h:this.height-this.options.padding.top-this.options.padding.bottom};
+MochiKit.DOM.updateNodeAttributes(this.container,{"style":{"position":"relative","width":this.width+"px"}});
+};
+PlotKit.SVGRenderer.prototype.render=function(){
+if(this.options.drawBackground){
+this._renderBackground();
+}
+if(this.layout.style=="bar"){
+this._renderBarChart();
+this._renderBarAxis();
+}else{
+if(this.layout.style=="pie"){
+this._renderPieChart();
+this._renderPieAxis();
+}else{
+if(this.layout.style=="line"){
+this._renderLineChart();
+this._renderLineAxis();
+}
+}
+}
+};
+PlotKit.SVGRenderer.prototype._renderBarOrLine=function(data,_247,_248,_249){
+var _250=this.options.colorScheme.length;
+var _251=this.options.colorScheme;
+var _252=MochiKit.Base.keys(this.layout.datasets);
+var _253=_252.length;
+for(var i=0;i<_253;i++){
+var _254=_252[i];
+var _255=new Array();
+var _256=_251[i%_250];
+if(this.options.shouldFill){
+_255["fill"]=_256.toRGBString();
+}else{
+_255["fill"]="none";
+}
+if(this.options.shouldStroke&&(this.options.strokeColor||this.options.strokeColorTransform)){
+if(this.options.strokeColor){
+_255["stroke"]=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_255["stroke"]=_256[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_255["strokeWidth"]=this.options.strokeWidth;
+}
+if(_248){
+_248(_255);
+}
+var _257=function(obj){
+if(obj.name==_254){
+_247(_255,obj);
+}
+};
+MochiKit.Iter.forEach(data,bind(_257,this));
+if(_249){
+_249(_255);
+}
+}
+};
+PlotKit.SVGRenderer.prototype._renderBarChart=function(){
+var bind=MochiKit.Base.bind;
+var _258=function(_259,bar){
+var x=this.area.w*bar.x+this.area.x;
+var y=this.area.h*bar.y+this.area.y;
+var w=this.area.w*bar.w;
+var h=this.area.h*bar.h;
+this._drawRect(x,y,w,h,_259);
+};
+this._renderBarOrLine(this.layout.bars,bind(_258,this));
+};
+PlotKit.SVGRenderer.prototype._renderLineChart=function(){
+var bind=MochiKit.Base.bind;
+var _260=function(_261,_262){
+this._tempPointsBuffer+=(this.area.w*_262.x+this.area.x)+","+(this.area.h*_262.y+this.area.y)+" ";
+};
+var _263=function(_264){
+this._tempPointsBuffer="";
+this._tempPointsBuffer+=(this.area.x)+","+(this.area.y+this.area.h)+" ";
+};
+var _265=function(_266){
+this._tempPointsBuffer+=(this.area.w+this.area.x)+","+(this.area.h+this.area.y);
+_266["points"]=this._tempPointsBuffer;
+var elem=this.createSVGElement("polygon",_266);
+this.root.appendChild(elem);
+};
+this._renderBarOrLine(this.layout.points,bind(_260,this),bind(_263,this),bind(_265,this));
+};
+PlotKit.SVGRenderer.prototype._renderPieChart=function(){
+var _268=this.options.colorScheme.length;
+var _269=this.layout.slices;
+var _270=this.area.x+this.area.w*0.5;
+var _271=this.area.y+this.area.h*0.5;
+var _272=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+if(_269.length==1&&(Math.abs(_269[0].startAngle)-Math.abs(_269[0].endAngle)<0.1)){
+var _273={"cx":_270,"cy":_271,"r":_272};
+var _274=this.options.colorScheme[0];
+if(this.options.shouldFill){
+_273["fill"]=_274.toRGBString();
+}else{
+_273["fill"]="none";
+}
+if(this.options.shouldStroke&&(this.options.strokeColor||this.options.strokeColorTransform)){
+if(this.options.strokeColor){
+_273["stroke"]=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_273["stroke"]=_274[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_273["style"]="stroke-width: "+this.options.strokeWidth;
+}
+this.root.appendChild(this.createSVGElement("circle",_273));
+return;
+}
+for(var i=0;i<_269.length;i++){
+var _273=new Array();
+var _274=this.options.colorScheme[i%_268];
+if(this.options.shouldFill){
+_273["fill"]=_274.toRGBString();
+}else{
+_273["fill"]="none";
+}
+if(this.options.shouldStroke&&(this.options.strokeColor||this.options.strokeColorTransform)){
+if(this.options.strokeColor){
+_273["stroke"]=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_273["stroke"]=_274[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_273["style"]="stroke-width:"+this.options.strokeWidth;
+}
+var _275=0;
+if(Math.abs(_269[i].endAngle-_269[i].startAngle)>Math.PI){
+_275=1;
+}
+var x1=Math.cos(_269[i].startAngle-Math.PI/2)*_272;
+var y1=Math.sin(_269[i].startAngle-Math.PI/2)*_272;
+var x2=Math.cos(_269[i].endAngle-Math.PI/2)*_272;
+var y2=Math.sin(_269[i].endAngle-Math.PI/2)*_272;
+var rx=x2-x1;
+var ry=y2-y1;
+var _282="M"+_270+","+_271+" ";
+_282+="l"+x1+","+y1+" ";
+_282+="a"+_272+","+_272+" 0 "+_275+",1 "+rx+","+ry+" z";
+_273["d"]=_282;
+var elem=this.createSVGElement("path",_273);
+this.root.appendChild(elem);
+}
+};
+PlotKit.SVGRenderer.prototype._renderBarAxis=function(){
+this._renderAxis();
+};
+PlotKit.SVGRenderer.prototype._renderLineAxis=function(){
+this._renderAxis();
+};
+PlotKit.SVGRenderer.prototype._renderAxis=function(){
+if(!this.options.drawXAxis&&!this.options.drawYAxis){
+return;
+}
+var _283={"style":{"position":"absolute","textAlign":"center","fontSize":this.options.axisLabelFontSize+"px","zIndex":10,"color":this.options.axisLabelColor.toRGBString(),"width":this.options.axisLabelWidth+"px","overflow":"hidden"}};
+var _284={"stroke":this.options.axisLineColor.toRGBString(),"strokeWidth":this.options.axisLineWidth};
+if(this.options.drawYAxis){
+if(this.layout.yticks){
+var _285=function(tick){
+var x=this.area.x;
+var y=this.area.y+tick[0]*this.area.h;
+this._drawLine(x,y,x-3,y,_284);
+if(this.options.axisLabelUseDiv){
+var _286=DIV(_283,tick[1]);
+_286.style.top=(y-this.options.axisLabelFontSize)+"px";
+_286.style.left=(x-this.options.padding.left+this.options.axisTickSize)+"px";
+_286.style.textAlign="left";
+_286.style.width=(this.options.padding.left-3)+"px";
+MochiKit.DOM.appendChildNodes(this.container,_286);
+this.ylabels.push(_286);
+}else{
+var _287={y:y+3,x:(x-this.options.padding.left+3),width:(this.options.padding.left-this.options.axisTickSize)+"px",height:(this.options.axisLabelFontSize+3)+"px",fontFamily:"Arial",fontSize:this.options.axisLabelFontSize+"px",fill:this.options.axisLabelColor.toRGBString()};
+var _286=this.createSVGElement("text",_287);
+_286.appendChild(this.document.createTextNode(tick[1]));
+this.root.appendChild(_286);
+}
+};
+MochiKit.Iter.forEach(this.layout.yticks,bind(_285,this));
+}
+this._drawLine(this.area.x,this.area.y,this.area.x,this.area.y+this.area.h,_284);
+}
+if(this.options.drawXAxis){
+if(this.layout.xticks){
+var _285=function(tick){
+var x=this.area.x+tick[0]*this.area.w;
+var y=this.area.y+this.area.h;
+this._drawLine(x,y,x,y+this.options.axisTickSize,_284);
+if(this.options.axisLabelUseDiv){
+var _288=DIV(_283,tick[1]);
+_288.style.top=(y+this.options.axisTickSize)+"px";
+_288.style.left=(x-this.options.axisLabelWidth/2)+"px";
+_288.style.textAlign="center";
+_288.style.width=this.options.axisLabelWidth+"px";
+MochiKit.DOM.appendChildNodes(this.container,_288);
+this.xlabels.push(_288);
+}else{
+var _289={y:(y+this.options.axisTickSize+this.options.axisLabelFontSize),x:x-3,width:this.options.axisLabelWidth+"px",height:(this.options.axisLabelFontSize+3)+"px",fontFamily:"Arial",fontSize:this.options.axisLabelFontSize+"px",fill:this.options.axisLabelColor.toRGBString(),textAnchor:"middle"};
+var _288=this.createSVGElement("text",_289);
+_288.appendChild(this.document.createTextNode(tick[1]));
+this.root.appendChild(_288);
+}
+};
+MochiKit.Iter.forEach(this.layout.xticks,bind(_285,this));
+}
+this._drawLine(this.area.x,this.area.y+this.area.h,this.area.x+this.area.w,this.area.y+this.area.h,_284);
+}
+};
+PlotKit.SVGRenderer.prototype._renderPieAxis=function(){
+if(this.layout.xticks){
+var _290=new Array();
+for(var i=0;i<this.layout.slices.length;i++){
+_290[this.layout.slices[i].xval]=this.layout.slices[i];
+}
+var _291=this.area.x+this.area.w*0.5;
+var _292=this.area.y+this.area.h*0.5;
+var _293=Math.min(this.area.w*this.options.pieRadius+10,this.area.h*this.options.pieRadius+10);
+var _294=this.options.axisLabelWidth;
+for(var i=0;i<this.layout.xticks.length;i++){
+var _295=_290[this.layout.xticks[i][0]];
+if(MochiKit.Base.isUndefinedOrNull(_295)){
+continue;
+}
+var _296=(_295.startAngle+_295.endAngle)/2;
+var _297=_296;
+if(_297>Math.PI*2){
+_297=_297-Math.PI*2;
+}else{
+if(_297<0){
+_297=_297+Math.PI*2;
+}
+}
+var _298=_291+Math.sin(_297)*(_293+10);
+var _299=_292-Math.cos(_297)*(_293+10);
+var _300={"position":"absolute","zIndex":11,"width":_294+"px","fontSize":this.options.axisLabelFontSize+"px","overflow":"hidden","color":this.options.axisLabelColor.toHexString()};
+var _301={"width":_294+"px","fontSize":this.options.axisLabelFontSize+"px","height":(this.options.axisLabelFontSize+3)+"px","fill":this.options.axisLabelColor.toRGBString()};
+if(_297<=Math.PI*0.5){
+MochiKit.Base.update(_300,{"textAlign":"left","verticalAlign":"top","left":_298+"px","top":(_299-this.options.axisLabelFontSize)+"px"});
+MochiKit.Base.update(_301,{"x":_298,"y":(_299-this.options.axisLabelFontSize),"textAnchor":"left"});
+}else{
+if((_297>Math.PI*0.5)&&(_297<=Math.PI)){
+MochiKit.Base.update(_300,{"textAlign":"left","verticalAlign":"bottom","left":_298+"px","top":_299+"px"});
+MochiKit.Base.update(_301,{"textAnchor":"left","x":_298,"y":_299});
+}else{
+if((_297>Math.PI)&&(_297<=Math.PI*1.5)){
+MochiKit.Base.update(_300,{"textAlign":"right","verticalAlign":"bottom","left":_298+"px","top":_299+"px"});
+MochiKit.Base.update(_301,{"textAnchor":"right","x":_298-_294,"y":_299});
+}else{
+MochiKit.Base.update(_300,{"textAlign":"left","verticalAlign":"bottom","left":_298+"px","top":_299+"px"});
+MochiKit.Base.update(_301,{"textAnchor":"left","x":_298-_294,"y":_299-this.options.axisLabelFontSize});
+}
+}
+}
+if(this.options.axisLabelUseDiv){
+var _302=DIV({"style":_300},this.layout.xticks[i][1]);
+this.xlabels.push(_302);
+MochiKit.DOM.appendChildNodes(this.container,_302);
+}else{
+var _302=this.createSVGElement("text",_301);
+_302.appendChild(this.document.createTextNode(this.layout.xticks[i][1]));
+this.root.appendChild(_302);
+}
+}
+}
+};
+PlotKit.SVGRenderer.prototype._renderBackground=function(){
+var opts={"stroke":"none","fill":this.options.backgroundColor.toRGBString()};
+this._drawRect(0,0,this.width,this.height,opts);
+};
+PlotKit.SVGRenderer.prototype._drawRect=function(x,y,w,h,_304){
+var _305={x:x+"px",y:y+"px",width:w+"px",height:h+"px"};
+if(_304){
+MochiKit.Base.update(_305,_304);
+}
+var elem=this.createSVGElement("rect",_305);
+this.root.appendChild(elem);
+};
+PlotKit.SVGRenderer.prototype._drawLine=function(x1,y1,x2,y2,_306){
+var _307={x1:x1+"px",y1:y1+"px",x2:x2+"px",y2:y2+"px"};
+if(_306){
+MochiKit.Base.update(_307,_306);
+}
+var elem=this.createSVGElement("line",_307);
+this.root.appendChild(elem);
+};
+PlotKit.SVGRenderer.prototype.clear=function(){
+while(this.element.firstChild){
+this.element.removeChild(this.element.firstChild);
+}
+if(this.options.axisLabelUseDiv){
+for(var i=0;i<this.xlabels.length;i++){
+MochiKit.DOM.removeElement(this.xlabels[i]);
+}
+for(var i=0;i<this.ylabels.length;i++){
+MochiKit.DOM.removeElement(this.ylabels[i]);
+}
+}
+this.xlabels=new Array();
+this.ylabels=new Array();
+};
+PlotKit.SVGRenderer.prototype.createSVGElement=function(name,_309){
+var _310=MochiKit.Base.isUndefinedOrNull;
+var elem;
+var doc=_310(this.document)?document:this.document;
+try{
+elem=doc.createElementNS(PlotKit.SVGRenderer.SVGNS,name);
+}
+catch(e){
+elem=doc.createElement(name);
+elem.setAttribute("xmlns",PlotKit.SVGRenderer.SVGNS);
+}
+if(_309){
+MochiKit.DOM.updateNodeAttributes(elem,_309);
+}
+return elem;
+};
+PlotKit.SVGRenderer.SVG=function(_312){
+var ie=navigator.appVersion.match(/MSIE (\d\.\d)/);
+var _313=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);
+if(ie&&(ie[1]>=6)&&(!_313)){
+var _314=_312["width"]?_312["width"]:"100";
+var _315=_312["height"]?_312["height"]:"100";
+var eid=_312["id"]?_312["id"]:"notunique";
+var html="<svg:svg width=\""+_314+"\" height=\""+_315+"\" ";
+html+="id=\""+eid+"\" version=\"1.1\" baseProfile=\"full\" />";
+var _318=document.createElement(html);
+var _319=_318.getSVGDocument().createElementNS(PlotKit.SVGRenderer.SVGNS,"svg");
+_319.setAttribute("width",_314);
+_319.setAttribute("height",_315);
+_318.getSVGDocument().appendChild(_319);
+return _318;
+}else{
+return PlotKit.SVGRenderer.prototype.createSVGElement("svg",_312);
+}
+};
+PlotKit.SVGRenderer.isSupported=function(){
+var _320=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);
+var _321=navigator.appVersion.match(/MSIE (\d\.\d)/);
+var _322=navigator.userAgent.match(/AppleWebKit\/(\d+)/);
+var _323=navigator.userAgent.match(/Opera\/(\d*\.\d*)/);
+var _324=navigator.userAgent.match(/rv:(\d*\.\d*).*Gecko/);
+var _325="http://www.w3.org/TR/SVG11/feature#SVG";
+if(_321&&(_321[1]>=6)&&!_320){
+return document.implementation.hasFeature(_325,"1.1");
+}
+if(_323&&(_323[1]>8.9)){
+return true;
+}
+if(_324&&(_324>1.7)){
+return true;
+}
+return false;
+};
+PlotKit.SVG={};
+PlotKit.SVG.SVGRenderer=PlotKit.SVGRenderer;
+PlotKit.SVG.EXPORT=["SVGRenderer"];
+PlotKit.SVG.EXPORT_OK=["SVGRenderer"];
+PlotKit.SVG.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.SVG.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.SVG);
+try{
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "SweetCanvas depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, Canvas}";
+}
+if(typeof (PlotKit.SweetCanvasRenderer)=="undefined"){
+PlotKit.SweetCanvasRenderer={};
+}
+PlotKit.SweetCanvasRenderer=function(_326,_327,_328){
+if(arguments.length>0){
+this.__init__(_326,_327,_328);
+}
+};
+PlotKit.SweetCanvasRenderer.NAME="PlotKit.SweetCanvasRenderer";
+PlotKit.SweetCanvasRenderer.VERSION=PlotKit.VERSION;
+PlotKit.SweetCanvasRenderer.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.SweetCanvasRenderer.toString=function(){
+return this.__repr__();
+};
+PlotKit.SweetCanvasRenderer.prototype=new PlotKit.CanvasRenderer();
+PlotKit.SweetCanvasRenderer.prototype.constructor=PlotKit.SweetCanvasRenderer;
+PlotKit.SweetCanvasRenderer.__super__=PlotKit.CanvasRenderer.prototype;
+PlotKit.SweetCanvasRenderer.prototype.__init__=function(el,_330,opts){
+var _331=PlotKit.Base.officeBlue();
+MochiKit.Base.update(_331,opts);
+PlotKit.SweetCanvasRenderer.__super__.__init__.call(this,el,_330,_331);
+};
+PlotKit.SweetCanvasRenderer.prototype._renderBarChart=function(){
+var bind=MochiKit.Base.bind;
+var _332=Color.blackColor().colorWithAlpha(0.1).toRGBString();
+var _333=function(_334,x,y,w,h){
+_334.fillStyle=_332;
+_334.fillRect(x-2,y-2,w+4,h+2);
+_334.fillStyle=_332;
+_334.fillRect(x-1,y-1,w+2,h+1);
+};
+var _335=this.options.colorScheme.length;
+var _336=this.options.colorScheme;
+var _337=PlotKit.Base.keys(this.layout.datasets);
+var _338=_337.length;
+var _339=function(name){
+for(var i=0;i<_338;i++){
+if(name==_337[i]){
+return _336[i%_335];
+}
+}
+return _336[0];
+};
+var _340=function(_341,bar){
+var x=this.area.w*bar.x+this.area.x;
+var y=this.area.h*bar.y+this.area.y;
+var w=this.area.w*bar.w;
+var h=this.area.h*bar.h;
+if((w<1)||(h<1)){
+return;
+}
+_341.save();
+_341.shadowBlur=5;
+_341.shadowColor=Color.fromHexString("#888888").toRGBString();
+if(this.isIE){
+_341.save();
+_341.fillStyle="#cccccc";
+_341.fillRect(x-2,y-2,w+4,h+2);
+_341.restore();
+}else{
+_333(_341,x,y,w,h);
+}
+if(this.options.shouldFill){
+_341.fillStyle=_339(bar.name).toRGBString();
+_341.fillRect(x,y,w,h);
+}
+_341.shadowBlur=0;
+_341.strokeStyle=Color.whiteColor().toRGBString();
+_341.lineWidth=2;
+if(this.options.shouldStroke){
+_341.strokeRect(x,y,w,h);
+}
+_341.restore();
+};
+this._renderBarChartWrap(this.layout.bars,bind(_340,this));
+};
+PlotKit.SweetCanvasRenderer.prototype._renderLineChart=function(){
+var _342=this.element.getContext("2d");
+var _343=this.options.colorScheme.length;
+var _344=this.options.colorScheme;
+var _345=PlotKit.Base.keys(this.layout.datasets);
+var _346=_345.length;
+var bind=MochiKit.Base.bind;
+for(var i=0;i<_346;i++){
+var _347=_345[i];
+var _348=_344[i%_343];
+var _349=this.options.strokeColorTransform;
+_342.save();
+var _350=function(ctx){
+ctx.beginPath();
+ctx.moveTo(this.area.x,this.area.y+this.area.h);
+var _351=function(ctx_,_352){
+if(_352.name==_347){
+ctx_.lineTo(this.area.w*_352.x+this.area.x,this.area.h*_352.y+this.area.y);
+}
+};
+MochiKit.Iter.forEach(this.layout.points,partial(_351,ctx),this);
+ctx.lineTo(this.area.w+this.area.x,this.area.h+this.area.y);
+ctx.lineTo(this.area.x,this.area.y+this.area.h);
+ctx.closePath();
+};
+if(this.options.shouldFill){
+_342.save();
+if(this.isIE){
+_342.fillStyle="#cccccc";
+}else{
+_342.fillStyle=Color.blackColor().colorWithAlpha(0.2).toRGBString();
+}
+_342.translate(-1,-2);
+bind(_350,this)(_342);
+if(this.options.shouldFill){
+_342.fill();
+}
+_342.restore();
+}
+_342.shadowBlur=5;
+_342.shadowColor=Color.fromHexString("#888888").toRGBString();
+_342.fillStyle=_348.toRGBString();
+_342.lineWidth=2;
+_342.strokeStyle=Color.whiteColor().toRGBString();
+if(this.options.shouldFill){
+bind(_350,this)(_342);
+_342.fill();
+}
+if(this.options.shouldStroke){
+bind(_350,this)(_342);
+_342.stroke();
+}
+_342.restore();
+}
+};
+PlotKit.SweetCanvasRenderer.prototype._renderPieChart=function(){
+var _353=this.element.getContext("2d");
+var _354=this.options.colorScheme.length;
+var _355=this.layout.slices;
+var _356=this.area.x+this.area.w*0.5;
+var _357=this.area.y+this.area.h*0.5;
+var _358=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+if(this.isIE){
+_356=parseInt(_356);
+_357=parseInt(_357);
+_358=parseInt(_358);
+}
+if(!this.isIE){
+_353.save();
+var _359=Color.blackColor().colorWithAlpha(0.2);
+_353.fillStyle=_359.toRGBString();
+_353.shadowBlur=5;
+_353.shadowColor=Color.fromHexString("#888888").toRGBString();
+_353.translate(1,1);
+_353.beginPath();
+_353.moveTo(_356,_357);
+_353.arc(_356,_357,_358+2,0,Math.PI*2,false);
+_353.closePath();
+_353.fill();
+_353.restore();
+}
+_353.save();
+_353.strokeStyle=Color.whiteColor().toRGBString();
+_353.lineWidth=2;
+for(var i=0;i<_355.length;i++){
+var _360=this.options.colorScheme[i%_354];
+_353.fillStyle=_360.toRGBString();
+var _361=function(){
+_353.beginPath();
+_353.moveTo(_356,_357);
+_353.arc(_356,_357,_358,_355[i].startAngle-Math.PI/2,_355[i].endAngle-Math.PI/2,false);
+_353.lineTo(_356,_357);
+_353.closePath();
+};
+if(Math.abs(_355[i].startAngle-_355[i].endAngle)>0.0001){
+if(this.options.shouldFill){
+_361();
+_353.fill();
+}
+if(this.options.shouldStroke){
+_361();
+_353.stroke();
+}
+}
+}
+_353.restore();
+};
+PlotKit.SweetCanvasRenderer.prototype._renderBackground=function(){
+var _362=this.element.getContext("2d");
+if(this.layout.style=="bar"||this.layout.style=="line"){
+_362.save();
+_362.fillStyle=this.options.backgroundColor.toRGBString();
+_362.fillRect(this.area.x,this.area.y,this.area.w,this.area.h);
+_362.strokeStyle=this.options.axisLineColor.toRGBString();
+_362.lineWidth=1;
+var _363=this.layout.yticks;
+var _364=false;
+if(this.layout.style=="bar"&&this.layout.options.barOrientation=="horizontal"){
+_363=this.layout.xticks;
+_364=true;
+}
+for(var i=0;i<_363.length;i++){
+var x1=0;
+var y1=0;
+var x2=0;
+var y2=0;
+if(_364){
+x1=_363[i][0]*this.area.w+this.area.x;
+y1=this.area.y;
+x2=x1;
+y2=y1+this.area.h;
+}else{
+x1=this.area.x;
+y1=_363[i][0]*this.area.h+this.area.y;
+x2=x1+this.area.w;
+y2=y1;
+}
+_362.beginPath();
+_362.moveTo(x1,y1);
+_362.lineTo(x2,y2);
+_362.closePath();
+_362.stroke();
+}
+_362.restore();
+}else{
+PlotKit.SweetCanvasRenderer.__super__._renderBackground.call(this);
+}
+};
+PlotKit.SweetCanvas={};
+PlotKit.SweetCanvas.SweetCanvasRenderer=PlotKit.SweetCanvasRenderer;
+PlotKit.SweetCanvas.EXPORT=["SweetCanvasRenderer"];
+PlotKit.SweetCanvas.EXPORT_OK=["SweetCanvasRenderer"];
+PlotKit.SweetCanvas.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.SweetCanvas.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.SweetCanvas);
+try{
+if(typeof (PlotKit.SVGRenderer)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "SweetSVG depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, SVG}";
+}
+if(typeof (PlotKit.SweetSVGRenderer)=="undefined"){
+PlotKit.SweetSVGRenderer={};
+}
+PlotKit.SweetSVGRenderer=function(_365,_366,_367){
+if(arguments.length>0){
+this.__init__(_365,_366,_367);
+}
+};
+PlotKit.SweetSVGRenderer.NAME="PlotKit.SweetSVGRenderer";
+PlotKit.SweetSVGRenderer.VERSION=PlotKit.VERSION;
+PlotKit.SweetSVGRenderer.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.SweetSVGRenderer.toString=function(){
+return this.__repr__();
+};
+PlotKit.SweetSVGRenderer.prototype=new PlotKit.SVGRenderer();
+PlotKit.SweetSVGRenderer.prototype.constructor=PlotKit.SweetSVGRenderer;
+PlotKit.SweetSVGRenderer.__super__=PlotKit.SVGRenderer.prototype;
+PlotKit.SweetSVGRenderer.prototype.__init__=function(_368,_369,_370){
+var _371=PlotKit.Base.officeBlue();
+MochiKit.Base.update(_371,_370);
+PlotKit.SweetSVGRenderer.__super__.__init__.call(this,_368,_369,_371);
+};
+PlotKit.SweetSVGRenderer.prototype._addDropShadowFilter=function(){
+var _372=this.createSVGElement("filter",{x:0,y:0,"id":"dropShadow"});
+var _373=this.createSVGElement("feOffset",{"in":"SourceGraphic","dx":0,"dy":0,"result":"topCopy"});
+var blur=this.createSVGElement("feGaussianBlur",{"in":"SourceAlpha","StdDeviation":2,"result":"shadow"});
+var _375=this.createSVGElement("feOffset",{"in":"shadow","dx":-1,"dy":-2,"result":"movedShadow"});
+var _376=this.createSVGElement("feMerge");
+var _377=this.createSVGElement("feMergeNode",{"in":"topCopy"});
+var _378=this.createSVGElement("feMergeNode",{"in":"movedShadow"});
+_376.appendChild(_377);
+_376.appendChild(_378);
+_372.appendChild(_373);
+_372.appendChild(blur);
+_372.appendChild(_375);
+_372.appendChild(_376);
+this.defs.appendChild(_372);
+};
+PlotKit.SweetSVGRenderer.prototype._renderBarChart=function(){
+var bind=MochiKit.Base.bind;
+var _379=Color.blackColor().toRGBString();
+var _380="fill:"+_379+";fill-opacity:0.15";
+var _381="stroke-width: 2.0; stroke:"+Color.whiteColor().toRGBString();
+var _382=function(_383,bar){
+var x=this.area.w*bar.x+this.area.x;
+var y=this.area.h*bar.y+this.area.y;
+var w=this.area.w*bar.w;
+var h=this.area.h*bar.h;
+if((w<1)||(h<1)){
+return;
+}
+_383["style"]=_381;
+this._drawRect(x-2,y-1,w+4,h+2,{"style":_380});
+this._drawRect(x,y,w,h,_383);
+};
+this._renderBarOrLine(this.layout.bars,bind(_382,this));
+};
+PlotKit.SweetSVGRenderer.prototype._renderLineChart=function(){
+var bind=MochiKit.Base.bind;
+var _384=Color.blackColor().toRGBString();
+var _385="fill:"+_384+";fill-opacity:0.15";
+var _386="stroke-width: 2.0; stroke:"+Color.whiteColor().toRGBString();
+var _387=function(_388,_389){
+this._tempPointsBuffer+=(this.area.w*_389.x+this.area.x)+","+(this.area.h*_389.y+this.area.y)+" ";
+};
+var _390=function(_391){
+this._tempPointsBuffer="";
+this._tempPointsBuffer+=(this.area.x)+","+(this.area.y+this.area.h)+" ";
+};
+var _392=function(_393){
+this._tempPointsBuffer+=(this.area.w+this.area.x)+","+(this.area.h+this.area.y);
+_393["points"]=this._tempPointsBuffer;
+_393["stroke"]="none";
+_393["transform"]="translate(-2, -1)";
+_393["style"]=_385;
+var _394=this.createSVGElement("polygon",_393);
+this.root.appendChild(_394);
+_393["transform"]="";
+_393["style"]=_386;
+var elem=this.createSVGElement("polygon",_393);
+this.root.appendChild(elem);
+};
+this._renderBarOrLine(this.layout.points,bind(_387,this),bind(_390,this),bind(_392,this));
+};
+PlotKit.SweetSVGRenderer.prototype._renderPieChart=function(){
+var _395=this.area.x+this.area.w*0.5;
+var _396=this.area.y+this.area.h*0.5;
+var _397=Color.blackColor().toRGBString();
+var _398=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+var _399="fill:"+_397+";fill-opacity:0.15";
+var _400=this.createSVGElement("circle",{"style":_399,"cx":_395+1,"cy":_396+1,"r":_398+1});
+this.root.appendChild(_400);
+PlotKit.SweetSVGRenderer.__super__._renderPieChart.call(this);
+};
+PlotKit.SweetSVGRenderer.prototype._renderBackground=function(){
+var _401={"fill":this.options.backgroundColor.toRGBString(),"stroke":"none"};
+if(this.layout.style=="bar"||this.layout.style=="line"){
+this._drawRect(this.area.x,this.area.y,this.area.w,this.area.h,_401);
+var _402=this.layout.yticks;
+var _403=false;
+if(this.layout.style=="bar"&&this.layout.options.barOrientation=="horizontal"){
+_402=this.layout.xticks;
+_403=true;
+}
+for(var i=0;i<_402.length;i++){
+var x=0;
+var y=0;
+var w=0;
+var h=0;
+if(_403){
+x=_402[i][0]*this.area.w+this.area.x;
+y=this.area.y;
+w=1;
+h=this.area.w;
+}else{
+x=this.area.x;
+y=_402[i][0]*this.area.h+this.area.y;
+w=this.area.w;
+h=1;
+}
+this._drawRect(x,y,w,h,{"fill":this.options.axisLineColor.toRGBString()});
+}
+}else{
+PlotKit.SweetSVGRenderer.__super__._renderBackground.call(this);
+}
+};
+PlotKit.SweetSVG={};
+PlotKit.SweetSVG.SweetSVGRenderer=PlotKit.SweetSVGRenderer;
+PlotKit.SweetSVG.EXPORT=["SweetSVGRenderer"];
+PlotKit.SweetSVG.EXPORT_OK=["SweetSVGRenderer"];
+PlotKit.SweetSVG.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.SweetSVG.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.SweetSVG);
+try{
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.EasyPlot depends on all of PlotKit's components";
+}
+if(typeof (PlotKit.EasyPlot)=="undefined"){
+PlotKit.EasyPlot={};
+}
+PlotKit.EasyPlot.NAME="PlotKit.EasyPlot";
+PlotKit.EasyPlot.VERSION=PlotKit.VERSION;
+PlotKit.EasyPlot.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.EasyPlot.toString=function(){
+return this.__repr__();
+};
+PlotKit.EasyPlot=function(_404,_405,_406,_407){
+this.layout=new Layout(_404,_405);
+this.divElem=_406;
+this.width=parseInt(_406.getAttribute("width"));
+this.height=parseInt(_406.getAttribute("height"));
+this.deferredCount=0;
+if(this.width<1){
+this.width=this.divElem.width?this.divElem.width:300;
+}
+if(this.height<1){
+this.height=this.divElem.height?this.divElem.height:300;
+}
+if(isArrayLike(_407)){
+for(var i=0;i<_407.length;i++){
+if(typeof (_407[i])=="string"){
+this.deferredCount++;
+var d=MochiKit.Async.doSimpleXMLHttpRequest(_407[i]);
+d.addCallback(MochiKit.Base.bind(PlotKit.EasyPlot.onDataLoaded,this));
+}else{
+if(isArrayLike(_407[i])){
+this.layout.addDataset("data-"+i,_407[i]);
+}
+}
+}
+}else{
+if(!isUndefinedOrNull(_407)){
+throw "Passed datasources are not Array like";
+}
+}
+if(CanvasRenderer.isSupported()){
+this.element=CANVAS({"id":this.divElem.getAttribute("id")+"-canvas","width":this.width,"height":this.height},"");
+this.divElem.appendChild(this.element);
+this.renderer=new SweetCanvasRenderer(this.element,this.layout,_405);
+}else{
+if(SVGRenderer.isSupported()){
+this.element=SVGRenderer.SVG({"id":this.divElem.getAttribute("id")+"-svg","width":this.width,"height":this.height,"version":"1.1","baseProfile":"full"},"");
+this.divElem.appendChild(this.element);
+this.renderer=new SweetSVGRenderer(this.element,this.layout,_405);
+}
+}
+if((this.deferredCount==0)&&(PlotKit.Base.keys(this.layout.datasets).length>0)){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+}
+};
+PlotKit.EasyPlot.onDataLoaded=function(_409){
+var _410=new Array();
+var _411=_409.responseText.split("\n");
+for(var i=0;i<_411.length;i++){
+var _412=MochiKit.Format.strip(_411[i]);
+if((_412.length>1)&&(_412.charAt(0)!="#")){
+_410.push(_412.split(","));
+}
+}
+this.layout.addDataset("data-ajax-"+this.deferredCount,_410);
+this.deferredCount--;
+if((this.deferredCount==0)&&(PlotKit.Base.keys(this.layout.datasets).length>0)){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+}
+};
+PlotKit.EasyPlot.prototype.reload=function(){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+};
+PlotKit.EasyPlotModule={};
+PlotKit.EasyPlotModule.EasyPlot=PlotKit.EasyPlot;
+PlotKit.EasyPlotModule.EXPORT=["EasyPlot"];
+PlotKit.EasyPlotModule.EXPORT_OK=[];
+PlotKit.EasyPlotModule.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.EasyPlotModule.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.EasyPlotModule);
+
+
diff --git a/share/web/static/js/PlotKit/SVG.js b/share/web/static/js/PlotKit/SVG.js
new file mode 100644
index 0000000..3687bc0
--- /dev/null
+++ b/share/web/static/js/PlotKit/SVG.js
@@ -0,0 +1,705 @@
+/*
+    PlotKit SVG
+    ===========
+    SVG Renderer for PlotKit
+
+    Copyright
+    ---------
+    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
+    For use under the BSD license. <http://www.liquidx.net/plotkit>
+*/
+
+// -------------------------------------------------------------------------
+// NOTES: - If you use XHTML1.1 strict, then you must include each MochiKit
+//          file individuall.
+//        - For IE support, you must include the AdobeSVG object hack.
+//          See tests/svg.html for details.
+// -------------------------------------------------------------------------
+// -------------------------------------------------------------------------
+// Check required components
+// -------------------------------------------------------------------------
+
+try {    
+    if (typeof(PlotKit.Layout) == 'undefined')
+    {
+        throw "";    
+    }
+} 
+catch (e) {    
+    throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Layout"
+}
+
+
+// ---------------------------------------------------------------------------
+//  SVG Renderer
+// ---------------------------------------------------------------------------
+
+PlotKit.SVGRenderer = function(element, layout, options) {
+    if (arguments.length > 0) 
+        this.__init__(element, layout, options);
+};
+
+PlotKit.SVGRenderer.NAME = "PlotKit.SVGRenderer";
+PlotKit.SVGRenderer.VERSION = PlotKit.VERSION;
+
+PlotKit.SVGRenderer.__repr__ = function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.SVGRenderer.toString = function() {
+    return this.__repr__();
+}
+
+PlotKit.SVGRenderer.SVGNS = 'http://www.w3.org/2000/svg';
+
+PlotKit.SVGRenderer.prototype.__init__ = function(element, layout, options) {
+    var isNil = MochiKit.Base.isUndefinedOrNull;
+
+    // default options
+    this.options = {
+        "drawBackground": true,
+        "backgroundColor": Color.whiteColor(),
+        "padding": {left: 30, right: 30, top: 5, bottom: 10},
+        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),
+        "strokeColor": Color.whiteColor(),
+        "strokeColorTransform": "asStrokeColor",
+        "strokeWidth": 0.5,
+        "shouldFill": true,
+        "shouldStroke": true,
+        "drawXAxis": true,
+        "drawYAxis": true,
+        "axisLineColor": Color.blackColor(),
+        "axisLineWidth": 0.5,
+        "axisTickSize": 3,
+        "axisLabelColor": Color.blackColor(),
+        "axisLabelFont": "Arial",
+        "axisLabelFontSize": 9,
+        "axisLabelWidth": 50,
+        "axisLabelUseDiv": true,
+        "pieRadius": 0.4,
+        "enableEvents": true
+    };
+
+    MochiKit.Base.update(this.options, options ? options : {});
+    this.layout = layout;
+    this.element = MochiKit.DOM.getElement(element);
+    this.container = this.element.parentNode;
+    this.height = parseInt(this.element.getAttribute("height"));
+    this.width = parseInt(this.element.getAttribute("width"));
+    this.document = document;
+    this.root = this.element;
+
+    // Adobe SVG Support:
+    // - if an exception is thrown, then no Adobe SVG Plugin support.
+    try {
+        this.document = this.element.getSVGDocument();
+        this.root = isNil(this.document.documentElement) ? this.element : this.document.documentElement;
+    }
+    catch (e) {
+    }
+
+    this.element.style.zIndex = 1;
+
+    if (isNil(this.element))
+        throw "SVGRenderer() - passed SVG object is not found";
+
+    if (isNil(this.container) || this.container.nodeName.toLowerCase() != "div")
+        throw "SVGRenderer() - No DIV's around the SVG.";
+
+    // internal state
+    this.xlabels = new Array();
+    this.ylabels = new Array();
+
+    // initialise some meta structures in SVG
+    this.defs = this.createSVGElement("defs");
+
+    this.area = {
+        x: this.options.padding.left,
+        y: this.options.padding.top,
+        w: this.width - this.options.padding.left - this.options.padding.right,
+        h: this.height - this.options.padding.top - this.options.padding.bottom
+    };
+
+    MochiKit.DOM.updateNodeAttributes(this.container, 
+    {"style":{ "position": "relative", "width": this.width + "px"}});
+
+    
+};
+
+
+PlotKit.SVGRenderer.prototype.render = function() {
+    if (this.options.drawBackground)
+        this._renderBackground();
+
+    if (this.layout.style == "bar") {
+        this._renderBarChart();
+        this._renderBarAxis();
+    }
+    else if (this.layout.style == "pie") {
+        this._renderPieChart();
+        this._renderPieAxis();
+    }
+    else if (this.layout.style == "line") {
+        this._renderLineChart();
+        this._renderLineAxis();
+    }
+};
+
+PlotKit.SVGRenderer.prototype._renderBarOrLine = function(data, plotFunc, startFunc, endFunc) {
+    
+    var colorCount = this.options.colorScheme.length;
+    var colorScheme = this.options.colorScheme;
+    var setNames = MochiKit.Base.keys(this.layout.datasets);
+    var setCount = setNames.length;
+
+    for (var i = 0; i < setCount; i++) {
+        var setName = setNames[i];
+        var attrs = new Array();
+        var color = colorScheme[i%colorCount];
+
+        if (this.options.shouldFill)
+            attrs["fill"] = color.toRGBString();
+        else
+            attrs["fill"] = "none";
+
+        if (this.options.shouldStroke && 
+            (this.options.strokeColor || this.options.strokeColorTransform)) {
+            if (this.options.strokeColor)
+                attrs["stroke"] = this.options.strokeColor.toRGBString();
+            else if (this.options.strokeColorTransform)
+                attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString();
+            attrs["strokeWidth"] = this.options.strokeWidth;
+        }
+
+        if (startFunc)
+            startFunc(attrs);
+
+        var forEachFunc = function(obj) {
+            if (obj.name == setName)
+                plotFunc(attrs, obj);
+        };                
+
+        MochiKit.Iter.forEach(data, bind(forEachFunc, this));
+        if (endFunc)
+            endFunc(attrs);
+    }
+};
+
+PlotKit.SVGRenderer.prototype._renderBarChart = function() {
+    var bind = MochiKit.Base.bind;
+
+    var drawRect = function(attrs, bar) {
+        var x = this.area.w * bar.x + this.area.x;
+        var y = this.area.h * bar.y + this.area.y;
+        var w = this.area.w * bar.w;
+        var h = this.area.h * bar.h;
+        this._drawRect(x, y, w, h, attrs);
+    };
+    this._renderBarOrLine(this.layout.bars, bind(drawRect, this));
+};
+
+PlotKit.SVGRenderer.prototype._renderLineChart = function() {
+    var bind = MochiKit.Base.bind;
+
+    var addPoint = function(attrs, point) {
+        this._tempPointsBuffer += (this.area.w * point.x + this.area.x) + "," +
+                                 (this.area.h * point.y + this.area.y) + " ";
+    };
+
+    var startLine = function(attrs) {
+        this._tempPointsBuffer = "";
+        this._tempPointsBuffer += (this.area.x) + "," + (this.area.y+this.area.h) + " ";
+    };
+
+    var endLine = function(attrs) {
+        this._tempPointsBuffer += (this.area.w + this.area.x) + ","  +(this.area.h + this.area.y);
+        attrs["points"] = this._tempPointsBuffer;
+        var elem = this.createSVGElement("polygon", attrs);
+        this.root.appendChild(elem);
+    };
+
+    this._renderBarOrLine(this.layout.points, 
+                          bind(addPoint, this), 
+                          bind(startLine, this), 
+                          bind(endLine, this));
+};
+
+
+PlotKit.SVGRenderer.prototype._renderPieChart = function() {
+    var colorCount = this.options.colorScheme.length;
+    var slices = this.layout.slices;
+
+    var centerx = this.area.x + this.area.w * 0.5;
+    var centery = this.area.y + this.area.h * 0.5;
+    var radius = Math.min(this.area.w * this.options.pieRadius, 
+                          this.area.h * this.options.pieRadius);
+
+    // NOTE NOTE!! Canvas Tag draws the circle clockwise from the y = 0, x = 1
+    // so we have to subtract 90 degrees to make it start at y = 1, x = 0
+
+	// workaround if we only have 1 slice of 100%
+	if (slices.length == 1 && (Math.abs(slices[0].startAngle) - Math.abs(slices[0].endAngle) < 0.1)) {
+        var attrs = {"cx": centerx , "cy": centery , "r": radius };
+        var color = this.options.colorScheme[0];
+        if (this.options.shouldFill)
+            attrs["fill"] = color.toRGBString();
+        else
+            attrs["fill"] = "none";
+
+        if (this.options.shouldStroke && 
+            (this.options.strokeColor || this.options.strokeColorTransform)) {
+            if (this.options.strokeColor)
+                attrs["stroke"] = this.options.strokeColor.toRGBString();
+            else if (this.options.strokeColorTransform)
+                attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString();
+            attrs["style"] = "stroke-width: " + this.options.strokeWidth;
+        }
+
+        this.root.appendChild(this.createSVGElement("circle", attrs));
+        return;
+	}
+
+    for (var i = 0; i < slices.length; i++) {
+        var attrs = new Array();
+        var color = this.options.colorScheme[i%colorCount];
+        if (this.options.shouldFill)
+            attrs["fill"] = color.toRGBString();
+        else
+            attrs["fill"] = "none";
+
+        if (this.options.shouldStroke &&
+            (this.options.strokeColor || this.options.strokeColorTransform)) {
+            if (this.options.strokeColor)
+                attrs["stroke"] = this.options.strokeColor.toRGBString();
+            else if (this.options.strokeColorTransform)
+                attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString();
+            attrs["style"] = "stroke-width:" + this.options.strokeWidth;
+        }
+
+        var largearc = 0;
+        if (Math.abs(slices[i].endAngle - slices[i].startAngle) > Math.PI)
+            largearc = 1;
+        var x1 = Math.cos(slices[i].startAngle - Math.PI/2) * radius;
+        var y1 = Math.sin(slices[i].startAngle - Math.PI/2) * radius;
+        var x2 = Math.cos(slices[i].endAngle - Math.PI/2) * radius;
+        var y2 = Math.sin(slices[i].endAngle - Math.PI/2) * radius;
+        var rx = x2 - x1;
+        var ry = y2 - y1;
+
+        var pathString = "M" + centerx + "," + centery + " ";       
+        pathString += "l" + x1 + "," + y1 + " ";
+        pathString += "a" + radius + "," + radius + " 0 " + largearc + ",1 " + rx + "," + ry + " z";
+
+        attrs["d"] = pathString;
+
+        var elem = this.createSVGElement("path", attrs);
+        this.root.appendChild(elem);
+    }
+};
+
+PlotKit.SVGRenderer.prototype._renderBarAxis = function() {
+    this._renderAxis();
+}
+
+PlotKit.SVGRenderer.prototype._renderLineAxis = function() {
+    this._renderAxis();
+};
+
+
+PlotKit.SVGRenderer.prototype._renderAxis = function() {
+
+    if (!this.options.drawXAxis && !this.options.drawYAxis)
+        return;
+
+    var labelStyle = {"style":
+         {"position": "absolute",
+          "textAlign": "center",
+          "fontSize": this.options.axisLabelFontSize + "px",
+          "zIndex": 10,
+          "color": this.options.axisLabelColor.toRGBString(),
+          "width": this.options.axisLabelWidth + "px",
+          "overflow": "hidden"
+         }
+    };
+
+    // axis lines
+    var lineAttrs = {
+        "stroke": this.options.axisLineColor.toRGBString(),
+        "strokeWidth": this.options.axisLineWidth
+    };
+    
+
+    if (this.options.drawYAxis) {
+        if (this.layout.yticks) {
+            var drawTick = function(tick) {
+                var x = this.area.x;
+                var y = this.area.y + tick[0] * this.area.h;
+                this._drawLine(x, y, x - 3, y, lineAttrs);
+                
+                if (this.options.axisLabelUseDiv) {
+                    var label = DIV(labelStyle, tick[1]);
+                    label.style.top = (y - this.options.axisLabelFontSize) + "px";
+                    label.style.left = (x - this.options.padding.left + this.options.axisTickSize) + "px";
+                    label.style.textAlign = "left";
+                    label.style.width = (this.options.padding.left - 3) + "px";
+                    MochiKit.DOM.appendChildNodes(this.container, label);
+                    this.ylabels.push(label);
+                }
+                else {
+                    var attrs = {
+                        y: y + 3,
+                        x: (x - this.options.padding.left + 3),
+                        width: (this.options.padding.left - this.options.axisTickSize) + "px",
+                        height: (this.options.axisLabelFontSize + 3) + "px",
+                        fontFamily: "Arial",
+                        fontSize: this.options.axisLabelFontSize + "px",
+                        fill: this.options.axisLabelColor.toRGBString()
+                    };
+                    
+                    /* we can do clipping just like DIVs
+                    http://www.xml.com/pub/a/2004/06/02/svgtype.html */
+                    /*
+                    var mask = this.createSVGElement("mask", {id: "mask" + tick[0]});
+                    var maskShape = this.createSVGElement("rect",
+                        {y: y + 3,
+                         x: (x - this.options.padding.left + 3),
+                         width: (this.options.padding.left - this.options.axisTickSize) + "px",
+                         height: (this.options.axisLabelFontSize + 3) + "px",
+                         style: {"fill": "#ffffff", "stroke": "#000000"}});
+                    mask.appendChild(maskShape);
+                    this.defs.appendChild(mask);
+                    
+                    attrs["filter"] = "url(#mask" + tick[0] + ")";
+                    */
+                    
+                    var label = this.createSVGElement("text", attrs);
+                    label.appendChild(this.document.createTextNode(tick[1]));
+                    this.root.appendChild(label);
+                }
+            };
+            
+            MochiKit.Iter.forEach(this.layout.yticks, bind(drawTick, this));
+        }
+
+        this._drawLine(this.area.x, this.area.y, this.area.x, this.area.y + this.area.h, lineAttrs);
+    }
+
+    if (this.options.drawXAxis) {
+        if (this.layout.xticks) {
+            var drawTick = function(tick) {
+                var x = this.area.x + tick[0] * this.area.w;
+                var y = this.area.y + this.area.h;
+                this._drawLine(x, y, x, y + this.options.axisTickSize, lineAttrs);
+
+                if (this.options.axisLabelUseDiv) {
+                    var label = DIV(labelStyle, tick[1]);
+                    label.style.top = (y + this.options.axisTickSize) + "px";
+                    label.style.left = (x - this.options.axisLabelWidth/2) + "px";
+                    label.style.textAlign = "center";
+                    label.style.width = this.options.axisLabelWidth + "px";
+                    MochiKit.DOM.appendChildNodes(this.container, label);
+                    this.xlabels.push(label);
+                }
+                else {
+                    var attrs = {
+                        y: (y + this.options.axisTickSize + this.options.axisLabelFontSize),
+                        x: x - 3,
+                        width: this.options.axisLabelWidth + "px",
+                        height: (this.options.axisLabelFontSize + 3) + "px",
+                        fontFamily: "Arial",
+                        fontSize: this.options.axisLabelFontSize + "px",
+                        fill: this.options.axisLabelColor.toRGBString(),
+                        textAnchor: "middle"
+                    };
+                    var label = this.createSVGElement("text", attrs);
+                    label.appendChild(this.document.createTextNode(tick[1]));
+                    this.root.appendChild(label);
+                }
+            };
+            
+            MochiKit.Iter.forEach(this.layout.xticks, bind(drawTick, this));
+        }
+
+        this._drawLine(this.area.x, this.area.y + this.area.h, this.area.x + this.area.w, this.area.y + this.area.h, lineAttrs)
+    }
+};
+
+PlotKit.SVGRenderer.prototype._renderPieAxis = function() {
+
+    if (this.layout.xticks) {
+        // make a lookup dict for x->slice values
+        var lookup = new Array();
+        for (var i = 0; i < this.layout.slices.length; i++) {
+            lookup[this.layout.slices[i].xval] = this.layout.slices[i];
+        }
+        
+        var centerx = this.area.x + this.area.w * 0.5;
+        var centery = this.area.y + this.area.h * 0.5;
+        var radius = Math.min(this.area.w * this.options.pieRadius + 10, 
+                              this.area.h * this.options.pieRadius + 10);
+        var labelWidth = this.options.axisLabelWidth;
+        
+        for (var i = 0; i < this.layout.xticks.length; i++) {
+            var slice = lookup[this.layout.xticks[i][0]];
+            if (MochiKit.Base.isUndefinedOrNull(slice))
+                continue;
+                
+                
+            var angle = (slice.startAngle + slice.endAngle)/2;
+            // normalize the angle
+            var normalisedAngle = angle;
+            if (normalisedAngle > Math.PI * 2)
+                normalisedAngle = normalisedAngle - Math.PI * 2;
+            else if (normalisedAngle < 0)
+                normalisedAngle = normalisedAngle + Math.PI * 2;
+                
+            var labelx = centerx + Math.sin(normalisedAngle) * (radius + 10);
+            var labely = centery - Math.cos(normalisedAngle) * (radius + 10);
+
+            var attrib = {
+                "position": "absolute",
+                 "zIndex": 11,
+                "width": labelWidth + "px",
+                "fontSize": this.options.axisLabelFontSize + "px",
+                "overflow": "hidden",
+                "color": this.options.axisLabelColor.toHexString()
+            };
+
+            var svgattrib = {
+                "width": labelWidth + "px",
+                "fontSize": this.options.axisLabelFontSize + "px",
+                "height": (this.options.axisLabelFontSize + 3) + "px",
+                "fill": this.options.axisLabelColor.toRGBString()
+            };
+
+            if (normalisedAngle <= Math.PI * 0.5) {
+                // text on top and align left
+                MochiKit.Base.update(attrib, {
+                    'textAlign': 'left', 'verticalAlign': 'top',
+                    'left': labelx + 'px',
+                    'top':  (labely - this.options.axisLabelFontSize) + "px"
+                });
+                MochiKit.Base.update(svgattrib, {
+                    "x": labelx,
+                    "y" :(labely - this.options.axisLabelFontSize),
+                    "textAnchor": "left"
+                        });
+            }
+            else if ((normalisedAngle > Math.PI * 0.5) && (normalisedAngle <= Math.PI)) {
+                // text on bottom and align left
+                MochiKit.Base.update(attrib, {
+                    'textAlign': 'left', 'verticalAlign': 'bottom',
+                    'left': labelx + 'px',
+                    'top':  labely + "px"
+                });
+                MochiKit.Base.update(svgattrib, {
+                    'textAnchor': 'left',
+                    'x': labelx,
+                    'y':  labely
+                });
+            }
+            else if ((normalisedAngle > Math.PI) && (normalisedAngle <= Math.PI*1.5)) {
+                // text on bottom and align right
+                MochiKit.Base.update(attrib, {
+                    'textAlign': 'right', 'verticalAlign': 'bottom',
+                    'left': labelx + 'px',
+                    'top':  labely + "px"
+                });
+                MochiKit.Base.update(svgattrib, {
+                    'textAnchor': 'right',
+                    'x': labelx - labelWidth,
+                    'y':  labely
+                });
+            }
+            else {
+                // text on top and align right
+                MochiKit.Base.update(attrib, {
+                    'textAlign': 'left', 'verticalAlign': 'bottom',
+                    'left': labelx + 'px',
+                    'top':  labely + "px"
+                });
+                MochiKit.Base.update(svgattrib, {
+                    'textAnchor': 'left',
+                    'x': labelx - labelWidth,
+                    'y':  labely - this.options.axisLabelFontSize
+                });
+            }
+
+            if (this.options.axisLabelUseDiv) {
+                var label = DIV({'style': attrib}, this.layout.xticks[i][1]);
+                this.xlabels.push(label);
+                MochiKit.DOM.appendChildNodes(this.container, label);
+            }
+            else {
+                var label = this.createSVGElement("text", svgattrib);
+                label.appendChild(this.document.createTextNode(this.layout.xticks[i][1]))
+                this.root.appendChild(label);
+            }
+      }
+        
+    }
+};
+
+PlotKit.SVGRenderer.prototype._renderBackground = function() {
+    var opts = {"stroke": "none",
+                  "fill": this.options.backgroundColor.toRGBString()
+    };
+    this._drawRect(0, 0, this.width, this.height, opts);
+};
+
+PlotKit.SVGRenderer.prototype._drawRect = function(x, y, w, h, moreattrs) {
+    var attrs = {x: x + "px", y: y + "px", width: w + "px", height: h + "px"};
+    if (moreattrs)
+        MochiKit.Base.update(attrs, moreattrs);
+
+    var elem = this.createSVGElement("rect", attrs);
+    this.root.appendChild(elem);
+};
+
+PlotKit.SVGRenderer.prototype._drawLine = function(x1, y1, x2, y2, moreattrs) {
+    var attrs = {x1: x1 + "px", y1: y1 + "px", x2: x2 + "px", y2: y2 + "px"};
+    if (moreattrs)
+        MochiKit.Base.update(attrs, moreattrs);
+
+    var elem = this.createSVGElement("line", attrs);
+    this.root.appendChild(elem);
+}
+
+PlotKit.SVGRenderer.prototype.clear = function() {
+    while(this.element.firstChild) {
+        this.element.removeChild(this.element.firstChild);
+    }
+    
+    if (this.options.axisLabelUseDiv) {
+        for (var i = 0; i < this.xlabels.length; i++) {
+            MochiKit.DOM.removeElement(this.xlabels[i]);
+        }        
+        for (var i = 0; i < this.ylabels.length; i++) {
+            MochiKit.DOM.removeElement(this.ylabels[i]);
+        }            
+    }
+    this.xlabels = new Array();
+    this.ylabels = new Array();
+};
+
+
+PlotKit.SVGRenderer.prototype.createSVGElement = function(name, attrs) {
+    var isNil = MochiKit.Base.isUndefinedOrNull;
+    var elem;
+    var doc = isNil(this.document) ? document : this.document;
+
+    try {
+        elem = doc.createElementNS(PlotKit.SVGRenderer.SVGNS, name);
+    }
+    catch (e) {
+        elem = doc.createElement(name);
+        elem.setAttribute("xmlns", PlotKit.SVGRenderer.SVGNS);
+    }
+
+    if (attrs)
+        MochiKit.DOM.updateNodeAttributes(elem, attrs);
+
+    // TODO: we don't completely emulate the MochiKit.DOM.createElement
+    //       as we don't care about nodes contained. We really should though.
+
+    return elem;
+
+};
+
+
+PlotKit.SVGRenderer.SVG = function(attrs) {
+    // we have to do things differently for IE+AdobeSVG.
+    // My guess this works (via trial and error) is that we need to
+    // have an SVG object in order to use SVGDocument.createElementNS
+    // but IE doesn't allow us to that.
+
+    var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
+    var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
+    if (ie && (ie[1] >= 6) && (!opera)) {
+        var width = attrs["width"] ? attrs["width"] : "100";
+        var height = attrs["height"] ? attrs["height"] : "100";
+        var eid = attrs["id"] ? attrs["id"] : "notunique";
+        
+        var html = '<svg:svg width="' + width + '" height="' + height + '" ';
+        html += 'id="' + eid + '" version="1.1" baseProfile="full" />';
+
+        var canvas = document.createElement(html);
+
+        // create embedded SVG inside SVG.
+        var group = canvas.getSVGDocument().createElementNS(PlotKit.SVGRenderer.SVGNS, "svg");
+        group.setAttribute("width", width);
+        group.setAttribute("height", height);
+        canvas.getSVGDocument().appendChild(group);
+
+        return canvas;
+    }
+    else {
+        return PlotKit.SVGRenderer.prototype.createSVGElement("svg", attrs);
+    }
+};
+
+PlotKit.SVGRenderer.isSupported = function() {
+    var isOpera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
+    var ieVersion = navigator.appVersion.match(/MSIE (\d\.\d)/);
+    var safariVersion = navigator.userAgent.match(/AppleWebKit\/(\d+)/);
+    var operaVersion = navigator.userAgent.match(/Opera\/(\d*\.\d*)/);
+    var mozillaVersion = navigator.userAgent.match(/rv:(\d*\.\d*).*Gecko/);
+    var svgFeature = "http://www.w3.org/TR/SVG11/feature#SVG";
+
+    if (ieVersion && (ieVersion[1] >= 6) && !isOpera) {
+        return document.implementation.hasFeature(svgFeature,"1.1");
+        /*
+        var dummysvg = document.createElement('<svg:svg width="1" height="1" baseProfile="full" version="1.1" id="dummy">');
+        try {
+            dummysvg.getSVGDocument();
+            dummysvg = null;
+            return true;
+        }
+        catch (e) {
+            return false;
+        }
+        */
+        
+    }
+    
+    /* support not really there yet. no text and paths are buggy
+    if (safariVersion && (safariVersion[1] > 419))
+        return true;
+    */
+
+    if (operaVersion && (operaVersion[1] > 8.9))
+        return true
+    
+    if (mozillaVersion && (mozillaVersion > 1.7))
+        return true;
+    
+    return false;
+};
+
+// Namespace Iniitialisation
+
+PlotKit.SVG = {}
+PlotKit.SVG.SVGRenderer = PlotKit.SVGRenderer;
+
+PlotKit.SVG.EXPORT = [
+    "SVGRenderer"
+];
+
+PlotKit.SVG.EXPORT_OK = [
+    "SVGRenderer"
+];
+
+PlotKit.SVG.__new__ = function() {
+    var m = MochiKit.Base;
+    
+    m.nameFunctions(this);
+    
+    this.EXPORT_TAGS = {
+        ":common": this.EXPORT,
+        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
+    };
+};
+
+PlotKit.SVG.__new__();
+MochiKit.Base._exportSymbols(this, PlotKit.SVG);
+
diff --git a/share/web/static/js/PlotKit/SweetCanvas.js b/share/web/static/js/PlotKit/SweetCanvas.js
new file mode 100644
index 0000000..bb6fe5c
--- /dev/null
+++ b/share/web/static/js/PlotKit/SweetCanvas.js
@@ -0,0 +1,348 @@
+/*
+    PlotKit Sweet Canvas Renderer
+    =============================
+    Canvas Renderer for PlotKit which looks pretty!
+
+    Copyright
+    ---------
+    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
+    For use under the BSD license. <http://www.liquidx.net/plotkit>
+*/
+
+// -------------------------------------------------------------------------
+// Check required components
+// -------------------------------------------------------------------------
+
+try {    
+    if (typeof(PlotKit.CanvasRenderer) == 'undefined')
+    {
+        throw "";    
+    }
+} 
+catch (e) {    
+    throw "SweetCanvas depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, Canvas}"
+}
+
+
+if (typeof(PlotKit.SweetCanvasRenderer) == 'undefined') {
+    PlotKit.SweetCanvasRenderer = {};
+}
+
+PlotKit.SweetCanvasRenderer = function(element, layout, options) {
+    if (arguments.length > 0) {
+        this.__init__(element, layout, options);
+    }
+};
+
+PlotKit.SweetCanvasRenderer.NAME = "PlotKit.SweetCanvasRenderer";
+PlotKit.SweetCanvasRenderer.VERSION = PlotKit.VERSION;
+
+PlotKit.SweetCanvasRenderer.__repr__ = function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.SweetCanvasRenderer.toString = function() {
+    return this.__repr__();
+};
+
+// ---------------------------------------------------------------------
+// Subclassing Magic
+// ---------------------------------------------------------------------
+
+PlotKit.SweetCanvasRenderer.prototype = new PlotKit.CanvasRenderer();
+PlotKit.SweetCanvasRenderer.prototype.constructor = PlotKit.SweetCanvasRenderer;
+PlotKit.SweetCanvasRenderer.__super__ = PlotKit.CanvasRenderer.prototype;
+
+// ---------------------------------------------------------------------
+// Constructor
+// ---------------------------------------------------------------------
+
+PlotKit.SweetCanvasRenderer.prototype.__init__ = function(el, layout, opts) { 
+    var moreOpts = PlotKit.Base.officeBlue();
+    MochiKit.Base.update(moreOpts, opts);
+    PlotKit.SweetCanvasRenderer.__super__.__init__.call(this, el, layout, moreOpts);
+};
+
+// ---------------------------------------------------------------------
+// Extended Plotting Functions
+// ---------------------------------------------------------------------
+
+PlotKit.SweetCanvasRenderer.prototype._renderBarChart = function() {
+    var bind = MochiKit.Base.bind;
+    var shadowColor = Color.blackColor().colorWithAlpha(0.1).toRGBString();
+
+    var prepareFakeShadow = function(context, x, y, w, h) {
+        context.fillStyle = shadowColor;
+        context.fillRect(x-2, y-2, w+4, h+2); 
+        context.fillStyle = shadowColor;
+        context.fillRect(x-1, y-1, w+2, h+1); 
+    };
+
+    var colorCount = this.options.colorScheme.length;
+    var colorScheme =  this.options.colorScheme;
+    var setNames = PlotKit.Base.keys(this.layout.datasets);
+    var setCount = setNames.length;
+
+    var chooseColor = function(name) {
+        for (var i = 0; i < setCount; i++) {
+            if (name == setNames[i])
+                return colorScheme[i%colorCount];
+        }
+        return colorScheme[0];
+    };
+
+    var drawRect = function(context, bar) {
+        var x = this.area.w * bar.x + this.area.x;
+        var y = this.area.h * bar.y + this.area.y;
+        var w = this.area.w * bar.w;
+        var h = this.area.h * bar.h;
+
+        if ((w < 1) || (h < 1))
+            return;        
+
+        context.save();
+
+        context.shadowBlur = 5.0;
+        context.shadowColor = Color.fromHexString("#888888").toRGBString();
+
+        if (this.isIE) {
+            context.save();
+            context.fillStyle = "#cccccc";
+            context.fillRect(x-2, y-2, w+4, h+2); 
+            context.restore();
+        }
+        else {
+            prepareFakeShadow(context, x, y, w, h);
+        }
+
+        if (this.options.shouldFill) {
+            context.fillStyle = chooseColor(bar.name).toRGBString();
+            context.fillRect(x, y, w, h);
+        }
+
+        context.shadowBlur = 0;
+        context.strokeStyle = Color.whiteColor().toRGBString();
+        context.lineWidth = 2.0;
+        
+        if (this.options.shouldStroke) {
+            context.strokeRect(x, y, w, h);                
+        }
+
+        context.restore();
+
+    };
+    this._renderBarChartWrap(this.layout.bars, bind(drawRect, this));
+};
+
+PlotKit.SweetCanvasRenderer.prototype._renderLineChart = function() {
+    var context = this.element.getContext("2d");
+    var colorCount = this.options.colorScheme.length;
+    var colorScheme = this.options.colorScheme;
+    var setNames = PlotKit.Base.keys(this.layout.datasets);
+    var setCount = setNames.length;
+    var bind = MochiKit.Base.bind;
+
+
+    for (var i = 0; i < setCount; i++) {
+        var setName = setNames[i];
+        var color = colorScheme[i%colorCount];
+        var strokeX = this.options.strokeColorTransform;
+
+        // setup graphics context
+        context.save();
+        
+        // create paths
+        var makePath = function(ctx) {
+            ctx.beginPath();
+            ctx.moveTo(this.area.x, this.area.y + this.area.h);
+            var addPoint = function(ctx_, point) {
+            if (point.name == setName)
+                ctx_.lineTo(this.area.w * point.x + this.area.x,
+                            this.area.h * point.y + this.area.y);
+            };
+            MochiKit.Iter.forEach(this.layout.points, partial(addPoint, ctx), this);
+            ctx.lineTo(this.area.w + this.area.x,
+                           this.area.h + this.area.y);
+            ctx.lineTo(this.area.x, this.area.y + this.area.h);
+            ctx.closePath();
+        };
+
+        // faux shadow for firefox
+        if (this.options.shouldFill) {
+            context.save();
+            if (this.isIE) {
+                context.fillStyle = "#cccccc";
+            }
+            else {
+                context.fillStyle = Color.blackColor().colorWithAlpha(0.2).toRGBString();
+            }
+            context.translate(-1, -2);
+            bind(makePath, this)(context);
+            if (this.options.shouldFill) {
+                context.fill();
+            }
+            context.restore();
+        }
+
+        context.shadowBlur = 5.0;
+        context.shadowColor = Color.fromHexString("#888888").toRGBString();
+        context.fillStyle = color.toRGBString();
+        context.lineWidth = 2.0;
+        context.strokeStyle = Color.whiteColor().toRGBString();
+
+        if (this.options.shouldFill) {
+            bind(makePath, this)(context);
+            context.fill();
+        }
+        if (this.options.shouldStroke) {
+            bind(makePath, this)(context);
+            context.stroke();
+        }
+        context.restore();
+    }
+};
+
+PlotKit.SweetCanvasRenderer.prototype._renderPieChart = function() {
+    var context = this.element.getContext("2d");
+
+    var colorCount = this.options.colorScheme.length;
+    var slices = this.layout.slices;
+
+    var centerx = this.area.x + this.area.w * 0.5;
+    var centery = this.area.y + this.area.h * 0.5;
+    var radius = Math.min(this.area.w * this.options.pieRadius, 
+                          this.area.h * this.options.pieRadius);
+
+    if (this.isIE) {
+        centerx = parseInt(centerx);
+        centery = parseInt(centery);
+        radius = parseInt(radius);
+    }
+
+	// NOTE NOTE!! Canvas Tag draws the circle clockwise from the y = 0, x = 1
+	// so we have to subtract 90 degrees to make it start at y = 1, x = 0
+
+    if (!this.isIE) {
+        context.save();
+        var shadowColor = Color.blackColor().colorWithAlpha(0.2);
+        context.fillStyle = shadowColor.toRGBString();
+        context.shadowBlur = 5.0;
+        context.shadowColor = Color.fromHexString("#888888").toRGBString();
+        context.translate(1, 1);
+        context.beginPath();
+        context.moveTo(centerx, centery);
+        context.arc(centerx, centery, radius + 2, 0, Math.PI*2, false);
+        context.closePath();
+        context.fill();
+        context.restore();
+    }
+
+    context.save();
+    context.strokeStyle = Color.whiteColor().toRGBString();
+    context.lineWidth = 2.0;    
+    for (var i = 0; i < slices.length; i++) {
+        var color = this.options.colorScheme[i%colorCount];
+        context.fillStyle = color.toRGBString();
+
+        var makePath = function() {
+            context.beginPath();
+            context.moveTo(centerx, centery);
+            context.arc(centerx, centery, radius, 
+                        slices[i].startAngle - Math.PI/2,
+                        slices[i].endAngle - Math.PI/2,
+                        false);
+            context.lineTo(centerx, centery);
+            context.closePath();
+        };
+
+        if (Math.abs(slices[i].startAngle - slices[i].endAngle) > 0.0001) {
+            if (this.options.shouldFill) {
+                makePath();
+                context.fill();
+            }
+            if (this.options.shouldStroke) {
+                makePath();
+                context.stroke();
+            }
+        }
+    }
+    context.restore();
+};
+
+PlotKit.SweetCanvasRenderer.prototype._renderBackground = function() {
+    var context = this.element.getContext("2d");
+   
+    if (this.layout.style == "bar" || this.layout.style == "line") {
+        context.save();
+        context.fillStyle = this.options.backgroundColor.toRGBString();
+        context.fillRect(this.area.x, this.area.y, this.area.w, this.area.h);
+        context.strokeStyle = this.options.axisLineColor.toRGBString();
+        context.lineWidth = 1.0;
+        
+        var ticks = this.layout.yticks;
+        var horiz = false;
+        if (this.layout.style == "bar" && 
+            this.layout.options.barOrientation == "horizontal") {
+                ticks = this.layout.xticks;
+                horiz = true;
+        }
+        
+        for (var i = 0; i < ticks.length; i++) {
+            var x1 = 0;
+            var y1 = 0;
+            var x2 = 0;
+            var y2 = 0;
+            
+            if (horiz) {
+                x1 = ticks[i][0] * this.area.w + this.area.x;
+                y1 = this.area.y;
+                x2 = x1;
+                y2 = y1 + this.area.h;
+            }
+            else {
+                x1 = this.area.x;
+                y1 = ticks[i][0] * this.area.h + this.area.y;
+                x2 = x1 + this.area.w;
+                y2 = y1;
+            }
+            
+            context.beginPath();
+            context.moveTo(x1, y1);
+            context.lineTo(x2, y2);
+            context.closePath();
+            context.stroke();
+        }
+        context.restore();
+    }
+    else {
+        PlotKit.SweetCanvasRenderer.__super__._renderBackground.call(this);
+    }
+};
+
+// Namespace Iniitialisation
+
+PlotKit.SweetCanvas = {}
+PlotKit.SweetCanvas.SweetCanvasRenderer = PlotKit.SweetCanvasRenderer;
+
+PlotKit.SweetCanvas.EXPORT = [
+    "SweetCanvasRenderer"
+];
+
+PlotKit.SweetCanvas.EXPORT_OK = [
+    "SweetCanvasRenderer"
+];
+
+PlotKit.SweetCanvas.__new__ = function() {
+    var m = MochiKit.Base;
+    
+    m.nameFunctions(this);
+    
+    this.EXPORT_TAGS = {
+        ":common": this.EXPORT,
+        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
+    };
+};
+
+PlotKit.SweetCanvas.__new__();
+MochiKit.Base._exportSymbols(this, PlotKit.SweetCanvas);
+
diff --git a/share/web/static/js/PlotKit/SweetSVG.js b/share/web/static/js/PlotKit/SweetSVG.js
new file mode 100644
index 0000000..4183058
--- /dev/null
+++ b/share/web/static/js/PlotKit/SweetSVG.js
@@ -0,0 +1,247 @@
+/*
+    PlotKit Sweet SVG Renderer
+    ==========================
+    SVG Renderer for PlotKit which looks pretty!
+
+    Copyright
+    ---------
+    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
+    For use under the BSD license. <http://www.liquidx.net/plotkit>
+*/
+
+
+// -------------------------------------------------------------------------
+// Check required components
+// -------------------------------------------------------------------------
+
+try {    
+    if (typeof(PlotKit.SVGRenderer) == 'undefined')
+    {
+        throw "";    
+    }
+} 
+catch (e) {    
+    throw "SweetSVG depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, SVG}"
+}
+
+
+if (typeof(PlotKit.SweetSVGRenderer) == 'undefined') {
+    PlotKit.SweetSVGRenderer = {};
+}
+
+PlotKit.SweetSVGRenderer = function(element, layout, options) {
+    if (arguments.length > 0) {
+        this.__init__(element, layout, options);
+    }
+};
+
+PlotKit.SweetSVGRenderer.NAME = "PlotKit.SweetSVGRenderer";
+PlotKit.SweetSVGRenderer.VERSION = PlotKit.VERSION;
+
+PlotKit.SweetSVGRenderer.__repr__ = function() {
+    return "[" + this.NAME + " " + this.VERSION + "]";
+};
+
+PlotKit.SweetSVGRenderer.toString = function() {
+    return this.__repr__();
+};
+
+// ---------------------------------------------------------------------
+// Subclassing Magic
+// ---------------------------------------------------------------------
+
+PlotKit.SweetSVGRenderer.prototype = new PlotKit.SVGRenderer();
+PlotKit.SweetSVGRenderer.prototype.constructor = PlotKit.SweetSVGRenderer;
+PlotKit.SweetSVGRenderer.__super__ = PlotKit.SVGRenderer.prototype;
+
+// ---------------------------------------------------------------------
+// Constructor
+// ---------------------------------------------------------------------
+
+PlotKit.SweetSVGRenderer.prototype.__init__ = function(element, layout, options) { 
+    var moreOpts = PlotKit.Base.officeBlue();
+    MochiKit.Base.update(moreOpts, options);
+    PlotKit.SweetSVGRenderer.__super__.__init__.call(this, element, layout, moreOpts);
+    //this._addDropShadowFilter();
+};
+
+PlotKit.SweetSVGRenderer.prototype._addDropShadowFilter = function() {
+    var filter = this.createSVGElement("filter", {x: 0, y: 0, "id":"dropShadow"});
+    var goffset = this.createSVGElement("feOffset",
+        {"in": "SourceGraphic", "dx": 0, "dy": 0, "result": "topCopy"});
+    var blur = this.createSVGElement("feGaussianBlur",
+        {"in": "SourceAlpha", "StdDeviation": 2, "result": "shadow"});
+    var soffset = this.createSVGElement("feOffset",
+        {"in": "shadow", "dx": -1, "dy": -2, "result":"movedShadow"});
+    var merge = this.createSVGElement("feMerge");
+    var gmerge = this.createSVGElement("feMergeNode", {"in":"topCopy"});
+    var smerge = this.createSVGElement("feMergeNode", {"in":"movedShadow"});
+    
+    merge.appendChild(gmerge);
+    merge.appendChild(smerge);
+    filter.appendChild(goffset);
+    filter.appendChild(blur);
+    filter.appendChild(soffset);
+    filter.appendChild(merge);
+    this.defs.appendChild(filter);
+};
+
+// ---------------------------------------------------------------------
+// Extended Plotting Functions
+// ---------------------------------------------------------------------
+
+PlotKit.SweetSVGRenderer.prototype._renderBarChart = function() {
+    var bind = MochiKit.Base.bind;
+    var shadowColor = Color.blackColor().toRGBString();
+    var shadowStyle = "fill:" + shadowColor + ";fill-opacity:0.15";
+    var strokeStyle = "stroke-width: 2.0; stroke:" + Color.whiteColor().toRGBString();
+    
+    var drawRect = function(attrs, bar) {
+        var x = this.area.w * bar.x + this.area.x;
+        var y = this.area.h * bar.y + this.area.y;
+        var w = this.area.w * bar.w;
+        var h = this.area.h * bar.h;
+
+        if ((w < 1) || (h < 1))
+            return;        
+
+        //attrs["filter"] = "url(#dropShadow)";
+        attrs["style"] = strokeStyle;
+        this._drawRect(x - 2, y - 1, w+4, h+2, {"style":shadowStyle});
+        this._drawRect(x, y, w, h, attrs);
+    };
+    this._renderBarOrLine(this.layout.bars, bind(drawRect, this));
+
+};
+
+PlotKit.SweetSVGRenderer.prototype._renderLineChart = function() {
+    var bind = MochiKit.Base.bind;
+    var shadowColor = Color.blackColor().toRGBString();
+    var shadowStyle = "fill:" + shadowColor + ";fill-opacity:0.15";
+    var strokeStyle = "stroke-width: 2.0; stroke:" + Color.whiteColor().toRGBString();
+
+    var addPoint = function(attrs, point) {
+        this._tempPointsBuffer += (this.area.w * point.x + this.area.x) + "," +
+                                 (this.area.h * point.y + this.area.y) + " ";
+    };
+
+    var startLine = function(attrs) {
+        this._tempPointsBuffer = "";
+        this._tempPointsBuffer += (this.area.x) + "," + (this.area.y+this.area.h) + " ";
+    };
+
+    var endLine = function(attrs) {
+        this._tempPointsBuffer += (this.area.w + this.area.x) + ","  +(this.area.h + this.area.y);
+        attrs["points"] = this._tempPointsBuffer;    
+            
+        attrs["stroke"] = "none";
+        attrs["transform"] = "translate(-2, -1)";
+        attrs["style"] = shadowStyle;
+        var shadow = this.createSVGElement("polygon", attrs);
+        this.root.appendChild(shadow);
+        
+        attrs["transform"] = "";
+        attrs["style"] = strokeStyle;
+        var elem = this.createSVGElement("polygon", attrs);
+        this.root.appendChild(elem);
+        
+       
+    };
+
+    this._renderBarOrLine(this.layout.points, 
+                             bind(addPoint, this), 
+                             bind(startLine, this), 
+                             bind(endLine, this));
+};
+
+PlotKit.SweetSVGRenderer.prototype._renderPieChart = function() {
+    var centerx = this.area.x + this.area.w * 0.5;
+    var centery = this.area.y + this.area.h * 0.5;
+    var shadowColor = Color.blackColor().toRGBString();
+    var radius = Math.min(this.area.w * this.options.pieRadius, 
+                          this.area.h * this.options.pieRadius);
+    var shadowStyle = "fill:" + shadowColor + ";fill-opacity:0.15";
+    
+    var shadow = this.createSVGElement("circle", 
+        {"style": shadowStyle, "cx": centerx + 1, "cy": centery + 1, "r": radius + 1});
+    this.root.appendChild(shadow);
+                             
+    PlotKit.SweetSVGRenderer.__super__._renderPieChart.call(this);
+};
+    
+
+PlotKit.SweetSVGRenderer.prototype._renderBackground = function() {
+    var attrs = {
+        "fill": this.options.backgroundColor.toRGBString(),
+        "stroke": "none"
+    };
+    
+
+    if (this.layout.style == "bar" || this.layout.style == "line") {
+        this._drawRect(this.area.x, this.area.y, 
+                       this.area.w, this.area.h, attrs);
+                       
+        var ticks = this.layout.yticks;
+        var horiz = false;
+        if (this.layout.style == "bar" && 
+            this.layout.options.barOrientation == "horizontal") {
+                ticks = this.layout.xticks;
+                horiz = true;
+        }
+        
+        for (var i = 0; i < ticks.length; i++) {
+            var x = 0;
+            var y = 0;
+            var w = 0;
+            var h = 0;
+            
+            if (horiz) {
+                x = ticks[i][0] * this.area.w + this.area.x;
+                y = this.area.y;
+                w = 1;
+                h = this.area.w;
+            }
+            else {
+                x = this.area.x;
+                y = ticks[i][0] * this.area.h + this.area.y;
+                w = this.area.w;
+                h = 1;
+            }
+            
+            this._drawRect(x, y, w, h,
+                           {"fill": this.options.axisLineColor.toRGBString()});
+        }
+    }
+    else {
+        PlotKit.SweetSVGRenderer.__super__._renderBackground.call(this);
+        
+    }
+    
+};
+
+// Namespace Iniitialisation
+
+PlotKit.SweetSVG = {}
+PlotKit.SweetSVG.SweetSVGRenderer = PlotKit.SweetSVGRenderer;
+
+PlotKit.SweetSVG.EXPORT = [
+    "SweetSVGRenderer"
+];
+
+PlotKit.SweetSVG.EXPORT_OK = [
+    "SweetSVGRenderer"
+];
+
+PlotKit.SweetSVG.__new__ = function() {
+    var m = MochiKit.Base;
+    
+    m.nameFunctions(this);
+    
+    this.EXPORT_TAGS = {
+        ":common": this.EXPORT,
+        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
+    };
+};
+
+PlotKit.SweetSVG.__new__();
+MochiKit.Base._exportSymbols(this, PlotKit.SweetSVG);
diff --git a/share/web/static/js/PlotKit/dummy.svg b/share/web/static/js/PlotKit/dummy.svg
new file mode 100644
index 0000000..6a82bd4
--- /dev/null
+++ b/share/web/static/js/PlotKit/dummy.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink">
+</svg>
diff --git a/share/web/static/js/PlotKit/excanvas.js b/share/web/static/js/PlotKit/excanvas.js
new file mode 100644
index 0000000..ca77da2
--- /dev/null
+++ b/share/web/static/js/PlotKit/excanvas.js
@@ -0,0 +1,723 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// TODO: Patterns
+// TODO: Radial gradient
+// TODO: Clipping paths
+// TODO: Coordsize (still need to support stretching)
+// TODO: Painting mode
+// TODO: Optimize
+// TODO: canvas width/height sets content size in moz, border size in ie
+
+// only add this code if we do not already have a canvas implementation
+if (!window.CanvasRenderingContext2D) {
+
+(function () {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+
+  var G_vmlCanvasManager_ = {
+    init: function (opt_doc) {
+      var doc = opt_doc || document;
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var self = this;
+        doc.attachEvent("onreadystatechange", function () {
+          self.init_(doc);
+        });
+      }
+    },
+
+    init_: function (doc, e) {
+      if (doc.readyState == "complete") {
+        // create xmlns
+        if (!doc.namespaces["g_vml_"]) {
+          doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
+        }
+
+        // setup default css
+        var ss = doc.createStyleSheet();
+        ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
+            "text-align:left;}" +
+            "g_vml_\\:*{behavior:url(#default#VML)}";
+
+        // find all canvas elements
+        var els = doc.getElementsByTagName("canvas");
+        for (var i = 0; i < els.length; i++) {
+          if (!els[i].getContext) {
+            this.initElement(els[i]);
+          }
+        }
+      }
+    },
+
+    fixElement_: function (el) {
+      // in IE before version 5.5 we would need to add HTML: to the tag name
+      // but we do not care about IE before version 6
+      var outerHTML = el.outerHTML;
+      var newEl = document.createElement(outerHTML);
+      // if the tag is still open IE has created the children as siblings and
+      // it has also created a tag with the name "/FOO"
+      if (outerHTML.slice(-2) != "/>") {
+        var tagName = "/" + el.tagName;
+        var ns;
+        // remove content
+        while ((ns = el.nextSibling) && ns.tagName != tagName) {
+          ns.removeNode();
+        }
+        // remove the incorrect closing tag
+        if (ns) {
+          ns.removeNode();
+        }
+      }
+      el.parentNode.replaceChild(newEl, el);
+      return newEl;
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function (el) {
+      el = this.fixElement_(el);
+      el.getContext = function () {
+        if (this.context_) {
+          return this.context_;
+        }
+        return this.context_ = new CanvasRenderingContext2D_(this);
+      };
+
+      // do not use inline function because that will leak memory
+      // el.attachEvent('onpropertychange', onPropertyChange)
+      el.attachEvent('onresize', onResize);
+
+      var attrs = el.attributes;
+      if (attrs.width && attrs.width.specified) {
+        // TODO: use runtimeStyle and coordsize
+        // el.getContext().setWidth_(attrs.width.nodeValue);
+        el.style.width = attrs.width.nodeValue + "px";
+      }
+      if (attrs.height && attrs.height.specified) {
+        // TODO: use runtimeStyle and coordsize
+        // el.getContext().setHeight_(attrs.height.nodeValue);
+        el.style.height = attrs.height.nodeValue + "px";
+      }
+      //el.getContext().setCoordsize_()
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    // we need to watch changes to width and height
+    switch (e.propertyName) {
+      case 'width':
+      case 'height':
+        // TODO: coordsize and size
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var dec2hex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.substring(0, 3) == "rgb") {
+      var start = styleString.indexOf("(", 3);
+      var end = styleString.indexOf(")", start + 1);
+      var guts = styleString.substring(start + 1, end).split(",");
+
+      str = "#";
+      for (var i = 0; i < 3; i++) {
+        str += dec2hex[parseInt(guts[i])];
+      }
+
+      if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
+        alpha = guts[3];
+      }
+    } else {
+      str = styleString;
+    }
+
+    return [str, alpha];
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case "butt":
+        return "flat";
+      case "round":
+        return "round";
+      case "square":
+      default:
+        return "square";
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+   function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = "#000";
+    this.fillStyle = "#ccc";
+
+    this.lineWidth = 1;
+    this.lineJoin = "miter";
+    this.lineCap = "butt";
+    this.miterLimit = 10;
+    this.globalAlpha = 1;
+
+    var el = document.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+  };
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    this.element_.innerHTML = "";
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    this.currentPath_.push({type: "moveTo", x: aX, y: aY});
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    this.currentPath_.push({type: "lineTo", x: aX, y: aY});
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    this.currentPath_.push({type: "bezierCurveTo",
+                           cp1x: aCP1x,
+                           cp1y: aCP1y,
+                           cp2x: aCP2x,
+                           cp2y: aCP2y,
+                           x: aX,
+                           y: aY});
+  };
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // VML's qb produces different output to Firefox's
+    // FF's behaviour seems to have changed in 1.5.0.1, check this
+    this.bezierCurveTo(aCPx, aCPy, aCPx, aCPy, aX, aY);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= 10;
+    var arcType = aClockwise ? "at" : "wa";
+
+    var xStart = aX + (mc(aStartAngle) * aRadius) - 5;
+    var yStart = aY + (ms(aStartAngle) * aRadius) - 5;
+
+    var xEnd = aX + (mc(aEndAngle) * aRadius) - 5;
+    var yEnd = aY + (ms(aEndAngle) * aRadius) - 5;
+
+    this.currentPath_.push({type: arcType,
+                           x: aX,
+                           y: aY,
+                           radius: aRadius,
+                           xStart: xStart,
+                           yStart: yStart,
+                           xEnd: xEnd,
+                           yEnd: yEnd});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    // Will destroy any existing path (same as FF behaviour)
+    this.beginPath();
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    // Will destroy any existing path (same as FF behaviour)
+    this.beginPath();
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_("gradient");
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0,
+                                                   aR0, aX1,
+                                                   aY1, aR1) {
+    var gradient = new CanvasGradient_("gradientradial");
+    gradient.radius1_ = aR0;
+    gradient.radius2_ = aR1;
+    gradient.focus_.x = aX0;
+    gradient.focus_.y = aY0;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function (image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+    var w = image.width;
+    var h = image.height;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw "Invalid number of arguments";
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = (sw / 2);
+    var h2 = (sh / 2);
+
+    var vmlStr = [];
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="1000,1000"',
+                ' coordorigin="0, 0"' ,
+                ' style="width:100px;height:100px;position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push("M11='", this.m_[0][0], "',",
+                  "M12='", this.m_[1][0], "',",
+                  "M21='", this.m_[0][1], "',",
+                  "M22='", this.m_[1][1], "',",
+                  "Dx='", d.x, "',",
+                  "Dy='", d.y, "'");
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx+dw, dy);
+      var c3 = this.getCoords_(dx, dy+dh);
+      var c4 = this.getCoords_(dx+dw, dy+dh);
+
+      max.x = Math.max(max.x, c2.x, c3.x, c4.x);
+      max.y = Math.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push(" padding:0 ", mr(max.x), "px ", mr(max.y),
+                  "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
+                  filter.join(""), ", sizingmethod='clip');")
+    } else {
+      vmlStr.push(" top:", d.y, "px;left:", d.x, "px;")
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', dw, ';',
+                ' height:', dh, ';"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML("BeforeEnd",
+                                    vmlStr.join(""));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var lineStr = [];
+    var lineOpen = false;
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+    var color = a[0];
+    var opacity = a[1] * this.globalAlpha;
+
+    lineStr.push('<g_vml_:shape',
+                 ' fillcolor="', color, '"',
+                 ' filled="', Boolean(aFill), '"',
+                 ' style="position:absolute;width:10;height:10;"',
+                 ' coordorigin="0 0" coordsize="100 100"',
+                 ' stroked="', !aFill, '"',
+                 ' strokeweight="', this.lineWidth, '"',
+                 ' strokecolor="', color, '"',
+                 ' path="');
+
+    var newSeq = false;
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var i = 0; i < this.currentPath_.length; i++) {
+      var p = this.currentPath_[i];
+
+      if (p.type == "moveTo") {
+        lineStr.push(" m ");
+        var c = this.getCoords_(p.x, p.y);
+        lineStr.push(mr(c.x), ",", mr(c.y));
+      } else if (p.type == "lineTo") {
+        lineStr.push(" l ");
+        var c = this.getCoords_(p.x, p.y);
+        lineStr.push(mr(c.x), ",", mr(c.y));
+      } else if (p.type == "close") {
+        lineStr.push(" x ");
+      } else if (p.type == "bezierCurveTo") {
+        lineStr.push(" c ");
+        var c = this.getCoords_(p.x, p.y);
+        var c1 = this.getCoords_(p.cp1x, p.cp1y);
+        var c2 = this.getCoords_(p.cp2x, p.cp2y);
+        lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
+                     mr(c2.x), ",", mr(c2.y), ",",
+                     mr(c.x), ",", mr(c.y));
+      } else if (p.type == "at" || p.type == "wa") {
+        lineStr.push(" ", p.type, " ");
+        var c  = this.getCoords_(p.x, p.y);
+        var cStart = this.getCoords_(p.xStart, p.yStart);
+        var cEnd = this.getCoords_(p.xEnd, p.yEnd);
+
+        lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
+                     mr(c.y - this.arcScaleY_ * p.radius), " ",
+                     mr(c.x + this.arcScaleX_ * p.radius), ",",
+                     mr(c.y + this.arcScaleY_ * p.radius), " ",
+                     mr(cStart.x), ",", mr(cStart.y), " ",
+                     mr(cEnd.x), ",", mr(cEnd.y));
+      }
+
+
+      // TODO: Following is broken for curves due to
+      //       move to proper paths.
+
+      // Figure out dimensions so we can do gradient fills
+      // properly
+      if(c) {
+        if (min.x == null || c.x < min.x) {
+          min.x = c.x;
+        }
+        if (max.x == null || c.x > max.x) {
+          max.x = c.x;
+        }
+        if (min.y == null || c.y < min.y) {
+          min.y = c.y;
+        }
+        if (max.y == null || c.y > max.y) {
+          max.y = c.y;
+        }
+      }
+    }
+    lineStr.push(' ">');
+
+    if (typeof this.fillStyle == "object") {
+      var focus = {x: "50%", y: "50%"};
+      var width = (max.x - min.x);
+      var height = (max.y - min.y);
+      var dimension = (width > height) ? width : height;
+
+      focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
+      focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
+
+      var colors = [];
+
+      // inside radius (%)
+      if (this.fillStyle.type_ == "gradientradial") {
+        var inside = (this.fillStyle.radius1_ / dimension * 100);
+
+        // percentage that outside radius exceeds inside radius
+        var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
+      } else {
+        var inside = 0;
+        var expansion = 100;
+      }
+
+      var insidecolor = {offset: null, color: null};
+      var outsidecolor = {offset: null, color: null};
+
+      // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
+      // won't interpret it correctly
+      this.fillStyle.colors_.sort(function (cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      for (var i = 0; i < this.fillStyle.colors_.length; i++) {
+        var fs = this.fillStyle.colors_[i];
+
+        colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
+
+        if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
+          insidecolor.offset = fs.offset;
+          insidecolor.color = fs.color;
+        }
+
+        if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
+          outsidecolor.offset = fs.offset;
+          outsidecolor.color = fs.color;
+        }
+      }
+      colors.pop();
+
+      lineStr.push('<g_vml_:fill',
+                   ' color="', outsidecolor.color, '"',
+                   ' color2="', insidecolor.color, '"',
+                   ' type="', this.fillStyle.type_, '"',
+                   ' focusposition="', focus.x, ', ', focus.y, '"',
+                   ' colors="', colors.join(""), '"',
+                   ' opacity="', opacity, '" />');
+    } else if (aFill) {
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
+    } else {
+      lineStr.push(
+        '<g_vml_:stroke',
+        ' opacity="', opacity,'"',
+        ' joinstyle="', this.lineJoin, '"',
+        ' miterlimit="', this.miterLimit, '"',
+        ' endcap="', processLineCap(this.lineCap) ,'"',
+        ' weight="', this.lineWidth, 'px"',
+        ' color="', color,'" />'
+      );
+    }
+
+    lineStr.push("</g_vml_:shape>");
+
+    this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
+
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  }
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: "close"});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    return {
+      x: 10 * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - 5,
+      y: 10 * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - 5
+    }
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    copyState(this.aStack_.pop(), this);
+    this.m_ = this.mStack_.pop();
+  };
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function() {
+    return new CanvasPattern_;
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.radius1_ = 0;
+    this.radius2_ = 0;
+    this.colors_ = [];
+    this.focus_ = {x: 0, y: 0};
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: 1-aOffset, color: aColor});
+  };
+
+  function CanvasPattern_() {}
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if
diff --git a/share/web/static/js/mochikit.noexport.js b/share/web/static/js/mochikit.noexport.js
new file mode 100644
index 0000000..3f0b733
--- /dev/null
+++ b/share/web/static/js/mochikit.noexport.js
@@ -0,0 +1 @@
+MochiKit = {__export__: false};

commit 0e62f206f351fdf44d139034b7a06b096b78758c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Aug 1 06:49:40 2007 +0000

    - Uncomment neccessary require
    - Make sure to handle undefined stuff
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3758 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
index 9d54517..eff4ada 100644
--- a/lib/Jifty/Plugin/Chart/Dispatcher.pm
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -64,7 +64,7 @@ on 'chart/gd_graph/*' => run {
     my $class = 'GD::Graph::' . $args->{type};
 
     # Load that class or die if it does not exist
-    #$class->require;
+    $class->require;
 
     # Remember the class name for the view
     $args->{class} = $class;
diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index 4723ab8..001321f 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -79,13 +79,15 @@ sub _transform_data {
     my $labels = shift @{ $args->{data} };
 
     for ( my $i = 0; $i < @$labels; $i++ ) {
-        push @{$args->{options}{xTicks}}, { v => $i, label => $labels->[$i] };
+        push @{$args->{options}{xTicks}}, { v => $i, label => $labels->[$i] }
+            if defined $labels->[$i];
     }
     
     for my $dataset ( @{ $args->{data} } ) {
         my @ds;
         for ( my $i = 0; $i < @$dataset; $i++ ) {
-            push @ds, [ $i, $dataset->[$i] ];
+            # PlotKit can't deal with undefined values
+            push @ds, [ $i, defined $dataset->[$i] ? $dataset->[$i] : '0' ];
         }
         push @data, \@ds;
     }
@@ -103,7 +105,7 @@ Thomas Sibley
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2007 Boomer Consulting, Inc.
+Copyright 2007 Best Practical Solutions, LLC
 
 This is free software and may be modified and distributed under the same terms as Perl itself.
 

commit c6cf99a08ac2b2d32fd09c0ea10beaeb1c76dda1
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Aug 2 02:33:42 2007 +0000

    *Very* custom packed PlotKit (from svn) that no longer depends on MochiKit exporting functions into the global namespace
    
    Still need to solve the issue of why MochiKit blows up when included in our honkin' JS file...
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3763 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index cdeec5e..a7ba7dd 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -80,12 +80,14 @@ sub init {
     $self->renderer( $args{renderer} );
 
     if ( $self->renderer =~ 'PlotKit' ) {
-        # XXX TODO: Why does MochiKit need to be loaded before everything else?
-        Jifty->web->javascript_libs([
-            'MochiKit/MochiKit.js',
-            @{ Jifty->web->javascript_libs },
-            'PlotKit/PlotKit_Packed.js'
-        ]);
+        Jifty->web->add_external_javascript(qw(
+            /static/js/mochikit.noexport.js
+            /static/js/MochiKit/MochiKit.js
+        ));
+        Jifty->web->add_javascript(qw(
+            PlotKit/excanvas.js
+            PlotKit/PlotKit_Packed-20060807-custom.js
+        ));
     }
 
     push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
diff --git a/share/web/static/js/PlotKit/Base.js b/share/web/static/js/PlotKit/Base.js
deleted file mode 100644
index 49988c9..0000000
--- a/share/web/static/js/PlotKit/Base.js
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
-    PlotKit
-    =======
-    PlotKit is a collection of Javascript classes that allows
-    you to quickly visualise data using different types of charts.
-
-    For license/info/documentation: http://www.liquidx.net/plotkit/
-
-    Copyright
-    ---------
-    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
-    For use under the BSD license. <http://www.liquidx.net/plotkit>
-*/
-
-// --------------------------------------------------------------------
-// Check required components
-// --------------------------------------------------------------------
-
-try {    
-    if (typeof(MochiKit.Base) == 'undefined'   ||
-        typeof(MochiKit.DOM) == 'undefined'    ||
-        typeof(MochiKit.Color) == 'undefined'  ||
-        typeof(MochiKit.Format) == 'undefined')
-    {
-        throw "";    
-    }
-} 
-catch (e) {    
-    throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format}"
-}
-
-// -------------------------------------------------------------------
-// Inject Common Shortcuts we use into MochiKit.Color.Color
-// -------------------------------------------------------------------
-
-MochiKit.Base.update(MochiKit.Color.Color.prototype, {
-    asFillColor: function() {
-        return this.lighterColorWithLevel(0.3);
-    },
-        
-    asStrokeColor: function() {
-        return this.darkerColorWithLevel(0.1);
-    },
-
-    asPointColor: function() {
-        return this.lighterColorWithLevel(0.1);
-    }
-});
-
-
-// -------------------------------------------------------------------
-// Define our own PlotKit namespace
-// -------------------------------------------------------------------
-
-if (typeof(PlotKit) == 'undefined') {
-    PlotKit = {};
-}
-
-PlotKit.NAME = "PlotKit";
-PlotKit.VERSION = "0.8";
-PlotKit.__repr__ = function() {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.toString = function() {
-    return this.__repr__();
-}
-
-// -------------------------------------------------------------------
-//  Encapsulate all our utility function into it's own namespace.
-// -------------------------------------------------------------------
-
-if (typeof(PlotKit.Base) == 'undefined') {
-    PlotKit.Base = {};
-}
-
-PlotKit.Base.NAME = 'PlotKit.Base';
-PlotKit.Base.VERSION = PlotKit.VERSION;
-
-PlotKit.Base.__repr__ = function() {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.Base.toString = function() {
-    return this.__repr__();
-}
-
-
-// Detect whether we are using prototype.js
-PlotKit.Base.usingPrototype =  function() {
-    try {
-        return (typeof(Object.extend) == 'function');
-    }
-    catch (e) {
-        return false;
-    }
-}
-
-
-MochiKit.Base.update(PlotKit.Base, {
-    roundInterval: function(range, intervals, precision) {
-        // We want to make the interval look regular,
-        var trunc = MochiKit.Format.roundToFixed;
-        var sep = range/intervals;
-        return parseFloat(trunc(sep, precision));
-    },
-
-    collapse: function(lst) {
-        var m = MochiKit.Base;
-        var biggerList = new Array();
-        for (var i = 0; i < lst.length; i++) {
-            biggerList = m.concat(biggerList, lst[i]);
-        }
-        if (PlotKit.Base.usingPrototype()) {
-            delete biggerList.extend;
-            delete biggerList.from;
-            delete biggerList.inspect;
-        }
-        
-        return biggerList;
-    },
-    
-    uniq: function(sortedList) {
-        // get unique elements in list, exactly the same as unix shell's uniq.
-        var m = MochiKit.Base;
-        
-        if (!m.isArrayLike(sortedList) || (sortedList.length < 1))
-            return new Array();
-
-        var uniq = new Array();
-        var lastElem = sortedList[0];    
-        uniq.push(sortedList[0]);
-        for (var i = 1; i < sortedList.length; i++) {
-            if (m.compare(sortedList[i], lastElem) != 0) {
-                lastElem = sortedList[i];
-                uniq.push(sortedList[i]);            
-            }
-        }
-        return uniq;
-    },
-    
-    colorScheme: function() {
-        var mb = MochiKit.Base;
-        var mc = MochiKit.Color
-        var scheme = ["red", "orange", "yellow", "green", "cyan", "blue", "purple", "magenta"];
-        
-        var makeColor = function(name) {
-            return mc.Color[name + "Color"]()
-        };
-        
-        return mb.map(makeColor, scheme);
-    },
-
-    baseDarkPrimaryColors: function () {
-        var hexColor = MochiKit.Color.Color.fromHexString;
-        return [hexColor("#ad3f40"),
-                hexColor("#ddac2c"),
-                hexColor("#dfdd0c"),
-                hexColor("#5276c4"),
-                hexColor("#739c5a")];
-    },
-
-    basePrimaryColors: function () {
-        var hexColor = MochiKit.Color.Color.fromHexString;
-        return [hexColor("#d24c4d"),
-                hexColor("#f2b32f"),
-                hexColor("#ece90e"),
-                hexColor("#5d83da"),
-                hexColor("#78a15d")];
-    },
-    
-    baseBlueColors: function () {
-         var hexColor = MochiKit.Color.Color.fromHexString;
-         return [hexColor("#4b6b94"), hexColor("#5d81b4"), hexColor("#acbad2")];
-    },
-
-    palette: function(baseColor, fromLevel, toLevel, increment) {
-        var isNil = MochiKit.Base.isUndefinedOrNull;
-        var fractions = new Array();
-        if (isNil(increment))
-            increment = 0.1;
-        if (isNil(toLevel))
-            toLevel = 0.4;
-        if (isNil(fromLevel))
-            fromLevel = -0.2;
-
-        var level = fromLevel;
-        while (level <= toLevel) {
-            fractions.push(level);
-            level += increment;
-        }
-            
-        var makeColor = function(color, fraction) {
-            return color.lighterColorWithLevel(fraction);
-        };
-        return MochiKit.Base.map(partial(makeColor, baseColor), fractions);
-    },
-    
-    excanvasSupported: function() {
-         if (/MSIE/.test(navigator.userAgent) && !window.opera) {
-             return true;
-         }
-         return false;
-    },
-
-    // The following functions are from quirksmode.org
-    // http://www.quirksmode.org/js/findpos.html
-
-    findPosX: function(obj) {
-        var curleft = 0;
-        if (obj.offsetParent) {
-            while (obj.offsetParent) {
-                    curleft += obj.offsetLeft
-                        obj = obj.offsetParent;
-            }
-        }
-        else if (obj.x)
-            curleft += obj.x;
-        return curleft;
-    },
-                       
-   findPosY: function(obj) {
-       var curtop = 0;
-       if (obj.offsetParent) {
-           while (obj.offsetParent) {
-               curtop += obj.offsetTop
-               obj = obj.offsetParent;
-           }
-       }
-       else if (obj.y)
-           curtop += obj.y;
-       return curtop;
-   },
-   
-   isFuncLike: function(obj) {
-       return (typeof(obj) == 'function');
-   }
-});    
-
-//
-// Prototype.js aware (crippled) versions of map and items.
-//
-
-PlotKit.Base.map = function(fn, lst) {
-    if (PlotKit.Base.usingPrototype()) {
-        var rval = [];
-        for (var x in lst) {
-            if (typeof(lst[x]) == 'function') continue;
-            rval.push(fn(lst[x]));
-        }
-        return rval;
-    }
-    else {
-        return MochiKit.Base.map(fn, lst);
-    }
-};
-
-PlotKit.Base.items = function(lst) {
-    if (PlotKit.Base.usingPrototype()) {
-        var rval = [];
-         for (var x in lst) {
-             if (typeof(lst[x]) == 'function') continue;
-             rval.push([x, lst[x]]);
-         }
-         return rval;
-    }
-    else {
-        return MochiKit.Base.items(lst);
-    }
-};
-
-PlotKit.Base.keys = function(lst) {
-    if (PlotKit.Base.usingPrototype()) {
-        var rval = [];
-         for (var x in lst) {
-             if (typeof(lst[x]) == 'function') continue;
-             rval.push(x);
-         }
-         return rval;
-    }
-    else {
-        return MochiKit.Base.keys(lst);
-    }
-};
-
-// 
-// colour schemes
-//
-
-PlotKit.Base.baseColors = function () {
-   var hexColor = MochiKit.Color.Color.fromHexString;
-   return [hexColor("#476fb2"),
-           hexColor("#be2c2b"),
-           hexColor("#85b730"),
-           hexColor("#734a99"),
-           hexColor("#26a1c5"),
-           hexColor("#fb8707"),
-           hexColor("#000000")];
-};
-
-PlotKit.Base.officeBaseStyle = {
-    "axisLineWidth": 2.0,
-    "axisLabelColor": Color.grayColor(),
-    "axisLineColor": Color.whiteColor(),
-    "padding": {top: 5, bottom: 10, left: 30, right: 30}
-};    
-
-MochiKit.Base.update(PlotKit.Base,{
-    officeBlue: function() {
-        var r = {
-        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),
-        "backgroundColor": PlotKit.Base.baseColors()[0].lighterColorWithLevel(0.45)
-        };
-        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
-        return r;
-    },
-    officeRed: function() {
-        var r = {
-        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),
-        "backgroundColor": PlotKit.Base.baseColors()[1].lighterColorWithLevel(0.5)
-        };
-        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
-        return r;
-    },
-    officeGreen: function() {
-        var r = {
-        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[2]),
-        "backgroundColor": PlotKit.Base.baseColors()[2].lighterColorWithLevel(0.5)
-        };
-        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
-        return r;
-    },
-    officePurple: function() {
-        var r = {
-        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[3]),
-        "backgroundColor": PlotKit.Base.baseColors()[3].lighterColorWithLevel(0.5)
-        };
-        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
-        return r;
-    },
-    
-    officeCyan: function() {
-        var r = {
-            "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[4]),
-            "backgroundColor": PlotKit.Base.baseColors()[4].lighterColorWithLevel(0.5)
-            };
-        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
-        return r;
-    },
-    
-    officeOrange: function() {
-        var r = {
-            "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[5]),
-            "backgroundColor": PlotKit.Base.baseColors()[5].lighterColorWithLevel(0.4)
-            };
-        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
-        return r;
-    },
-    
-    officeBlack: function() {
-        var r = {
-        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[6], 0.0, 0.6),
-        "backgroundColor": PlotKit.Base.baseColors()[6].lighterColorWithLevel(0.9)
-        };
-        MochiKit.Base.update(r, PlotKit.Base.officeBaseStyle);
-        return r;
-    }
-});
-
-
-PlotKit.Base.EXPORT = [
-   "baseColors",
-   "collapse",
-   "colorScheme",
-   "findPosX",
-   "findPosY",
-   "officeBaseStyle",
-   "officeBlue",
-   "officeRed",
-   "officeGreen",
-   "officePurple",
-   "officeCyan",
-   "officeOrange",
-   "officeBlack",
-   "roundInterval",
-   "uniq",
-   "isFuncLike",
-   "excanvasSupported"
-];
-
-PlotKit.Base.EXPORT_OK = [];
-
-PlotKit.Base.__new__ = function() {
-    var m = MochiKit.Base;
-    
-    m.nameFunctions(this);
-    
-    this.EXPORT_TAGS = {
-        ":common": this.EXPORT,
-        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
-    };
-};
-
-PlotKit.Base.__new__();
-MochiKit.Base._exportSymbols(this, PlotKit.Base);
-
diff --git a/share/web/static/js/PlotKit/Canvas.js b/share/web/static/js/PlotKit/Canvas.js
deleted file mode 100644
index 1e88e3c..0000000
--- a/share/web/static/js/PlotKit/Canvas.js
+++ /dev/null
@@ -1,683 +0,0 @@
-/* 
-    PlotKit Canvas
-    ==============
-    
-    Provides HTML Canvas Renderer. This is supported under:
-    
-    - Safari 2.0
-    - Mozilla Firefox 1.5
-    - Opera 9.0 preview 2
-    - IE 6 (via VML Emulation)
-    
-    It uses DIVs for labels.
-    
-    Copyright
-    ---------
-    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
-    For use under the BSD license. <http://www.liquidx.net/plotkit>
-    
-*/
-// --------------------------------------------------------------------
-// Check required components
-// --------------------------------------------------------------------
-
-try {    
-    if ((typeof(PlotKit.Base) == 'undefined') ||
-        (typeof(PlotKit.Layout) == 'undefined'))
-    {
-        throw "";    
-    }
-} 
-catch (e) {    
-    throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Base,Layout}"
-}
-
-
-// ------------------------------------------------------------------------
-//  Defines the renderer class
-// ------------------------------------------------------------------------
-
-if (typeof(PlotKit.CanvasRenderer) == 'undefined') {
-    PlotKit.CanvasRenderer = {};
-}
-
-PlotKit.CanvasRenderer.NAME = "PlotKit.CanvasRenderer";
-PlotKit.CanvasRenderer.VERSION = PlotKit.VERSION;
-
-PlotKit.CanvasRenderer.__repr__ = function() {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.CanvasRenderer.toString = function() {
-    return this.__repr__();
-}
-
-PlotKit.CanvasRenderer = function(element, layout, options) {
-    if (arguments.length  > 0)
-        this.__init__(element, layout, options);
-};
-
-PlotKit.CanvasRenderer.prototype.__init__ = function(element, layout, options) {
-    var isNil = MochiKit.Base.isUndefinedOrNull;
-    var Color = MochiKit.Color.Color;
-    
-    // default options
-    this.options = {
-        "drawBackground": true,
-        "backgroundColor": Color.whiteColor(),
-        "padding": {left: 30, right: 30, top: 5, bottom: 10},
-        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),
-        "strokeColor": Color.whiteColor(),
-        "strokeColorTransform": "asStrokeColor",
-        "strokeWidth": 0.5,
-        "shouldFill": true,
-        "shouldStroke": true,
-        "drawXAxis": true,
-        "drawYAxis": true,
-        "axisLineColor": Color.blackColor(),
-        "axisLineWidth": 0.5,
-        "axisTickSize": 3,
-        "axisLabelColor": Color.blackColor(),
-        "axisLabelFont": "Arial",
-        "axisLabelFontSize": 9,
-		"axisLabelWidth": 50,
-		"pieRadius": 0.4,
-        "enableEvents": true
-    };
-    MochiKit.Base.update(this.options, options ? options : {});
-
-    this.layout = layout;
-    this.element = MochiKit.DOM.getElement(element);
-    this.container = this.element.parentNode;
-
-    // Stuff relating to Canvas on IE support    
-    this.isIE = PlotKit.Base.excanvasSupported();
-
-    if (this.isIE && !isNil(G_vmlCanvasManager)) {
-        this.IEDelay = 0.5;
-        this.maxTries = 5;
-        this.renderDelay = null;
-        this.clearDelay = null;
-        this.element = G_vmlCanvasManager.initElement(this.element);
-    }
-
-    this.height = this.element.height;
-    this.width = this.element.width;
-
-    // --- check whether everything is ok before we return
-
-    if (isNil(this.element))
-        throw "CanvasRenderer() - passed canvas is not found";
-
-    if (!this.isIE && !(PlotKit.CanvasRenderer.isSupported(this.element)))
-        throw "CanvasRenderer() - Canvas is not supported.";
-
-    if (isNil(this.container) || (this.container.nodeName.toLowerCase() != "div"))
-        throw "CanvasRenderer() - <canvas> needs to be enclosed in <div>";
-
-    // internal state
-    this.xlabels = new Array();
-    this.ylabels = new Array();
-    this.isFirstRender = true;
-
-    this.area = {
-        x: this.options.padding.left,
-        y: this.options.padding.top,
-        w: this.width - this.options.padding.left - this.options.padding.right,
-        h: this.height - this.options.padding.top - this.options.padding.bottom
-    };
-
-    MochiKit.DOM.updateNodeAttributes(this.container, 
-    {"style":{ "position": "relative", "width": this.width + "px"}});
-
-    // load event system if we have Signals
-    /* Disabled until we have a proper implementation
-    try {
-        this.event_isinside = null;
-        if (MochiKit.Signal && this.options.enableEvents) {
-            this._initialiseEvents();
-        }
-    }
-    catch (e) {
-        // still experimental
-    }
-    */
-};
-
-PlotKit.CanvasRenderer.prototype.render = function() {
-    if (this.isIE) {
-        // VML takes a while to start up, so we just poll every this.IEDelay
-        try {
-            if (this.renderDelay) {
-                this.renderDelay.cancel();
-                this.renderDelay = null;
-            }
-            var context = this.element.getContext("2d");
-        }
-        catch (e) {
-            this.isFirstRender = false;
-            if (this.maxTries-- > 0) {
-                this.renderDelay = MochiKit.Async.wait(this.IEDelay);
-                this.renderDelay.addCallback(bind(this.render, this));
-            }
-            return;
-        }
-    }
-
-    if (this.options.drawBackground)
-        this._renderBackground();
-
-    if (this.layout.style == "bar") {
-        this._renderBarChart();
-		this._renderBarAxis(); 
-	}
-    else if (this.layout.style == "pie") {
-        this._renderPieChart();
-		this._renderPieAxis();
-	}
-    else if (this.layout.style == "line") {
-        this._renderLineChart();
-		this._renderLineAxis();
-	}
-};
-
-PlotKit.CanvasRenderer.prototype._renderBarChartWrap = function(data, plotFunc) {
-    var context = this.element.getContext("2d");
-    var colorCount = this.options.colorScheme.length;
-    var colorScheme = this.options.colorScheme;
-    var setNames = MochiKit.Base.keys(this.layout.datasets);
-    var setCount = setNames.length;
-
-    for (var i = 0; i < setCount; i++) {
-        var setName = setNames[i];
-        var color = colorScheme[i%colorCount];
-        context.save();
-        context.fillStyle = color.toRGBString();
-        if (this.options.strokeColor)
-            context.strokeStyle = this.options.strokeColor.toRGBString();
-        else if (this.options.strokeColorTransform) 
-            context.strokeStyle = color[this.options.strokeColorTransform]().toRGBString();
-        
-        context.lineWidth = this.options.strokeWidth;
-        var forEachFunc = function(obj) {
-            if (obj.name == setName)
-                plotFunc(context, obj);
-        };                
-
-        MochiKit.Iter.forEach(data, bind(forEachFunc, this));
-        context.restore();
-    }
-};
-
-PlotKit.CanvasRenderer.prototype._renderBarChart = function() {
-    var bind = MochiKit.Base.bind;
-
-    var drawRect = function(context, bar) {
-        var x = this.area.w * bar.x + this.area.x;
-        var y = this.area.h * bar.y + this.area.y;
-        var w = this.area.w * bar.w;
-        var h = this.area.h * bar.h;       
-        if ((w < 1) || (h < 1))
-            return;
-        if (this.options.shouldFill)
-            context.fillRect(x, y, w, h);
-        if (this.options.shouldStroke)
-            context.strokeRect(x, y, w, h);                
-    };
-    this._renderBarChartWrap(this.layout.bars, bind(drawRect, this));
-};
-
-PlotKit.CanvasRenderer.prototype._renderLineChart = function() {
-    var context = this.element.getContext("2d");
-    var colorCount = this.options.colorScheme.length;
-    var colorScheme = this.options.colorScheme;
-    var setNames = MochiKit.Base.keys(this.layout.datasets);
-    var setCount = setNames.length;
-    var bind = MochiKit.Base.bind;
-    var partial = MochiKit.Base.partial;
-
-    for (var i = 0; i < setCount; i++) {
-        var setName = setNames[i];
-        var color = colorScheme[i%colorCount];
-        var strokeX = this.options.strokeColorTransform;
-
-        // setup graphics context
-        context.save();
-        context.fillStyle = color.toRGBString();
-        if (this.options.strokeColor)
-            context.strokeStyle = this.options.strokeColor.toRGBString();
-        else if (this.options.strokeColorTransform) 
-            context.strokeStyle = color[strokeX]().toRGBString();
-        
-        context.lineWidth = this.options.strokeWidth;
-        
-        // create paths
-        var makePath = function(ctx) {
-            ctx.beginPath();
-            ctx.moveTo(this.area.x, this.area.y + this.area.h);
-            var addPoint = function(ctx_, point) {
-                if (point.name == setName)
-                    ctx_.lineTo(this.area.w * point.x + this.area.x,
-                                this.area.h * point.y + this.area.y);
-            };
-            MochiKit.Iter.forEach(this.layout.points, partial(addPoint, ctx), this);
-            ctx.lineTo(this.area.w + this.area.x,
-                           this.area.h + this.area.y);
-            ctx.lineTo(this.area.x, this.area.y + this.area.h);
-            ctx.closePath();
-        };
-
-        if (this.options.shouldFill) {
-            bind(makePath, this)(context);
-            context.fill();
-        }
-        if (this.options.shouldStroke) {
-            bind(makePath, this)(context);
-            context.stroke();
-        }
-
-        context.restore();
-    }
-};
-
-PlotKit.CanvasRenderer.prototype._renderPieChart = function() {
-    var context = this.element.getContext("2d");
-    var colorCount = this.options.colorScheme.length;
-    var slices = this.layout.slices;
-
-    var centerx = this.area.x + this.area.w * 0.5;
-    var centery = this.area.y + this.area.h * 0.5;
-    var radius = Math.min(this.area.w * this.options.pieRadius, 
-                          this.area.h * this.options.pieRadius);
-
-    if (this.isIE) {
-        centerx = parseInt(centerx);
-        centery = parseInt(centery);
-        radius = parseInt(radius);
-    }
-
-
-	// NOTE NOTE!! Canvas Tag draws the circle clockwise from the y = 0, x = 1
-	// so we have to subtract 90 degrees to make it start at y = 1, x = 0
-
-    for (var i = 0; i < slices.length; i++) {
-        var color = this.options.colorScheme[i%colorCount];
-        context.save();
-        context.fillStyle = color.toRGBString();
-
-        var makePath = function() {
-            context.beginPath();
-            context.moveTo(centerx, centery);
-            context.arc(centerx, centery, radius, 
-                        slices[i].startAngle - Math.PI/2,
-                        slices[i].endAngle - Math.PI/2,
-                        false);
-            context.lineTo(centerx, centery);
-            context.closePath();
-        };
-
-        if (Math.abs(slices[i].startAngle - slices[i].endAngle) > 0.001) {
-            if (this.options.shouldFill) {
-                makePath();
-                context.fill();
-            }
-            
-            if (this.options.shouldStroke) {
-                makePath();
-                context.lineWidth = this.options.strokeWidth;
-                if (this.options.strokeColor)
-                    context.strokeStyle = this.options.strokeColor.toRGBString();
-                else if (this.options.strokeColorTransform)
-                    context.strokeStyle = color[this.options.strokeColorTransform]().toRGBString();
-                context.stroke();
-            }
-        }
-        context.restore();
-    }
-};
-
-PlotKit.CanvasRenderer.prototype._renderBarAxis = function() {
-	this._renderAxis();
-}
-
-PlotKit.CanvasRenderer.prototype._renderLineAxis = function() {
-	this._renderAxis();
-};
-
-
-PlotKit.CanvasRenderer.prototype._renderAxis = function() {
-    if (!this.options.drawXAxis && !this.options.drawYAxis)
-        return;
-
-    var context = this.element.getContext("2d");
-
-    var labelStyle = {"style":
-         {"position": "absolute",
-          "fontSize": this.options.axisLabelFontSize + "px",
-          "zIndex": 10,
-          "color": this.options.axisLabelColor.toRGBString(),
-          "width": this.options.axisLabelWidth + "px",
-          "overflow": "hidden"
-         }
-    };
-
-    // axis lines
-    context.save();
-    context.strokeStyle = this.options.axisLineColor.toRGBString();
-    context.lineWidth = this.options.axisLineWidth;
-
-
-    if (this.options.drawYAxis) {
-        if (this.layout.yticks) {
-            var drawTick = function(tick) {
-                if (typeof(tick) == "function") return;
-                var x = this.area.x;
-                var y = this.area.y + tick[0] * this.area.h;
-                context.beginPath();
-                context.moveTo(x, y);
-                context.lineTo(x - this.options.axisTickSize, y);
-                context.closePath();
-                context.stroke();
-
-                var label = DIV(labelStyle, tick[1]);
-                label.style.top = (y - this.options.axisLabelFontSize) + "px";
-                label.style.left = (x - this.options.padding.left - this.options.axisTickSize) + "px";
-                label.style.textAlign = "right";
-                label.style.width = (this.options.padding.left - this.options.axisTickSize * 2) + "px";
-                MochiKit.DOM.appendChildNodes(this.container, label);
-                this.ylabels.push(label);
-            };
-            
-            MochiKit.Iter.forEach(this.layout.yticks, bind(drawTick, this));
-        }
-
-        context.beginPath();
-        context.moveTo(this.area.x, this.area.y);
-        context.lineTo(this.area.x, this.area.y + this.area.h);
-        context.closePath();
-        context.stroke();
-    }
-
-    if (this.options.drawXAxis) {
-        if (this.layout.xticks) {
-            var drawTick = function(tick) {
-                if (typeof(dataset) == "function") return;
-                
-                var x = this.area.x + tick[0] * this.area.w;
-                var y = this.area.y + this.area.h;
-                context.beginPath();
-                context.moveTo(x, y);
-                context.lineTo(x, y + this.options.axisTickSize);
-                context.closePath();
-                context.stroke();
-
-                var label = DIV(labelStyle, tick[1]);
-                label.style.top = (y + this.options.axisTickSize) + "px";
-                label.style.left = (x - this.options.axisLabelWidth/2) + "px";
-                label.style.textAlign = "center";
-                label.style.width = this.options.axisLabelWidth + "px";
-                MochiKit.DOM.appendChildNodes(this.container, label);
-                this.xlabels.push(label);
-            };
-            
-            MochiKit.Iter.forEach(this.layout.xticks, bind(drawTick, this));
-        }
-
-        context.beginPath();
-        context.moveTo(this.area.x, this.area.y + this.area.h);
-        context.lineTo(this.area.x + this.area.w, this.area.y + this.area.h);
-        context.closePath();
-        context.stroke();
-    }
-
-    context.restore();
-
-};
-
-PlotKit.CanvasRenderer.prototype._renderPieAxis = function() {
-    if (!this.options.drawXAxis)
-        return;
-
-	if (this.layout.xticks) {
-		// make a lookup dict for x->slice values
-		var lookup = new Array();
-		for (var i = 0; i < this.layout.slices.length; i++) {
-			lookup[this.layout.slices[i].xval] = this.layout.slices[i];
-		}
-		
-		var centerx = this.area.x + this.area.w * 0.5;
-	    var centery = this.area.y + this.area.h * 0.5;
-	    var radius = Math.min(this.area.w * this.options.pieRadius,
-	                          this.area.h * this.options.pieRadius);
-		var labelWidth = this.options.axisLabelWidth;
-		
-		for (var i = 0; i < this.layout.xticks.length; i++) {
-			var slice = lookup[this.layout.xticks[i][0]];
-			if (MochiKit.Base.isUndefinedOrNull(slice))
-				continue;
-				
-				
-			var angle = (slice.startAngle + slice.endAngle)/2;
-			// normalize the angle
-			var normalisedAngle = angle;
-			if (normalisedAngle > Math.PI * 2)
-				normalisedAngle = normalisedAngle - Math.PI * 2;
-			else if (normalisedAngle < 0)
-				normalisedAngle = normalisedAngle + Math.PI * 2;
-				
-			var labelx = centerx + Math.sin(normalisedAngle) * (radius + 10);
-	        var labely = centery - Math.cos(normalisedAngle) * (radius + 10);
-
-			var attrib = {"position": "absolute",
-	                      "zIndex": 11,
-	                      "width": labelWidth + "px",
-	                      "fontSize": this.options.axisLabelFontSize + "px",
-	                      "overflow": "hidden",
-						  "color": this.options.axisLabelColor.toHexString()
-						};
-
-			if (normalisedAngle <= Math.PI * 0.5) {
-	            // text on top and align left
-	            attrib["textAlign"] = "left";
-	            attrib["verticalAlign"] = "top";
-	            attrib["left"] = labelx + "px";
-	            attrib["top"] = (labely - this.options.axisLabelFontSize) + "px";
-	        }
-	        else if ((normalisedAngle > Math.PI * 0.5) && (normalisedAngle <= Math.PI)) {
-	            // text on bottom and align left
-	            attrib["textAlign"] = "left";
-	            attrib["verticalAlign"] = "bottom";     
-	            attrib["left"] = labelx + "px";
-	            attrib["top"] = labely + "px";
-
-	        }
-	        else if ((normalisedAngle > Math.PI) && (normalisedAngle <= Math.PI*1.5)) {
-	            // text on bottom and align right
-	            attrib["textAlign"] = "right";
-	            attrib["verticalAlign"] = "bottom"; 
-	            attrib["left"] = (labelx  - labelWidth) + "px";
-	            attrib["top"] = labely + "px";
-	        }
-	        else {
-	            // text on top and align right
-	            attrib["textAlign"] = "right";
-	            attrib["verticalAlign"] = "bottom";  
-	            attrib["left"] = (labelx  - labelWidth) + "px";
-	            attrib["top"] = (labely - this.options.axisLabelFontSize) + "px";
-	        }
-	
-			var label = DIV({'style': attrib}, this.layout.xticks[i][1]);
-			this.xlabels.push(label);
-			MochiKit.DOM.appendChildNodes(this.container, label);
-	  }
-		
-	}
-};
-
-PlotKit.CanvasRenderer.prototype._renderBackground = function() {
-    var context = this.element.getContext("2d");
-    context.save();
-    context.fillStyle = this.options.backgroundColor.toRGBString();
-    context.fillRect(0, 0, this.width, this.height);
-    context.restore();
-};
-
-PlotKit.CanvasRenderer.prototype.clear = function() {
-    if (this.isIE) {
-        // VML takes a while to start up, so we just poll every this.IEDelay
-        try {
-            if (this.clearDelay) {
-                this.clearDelay.cancel();
-                this.clearDelay = null;
-            }
-            var context = this.element.getContext("2d");
-        }
-        catch (e) {
-            this.isFirstRender = false;
-            this.clearDelay = MochiKit.Async.wait(this.IEDelay);
-            this.clearDelay.addCallback(bind(this.clear, this));
-            return;
-        }
-    }
-
-    var context = this.element.getContext("2d");
-    context.clearRect(0, 0, this.width, this.height);
-
-    MochiKit.Iter.forEach(this.xlabels, MochiKit.DOM.removeElement);
-    MochiKit.Iter.forEach(this.ylabels, MochiKit.DOM.removeElement);
-    this.xlabels = new Array();
-    this.ylabels = new Array();
-};
-
-// ----------------------------------------------------------------
-//  Everything below here is experimental and undocumented.
-// ----------------------------------------------------------------
-
-PlotKit.CanvasRenderer.prototype._initialiseEvents = function() {
-    var connect = MochiKit.Signal.connect;
-    var bind = MochiKit.Base.bind;
-    //MochiKit.Signal.registerSignals(this, ['onmouseover', 'onclick', 'onmouseout', 'onmousemove']);
-    //connect(this.element, 'onmouseover', bind(this.onmouseover, this));
-    //connect(this.element, 'onmouseout', bind(this.onmouseout, this));
-    //connect(this.element, 'onmousemove', bind(this.onmousemove, this));
-    connect(this.element, 'onclick', bind(this.onclick, this));
-};
-
-PlotKit.CanvasRenderer.prototype._resolveObject = function(e) {
-    // does not work in firefox
-	//var x = (e.event().offsetX - this.area.x) / this.area.w;
-	//var y = (e.event().offsetY - this.area.y) / this.area.h;
-
-    var x = (e.mouse().page.x - PlotKit.Base.findPosX(this.element) - this.area.x) / this.area.w;
-    var y = (e.mouse().page.y - PlotKit.Base.findPosY(this.element) - this.area.y) / this.area.h;
-	
-    //log(x, y);
-
-    var isHit = this.layout.hitTest(x, y);
-    if (isHit)
-        return isHit;
-    return null;
-};
-
-PlotKit.CanvasRenderer.prototype._createEventObject = function(layoutObj, e) {
-    if (layoutObj == null) {
-        return null;
-    }
-
-    e.chart = layoutObj
-    return e;
-};
-
-
-PlotKit.CanvasRenderer.prototype.onclick = function(e) {
-    var layoutObject = this._resolveObject(e);
-    var eventObject = this._createEventObject(layoutObject, e);
-    if (eventObject != null)
-        MochiKit.Signal.signal(this, "onclick", eventObject);
-};
-
-PlotKit.CanvasRenderer.prototype.onmouseover = function(e) {
-    var layoutObject = this._resolveObject(e);
-    var eventObject = this._createEventObject(layoutObject, e);
-    if (eventObject != null) 
-        signal(this, "onmouseover", eventObject);
-};
-
-PlotKit.CanvasRenderer.prototype.onmouseout = function(e) {
-    var layoutObject = this._resolveObject(e);
-    var eventObject = this._createEventObject(layoutObject, e);
-    if (eventObject == null)
-        signal(this, "onmouseout", e);
-    else 
-        signal(this, "onmouseout", eventObject);
-
-};
-
-PlotKit.CanvasRenderer.prototype.onmousemove = function(e) {
-    var layoutObject = this._resolveObject(e);
-    var eventObject = this._createEventObject(layoutObject, e);
-
-    if ((layoutObject == null) && (this.event_isinside == null)) {
-        // TODO: should we emit an event anyway?
-        return;
-    }
-
-    if ((layoutObject != null) && (this.event_isinside == null))
-        signal(this, "onmouseover", eventObject);
-
-    if ((layoutObject == null) && (this.event_isinside != null))
-        signal(this, "onmouseout", eventObject);
-
-    if ((layoutObject != null) && (this.event_isinside != null))
-        signal(this, "onmousemove", eventObject);
-
-    this.event_isinside = layoutObject;
-    //log("move", x, y);    
-};
-
-PlotKit.CanvasRenderer.isSupported = function(canvasName) {
-    var canvas = null;
-    try {
-        if (MochiKit.Base.isUndefinedOrNull(canvasName)) 
-            canvas = MochiKit.DOM.CANVAS({});
-        else
-            canvas = MochiKit.DOM.getElement(canvasName);
-        var context = canvas.getContext("2d");
-    }
-    catch (e) {
-        var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
-        var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
-        if ((!ie) || (ie[1] < 6) || (opera))
-            return false;
-        return true;
-    }
-    return true;
-};
-
-// Namespace Iniitialisation
-
-PlotKit.Canvas = {}
-PlotKit.Canvas.CanvasRenderer = PlotKit.CanvasRenderer;
-
-PlotKit.Canvas.EXPORT = [
-    "CanvasRenderer"
-];
-
-PlotKit.Canvas.EXPORT_OK = [
-    "CanvasRenderer"
-];
-
-PlotKit.Canvas.__new__ = function() {
-    var m = MochiKit.Base;
-    
-    m.nameFunctions(this);
-    
-    this.EXPORT_TAGS = {
-        ":common": this.EXPORT,
-        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
-    };
-};
-
-PlotKit.Canvas.__new__();
-MochiKit.Base._exportSymbols(this, PlotKit.Canvas);
-
diff --git a/share/web/static/js/PlotKit/EasyPlot.js b/share/web/static/js/PlotKit/EasyPlot.js
deleted file mode 100644
index 7607731..0000000
--- a/share/web/static/js/PlotKit/EasyPlot.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/* 
-    PlotKit EasyPlot
-    ================
-
-    User friendly wrapper around the common plotting functions.
-
-    Copyright
-    ---------
-    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
-    For use under the BSD license. <http://www.liquidx.net/plotkit>
-    
-*/
-
-try {    
-    if (typeof(PlotKit.CanvasRenderer) == 'undefined')
-    {
-        throw ""
-    }
-} 
-catch (e) {    
-    throw "PlotKit.EasyPlot depends on all of PlotKit's components";
-}
-
-// --------------------------------------------------------------------
-// Start of EasyPlot definition
-// --------------------------------------------------------------------
-
-if (typeof(PlotKit.EasyPlot) == 'undefined') {
-    PlotKit.EasyPlot = {};
-}
-
-PlotKit.EasyPlot.NAME = "PlotKit.EasyPlot";
-PlotKit.EasyPlot.VERSION = PlotKit.VERSION;
-
-PlotKit.EasyPlot.__repr__ = function() {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.EasyPlot.toString = function() {
-    return this.__repr__();
-}
-
-// --------------------------------------------------------------------
-// Start of EasyPlot definition
-// --------------------------------------------------------------------
-
-PlotKit.EasyPlot = function(style, options, divElem, datasources) {
-    this.layout = new Layout(style, options);
-    this.divElem = divElem;
-    this.width = parseInt(divElem.getAttribute('width'));
-    this.height = parseInt(divElem.getAttribute('height'));
-    this.deferredCount = 0;
-
-    // make sure we have non-zero width
-    if (this.width < 1) {
-        this.width = this.divElem.width ? this.divElem.width : 300;
-    }
-    
-    if (this.height < 1) {
-        this.height = this.divElem.height ? this.divElem.height : 300;
-    }
-    
-    // load data sources
-    if (isArrayLike(datasources)) {
-        for (var i = 0; i < datasources.length; i++) {
-            if (typeof(datasources[i]) == "string") {
-                this.deferredCount++;
-                // load CSV via ajax
-                var d = MochiKit.Async.doSimpleXMLHttpRequest(datasources[i]);
-                d.addCallback(MochiKit.Base.bind(PlotKit.EasyPlot.onDataLoaded, this));
-            }
-            else if (isArrayLike(datasources[i])) {
-                this.layout.addDataset("data-" + i, datasources[i]);
-            }
-        }
-    }
-    else if (!isUndefinedOrNull(datasources)) {
-        throw "Passed datasources are not Array like";
-    }
-    
-    // setup canvas to render
-    
-    if (CanvasRenderer.isSupported()) {
-        this.element = CANVAS({"id": this.divElem.getAttribute("id") + "-canvas",
-                               "width": this.width,
-                               "height": this.height}, "");
-        this.divElem.appendChild(this.element);
-        this.renderer = new SweetCanvasRenderer(this.element, this.layout, options);
-    }
-    else if (SVGRenderer.isSupported()) {
-        this.element = SVGRenderer.SVG({"id": this.divElem.getAttribute("id") + "-svg",
-                                        "width": this.width,
-                                        "height": this.height,
-                                        "version": "1.1",
-                                        "baseProfile": "full"}, "");
-        this.divElem.appendChild(this.element);
-        this.renderer = new SweetSVGRenderer(this.element, this.layout, options);
-    }
-    
-    if ((this.deferredCount == 0) && (PlotKit.Base.keys(this.layout.datasets).length > 0)) {
-        this.layout.evaluate();
-        this.renderer.clear();
-        this.renderer.render();    
-    }
-    
-};
-
-PlotKit.EasyPlot.onDataLoaded = function(request) {
-    
-    // very primitive CSV parser, should fix to make it more compliant.
-    var table = new Array();
-    var lines = request.responseText.split('\n');
-    for (var i = 0; i < lines.length; i++) {
-        var stripped = MochiKit.Format.strip(lines[i]);
-        if ((stripped.length > 1) && (stripped.charAt(0) != '#')) {
-            table.push(stripped.split(','));
-        }
-    }
-  
-    this.layout.addDataset("data-ajax-" + this.deferredCount, table);
-    this.deferredCount--;
-    
-    if ((this.deferredCount == 0) && (PlotKit.Base.keys(this.layout.datasets).length > 0)) {
-        this.layout.evaluate();
-        this.renderer.clear();
-        this.renderer.render();
-    }
-};
-
-PlotKit.EasyPlot.prototype.reload = function() {
-    this.layout.evaluate();
-    this.renderer.clear();
-    this.renderer.render();
-};
-
-// Namespace Iniitialisation
-
-PlotKit.EasyPlotModule = {};
-PlotKit.EasyPlotModule.EasyPlot = PlotKit.EasyPlot;
-
-PlotKit.EasyPlotModule.EXPORT = [
-    "EasyPlot"
-];
-
-PlotKit.EasyPlotModule.EXPORT_OK = [];
-
-PlotKit.EasyPlotModule.__new__ = function() {
-    var m = MochiKit.Base;
-    
-    m.nameFunctions(this);
-    
-    this.EXPORT_TAGS = {
-        ":common": this.EXPORT,
-        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
-    };
-};
-
-PlotKit.EasyPlotModule.__new__();
-MochiKit.Base._exportSymbols(this, PlotKit.EasyPlotModule);
-
-
diff --git a/share/web/static/js/PlotKit/Layout.js b/share/web/static/js/PlotKit/Layout.js
deleted file mode 100644
index ff1c9db..0000000
--- a/share/web/static/js/PlotKit/Layout.js
+++ /dev/null
@@ -1,756 +0,0 @@
-/* 
-    PlotKit Layout
-    ==============
-    
-    Handles laying out data on to a virtual canvas square canvas between 0.0 
-    and 1.0. If you want to add new chart/plot types such as point plots,
-    you need to add them here.
-    
-    Copyright
-    ---------
-    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
-    For use under the BSD license. <http://www.liquidx.net/plotkit>
-    
-*/
-
-try {    
-    if (typeof(PlotKit.Base) == 'undefined')
-    {
-        throw ""
-    }
-} 
-catch (e) {    
-    throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base"
-}
-
-// --------------------------------------------------------------------
-// Start of Layout definition
-// --------------------------------------------------------------------
-
-if (typeof(PlotKit.Layout) == 'undefined') {
-    PlotKit.Layout = {};
-}
-
-PlotKit.Layout.NAME = "PlotKit.Layout";
-PlotKit.Layout.VERSION = PlotKit.VERSION;
-
-PlotKit.Layout.__repr__ = function() {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.Layout.toString = function() {
-    return this.__repr__();
-}
-
-PlotKit.Layout.valid_styles = ["bar", "line", "pie", "point"];
-
-// --------------------------------------------------------------------
-// Start of Layout definition
-// --------------------------------------------------------------------
-
-PlotKit.Layout = function(style, options) {
-  
-    this.options = {
-        "barWidthFillFraction": 0.75,
-        "barOrientation": "vertical",
-        "xOriginIsZero": true,
-        "yOriginIsZero": true,
-        "xAxis": null, // [xmin, xmax]
-        "yAxis": null, // [ymin, ymax]
-        "xTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.)
-        "yTicks": null, // [{label: "somelabel", v: value}, ..] (label opt.)
-        "xNumberOfTicks": 10,
-        "yNumberOfTicks": 5,
-        "xTickPrecision": 1,
-        "yTickPrecision": 1,
-        "pieRadius": 0.4
-    };
-
-    // valid external options : TODO: input verification
-    this.style = style; 
-    MochiKit.Base.update(this.options, options ? options : {});
-
-    // externally visible states
-    // overriden if xAxis and yAxis are set in options
-    if (!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)) {
-        this.minxval = this.options.xAxis[0];
-        this.maxxval = this.options.xAxis[1];
-        this.xscale = this.maxxval - this.minxval; 
-    }
-    else {
-        this.minxval = 0;
-        this.maxxval = null;
-        this.xscale = null; // val -> pos factor (eg, xval * xscale = xpos)
-    }
-
-    if (!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)) {
-        this.minyval = this.options.yAxis[0];
-        this.maxyval = this.options.yAxis[1];
-        this.yscale = this.maxyval - this.minyval;
-    }
-    else {
-        this.minyval = 0;
-        this.maxyval = null;
-        this.yscale = null;
-    }
-
-    this.bars = new Array();   // array of bars to plot for bar charts
-    this.points = new Array(); // array of points to plot for line plots
-    this.slices = new Array(); // array of slices to draw for pie charts
-
-    this.xticks = new Array();
-    this.yticks = new Array();
-
-    // internal states
-    this.datasets = new Array();
-    this.minxdelta = 0;
-    this.xrange = 1;
-    this.yrange = 1;
-
-    this.hitTestCache = {x2maxy: null};
-    
-};
-
-// --------------------------------------------------------------------
-// Dataset Manipulation
-// --------------------------------------------------------------------
-
-
-PlotKit.Layout.prototype.addDataset = function(setname, set_xy) {
-    this.datasets[setname] = set_xy;
-};
-
-PlotKit.Layout.prototype.removeDataset = function(setname, set_xy) {
-    delete this.datasets[setname];
-};
-
-PlotKit.Layout.prototype.addDatasetFromTable = function(name, tableElement, xcol, ycol,  lcol) {
-	var isNil = MochiKit.Base.isUndefinedOrNull;
-	var scrapeText = MochiKit.DOM.scrapeText;
-	var strip = MochiKit.Format.strip;
-	
-	if (isNil(xcol))
-		xcol = 0;
-	if (isNil(ycol))
-		ycol = 1;
-	if (isNil(lcol))
-	    lcol = -1;
-        
-    var rows = tableElement.tBodies[0].rows;
-    var data = new Array();
-    var labels = new Array();
-    
-    if (!isNil(rows)) {
-        for (var i = 0; i < rows.length; i++) {
-            data.push([parseFloat(strip(scrapeText(rows[i].cells[xcol]))),
-                       parseFloat(strip(scrapeText(rows[i].cells[ycol])))]);
-            if (lcol >= 0){
-               labels.push({v: parseFloat(strip(scrapeText(rows[i].cells[xcol]))),
-                            label:  strip(scrapeText(rows[i].cells[lcol]))});
-            }
-        }
-        this.addDataset(name, data);
-        if (lcol >= 0) {
-            this.options.xTicks = labels;
-        }
-        return true;
-    }
-    return false;
-};
-
-// --------------------------------------------------------------------
-// Evaluates the layout for the current data and style.
-// --------------------------------------------------------------------
-
-PlotKit.Layout.prototype.evaluate = function() {
-    this._evaluateLimits();
-    this._evaluateScales();
-    if (this.style == "bar") {
-        if (this.options.barOrientation == "horizontal") {
-            this._evaluateHorizBarCharts();
-        }
-        else {
-            this._evaluateBarCharts();
-        }
-        this._evaluateBarTicks();
-    }
-    else if (this.style == "line") {
-        this._evaluateLineCharts();
-        this._evaluateLineTicks();
-    }
-    else if (this.style == "pie") {
-        this._evaluatePieCharts();
-        this._evaluatePieTicks();
-    }
-};
-
-
-
-// Given the fractional x, y positions, report the corresponding
-// x, y values.
-PlotKit.Layout.prototype.hitTest = function(x, y) {
-    // TODO: make this more efficient with better datastructures
-    //       for this.bars, this.points and this.slices
-
-    var f = MochiKit.Format.twoDigitFloat;
-
-    if ((this.style == "bar") && this.bars && (this.bars.length > 0)) {
-        for (var i = 0; i < this.bars.length; i++) {
-            var bar = this.bars[i];
-            if ((x >= bar.x) && (x <= bar.x + bar.w) 
-                && (y >= bar.y) && (y - bar.y <= bar.h))
-                return bar;
-        }
-    }
-
-    else if (this.style == "line") {
-        if (this.hitTestCache.x2maxy == null) {
-            this._regenerateHitTestCache();
-        }
-
-        // 1. find the xvalues that equal or closest to the give x
-        var xval = x / this.xscale;
-        var xvalues = this.hitTestCache.xvalues;
-        var xbefore = null;
-        var xafter = null;
-
-        for (var i = 1; i < xvalues.length; i++) {
-            if (xvalues[i] > xval) {
-                xbefore = xvalues[i-1];
-                xafter = xvalues[i];
-                break;
-            }
-        }
-
-        if ((xbefore != null)) {
-            var ybefore = this.hitTestCache.x2maxy[xbefore];
-            var yafter = this.hitTestCache.x2maxy[xafter];
-            var yval = (1.0 - y)/this.yscale;
-
-            // interpolate whether we will fall inside or outside
-            var gradient = (yafter - ybefore) / (xafter - xbefore);
-            var projmaxy = ybefore + gradient * (xval - xbefore);
-            if (projmaxy >= yval) {
-                // inside the highest curve (roughly)
-                var obj = {xval: xval, yval: yval,
-                           xafter: xafter, yafter: yafter,
-                           xbefore: xbefore, ybefore: ybefore,
-                           yprojected: projmaxy
-                };
-                return obj;
-            }
-        }
-    }
-
-    else if (this.style == "pie") {
-        var dist = Math.sqrt((y-0.5)*(y-0.5) + (x-0.5)*(x-0.5));
-        if (dist > this.options.pieRadius)
-            return null;
-
-        // TODO: actually doesn't work if we don't know how the Canvas
-        //       lays it out, need to fix!
-        var angle = Math.atan2(y - 0.5, x - 0.5) - Math.PI/2;
-        for (var i = 0; i < this.slices.length; i++) {
-            var slice = this.slices[i];
-            if (slice.startAngle < angle && slice.endAngle >= angle)
-                return slice;
-        }
-    }
-
-    return null;
-};
-
-// Reports valid position rectangle for X value (only valid for bar charts)
-PlotKit.Layout.prototype.rectForX = function(x) {
-    return null;
-};
-
-// Reports valid angles through which X value encloses (only valid for pie charts)
-PlotKit.Layout.prototype.angleRangeForX = function(x) {
-    return null;
-};
-
-// --------------------------------------------------------------------
-// START Internal Functions
-// --------------------------------------------------------------------
-
-PlotKit.Layout.prototype._evaluateLimits = function() {
-    // take all values from all datasets and find max and min
-    var map = PlotKit.Base.map;
-    var items = PlotKit.Base.items;
-    var itemgetter = MochiKit.Base.itemgetter;
-    var collapse = PlotKit.Base.collapse;
-    var listMin = MochiKit.Base.listMin;
-    var listMax = MochiKit.Base.listMax;
-    var isNil = MochiKit.Base.isUndefinedOrNull;
-
-
-    var all = collapse(map(itemgetter(1), items(this.datasets)));
-    if (isNil(this.options.xAxis)) {
-        if (this.options.xOriginIsZero)
-            this.minxval = 0;
-        else
-            this.minxval = listMin(map(parseFloat, map(itemgetter(0), all)));
-
-        this.maxxval = listMax(map(parseFloat, map(itemgetter(0), all)));
-    }
-    else {
-        this.minxval = this.options.xAxis[0];
-        this.maxxval = this.options.xAxis[1];
-        this.xscale = this.maxval - this.minxval;
-    }
-    
-    if (isNil(this.options.yAxis)) {
-        if (this.options.yOriginIsZero)
-            this.minyval = 0;
-        else
-            this.minyval = listMin(map(parseFloat, map(itemgetter(1), all)));
-
-        this.maxyval = listMax(map(parseFloat, map(itemgetter(1), all)));
-    }
-    else {
-        this.minyval = this.options.yAxis[0];
-        this.maxyval = this.options.yAxis[1];
-        this.yscale = this.maxyval - this.minyval;
-    }
-
-};
-
-PlotKit.Layout.prototype._evaluateScales = function() {
-    var isNil = MochiKit.Base.isUndefinedOrNull;
-
-    this.xrange = this.maxxval - this.minxval;
-    if (this.xrange == 0)
-        this.xscale = 1.0;
-    else
-        this.xscale = 1/this.xrange;
-
-    this.yrange = this.maxyval - this.minyval;
-    if (this.yrange == 0)
-        this.yscale = 1.0;
-    else
-        this.yscale = 1/this.yrange;
-};
-
-PlotKit.Layout.prototype._uniqueXValues = function() {
-    var collapse = PlotKit.Base.collapse;
-    var map = PlotKit.Base.map;
-    var uniq = PlotKit.Base.uniq;
-    var getter = MochiKit.Base.itemgetter;
-    var items = PlotKit.Base.items;
-    
-    var xvalues = map(parseFloat, map(getter(0), collapse(map(getter(1), items(this.datasets)))));
-    xvalues.sort(MochiKit.Base.compare);
-    return uniq(xvalues);
-};
-
-// Create the bars
-PlotKit.Layout.prototype._evaluateBarCharts = function() {
-    var items = PlotKit.Base.items;
-
-    var setCount = items(this.datasets).length;
-
-    // work out how far separated values are
-    var xdelta = 10000000;
-    var xvalues = this._uniqueXValues();
-    for (var i = 1; i < xvalues.length; i++) {
-        xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta);
-    }
-
-    var barWidth = 0;
-    var barWidthForSet = 0;
-    var barMargin = 0;
-    if (xvalues.length == 1) {
-        // note we have to do something smarter if we only plot one value
-        xdelta = 1.0;
-        this.xscale = 1.0;
-        this.minxval = xvalues[0];
-        barWidth = 1.0 * this.options.barWidthFillFraction;
-        barWidthForSet = barWidth/setCount;
-        barMargin = (1.0 - this.options.barWidthFillFraction)/2;
-    }
-    else {
-        // readjust xscale to fix with bar charts
-        if (this.xrange == 1) {
-            this.xscale = 0.5;
-        }
-        else if (this.xrange == 2) {
-            this.xscale = 1/3.0;
-        }
-        else {
-            this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
-        }
-        barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
-        barWidthForSet = barWidth / setCount;
-        barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
-    }
-    
-    this.minxdelta = xdelta; // need this for tick positions
-
-    // add all the rects
-    this.bars = new Array();
-    var i = 0;
-    for (var setName in this.datasets) {
-        var dataset = this.datasets[setName];
-        if (PlotKit.Base.isFuncLike(dataset)) continue;
-        for (var j = 0; j < dataset.length; j++) {
-            var item = dataset[j];
-            var rect = {
-                x: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
-                y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
-                w: barWidthForSet,
-                h: ((parseFloat(item[1]) - this.minyval) * this.yscale),
-                xval: parseFloat(item[0]),
-                yval: parseFloat(item[1]),
-                name: setName
-            };
-            if ((rect.x >= 0.0) && (rect.x <= 1.0) && 
-                (rect.y >= 0.0) && (rect.y <= 1.0)) {
-                this.bars.push(rect);
-            }
-        }
-        i++;
-    }
-};
-
-// Create the horizontal bars
-PlotKit.Layout.prototype._evaluateHorizBarCharts = function() {
-    var items = PlotKit.Base.items;
-
-    var setCount = items(this.datasets).length;
-
-    // work out how far separated values are
-    var xdelta = 10000000;
-    var xvalues = this._uniqueXValues();
-    for (var i = 1; i < xvalues.length; i++) {
-        xdelta = Math.min(Math.abs(xvalues[i] - xvalues[i-1]), xdelta);
-    }
-
-    var barWidth = 0;
-    var barWidthForSet = 0;
-    var barMargin = 0;
-    
-    // work out how far each far each bar is separated
-    if (xvalues.length == 1) {
-        // do something smarter if we only plot one value
-        xdelta = 1.0;
-        this.xscale = 1.0;
-        this.minxval = xvalues[0];
-        barWidth = 1.0 * this.options.barWidthFillFraction;
-        barWidthForSet = barWidth/setCount;
-        barMargin = (1.0 - this.options.barWidthFillFraction)/2;
-    }
-    else {
-        // readjust yscale to fix with bar charts
-        this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
-        barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
-        barWidthForSet = barWidth / setCount;
-        barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
-    }
-
-    this.minxdelta = xdelta; // need this for tick positions
-
-    // add all the rects
-    this.bars = new Array();
-    var i = 0;
-    for (var setName in this.datasets) {
-        var dataset = this.datasets[setName];
-        if (PlotKit.Base.isFuncLike(dataset)) continue;
-        for (var j = 0; j < dataset.length; j++) {
-            var item = dataset[j];
-            var rect = {
-                y: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
-                x: 0.0,
-                h: barWidthForSet,
-                w: ((parseFloat(item[1]) - this.minyval) * this.yscale),
-                xval: parseFloat(item[0]),
-                yval: parseFloat(item[1]),
-                name: setName
-            };
-
-            // limit the x, y values so they do not overdraw
-            if (rect.y <= 0.0) {
-                rect.y = 0.0;
-            }
-            if (rect.y >= 1.0) {
-                rect.y = 1.0;
-            }
-            if ((rect.x >= 0.0) && (rect.x <= 1.0)) {
-                this.bars.push(rect);
-            }
-        }
-        i++;
-    }
-};
-
-
-// Create the line charts
-PlotKit.Layout.prototype._evaluateLineCharts = function() {
-    var items = PlotKit.Base.items;
-
-    var setCount = items(this.datasets).length;
-
-    // add all the rects
-    this.points = new Array();
-    var i = 0;
-    for (var setName in this.datasets) {
-        var dataset = this.datasets[setName];
-        if (PlotKit.Base.isFuncLike(dataset)) continue;
-        dataset.sort(function(a, b) { return compare(parseFloat(a[0]), parseFloat(b[0])); });
-        for (var j = 0; j < dataset.length; j++) {
-            var item = dataset[j];
-            var point = {
-                x: ((parseFloat(item[0]) - this.minxval) * this.xscale),
-                y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
-                xval: parseFloat(item[0]),
-                yval: parseFloat(item[1]),
-                name: setName
-            };
-
-            // limit the x, y values so they do not overdraw
-            if (point.y <= 0.0) {
-                point.y = 0.0;
-            }
-            if (point.y >= 1.0) {
-                point.y = 1.0;
-            }
-            if ((point.x >= 0.0) && (point.x <= 1.0)) {
-                this.points.push(point);
-            }
-        }
-        i++;
-    }
-};
-
-// Create the pie charts
-PlotKit.Layout.prototype._evaluatePieCharts = function() {
-    var items = PlotKit.Base.items;
-    var sum = MochiKit.Iter.sum;
-    var getter = MochiKit.Base.itemgetter;
-
-    var setCount = items(this.datasets).length;
-
-    // we plot the y values of the first dataset
-    var dataset = items(this.datasets)[0][1];
-    var total = sum(map(getter(1), dataset));
-
-    this.slices = new Array();
-    var currentAngle = 0.0;
-    for (var i = 0; i < dataset.length; i++) {
-        var fraction = dataset[i][1] / total;
-		var startAngle = currentAngle * Math.PI * 2;
-		var endAngle = (currentAngle + fraction) * Math.PI * 2;
-			
-        var slice = {fraction: fraction,
-                     xval: dataset[i][0],
-                     yval: dataset[i][1],
-                     startAngle: startAngle,
-                     endAngle: endAngle
-        };
-        if (dataset[i][1] != 0) {
-            this.slices.push(slice);
-        }
-        currentAngle += fraction;
-    }
-};
-
-PlotKit.Layout.prototype._evaluateLineTicksForXAxis = function() {
-    var isNil = MochiKit.Base.isUndefinedOrNull;
-    
-    if (this.options.xTicks) {
-        // we use use specified ticks with optional labels
-
-        this.xticks = new Array();
-        var makeTicks = function(tick) {
-            var label = tick.label;
-            if (isNil(label))
-                label = tick.v.toString();
-            var pos = this.xscale * (tick.v - this.minxval);
-            if ((pos >= 0.0) && (pos <= 1.0)) {
-                this.xticks.push([pos, label]);
-            }
-        };
-        MochiKit.Iter.forEach(this.options.xTicks, bind(makeTicks, this));
-    }
-    else if (this.options.xNumberOfTicks) {
-        // we use defined number of ticks as hint to auto generate
-        var xvalues = this._uniqueXValues();
-        var roughSeparation = this.xrange / this.options.xNumberOfTicks;
-        var tickCount = 0;
-
-        this.xticks = new Array();
-        for (var i = 0; i <= xvalues.length; i++) {
-            if ((xvalues[i] - this.minxval) >= (tickCount * roughSeparation)) {
-                var pos = this.xscale * (xvalues[i] - this.minxval);
-                if ((pos > 1.0) || (pos < 0.0))
-                    continue;
-                this.xticks.push([pos, xvalues[i]]);
-                tickCount++;
-            }
-            if (tickCount > this.options.xNumberOfTicks)
-                break;
-        }
-    }
-};
-
-PlotKit.Layout.prototype._evaluateLineTicksForYAxis = function() {
-    var isNil = MochiKit.Base.isUndefinedOrNull;
-
-
-    if (this.options.yTicks) {
-        this.yticks = new Array();
-        var makeTicks = function(tick) {
-            var label = tick.label;
-            if (isNil(label))
-                label = tick.v.toString();
-            var pos = 1.0 - (this.yscale * (tick.v - this.minyval));
-            if ((pos >= 0.0) && (pos <= 1.0)) {
-                this.yticks.push([pos, label]);
-            }
-        };
-        MochiKit.Iter.forEach(this.options.yTicks, bind(makeTicks, this));
-    }
-    else if (this.options.yNumberOfTicks) {
-        // We use the optionally defined number of ticks as a guide        
-        this.yticks = new Array();
-
-        // if we get this separation right, we'll have good looking graphs
-        var roundInt = PlotKit.Base.roundInterval;
-        var prec = this.options.yTickPrecision;
-        var roughSeparation = roundInt(this.yrange, 
-                                       this.options.yNumberOfTicks, prec);
-
-        // round off each value of the y-axis to the precision
-        // eg. 1.3333 at precision 1 -> 1.3
-        for (var i = 0; i <= this.options.yNumberOfTicks; i++) {
-            var yval = this.minyval + (i * roughSeparation);
-            var pos = 1.0 - ((yval - this.minyval) * this.yscale);
-            if ((pos > 1.0) || (pos < 0.0))
-                continue;
-            this.yticks.push([pos, MochiKit.Format.roundToFixed(yval, prec)]);
-        }
-    }
-};
-
-PlotKit.Layout.prototype._evaluateLineTicks = function() {
-    this._evaluateLineTicksForXAxis();
-    this._evaluateLineTicksForYAxis();
-};
-
-PlotKit.Layout.prototype._evaluateBarTicks = function() {
-    this._evaluateLineTicks();
-    var centerInBar = function(tick) {
-        return [tick[0] + (this.minxdelta * this.xscale)/2, tick[1]];
-    };
-    this.xticks = MochiKit.Base.map(bind(centerInBar, this), this.xticks);
-    
-    if (this.options.barOrientation == "horizontal") {
-        // swap scales
-        var tempticks = this.xticks;
-        this.xticks = this.yticks;
-        this.yticks = tempticks;
-
-        // we need to invert the "yaxis" (which is now the xaxis when drawn)
-        var invert = function(tick) {
-            return [1.0 - tick[0], tick[1]];
-        }
-        this.xticks = MochiKit.Base.map(invert, this.xticks);
-    }
-};
-
-PlotKit.Layout.prototype._evaluatePieTicks = function() {
-    var isNil = MochiKit.Base.isUndefinedOrNull;
-	var formatter = MochiKit.Format.numberFormatter("#%");
-
-    this.xticks = new Array();
-	if (this.options.xTicks) {
-		// make a lookup dict for x->slice values
-		var lookup = new Array();
-		for (var i = 0; i < this.slices.length; i++) {
-			lookup[this.slices[i].xval] = this.slices[i];
-		}
-		
-		for (var i =0; i < this.options.xTicks.length; i++) {
-			var tick = this.options.xTicks[i];
-			var slice = lookup[tick.v]; 
-            var label = tick.label;
-			if (slice) {
-                if (isNil(label))
-                    label = tick.v.toString();
-				label += " (" + formatter(slice.fraction) + ")";
-				this.xticks.push([tick.v, label]);
-			}
-		}
-	}
-	else {
-		// we make our own labels from all the slices
-		for (var i =0; i < this.slices.length; i++) {
-			var slice = this.slices[i];
-			var label = slice.xval + " (" + formatter(slice.fraction) + ")";
-			this.xticks.push([slice.xval, label]);
-		}
-	}
-};
-
-PlotKit.Layout.prototype._regenerateHitTestCache = function() {
-    this.hitTestCache.xvalues = this._uniqueXValues();
-    this.hitTestCache.xlookup = new Array();
-    this.hitTestCache.x2maxy = new Array();
-
-    var listMax = MochiKit.Base.listMax;
-    var itemgetter = MochiKit.Base.itemgetter;
-    var map = MochiKit.Base.map;
-
-    // generate a lookup table for x values to y values
-    var setNames = keys(this.datasets);
-    for (var i = 0; i < setNames.length; i++) {
-        var dataset = this.datasets[setNames[i]];
-        for (var j = 0; j < dataset.length; j++) {
-            var xval = dataset[j][0];
-            var yval = dataset[j][1];
-            if (this.hitTestCache.xlookup[xval])
-                this.hitTestCache.xlookup[xval].push([yval, setNames[i]]);
-            else 
-                this.hitTestCache.xlookup[xval] = [[yval, setNames[i]]];
-        }
-    }
-
-    for (var x in this.hitTestCache.xlookup) {
-        var yvals = this.hitTestCache.xlookup[x];
-        this.hitTestCache.x2maxy[x] = listMax(map(itemgetter(0), yvals));
-    }
-
-
-};
-
-// --------------------------------------------------------------------
-// END Internal Functions
-// --------------------------------------------------------------------
-
-
-// Namespace Iniitialisation
-
-PlotKit.LayoutModule = {};
-PlotKit.LayoutModule.Layout = PlotKit.Layout;
-
-PlotKit.LayoutModule.EXPORT = [
-    "Layout"
-];
-
-PlotKit.LayoutModule.EXPORT_OK = [];
-
-PlotKit.LayoutModule.__new__ = function() {
-    var m = MochiKit.Base;
-    
-    m.nameFunctions(this);
-    
-    this.EXPORT_TAGS = {
-        ":common": this.EXPORT,
-        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
-    };
-};
-
-PlotKit.LayoutModule.__new__();
-MochiKit.Base._exportSymbols(this, PlotKit.LayoutModule);
-
-
diff --git a/share/web/static/js/PlotKit/PlotKit.js b/share/web/static/js/PlotKit/PlotKit.js
deleted file mode 100644
index e79abf7..0000000
--- a/share/web/static/js/PlotKit/PlotKit.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/***
-
-PlotKit Autoload Javascript Module.
-
-This file was adapted from MochiKit.
-See <http://mochikit.com/> for documentation, downloads, license, etc.
-(c) 2005 Bob Ippolito.  All rights Reserved.
-
-Modified by Alastair Tse, 2006, for PlotKit.
-
-***/
-
-if (typeof(PlotKit) == 'undefined') {
-    PlotKit = {};
-}
-
-if (typeof(PlotKit.PlotKit) == 'undefined') {
-    PlotKit.PlotKit = {};
-}
-
-PlotKit.PlotKit.NAME = "PlotKit.PlotKit";
-PlotKit.PlotKit.VERSION = "0.9.1";
-PlotKit.PlotKit.__repr__ = function () {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.PlotKit.toString = function () {
-    return this.__repr__();
-};
-
-PlotKit.PlotKit.SUBMODULES = [
-    "Base",
-    "Layout",
-    "Canvas",
-    "SVG",
-    "SweetCanvas",
-    "SweetSVG",
-    "EasyPlot"
-];
-
-if (typeof(JSAN) != 'undefined' || typeof(dojo) != 'undefined') {
-    if (typeof(dojo) != 'undefined') {
-        dojo.provide('PlotKit.PlotKit');
-        dojo.require("PlotKit.*");
-    }
-    if (typeof(JSAN) != 'undefined') {
-        // hopefully this makes it easier for static analysis?
-        JSAN.use("PlotKit.Base", []);
-        JSAN.use("PlotKit.Layout", []);
-        JSAN.use("PlotKit.Canvas", []);
-        JSAN.use("PlotKit.SweetCanvas", []);
-        JSAN.use("PlotKit.SVG", []);
-        JSAN.use("PlotKit.SweetSVG", []);
-    }
-    (function () {
-        var extend = MochiKit.Base.extend;
-        var self = PlotKit.PlotKit;
-        var modules = self.SUBMODULES;
-        var EXPORT = [];
-        var EXPORT_OK = [];
-        var EXPORT_TAGS = {};
-        var i, k, m, all;
-        for (i = 0; i < modules.length; i++) {
-            m = PlotKit[modules[i]];
-            extend(EXPORT, m.EXPORT);
-            extend(EXPORT_OK, m.EXPORT_OK);
-            for (k in m.EXPORT_TAGS) {
-                EXPORT_TAGS[k] = extend(EXPORT_TAGS[k], m.EXPORT_TAGS[k]);
-            }
-            all = m.EXPORT_TAGS[":all"];
-            if (!all) {
-                all = extend(null, m.EXPORT, m.EXPORT_OK);
-            }
-            var j;
-            for (j = 0; j < all.length; j++) {
-                k = all[j];
-                self[k] = m[k];
-            }
-        }
-        self.EXPORT = EXPORT;
-        self.EXPORT_OK = EXPORT_OK;
-        self.EXPORT_TAGS = EXPORT_TAGS;
-    }());
-    
-} else {
-    if (typeof(PlotKit.__compat__) == 'undefined') {
-        PlotKit.__compat__ = true;
-    }
-    (function () {
-        if (typeof(document) == "undefined") {
-              return;
-        }
-        
-        var scripts = document.getElementsByTagName("script");
-        var kXULNSURI = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-        var base = null;
-        var baseElem = null;
-        var allScripts = {};
-        var i;
-        for (i = 0; i < scripts.length; i++) {
-            var src = scripts[i].getAttribute("src");
-            if (!src) {
-                continue;
-            }
-            allScripts[src] = true;
-            if (src.match(/PlotKit.js$/)) {
-                base = src.substring(0, src.lastIndexOf('PlotKit.js'));
-                baseElem = scripts[i];
-            }
-
-        }
-
-        if (base === null) {
-            return;
-        }
-        var modules = PlotKit.PlotKit.SUBMODULES;
-        for (var i = 0; i < modules.length; i++) {
-            if (PlotKit[modules[i]]) {
-                continue;
-            }
-            var uri = base + modules[i] + '.js';
-            if (uri in allScripts) {
-                continue;
-            }
-            if (document.documentElement &&
-                document.documentElement.namespaceURI == kXULNSURI) {
-                // XUL
-                var s = document.createElementNS(kXULNSURI, 'script');
-                s.setAttribute("id", "PlotKit_" + base + modules[i]);
-                s.setAttribute("src", uri);
-                s.setAttribute("type", "application/x-javascript");
-                baseElem.parentNode.appendChild(s);
-            } else {
-                // HTML
-                /*
-                    DOM can not be used here because Safari does
-                    deferred loading of scripts unless they are
-                    in the document or inserted with document.write
-
-                    This is not XHTML compliant.  If you want XHTML
-                    compliance then you must use the packed version of MochiKit
-                    or include each script individually (basically unroll
-                    these document.write calls into your XHTML source)
-
-                */
-                document.write('<script src="' + uri +
-                    '" type="text/javascript"></script>');
-            }
-        };
-    })();
-}
diff --git a/share/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js b/share/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
new file mode 100644
index 0000000..00c2199
--- /dev/null
+++ b/share/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
@@ -0,0 +1,1592 @@
+/***
+
+    PlotKit.PlotKit 0.9 : PACKED VERSION
+
+    THIS FILE IS AUTOMATICALLY GENERATED.  If creating patches, please
+    diff against the source tree, not this file.
+
+    For more information, <http://www.liquidx.net/plotkit/>.
+    
+    Copyright (c) 2006. Alastair Tse.
+
+***/
+
+try{
+if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.DOM)=="undefined"||typeof (MochiKit.Color)=="undefined"||typeof (MochiKit.Format)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format}";
+}
+MochiKit.Base.update(MochiKit.Color.Color.prototype,{asFillColor:function(){
+return this.lighterColorWithLevel(0.3);
+},asStrokeColor:function(){
+return this.darkerColorWithLevel(0.1);
+},asPointColor:function(){
+return this.lighterColorWithLevel(0.1);
+}});
+if(typeof (PlotKit)=="undefined"){
+PlotKit={};
+}
+PlotKit.NAME="PlotKit";
+PlotKit.VERSION="0.8";
+PlotKit.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.toString=function(){
+return this.__repr__();
+};
+if(typeof (PlotKit.Base)=="undefined"){
+PlotKit.Base={};
+}
+PlotKit.Base.NAME="PlotKit.Base";
+PlotKit.Base.VERSION=PlotKit.VERSION;
+PlotKit.Base.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.Base.toString=function(){
+return this.__repr__();
+};
+PlotKit.Base.usingPrototype=function(){
+try{
+return (typeof (Object.extend)=="function");
+}
+catch(e){
+return false;
+}
+};
+MochiKit.Base.update(PlotKit.Base,{roundInterval:function(_1,_2,_3){
+var _4=MochiKit.Format.roundToFixed;
+var _5=_1/_2;
+return parseFloat(_4(_5,_3));
+},collapse:function(_6){
+var m=MochiKit.Base;
+var _8=new Array();
+for(var i=0;i<_6.length;i++){
+_8=m.concat(_8,_6[i]);
+}
+if(PlotKit.Base.usingPrototype()){
+delete _8.extend;
+delete _8.from;
+delete _8.inspect;
+}
+return _8;
+},uniq:function(_10){
+var m=MochiKit.Base;
+if(!m.isArrayLike(_10)||(_10.length<1)){
+return new Array();
+}
+var _11=new Array();
+var _12=_10[0];
+_11.push(_10[0]);
+for(var i=1;i<_10.length;i++){
+if(m.compare(_10[i],_12)!=0){
+_12=_10[i];
+_11.push(_10[i]);
+}
+}
+return _11;
+},colorScheme:function(){
+var mb=MochiKit.Base;
+var mc=MochiKit.Color;
+var _15=["red","orange","yellow","green","cyan","blue","purple","magenta"];
+var _16=function(_17){
+return mc.Color[_17+"Color"]();
+};
+return mb.map(_16,_15);
+},baseDarkPrimaryColors:function(){
+var _18=MochiKit.Color.Color.fromHexString;
+return [_18("#ad3f40"),_18("#ddac2c"),_18("#dfdd0c"),_18("#5276c4"),_18("#739c5a")];
+},basePrimaryColors:function(){
+var _19=MochiKit.Color.Color.fromHexString;
+return [_19("#d24c4d"),_19("#f2b32f"),_19("#ece90e"),_19("#5d83da"),_19("#78a15d")];
+},baseBlueColors:function(){
+var _20=MochiKit.Color.Color.fromHexString;
+return [_20("#4b6b94"),_20("#5d81b4"),_20("#acbad2")];
+},palette:function(_21,_22,_23,_24){
+var _25=MochiKit.Base.isUndefinedOrNull;
+var _26=new Array();
+if(_25(_24)){
+_24=0.1;
+}
+if(_25(_23)){
+_23=0.4;
+}
+if(_25(_22)){
+_22=-0.2;
+}
+var _27=_22;
+while(_27<=_23){
+_26.push(_27);
+_27+=_24;
+}
+var _28=function(_29,_30){
+return _29.lighterColorWithLevel(_30);
+};
+return MochiKit.Base.map(MochiKit.Base.partial(_28,_21),_26);
+},excanvasSupported:function(){
+if(/MSIE/.test(navigator.userAgent)&&!window.opera){
+return true;
+}
+return false;
+},findPosX:function(obj){
+var _32=0;
+if(obj.offsetParent){
+while(obj.offsetParent){
+_32+=obj.offsetLeft;
+obj=obj.offsetParent;
+}
+}else{
+if(obj.x){
+_32+=obj.x;
+}
+}
+return _32;
+},findPosY:function(obj){
+var _33=0;
+if(obj.offsetParent){
+while(obj.offsetParent){
+_33+=obj.offsetTop;
+obj=obj.offsetParent;
+}
+}else{
+if(obj.y){
+_33+=obj.y;
+}
+}
+return _33;
+},isFuncLike:function(obj){
+return (typeof (obj)=="function");
+}});
+PlotKit.Base.map=function(fn,lst){
+if(PlotKit.Base.usingPrototype()){
+var _36=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_36.push(fn(lst[x]));
+}
+return _36;
+}else{
+return MochiKit.Base.map(fn,lst);
+}
+};
+PlotKit.Base.items=function(lst){
+if(PlotKit.Base.usingPrototype()){
+var _38=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_38.push([x,lst[x]]);
+}
+return _38;
+}else{
+return MochiKit.Base.items(lst);
+}
+};
+PlotKit.Base.keys=function(lst){
+if(PlotKit.Base.usingPrototype()){
+var _39=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_39.push(x);
+}
+return _39;
+}else{
+return MochiKit.Base.keys(lst);
+}
+};
+PlotKit.Base.baseColors=function(){
+var _40=MochiKit.Color.Color.fromHexString;
+return [_40("#476fb2"),_40("#be2c2b"),_40("#85b730"),_40("#734a99"),_40("#26a1c5"),_40("#fb8707"),_40("#000000")];
+};
+PlotKit.Base.officeBaseStyle={"axisLineWidth":2,"axisLabelColor":MochiKit.Color.Color.grayColor(),"axisLineColor":MochiKit.Color.Color.whiteColor(),"padding":{top:5,bottom:10,left:30,right:30}};
+MochiKit.Base.update(PlotKit.Base,{officeBlue:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),"backgroundColor":PlotKit.Base.baseColors()[0].lighterColorWithLevel(0.45)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeRed:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),"backgroundColor":PlotKit.Base.baseColors()[1].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeGreen:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[2]),"backgroundColor":PlotKit.Base.baseColors()[2].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officePurple:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[3]),"backgroundColor":PlotKit.Base.baseColors()[3].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeCyan:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[4]),"backgroundColor":PlotKit.Base.baseColors()[4].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeOrange:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[5]),"backgroundColor":PlotKit.Base.baseColors()[5].lighterColorWithLevel(0.4)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeBlack:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[6],0,0.6),"backgroundColor":PlotKit.Base.baseColors()[6].lighterColorWithLevel(0.9)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+}});
+PlotKit.Base.EXPORT=["baseColors","collapse","colorScheme","findPosX","findPosY","officeBaseStyle","officeBlue","officeRed","officeGreen","officePurple","officeCyan","officeOrange","officeBlack","roundInterval","uniq","isFuncLike","excanvasSupported"];
+PlotKit.Base.EXPORT_OK=[];
+PlotKit.Base.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.Base.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.Base);
+try{
+if(typeof (PlotKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base";
+}
+if(typeof (PlotKit.Layout)=="undefined"){
+PlotKit.Layout={};
+}
+PlotKit.Layout.NAME="PlotKit.Layout";
+PlotKit.Layout.VERSION=PlotKit.VERSION;
+PlotKit.Layout.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.Layout.toString=function(){
+return this.__repr__();
+};
+PlotKit.Layout.valid_styles=["bar","line","pie","point"];
+PlotKit.Layout=function(_42,_43){
+this.options={"barWidthFillFraction":0.75,"barOrientation":"vertical","xOriginIsZero":true,"yOriginIsZero":true,"xAxis":null,"yAxis":null,"xTicks":null,"yTicks":null,"xNumberOfTicks":10,"yNumberOfTicks":5,"xTickPrecision":1,"yTickPrecision":1,"pieRadius":0.4};
+this.style=_42;
+MochiKit.Base.update(this.options,_43?_43:{});
+if(!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)){
+this.minxval=this.options.xAxis[0];
+this.maxxval=this.options.xAxis[1];
+this.xscale=this.maxxval-this.minxval;
+}else{
+this.minxval=0;
+this.maxxval=null;
+this.xscale=null;
+}
+if(!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)){
+this.minyval=this.options.yAxis[0];
+this.maxyval=this.options.yAxis[1];
+this.yscale=this.maxyval-this.minyval;
+}else{
+this.minyval=0;
+this.maxyval=null;
+this.yscale=null;
+}
+this.bars=new Array();
+this.points=new Array();
+this.slices=new Array();
+this.xticks=new Array();
+this.yticks=new Array();
+this.datasets=new Array();
+this.minxdelta=0;
+this.xrange=1;
+this.yrange=1;
+this.hitTestCache={x2maxy:null};
+};
+PlotKit.Layout.prototype.addDataset=function(_44,_45){
+this.datasets[_44]=_45;
+};
+PlotKit.Layout.prototype.removeDataset=function(_46,_47){
+delete this.datasets[_46];
+};
+PlotKit.Layout.prototype.addDatasetFromTable=function(_48,_49,_50,_51){
+var _52=MochiKit.Base.isUndefinedOrNull;
+var _53=MochiKit.DOM.scrapeText;
+var _54=MochiKit.Format.strip;
+if(_52(_50)){
+_50=0;
+}
+if(_52(_51)){
+_51=1;
+}
+var _55=_49.tBodies[0].rows;
+var _56=new Array();
+if(!_52(_55)){
+for(var i=0;i<_55.length;i++){
+_56.push([parseFloat(_54(_53(_55[i].cells[_50]))),parseFloat(_54(_53(_55[i].cells[_51])))]);
+}
+this.addDataset(_48,_56);
+return true;
+}
+return false;
+};
+PlotKit.Layout.prototype.evaluate=function(){
+this._evaluateLimits();
+this._evaluateScales();
+if(this.style=="bar"){
+if(this.options.barOrientation=="horizontal"){
+this._evaluateHorizBarCharts();
+}else{
+this._evaluateBarCharts();
+}
+this._evaluateBarTicks();
+}else{
+if(this.style=="line"){
+this._evaluateLineCharts();
+this._evaluateLineTicks();
+}else{
+if(this.style=="pie"){
+this._evaluatePieCharts();
+this._evaluatePieTicks();
+}
+}
+}
+};
+PlotKit.Layout.prototype.hitTest=function(x,y){
+var f=MochiKit.Format.twoDigitFloat;
+if((this.style=="bar")&&this.bars&&(this.bars.length>0)){
+for(var i=0;i<this.bars.length;i++){
+var bar=this.bars[i];
+if((x>=bar.x)&&(x<=bar.x+bar.w)&&(y>=bar.y)&&(y-bar.y<=bar.h)){
+return bar;
+}
+}
+}else{
+if(this.style=="line"){
+if(this.hitTestCache.x2maxy==null){
+this._regenerateHitTestCache();
+}
+var _60=x/this.xscale;
+var _61=this.hitTestCache.xvalues;
+var _62=null;
+var _63=null;
+for(var i=1;i<_61.length;i++){
+if(_61[i]>_60){
+_62=_61[i-1];
+_63=_61[i];
+break;
+}
+}
+if((_62!=null)){
+var _64=this.hitTestCache.x2maxy[_62];
+var _65=this.hitTestCache.x2maxy[_63];
+var _66=(1-y)/this.yscale;
+var _67=(_65-_64)/(_63-_62);
+var _68=_64+_67*(_60-_62);
+if(_68>=_66){
+var obj={xval:_60,yval:_66,xafter:_63,yafter:_65,xbefore:_62,ybefore:_64,yprojected:_68};
+return obj;
+}
+}
+}else{
+if(this.style=="pie"){
+var _69=Math.sqrt((y-0.5)*(y-0.5)+(x-0.5)*(x-0.5));
+if(_69>this.options.pieRadius){
+return null;
+}
+var _70=Math.atan2(y-0.5,x-0.5)-Math.PI/2;
+for(var i=0;i<this.slices.length;i++){
+var _71=this.slices[i];
+if(_71.startAngle<_70&&_71.endAngle>=_70){
+return _71;
+}
+}
+}
+}
+}
+return null;
+};
+PlotKit.Layout.prototype.rectForX=function(x){
+return null;
+};
+PlotKit.Layout.prototype.angleRangeForX=function(x){
+return null;
+};
+PlotKit.Layout.prototype._evaluateLimits=function(){
+var map=PlotKit.Base.map;
+var _73=PlotKit.Base.items;
+var _74=MochiKit.Base.itemgetter;
+var _75=PlotKit.Base.collapse;
+var _76=MochiKit.Base.listMin;
+var _77=MochiKit.Base.listMax;
+var _78=MochiKit.Base.isUndefinedOrNull;
+var all=_75(map(_74(1),_73(this.datasets)));
+if(_78(this.options.xAxis)){
+if(this.options.xOriginIsZero){
+this.minxval=0;
+}else{
+this.minxval=_76(map(parseFloat,map(_74(0),all)));
+}
+this.maxxval=_77(map(parseFloat,map(_74(0),all)));
+}else{
+this.minxval=this.options.xAxis[0];
+this.maxxval=this.options.xAxis[1];
+this.xscale=this.maxval-this.minxval;
+}
+if(_78(this.options.yAxis)){
+if(this.options.yOriginIsZero){
+this.minyval=0;
+}else{
+this.minyval=_76(map(parseFloat,map(_74(1),all)));
+}
+this.maxyval=_77(map(parseFloat,map(_74(1),all)));
+}else{
+this.minyval=this.options.yAxis[0];
+this.maxyval=this.options.yAxis[1];
+this.yscale=this.maxyval-this.minyval;
+}
+};
+PlotKit.Layout.prototype._evaluateScales=function(){
+var _80=MochiKit.Base.isUndefinedOrNull;
+this.xrange=this.maxxval-this.minxval;
+if(this.xrange==0){
+this.xscale=1;
+}else{
+this.xscale=1/this.xrange;
+}
+this.yrange=this.maxyval-this.minyval;
+if(this.yrange==0){
+this.yscale=1;
+}else{
+this.yscale=1/this.yrange;
+}
+};
+PlotKit.Layout.prototype._uniqueXValues=function(){
+var _81=PlotKit.Base.collapse;
+var map=PlotKit.Base.map;
+var _82=PlotKit.Base.uniq;
+var _83=MochiKit.Base.itemgetter;
+var _84=PlotKit.Base.items;
+var _85=map(parseFloat,map(_83(0),_81(map(_83(1),_84(this.datasets)))));
+_85.sort(MochiKit.Base.compare);
+return _82(_85);
+};
+PlotKit.Layout.prototype._evaluateBarCharts=function(){
+var _86=PlotKit.Base.items;
+var _87=_86(this.datasets).length;
+var _88=10000000;
+var _89=this._uniqueXValues();
+for(var i=1;i<_89.length;i++){
+_88=Math.min(Math.abs(_89[i]-_89[i-1]),_88);
+}
+var _90=0;
+var _91=0;
+var _92=0;
+if(_89.length==1){
+_88=1;
+this.xscale=1;
+this.minxval=_89[0];
+_90=1*this.options.barWidthFillFraction;
+_91=_90/_87;
+_92=(1-this.options.barWidthFillFraction)/2;
+}else{
+this.xscale=(1-_88/this.xrange)/this.xrange;
+_90=_88*this.xscale*this.options.barWidthFillFraction;
+_91=_90/_87;
+_92=_88*this.xscale*(1-this.options.barWidthFillFraction)/2;
+}
+this.minxdelta=_88;
+this.bars=new Array();
+var i=0;
+for(var _93 in this.datasets){
+var _94=this.datasets[_93];
+if(PlotKit.Base.isFuncLike(_94)){
+continue;
+}
+for(var j=0;j<_94.length;j++){
+var _96=_94[j];
+var _97={x:((parseFloat(_96[0])-this.minxval)*this.xscale)+(i*_91)+_92,y:1-((parseFloat(_96[1])-this.minyval)*this.yscale),w:_91,h:((parseFloat(_96[1])-this.minyval)*this.yscale),xval:parseFloat(_96[0]),yval:parseFloat(_96[1]),name:_93};
+if((_97.x>=0)&&(_97.x<=1)&&(_97.y>=0)&&(_97.y<=1)){
+this.bars.push(_97);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluateHorizBarCharts=function(){
+var _98=PlotKit.Base.items;
+var _99=_98(this.datasets).length;
+var _100=10000000;
+var _101=this._uniqueXValues();
+for(var i=1;i<_101.length;i++){
+_100=Math.min(Math.abs(_101[i]-_101[i-1]),_100);
+}
+var _102=0;
+var _103=0;
+var _104=0;
+if(_101.length==1){
+_100=1;
+this.xscale=1;
+this.minxval=_101[0];
+_102=1*this.options.barWidthFillFraction;
+_103=_102/_99;
+_104=(1-this.options.barWidthFillFraction)/2;
+}else{
+this.xscale=(1-_100/this.xrange)/this.xrange;
+_102=_100*this.xscale*this.options.barWidthFillFraction;
+_103=_102/_99;
+_104=_100*this.xscale*(1-this.options.barWidthFillFraction)/2;
+}
+this.minxdelta=_100;
+this.bars=new Array();
+var i=0;
+for(var _105 in this.datasets){
+var _106=this.datasets[_105];
+if(PlotKit.Base.isFuncLike(_106)){
+continue;
+}
+for(var j=0;j<_106.length;j++){
+var item=_106[j];
+var rect={y:((parseFloat(item[0])-this.minxval)*this.xscale)+(i*_103)+_104,x:0,h:_103,w:((parseFloat(item[1])-this.minyval)*this.yscale),xval:parseFloat(item[0]),yval:parseFloat(item[1]),name:_105};
+if(rect.y<=0){
+rect.y=0;
+}
+if(rect.y>=1){
+rect.y=1;
+}
+if((rect.x>=0)&&(rect.x<=1)){
+this.bars.push(rect);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluateLineCharts=function(){
+var _109=PlotKit.Base.items;
+var _110=_109(this.datasets).length;
+this.points=new Array();
+var i=0;
+for(var _111 in this.datasets){
+var _112=this.datasets[_111];
+if(PlotKit.Base.isFuncLike(_112)){
+continue;
+}
+_112.sort(function(a,b){
+return compare(parseFloat(a[0]),parseFloat(b[0]));
+});
+for(var j=0;j<_112.length;j++){
+var item=_112[j];
+var _115={x:((parseFloat(item[0])-this.minxval)*this.xscale),y:1-((parseFloat(item[1])-this.minyval)*this.yscale),xval:parseFloat(item[0]),yval:parseFloat(item[1]),name:_111};
+if(_115.y<=0){
+_115.y=0;
+}
+if(_115.y>=1){
+_115.y=1;
+}
+if((_115.x>=0)&&(_115.x<=1)){
+this.points.push(_115);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluatePieCharts=function(){
+var _116=PlotKit.Base.items;
+var sum=MochiKit.Iter.sum;
+var _118=MochiKit.Base.itemgetter;
+var _119=_116(this.datasets).length;
+var _120=_116(this.datasets)[0][1];
+var _121=sum(map(_118(1),_120));
+this.slices=new Array();
+var _122=0;
+for(var i=0;i<_120.length;i++){
+var _123=_120[i][1]/_121;
+var _124=_122*Math.PI*2;
+var _125=(_122+_123)*Math.PI*2;
+var _126={fraction:_123,xval:_120[i][0],yval:_120[i][1],startAngle:_124,endAngle:_125};
+this.slices.push(_126);
+_122+=_123;
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicksForXAxis=function(){
+var _127=MochiKit.Base.isUndefinedOrNull;
+if(this.options.xTicks){
+this.xticks=new Array();
+var _128=function(tick){
+var _130=tick.label;
+if(_127(_130)){
+_130=tick.v.toString();
+}
+var pos=this.xscale*(tick.v-this.minxval);
+if((pos>=0)&&(pos<=1)){
+this.xticks.push([pos,_130]);
+}
+};
+MochiKit.Iter.forEach(this.options.xTicks,MochiKit.Base.bind(_128,this));
+}else{
+if(this.options.xNumberOfTicks){
+var _132=this._uniqueXValues();
+var _133=this.xrange/this.options.xNumberOfTicks;
+var _134=0;
+this.xticks=new Array();
+for(var i=0;i<=_132.length;i++){
+if((_132[i]-this.minxval)>=(_134*_133)){
+var pos=this.xscale*(_132[i]-this.minxval);
+if((pos>1)||(pos<0)){
+continue;
+}
+this.xticks.push([pos,_132[i]]);
+_134++;
+}
+if(_134>this.options.xNumberOfTicks){
+break;
+}
+}
+}
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicksForYAxis=function(){
+var _135=MochiKit.Base.isUndefinedOrNull;
+if(this.options.yTicks){
+this.yticks=new Array();
+var _136=function(tick){
+var _137=tick.label;
+if(_135(_137)){
+_137=tick.v.toString();
+}
+var pos=1-(this.yscale*(tick.v-this.minyval));
+if((pos>=0)&&(pos<=1)){
+this.yticks.push([pos,_137]);
+}
+};
+MochiKit.Iter.forEach(this.options.yTicks,MochiKit.Base.bind(_136,this));
+}else{
+if(this.options.yNumberOfTicks){
+this.yticks=new Array();
+var _138=PlotKit.Base.roundInterval;
+var prec=this.options.yTickPrecision;
+var _140=_138(this.yrange,this.options.yNumberOfTicks,prec);
+for(var i=0;i<=this.options.yNumberOfTicks;i++){
+var yval=this.minyval+(i*_140);
+var pos=1-((yval-this.minyval)*this.yscale);
+if((pos>1)||(pos<0)){
+continue;
+}
+this.yticks.push([pos,MochiKit.Format.roundToFixed(yval,prec)]);
+}
+}
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicks=function(){
+this._evaluateLineTicksForXAxis();
+this._evaluateLineTicksForYAxis();
+};
+PlotKit.Layout.prototype._evaluateBarTicks=function(){
+this._evaluateLineTicks();
+var _142=function(tick){
+return [tick[0]+(this.minxdelta*this.xscale)/2,tick[1]];
+};
+this.xticks=MochiKit.Base.map(MochiKit.Base.bind(_142,this),this.xticks);
+if(this.options.barOrientation=="horizontal"){
+var _143=this.xticks;
+this.xticks=this.yticks;
+this.yticks=_143;
+var _144=function(tick){
+return [1-tick[0],tick[1]];
+};
+this.xticks=MochiKit.Base.map(_144,this.xticks);
+}
+};
+PlotKit.Layout.prototype._evaluatePieTicks=function(){
+var _145=MochiKit.Base.isUndefinedOrNull;
+var _146=MochiKit.Format.numberFormatter("#%");
+this.xticks=new Array();
+if(this.options.xTicks){
+var _147=new Array();
+for(var i=0;i<this.slices.length;i++){
+_147[this.slices[i].xval]=this.slices[i];
+}
+for(var i=0;i<this.options.xTicks.length;i++){
+var tick=this.options.xTicks[i];
+var _148=_147[tick.v];
+var _149=tick.label;
+if(_148){
+if(_145(_149)){
+_149=tick.v.toString();
+}
+_149+=" ("+_146(_148.fraction)+")";
+this.xticks.push([tick.v,_149]);
+}
+}
+}else{
+for(var i=0;i<this.slices.length;i++){
+var _148=this.slices[i];
+var _149=_148.xval+" ("+_146(_148.fraction)+")";
+this.xticks.push([_148.xval,_149]);
+}
+}
+};
+PlotKit.Layout.prototype._regenerateHitTestCache=function(){
+this.hitTestCache.xvalues=this._uniqueXValues();
+this.hitTestCache.xlookup=new Array();
+this.hitTestCache.x2maxy=new Array();
+var _150=MochiKit.Base.listMax;
+var _151=MochiKit.Base.itemgetter;
+var map=MochiKit.Base.map;
+var _152=keys(this.datasets);
+for(var i=0;i<_152.length;i++){
+var _153=this.datasets[_152[i]];
+for(var j=0;j<_153.length;j++){
+var xval=_153[j][0];
+var yval=_153[j][1];
+if(this.hitTestCache.xlookup[xval]){
+this.hitTestCache.xlookup[xval].push([yval,_152[i]]);
+}else{
+this.hitTestCache.xlookup[xval]=[[yval,_152[i]]];
+}
+}
+}
+for(var x in this.hitTestCache.xlookup){
+var _155=this.hitTestCache.xlookup[x];
+this.hitTestCache.x2maxy[x]=_150(map(_151(0),_155));
+}
+};
+PlotKit.LayoutModule={};
+PlotKit.LayoutModule.Layout=PlotKit.Layout;
+PlotKit.LayoutModule.EXPORT=["Layout"];
+PlotKit.LayoutModule.EXPORT_OK=[];
+PlotKit.LayoutModule.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.LayoutModule.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.LayoutModule);
+try{
+if((typeof (PlotKit.Base)=="undefined")||(typeof (PlotKit.Layout)=="undefined")){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Base,Layout}";
+}
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+PlotKit.CanvasRenderer={};
+}
+PlotKit.CanvasRenderer.NAME="PlotKit.CanvasRenderer";
+PlotKit.CanvasRenderer.VERSION=PlotKit.VERSION;
+PlotKit.CanvasRenderer.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.CanvasRenderer.toString=function(){
+return this.__repr__();
+};
+PlotKit.CanvasRenderer=function(_156,_157,_158){
+if(arguments.length>0){
+this.__init__(_156,_157,_158);
+}
+};
+PlotKit.CanvasRenderer.prototype.__init__=function(_159,_160,_161){
+var _162=MochiKit.Base.isUndefinedOrNull;
+var _163=MochiKit.Color.Color;
+this.options={"drawBackground":true,"backgroundColor":_163.whiteColor(),"padding":{left:30,right:30,top:5,bottom:10},"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),"strokeColor":_163.whiteColor(),"strokeColorTransform":"asStrokeColor","strokeWidth":0.5,"shouldFill":true,"shouldStroke":true,"drawXAxis":true,"drawYAxis":true,"axisLineColor":_163.blackColor(),"axisLineWidth":0.5,"axisTickSize":3,"axisLabelColor":_163.blackColor(),"axisLabelFont":"Arial","axisLabelFontSize":9,"axisLabelWidth":50,"pieRadius":0.4,"enableEvents":true};
+MochiKit.Base.update(this.options,_161?_161:{});
+this.layout=_160;
+this.element=MochiKit.DOM.getElement(_159);
+this.container=this.element.parentNode;
+this.isIE=PlotKit.Base.excanvasSupported();
+if(this.isIE&&!_162(G_vmlCanvasManager)){
+this.IEDelay=0.5;
+this.maxTries=5;
+this.renderDelay=null;
+this.clearDelay=null;
+this.element=G_vmlCanvasManager.initElement(this.element);
+}
+this.height=this.element.height;
+this.width=this.element.width;
+if(_162(this.element)){
+throw "CanvasRenderer() - passed canvas is not found";
+}
+if(!this.isIE&&!(PlotKit.CanvasRenderer.isSupported(this.element))){
+throw "CanvasRenderer() - Canvas is not supported.";
+}
+if(_162(this.container)||(this.container.nodeName.toLowerCase()!="div")){
+throw "CanvasRenderer() - <canvas> needs to be enclosed in <div>";
+}
+this.xlabels=new Array();
+this.ylabels=new Array();
+this.isFirstRender=true;
+this.area={x:this.options.padding.left,y:this.options.padding.top,w:this.width-this.options.padding.left-this.options.padding.right,h:this.height-this.options.padding.top-this.options.padding.bottom};
+MochiKit.DOM.updateNodeAttributes(this.container,{"style":{"position":"relative","width":this.width+"px"}});
+};
+PlotKit.CanvasRenderer.prototype.render=function(){
+if(this.isIE){
+try{
+if(this.renderDelay){
+this.renderDelay.cancel();
+this.renderDelay=null;
+}
+var _164=this.element.getContext("2d");
+}
+catch(e){
+this.isFirstRender=false;
+if(this.maxTries-->0){
+this.renderDelay=MochiKit.Async.wait(this.IEDelay);
+this.renderDelay.addCallback(MochiKit.Base.bind(this.render,this));
+}
+return;
+}
+}
+if(this.options.drawBackground){
+this._renderBackground();
+}
+if(this.layout.style=="bar"){
+this._renderBarChart();
+this._renderBarAxis();
+}else{
+if(this.layout.style=="pie"){
+this._renderPieChart();
+this._renderPieAxis();
+}else{
+if(this.layout.style=="line"){
+this._renderLineChart();
+this._renderLineAxis();
+}
+}
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarChartWrap=function(data,_166){
+var _167=this.element.getContext("2d");
+var _168=this.options.colorScheme.length;
+var _169=this.options.colorScheme;
+var _170=MochiKit.Base.keys(this.layout.datasets);
+var _171=_170.length;
+for(var i=0;i<_171;i++){
+var _172=_170[i];
+var _173=_169[i%_168];
+_167.save();
+_167.fillStyle=_173.toRGBString();
+if(this.options.strokeColor){
+_167.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_167.strokeStyle=_173[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_167.lineWidth=this.options.strokeWidth;
+var _174=function(obj){
+if(obj.name==_172){
+_166(_167,obj);
+}
+};
+MochiKit.Iter.forEach(data,MochiKit.Base.bind(_174,this));
+_167.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarChart=function(){
+var bind=MochiKit.Base.bind;
+var _176=function(_177,bar){
+var x=this.area.w*bar.x+this.area.x;
+var y=this.area.h*bar.y+this.area.y;
+var w=this.area.w*bar.w;
+var h=this.area.h*bar.h;
+if((w<1)||(h<1)){
+return;
+}
+if(this.options.shouldFill){
+_177.fillRect(x,y,w,h);
+}
+if(this.options.shouldStroke){
+_177.strokeRect(x,y,w,h);
+}
+};
+this._renderBarChartWrap(this.layout.bars,bind(_176,this));
+};
+PlotKit.CanvasRenderer.prototype._renderLineChart=function(){
+var _180=this.element.getContext("2d");
+var _181=this.options.colorScheme.length;
+var _182=this.options.colorScheme;
+var _183=MochiKit.Base.keys(this.layout.datasets);
+var _184=_183.length;
+var bind=MochiKit.Base.bind;
+var _185=MochiKit.Base.partial;
+for(var i=0;i<_184;i++){
+var _186=_183[i];
+var _187=_182[i%_181];
+var _188=this.options.strokeColorTransform;
+_180.save();
+_180.fillStyle=_187.toRGBString();
+if(this.options.strokeColor){
+_180.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_180.strokeStyle=_187[_188]().toRGBString();
+}
+}
+_180.lineWidth=this.options.strokeWidth;
+var _189=function(ctx){
+ctx.beginPath();
+ctx.moveTo(this.area.x,this.area.y+this.area.h);
+var _191=function(ctx_,_193){
+if(_193.name==_186){
+ctx_.lineTo(this.area.w*_193.x+this.area.x,this.area.h*_193.y+this.area.y);
+}
+};
+MochiKit.Iter.forEach(this.layout.points,_185(_191,ctx),this);
+ctx.lineTo(this.area.w+this.area.x,this.area.h+this.area.y);
+ctx.lineTo(this.area.x,this.area.y+this.area.h);
+ctx.closePath();
+};
+if(this.options.shouldFill){
+bind(_189,this)(_180);
+_180.fill();
+}
+if(this.options.shouldStroke){
+bind(_189,this)(_180);
+_180.stroke();
+}
+_180.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderPieChart=function(){
+var _194=this.element.getContext("2d");
+var _195=this.options.colorScheme.length;
+var _196=this.layout.slices;
+var _197=this.area.x+this.area.w*0.5;
+var _198=this.area.y+this.area.h*0.5;
+var _199=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+if(this.isIE){
+_197=parseInt(_197);
+_198=parseInt(_198);
+_199=parseInt(_199);
+}
+for(var i=0;i<_196.length;i++){
+var _200=this.options.colorScheme[i%_195];
+_194.save();
+_194.fillStyle=_200.toRGBString();
+var _201=function(){
+_194.beginPath();
+_194.moveTo(_197,_198);
+_194.arc(_197,_198,_199,_196[i].startAngle-Math.PI/2,_196[i].endAngle-Math.PI/2,false);
+_194.lineTo(_197,_198);
+_194.closePath();
+};
+if(Math.abs(_196[i].startAngle-_196[i].endAngle)>0.001){
+if(this.options.shouldFill){
+_201();
+_194.fill();
+}
+if(this.options.shouldStroke){
+_201();
+_194.lineWidth=this.options.strokeWidth;
+if(this.options.strokeColor){
+_194.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_194.strokeStyle=_200[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_194.stroke();
+}
+}
+_194.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarAxis=function(){
+this._renderAxis();
+};
+PlotKit.CanvasRenderer.prototype._renderLineAxis=function(){
+this._renderAxis();
+};
+PlotKit.CanvasRenderer.prototype._renderAxis=function(){
+if(!this.options.drawXAxis&&!this.options.drawYAxis){
+return;
+}
+var _202=this.element.getContext("2d");
+var _203={"style":{"position":"absolute","fontSize":this.options.axisLabelFontSize+"px","zIndex":10,"color":this.options.axisLabelColor.toRGBString(),"width":this.options.axisLabelWidth+"px","overflow":"hidden"}};
+_202.save();
+_202.strokeStyle=this.options.axisLineColor.toRGBString();
+_202.lineWidth=this.options.axisLineWidth;
+if(this.options.drawYAxis){
+if(this.layout.yticks){
+var _204=function(tick){
+if(typeof (tick)=="function"){
+return;
+}
+var x=this.area.x;
+var y=this.area.y+tick[0]*this.area.h;
+_202.beginPath();
+_202.moveTo(x,y);
+_202.lineTo(x-this.options.axisTickSize,y);
+_202.closePath();
+_202.stroke();
+var _205=MochiKit.DOM.DIV(_203,tick[1]);
+_205.style.top=(y-this.options.axisLabelFontSize)+"px";
+_205.style.left=(x-this.options.padding.left-this.options.axisTickSize)+"px";
+_205.style.textAlign="right";
+_205.style.width=(this.options.padding.left-this.options.axisTickSize*2)+"px";
+MochiKit.DOM.appendChildNodes(this.container,_205);
+this.ylabels.push(_205);
+};
+MochiKit.Iter.forEach(this.layout.yticks,MochiKit.Base.bind(_204,this));
+}
+_202.beginPath();
+_202.moveTo(this.area.x,this.area.y);
+_202.lineTo(this.area.x,this.area.y+this.area.h);
+_202.closePath();
+_202.stroke();
+}
+if(this.options.drawXAxis){
+if(this.layout.xticks){
+var _204=function(tick){
+if(typeof (dataset)=="function"){
+return;
+}
+var x=this.area.x+tick[0]*this.area.w;
+var y=this.area.y+this.area.h;
+_202.beginPath();
+_202.moveTo(x,y);
+_202.lineTo(x,y+this.options.axisTickSize);
+_202.closePath();
+_202.stroke();
+var _206=MochiKit.DOM.DIV(_203,tick[1]);
+_206.style.top=(y+this.options.axisTickSize)+"px";
+_206.style.left=(x-this.options.axisLabelWidth/2)+"px";
+_206.style.textAlign="center";
+_206.style.width=this.options.axisLabelWidth+"px";
+MochiKit.DOM.appendChildNodes(this.container,_206);
+this.xlabels.push(_206);
+};
+MochiKit.Iter.forEach(this.layout.xticks,MochiKit.Base.bind(_204,this));
+}
+_202.beginPath();
+_202.moveTo(this.area.x,this.area.y+this.area.h);
+_202.lineTo(this.area.x+this.area.w,this.area.y+this.area.h);
+_202.closePath();
+_202.stroke();
+}
+_202.restore();
+};
+PlotKit.CanvasRenderer.prototype._renderPieAxis=function(){
+if(!this.options.drawXAxis){
+return;
+}
+if(this.layout.xticks){
+var _207=new Array();
+for(var i=0;i<this.layout.slices.length;i++){
+_207[this.layout.slices[i].xval]=this.layout.slices[i];
+}
+var _208=this.area.x+this.area.w*0.5;
+var _209=this.area.y+this.area.h*0.5;
+var _210=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+var _211=this.options.axisLabelWidth;
+for(var i=0;i<this.layout.xticks.length;i++){
+var _212=_207[this.layout.xticks[i][0]];
+if(MochiKit.Base.isUndefinedOrNull(_212)){
+continue;
+}
+var _213=(_212.startAngle+_212.endAngle)/2;
+var _214=_213;
+if(_214>Math.PI*2){
+_214=_214-Math.PI*2;
+}else{
+if(_214<0){
+_214=_214+Math.PI*2;
+}
+}
+var _215=_208+Math.sin(_214)*(_210+10);
+var _216=_209-Math.cos(_214)*(_210+10);
+var _217={"position":"absolute","zIndex":11,"width":_211+"px","fontSize":this.options.axisLabelFontSize+"px","overflow":"hidden","color":this.options.axisLabelColor.toHexString()};
+if(_214<=Math.PI*0.5){
+_217["textAlign"]="left";
+_217["verticalAlign"]="top";
+_217["left"]=_215+"px";
+_217["top"]=(_216-this.options.axisLabelFontSize)+"px";
+}else{
+if((_214>Math.PI*0.5)&&(_214<=Math.PI)){
+_217["textAlign"]="left";
+_217["verticalAlign"]="bottom";
+_217["left"]=_215+"px";
+_217["top"]=_216+"px";
+}else{
+if((_214>Math.PI)&&(_214<=Math.PI*1.5)){
+_217["textAlign"]="right";
+_217["verticalAlign"]="bottom";
+_217["left"]=(_215-_211)+"px";
+_217["top"]=_216+"px";
+}else{
+_217["textAlign"]="right";
+_217["verticalAlign"]="bottom";
+_217["left"]=(_215-_211)+"px";
+_217["top"]=(_216-this.options.axisLabelFontSize)+"px";
+}
+}
+}
+var _218=MochiKit.DOM.DIV({"style":_217},this.layout.xticks[i][1]);
+this.xlabels.push(_218);
+MochiKit.DOM.appendChildNodes(this.container,_218);
+}
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBackground=function(){
+var _219=this.element.getContext("2d");
+_219.save();
+_219.fillStyle=this.options.backgroundColor.toRGBString();
+_219.fillRect(0,0,this.width,this.height);
+_219.restore();
+};
+PlotKit.CanvasRenderer.prototype.clear=function(){
+if(this.isIE){
+try{
+if(this.clearDelay){
+this.clearDelay.cancel();
+this.clearDelay=null;
+}
+var _220=this.element.getContext("2d");
+}
+catch(e){
+this.isFirstRender=false;
+this.clearDelay=MochiKit.Async.wait(this.IEDelay);
+this.clearDelay.addCallback(MochiKit.Base.bind(this.clear,this));
+return;
+}
+}
+var _220=this.element.getContext("2d");
+_220.clearRect(0,0,this.width,this.height);
+MochiKit.Iter.forEach(this.xlabels,MochiKit.DOM.removeElement);
+MochiKit.Iter.forEach(this.ylabels,MochiKit.DOM.removeElement);
+this.xlabels=new Array();
+this.ylabels=new Array();
+};
+PlotKit.CanvasRenderer.prototype._initialiseEvents=function(){
+var _221=MochiKit.Signal.connect;
+var bind=MochiKit.Base.bind;
+MochiKit.Signal.registerSignals(this,["onmouseover","onclick","onmouseout","onmousemove"]);
+_221(this.element,"onclick",bind(this.onclick,this));
+};
+PlotKit.CanvasRenderer.prototype._resolveObject=function(e){
+var x=(e.mouse().page.x-PlotKit.Base.findPosX(this.element)-this.area.x)/this.area.w;
+var y=(e.mouse().page.y-PlotKit.Base.findPosY(this.element)-this.area.y)/this.area.h;
+var _223=this.layout.hitTest(x,y);
+if(_223){
+return _223;
+}
+return null;
+};
+PlotKit.CanvasRenderer.prototype._createEventObject=function(_224,e){
+if(_224==null){
+return null;
+}
+e.chart=_224;
+return e;
+};
+PlotKit.CanvasRenderer.prototype.onclick=function(e){
+var _225=this._resolveObject(e);
+var _226=this._createEventObject(_225,e);
+if(_226!=null){
+MochiKit.Signal.signal(this,"onclick",_226);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmouseover=function(e){
+var _227=this._resolveObject(e);
+var _228=this._createEventObject(_227,e);
+if(_228!=null){
+signal(this,"onmouseover",_228);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmouseout=function(e){
+var _229=this._resolveObject(e);
+var _230=this._createEventObject(_229,e);
+if(_230==null){
+signal(this,"onmouseout",e);
+}else{
+signal(this,"onmouseout",_230);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmousemove=function(e){
+var _231=this._resolveObject(e);
+var _232=this._createEventObject(_231,e);
+if((_231==null)&&(this.event_isinside==null)){
+return;
+}
+if((_231!=null)&&(this.event_isinside==null)){
+signal(this,"onmouseover",_232);
+}
+if((_231==null)&&(this.event_isinside!=null)){
+signal(this,"onmouseout",_232);
+}
+if((_231!=null)&&(this.event_isinside!=null)){
+signal(this,"onmousemove",_232);
+}
+this.event_isinside=_231;
+};
+PlotKit.CanvasRenderer.isSupported=function(_233){
+var _234=null;
+try{
+if(MochiKit.Base.isUndefinedOrNull(_233)){
+_234=MochiKit.DOM.CANVAS({});
+}else{
+_234=MochiKit.DOM.getElement(_233);
+}
+var _235=_234.getContext("2d");
+}
+catch(e){
+var ie=navigator.appVersion.match(/MSIE (\d\.\d)/);
+var _237=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);
+if((!ie)||(ie[1]<6)||(_237)){
+return false;
+}
+return true;
+}
+return true;
+};
+PlotKit.Canvas={};
+PlotKit.Canvas.CanvasRenderer=PlotKit.CanvasRenderer;
+PlotKit.Canvas.EXPORT=["CanvasRenderer"];
+PlotKit.Canvas.EXPORT_OK=["CanvasRenderer"];
+PlotKit.Canvas.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.Canvas.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.Canvas);
+try{
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "SweetCanvas depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, Canvas}";
+}
+if(typeof (PlotKit.SweetCanvasRenderer)=="undefined"){
+PlotKit.SweetCanvasRenderer={};
+}
+PlotKit.SweetCanvasRenderer=function(_238,_239,_240){
+if(arguments.length>0){
+this.__init__(_238,_239,_240);
+}
+};
+PlotKit.SweetCanvasRenderer.NAME="PlotKit.SweetCanvasRenderer";
+PlotKit.SweetCanvasRenderer.VERSION=PlotKit.VERSION;
+PlotKit.SweetCanvasRenderer.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.SweetCanvasRenderer.toString=function(){
+return this.__repr__();
+};
+PlotKit.SweetCanvasRenderer.prototype=new PlotKit.CanvasRenderer();
+PlotKit.SweetCanvasRenderer.prototype.constructor=PlotKit.SweetCanvasRenderer;
+PlotKit.SweetCanvasRenderer.__super__=PlotKit.CanvasRenderer.prototype;
+PlotKit.SweetCanvasRenderer.prototype.__init__=function(el,_242,opts){
+var _244=PlotKit.Base.officeBlue();
+MochiKit.Base.update(_244,opts);
+PlotKit.SweetCanvasRenderer.__super__.__init__.call(this,el,_242,_244);
+};
+PlotKit.SweetCanvasRenderer.prototype._renderBarChart=function(){
+var bind=MochiKit.Base.bind;
+var _245=MochiKit.Color.Color.blackColor().colorWithAlpha(0.1).toRGBString();
+var _246=function(_247,x,y,w,h){
+_247.fillStyle=_245;
+_247.fillRect(x-2,y-2,w+4,h+2);
+_247.fillStyle=_245;
+_247.fillRect(x-1,y-1,w+2,h+1);
+};
+var _248=this.options.colorScheme.length;
+var _249=this.options.colorScheme;
+var _250=PlotKit.Base.keys(this.layout.datasets);
+var _251=_250.length;
+var _252=function(name){
+for(var i=0;i<_251;i++){
+if(name==_250[i]){
+return _249[i%_248];
+}
+}
+return _249[0];
+};
+var _254=function(_255,bar){
+var x=this.area.w*bar.x+this.area.x;
+var y=this.area.h*bar.y+this.area.y;
+var w=this.area.w*bar.w;
+var h=this.area.h*bar.h;
+if((w<1)||(h<1)){
+return;
+}
+_255.save();
+_255.shadowBlur=5;
+_255.shadowColor=MochiKit.Color.Color.fromHexString("#888888").toRGBString();
+if(this.isIE){
+_255.save();
+_255.fillStyle="#cccccc";
+_255.fillRect(x-2,y-2,w+4,h+2);
+_255.restore();
+}else{
+_246(_255,x,y,w,h);
+}
+if(this.options.shouldFill){
+_255.fillStyle=_252(bar.name).toRGBString();
+_255.fillRect(x,y,w,h);
+}
+_255.shadowBlur=0;
+_255.strokeStyle=MochiKit.Color.Color.whiteColor().toRGBString();
+_255.lineWidth=2;
+if(this.options.shouldStroke){
+_255.strokeRect(x,y,w,h);
+}
+_255.restore();
+};
+this._renderBarChartWrap(this.layout.bars,MochiKit.Base.bind(_254,this));
+};
+PlotKit.SweetCanvasRenderer.prototype._renderLineChart=function(){
+var _256=this.element.getContext("2d");
+var _257=this.options.colorScheme.length;
+var _258=this.options.colorScheme;
+var _259=PlotKit.Base.keys(this.layout.datasets);
+var _260=_259.length;
+var bind=MochiKit.Base.bind;
+for(var i=0;i<_260;i++){
+var _261=_259[i];
+var _262=_258[i%_257];
+var _263=this.options.strokeColorTransform;
+_256.save();
+var _264=function(ctx){
+ctx.beginPath();
+ctx.moveTo(this.area.x,this.area.y+this.area.h);
+var _265=function(ctx_,_266){
+if(_266.name==_261){
+ctx_.lineTo(this.area.w*_266.x+this.area.x,this.area.h*_266.y+this.area.y);
+}
+};
+MochiKit.Iter.forEach(this.layout.points,MochiKit.Base.partial(_265,ctx),this);
+ctx.lineTo(this.area.w+this.area.x,this.area.h+this.area.y);
+ctx.lineTo(this.area.x,this.area.y+this.area.h);
+ctx.closePath();
+};
+if(this.options.shouldFill){
+_256.save();
+if(this.isIE){
+_256.fillStyle="#cccccc";
+}else{
+_256.fillStyle=MochiKit.Color.Color.blackColor().colorWithAlpha(0.2).toRGBString();
+}
+_256.translate(-1,-2);
+bind(_264,this)(_256);
+if(this.options.shouldFill){
+_256.fill();
+}
+_256.restore();
+}
+_256.shadowBlur=5;
+_256.shadowColor=MochiKit.Color.Color.fromHexString("#888888").toRGBString();
+_256.fillStyle=_262.toRGBString();
+_256.lineWidth=2;
+_256.strokeStyle=MochiKit.Color.Color.whiteColor().toRGBString();
+if(this.options.shouldFill){
+bind(_264,this)(_256);
+_256.fill();
+}
+if(this.options.shouldStroke){
+bind(_264,this)(_256);
+_256.stroke();
+}
+_256.restore();
+}
+};
+PlotKit.SweetCanvasRenderer.prototype._renderPieChart=function(){
+var _267=this.element.getContext("2d");
+var _268=this.options.colorScheme.length;
+var _269=this.layout.slices;
+var _270=this.area.x+this.area.w*0.5;
+var _271=this.area.y+this.area.h*0.5;
+var _272=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+if(this.isIE){
+_270=parseInt(_270);
+_271=parseInt(_271);
+_272=parseInt(_272);
+}
+if(!this.isIE){
+_267.save();
+var _273=MochiKit.Color.Color.blackColor().colorWithAlpha(0.2);
+_267.fillStyle=_273.toRGBString();
+_267.shadowBlur=5;
+_267.shadowColor=MochiKit.Color.Color.fromHexString("#888888").toRGBString();
+_267.translate(1,1);
+_267.beginPath();
+_267.moveTo(_270,_271);
+_267.arc(_270,_271,_272+2,0,Math.PI*2,false);
+_267.closePath();
+_267.fill();
+_267.restore();
+}
+_267.save();
+_267.strokeStyle=MochiKit.Color.Color.whiteColor().toRGBString();
+_267.lineWidth=2;
+for(var i=0;i<_269.length;i++){
+var _274=this.options.colorScheme[i%_268];
+_267.fillStyle=_274.toRGBString();
+var _275=function(){
+_267.beginPath();
+_267.moveTo(_270,_271);
+_267.arc(_270,_271,_272,_269[i].startAngle-Math.PI/2,_269[i].endAngle-Math.PI/2,false);
+_267.lineTo(_270,_271);
+_267.closePath();
+};
+if(Math.abs(_269[i].startAngle-_269[i].endAngle)>0.0001){
+if(this.options.shouldFill){
+_275();
+_267.fill();
+}
+if(this.options.shouldStroke){
+_275();
+_267.stroke();
+}
+}
+}
+_267.restore();
+};
+PlotKit.SweetCanvasRenderer.prototype._renderBackground=function(){
+var _276=this.element.getContext("2d");
+if(this.layout.style=="bar"||this.layout.style=="line"){
+_276.save();
+_276.fillStyle=this.options.backgroundColor.toRGBString();
+_276.fillRect(this.area.x,this.area.y,this.area.w,this.area.h);
+_276.strokeStyle=this.options.axisLineColor.toRGBString();
+_276.lineWidth=1;
+var _277=this.layout.yticks;
+var _278=false;
+if(this.layout.style=="bar"&&this.layout.options.barOrientation=="horizontal"){
+_277=this.layout.xticks;
+_278=true;
+}
+for(var i=0;i<_277.length;i++){
+var x1=0;
+var y1=0;
+var x2=0;
+var y2=0;
+if(_278){
+x1=_277[i][0]*this.area.w+this.area.x;
+y1=this.area.y;
+x2=x1;
+y2=y1+this.area.h;
+}else{
+x1=this.area.x;
+y1=_277[i][0]*this.area.h+this.area.y;
+x2=x1+this.area.w;
+y2=y1;
+}
+_276.beginPath();
+_276.moveTo(x1,y1);
+_276.lineTo(x2,y2);
+_276.closePath();
+_276.stroke();
+}
+_276.restore();
+}else{
+PlotKit.SweetCanvasRenderer.__super__._renderBackground.call(this);
+}
+};
+PlotKit.SweetCanvas={};
+PlotKit.SweetCanvas.SweetCanvasRenderer=PlotKit.SweetCanvasRenderer;
+PlotKit.SweetCanvas.EXPORT=["SweetCanvasRenderer"];
+PlotKit.SweetCanvas.EXPORT_OK=["SweetCanvasRenderer"];
+PlotKit.SweetCanvas.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.SweetCanvas.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.SweetCanvas);
+try{
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.EasyPlot depends on all of PlotKit's components";
+}
+if(typeof (PlotKit.EasyPlot)=="undefined"){
+PlotKit.EasyPlot={};
+}
+PlotKit.EasyPlot.NAME="PlotKit.EasyPlot";
+PlotKit.EasyPlot.VERSION=PlotKit.VERSION;
+PlotKit.EasyPlot.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.EasyPlot.toString=function(){
+return this.__repr__();
+};
+PlotKit.EasyPlot=function(_283,_284,_285,_286){
+this.layout=new PlotKit.Layout(_283,_284);
+this.divElem=_285;
+this.width=parseInt(_285.getAttribute("width"));
+this.height=parseInt(_285.getAttribute("height"));
+this.deferredCount=0;
+if(this.width<1){
+this.width=this.divElem.width?this.divElem.width:300;
+}
+if(this.height<1){
+this.height=this.divElem.height?this.divElem.height:300;
+}
+if(MochiKit.Base.isArrayLike(_286)){
+for(var i=0;i<_286.length;i++){
+if(typeof (_286[i])=="string"){
+this.deferredCount++;
+var d=MochiKit.Async.doSimpleXMLHttpRequest(_286[i]);
+d.addCallback(MochiKit.Base.bind(PlotKit.EasyPlot.onDataLoaded,this));
+}else{
+if(MochiKit.Base.isArrayLike(_286[i])){
+this.layout.addDataset("data-"+i,_286[i]);
+}
+}
+}
+}else{
+if(!MochiKit.Base.isUndefinedOrNull(_286)){
+throw "Passed datasources are not Array like";
+}
+}
+if(PlotKit.CanvasRenderer.isSupported()){
+this.element=MochiKit.DOM.CANVAS({"id":this.divElem.getAttribute("id")+"-canvas","width":this.width,"height":this.height},"");
+this.divElem.appendChild(this.element);
+this.renderer=new PlotKit.SweetCanvasRenderer(this.element,this.layout,_284);
+}else{
+if(PlotKit.SVGRenderer.isSupported()){
+this.element=PlotKit.SVGRenderer.SVG({"id":this.divElem.getAttribute("id")+"-svg","width":this.width,"height":this.height,"version":"1.1","baseProfile":"full"},"");
+this.divElem.appendChild(this.element);
+this.renderer=new PlotKit.SweetSVGRenderer(this.element,this.layout,_284);
+}
+}
+if((this.deferredCount==0)&&(PlotKit.Base.keys(this.layout.datasets).length>0)){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+}
+};
+PlotKit.EasyPlot.onDataLoaded=function(_288){
+var _289=new Array();
+var _290=_288.responseText.split("\n");
+for(var i=0;i<_290.length;i++){
+var _291=MochiKit.Format.strip(_290[i]);
+if((_291.length>1)&&(_291.charAt(0)!="#")){
+_289.push(_291.split(","));
+}
+}
+this.layout.addDataset("data-ajax-"+this.deferredCount,_289);
+this.deferredCount--;
+if((this.deferredCount==0)&&(PlotKit.Base.keys(this.layout.datasets).length>0)){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+}
+};
+PlotKit.EasyPlot.prototype.reload=function(){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+};
+PlotKit.EasyPlotModule={};
+PlotKit.EasyPlotModule.EasyPlot=PlotKit.EasyPlot;
+PlotKit.EasyPlotModule.EXPORT=["EasyPlot"];
+PlotKit.EasyPlotModule.EXPORT_OK=[];
+PlotKit.EasyPlotModule.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.EasyPlotModule.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.EasyPlotModule);
+
+
diff --git a/share/web/static/js/PlotKit/PlotKit_Packed.js b/share/web/static/js/PlotKit/PlotKit_Packed.js
deleted file mode 100644
index 363042a..0000000
--- a/share/web/static/js/PlotKit/PlotKit_Packed.js
+++ /dev/null
@@ -1,2177 +0,0 @@
-/***
-
-    PlotKit.PlotKit 0.9.1 : PACKED VERSION
-
-    THIS FILE IS AUTOMATICALLY GENERATED.  If creating patches, please
-    diff against the source tree, not this file.
-
-    For more information, <http://www.liquidx.net/plotkit/>.
-    
-    Copyright (c) 2006. Alastair Tse.
-
-***/
-
-try{
-if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.DOM)=="undefined"||typeof (MochiKit.Color)=="undefined"||typeof (MochiKit.Format)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format}";
-}
-MochiKit.Base.update(MochiKit.Color.Color.prototype,{asFillColor:function(){
-return this.lighterColorWithLevel(0.3);
-},asStrokeColor:function(){
-return this.darkerColorWithLevel(0.1);
-},asPointColor:function(){
-return this.lighterColorWithLevel(0.1);
-}});
-if(typeof (PlotKit)=="undefined"){
-PlotKit={};
-}
-PlotKit.NAME="PlotKit";
-PlotKit.VERSION="0.8";
-PlotKit.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-PlotKit.toString=function(){
-return this.__repr__();
-};
-if(typeof (PlotKit.Base)=="undefined"){
-PlotKit.Base={};
-}
-PlotKit.Base.NAME="PlotKit.Base";
-PlotKit.Base.VERSION=PlotKit.VERSION;
-PlotKit.Base.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-PlotKit.Base.toString=function(){
-return this.__repr__();
-};
-PlotKit.Base.usingPrototype=function(){
-try{
-return (typeof (Object.extend)=="function");
-}
-catch(e){
-return false;
-}
-};
-MochiKit.Base.update(PlotKit.Base,{roundInterval:function(_1,_2,_3){
-var _4=MochiKit.Format.roundToFixed;
-var _5=_1/_2;
-return parseFloat(_4(_5,_3));
-},collapse:function(_6){
-var m=MochiKit.Base;
-var _8=new Array();
-for(var i=0;i<_6.length;i++){
-_8=m.concat(_8,_6[i]);
-}
-if(PlotKit.Base.usingPrototype()){
-delete _8.extend;
-delete _8.from;
-delete _8.inspect;
-}
-return _8;
-},uniq:function(_10){
-var m=MochiKit.Base;
-if(!m.isArrayLike(_10)||(_10.length<1)){
-return new Array();
-}
-var _11=new Array();
-var _12=_10[0];
-_11.push(_10[0]);
-for(var i=1;i<_10.length;i++){
-if(m.compare(_10[i],_12)!=0){
-_12=_10[i];
-_11.push(_10[i]);
-}
-}
-return _11;
-},colorScheme:function(){
-var mb=MochiKit.Base;
-var mc=MochiKit.Color;
-var _15=["red","orange","yellow","green","cyan","blue","purple","magenta"];
-var _16=function(_17){
-return mc.Color[_17+"Color"]();
-};
-return mb.map(_16,_15);
-},baseDarkPrimaryColors:function(){
-var _18=MochiKit.Color.Color.fromHexString;
-return [_18("#ad3f40"),_18("#ddac2c"),_18("#dfdd0c"),_18("#5276c4"),_18("#739c5a")];
-},basePrimaryColors:function(){
-var _19=MochiKit.Color.Color.fromHexString;
-return [_19("#d24c4d"),_19("#f2b32f"),_19("#ece90e"),_19("#5d83da"),_19("#78a15d")];
-},baseBlueColors:function(){
-var _20=MochiKit.Color.Color.fromHexString;
-return [_20("#4b6b94"),_20("#5d81b4"),_20("#acbad2")];
-},palette:function(_21,_22,_23,_24){
-var _25=MochiKit.Base.isUndefinedOrNull;
-var _26=new Array();
-if(_25(_24)){
-_24=0.1;
-}
-if(_25(_23)){
-_23=0.4;
-}
-if(_25(_22)){
-_22=-0.2;
-}
-var _27=_22;
-while(_27<=_23){
-_26.push(_27);
-_27+=_24;
-}
-var _28=function(_29,_30){
-return _29.lighterColorWithLevel(_30);
-};
-return MochiKit.Base.map(partial(_28,_21),_26);
-},excanvasSupported:function(){
-if(/MSIE/.test(navigator.userAgent)&&!window.opera){
-return true;
-}
-return false;
-},findPosX:function(obj){
-var _32=0;
-if(obj.offsetParent){
-while(obj.offsetParent){
-_32+=obj.offsetLeft;
-obj=obj.offsetParent;
-}
-}else{
-if(obj.x){
-_32+=obj.x;
-}
-}
-return _32;
-},findPosY:function(obj){
-var _33=0;
-if(obj.offsetParent){
-while(obj.offsetParent){
-_33+=obj.offsetTop;
-obj=obj.offsetParent;
-}
-}else{
-if(obj.y){
-_33+=obj.y;
-}
-}
-return _33;
-},isFuncLike:function(obj){
-return (typeof (obj)=="function");
-}});
-PlotKit.Base.map=function(fn,lst){
-if(PlotKit.Base.usingPrototype()){
-var _36=[];
-for(var x in lst){
-if(typeof (lst[x])=="function"){
-continue;
-}
-_36.push(fn(lst[x]));
-}
-return _36;
-}else{
-return MochiKit.Base.map(fn,lst);
-}
-};
-PlotKit.Base.items=function(lst){
-if(PlotKit.Base.usingPrototype()){
-var _38=[];
-for(var x in lst){
-if(typeof (lst[x])=="function"){
-continue;
-}
-_38.push([x,lst[x]]);
-}
-return _38;
-}else{
-return MochiKit.Base.items(lst);
-}
-};
-PlotKit.Base.keys=function(lst){
-if(PlotKit.Base.usingPrototype()){
-var _39=[];
-for(var x in lst){
-if(typeof (lst[x])=="function"){
-continue;
-}
-_39.push(x);
-}
-return _39;
-}else{
-return MochiKit.Base.keys(lst);
-}
-};
-PlotKit.Base.baseColors=function(){
-var _40=MochiKit.Color.Color.fromHexString;
-return [_40("#476fb2"),_40("#be2c2b"),_40("#85b730"),_40("#734a99"),_40("#26a1c5"),_40("#fb8707"),_40("#000000")];
-};
-PlotKit.Base.officeBaseStyle={"axisLineWidth":2,"axisLabelColor":Color.grayColor(),"axisLineColor":Color.whiteColor(),"padding":{top:5,bottom:10,left:30,right:30}};
-MochiKit.Base.update(PlotKit.Base,{officeBlue:function(){
-var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),"backgroundColor":PlotKit.Base.baseColors()[0].lighterColorWithLevel(0.45)};
-MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
-return r;
-},officeRed:function(){
-var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),"backgroundColor":PlotKit.Base.baseColors()[1].lighterColorWithLevel(0.5)};
-MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
-return r;
-},officeGreen:function(){
-var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[2]),"backgroundColor":PlotKit.Base.baseColors()[2].lighterColorWithLevel(0.5)};
-MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
-return r;
-},officePurple:function(){
-var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[3]),"backgroundColor":PlotKit.Base.baseColors()[3].lighterColorWithLevel(0.5)};
-MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
-return r;
-},officeCyan:function(){
-var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[4]),"backgroundColor":PlotKit.Base.baseColors()[4].lighterColorWithLevel(0.5)};
-MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
-return r;
-},officeOrange:function(){
-var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[5]),"backgroundColor":PlotKit.Base.baseColors()[5].lighterColorWithLevel(0.4)};
-MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
-return r;
-},officeBlack:function(){
-var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[6],0,0.6),"backgroundColor":PlotKit.Base.baseColors()[6].lighterColorWithLevel(0.9)};
-MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
-return r;
-}});
-PlotKit.Base.EXPORT=["baseColors","collapse","colorScheme","findPosX","findPosY","officeBaseStyle","officeBlue","officeRed","officeGreen","officePurple","officeCyan","officeOrange","officeBlack","roundInterval","uniq","isFuncLike","excanvasSupported"];
-PlotKit.Base.EXPORT_OK=[];
-PlotKit.Base.__new__=function(){
-var m=MochiKit.Base;
-m.nameFunctions(this);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-};
-PlotKit.Base.__new__();
-MochiKit.Base._exportSymbols(this,PlotKit.Base);
-try{
-if(typeof (PlotKit.Base)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base";
-}
-if(typeof (PlotKit.Layout)=="undefined"){
-PlotKit.Layout={};
-}
-PlotKit.Layout.NAME="PlotKit.Layout";
-PlotKit.Layout.VERSION=PlotKit.VERSION;
-PlotKit.Layout.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-PlotKit.Layout.toString=function(){
-return this.__repr__();
-};
-PlotKit.Layout.valid_styles=["bar","line","pie","point"];
-PlotKit.Layout=function(_42,_43){
-this.options={"barWidthFillFraction":0.75,"barOrientation":"vertical","xOriginIsZero":true,"yOriginIsZero":true,"xAxis":null,"yAxis":null,"xTicks":null,"yTicks":null,"xNumberOfTicks":10,"yNumberOfTicks":5,"xTickPrecision":1,"yTickPrecision":1,"pieRadius":0.4};
-this.style=_42;
-MochiKit.Base.update(this.options,_43?_43:{});
-if(!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)){
-this.minxval=this.options.xAxis[0];
-this.maxxval=this.options.xAxis[1];
-this.xscale=this.maxxval-this.minxval;
-}else{
-this.minxval=0;
-this.maxxval=null;
-this.xscale=null;
-}
-if(!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)){
-this.minyval=this.options.yAxis[0];
-this.maxyval=this.options.yAxis[1];
-this.yscale=this.maxyval-this.minyval;
-}else{
-this.minyval=0;
-this.maxyval=null;
-this.yscale=null;
-}
-this.bars=new Array();
-this.points=new Array();
-this.slices=new Array();
-this.xticks=new Array();
-this.yticks=new Array();
-this.datasets=new Array();
-this.minxdelta=0;
-this.xrange=1;
-this.yrange=1;
-this.hitTestCache={x2maxy:null};
-};
-PlotKit.Layout.prototype.addDataset=function(_44,_45){
-this.datasets[_44]=_45;
-};
-PlotKit.Layout.prototype.removeDataset=function(_46,_47){
-delete this.datasets[_46];
-};
-PlotKit.Layout.prototype.addDatasetFromTable=function(_48,_49,_50,_51,_52){
-var _53=MochiKit.Base.isUndefinedOrNull;
-var _54=MochiKit.DOM.scrapeText;
-var _55=MochiKit.Format.strip;
-if(_53(_50)){
-_50=0;
-}
-if(_53(_51)){
-_51=1;
-}
-if(_53(_52)){
-_52=-1;
-}
-var _56=_49.tBodies[0].rows;
-var _57=new Array();
-var _58=new Array();
-if(!_53(_56)){
-for(var i=0;i<_56.length;i++){
-_57.push([parseFloat(_55(_54(_56[i].cells[_50]))),parseFloat(_55(_54(_56[i].cells[_51])))]);
-if(_52>=0){
-_58.push({v:parseFloat(_55(_54(_56[i].cells[_50]))),label:_55(_54(_56[i].cells[_52]))});
-}
-}
-this.addDataset(_48,_57);
-if(_52>=0){
-this.options.xTicks=_58;
-}
-return true;
-}
-return false;
-};
-PlotKit.Layout.prototype.evaluate=function(){
-this._evaluateLimits();
-this._evaluateScales();
-if(this.style=="bar"){
-if(this.options.barOrientation=="horizontal"){
-this._evaluateHorizBarCharts();
-}else{
-this._evaluateBarCharts();
-}
-this._evaluateBarTicks();
-}else{
-if(this.style=="line"){
-this._evaluateLineCharts();
-this._evaluateLineTicks();
-}else{
-if(this.style=="pie"){
-this._evaluatePieCharts();
-this._evaluatePieTicks();
-}
-}
-}
-};
-PlotKit.Layout.prototype.hitTest=function(x,y){
-var f=MochiKit.Format.twoDigitFloat;
-if((this.style=="bar")&&this.bars&&(this.bars.length>0)){
-for(var i=0;i<this.bars.length;i++){
-var bar=this.bars[i];
-if((x>=bar.x)&&(x<=bar.x+bar.w)&&(y>=bar.y)&&(y-bar.y<=bar.h)){
-return bar;
-}
-}
-}else{
-if(this.style=="line"){
-if(this.hitTestCache.x2maxy==null){
-this._regenerateHitTestCache();
-}
-var _62=x/this.xscale;
-var _63=this.hitTestCache.xvalues;
-var _64=null;
-var _65=null;
-for(var i=1;i<_63.length;i++){
-if(_63[i]>_62){
-_64=_63[i-1];
-_65=_63[i];
-break;
-}
-}
-if((_64!=null)){
-var _66=this.hitTestCache.x2maxy[_64];
-var _67=this.hitTestCache.x2maxy[_65];
-var _68=(1-y)/this.yscale;
-var _69=(_67-_66)/(_65-_64);
-var _70=_66+_69*(_62-_64);
-if(_70>=_68){
-var obj={xval:_62,yval:_68,xafter:_65,yafter:_67,xbefore:_64,ybefore:_66,yprojected:_70};
-return obj;
-}
-}
-}else{
-if(this.style=="pie"){
-var _71=Math.sqrt((y-0.5)*(y-0.5)+(x-0.5)*(x-0.5));
-if(_71>this.options.pieRadius){
-return null;
-}
-var _72=Math.atan2(y-0.5,x-0.5)-Math.PI/2;
-for(var i=0;i<this.slices.length;i++){
-var _73=this.slices[i];
-if(_73.startAngle<_72&&_73.endAngle>=_72){
-return _73;
-}
-}
-}
-}
-}
-return null;
-};
-PlotKit.Layout.prototype.rectForX=function(x){
-return null;
-};
-PlotKit.Layout.prototype.angleRangeForX=function(x){
-return null;
-};
-PlotKit.Layout.prototype._evaluateLimits=function(){
-var map=PlotKit.Base.map;
-var _75=PlotKit.Base.items;
-var _76=MochiKit.Base.itemgetter;
-var _77=PlotKit.Base.collapse;
-var _78=MochiKit.Base.listMin;
-var _79=MochiKit.Base.listMax;
-var _80=MochiKit.Base.isUndefinedOrNull;
-var all=_77(map(_76(1),_75(this.datasets)));
-if(_80(this.options.xAxis)){
-if(this.options.xOriginIsZero){
-this.minxval=0;
-}else{
-this.minxval=_78(map(parseFloat,map(_76(0),all)));
-}
-this.maxxval=_79(map(parseFloat,map(_76(0),all)));
-}else{
-this.minxval=this.options.xAxis[0];
-this.maxxval=this.options.xAxis[1];
-this.xscale=this.maxval-this.minxval;
-}
-if(_80(this.options.yAxis)){
-if(this.options.yOriginIsZero){
-this.minyval=0;
-}else{
-this.minyval=_78(map(parseFloat,map(_76(1),all)));
-}
-this.maxyval=_79(map(parseFloat,map(_76(1),all)));
-}else{
-this.minyval=this.options.yAxis[0];
-this.maxyval=this.options.yAxis[1];
-this.yscale=this.maxyval-this.minyval;
-}
-};
-PlotKit.Layout.prototype._evaluateScales=function(){
-var _82=MochiKit.Base.isUndefinedOrNull;
-this.xrange=this.maxxval-this.minxval;
-if(this.xrange==0){
-this.xscale=1;
-}else{
-this.xscale=1/this.xrange;
-}
-this.yrange=this.maxyval-this.minyval;
-if(this.yrange==0){
-this.yscale=1;
-}else{
-this.yscale=1/this.yrange;
-}
-};
-PlotKit.Layout.prototype._uniqueXValues=function(){
-var _83=PlotKit.Base.collapse;
-var map=PlotKit.Base.map;
-var _84=PlotKit.Base.uniq;
-var _85=MochiKit.Base.itemgetter;
-var _86=PlotKit.Base.items;
-var _87=map(parseFloat,map(_85(0),_83(map(_85(1),_86(this.datasets)))));
-_87.sort(MochiKit.Base.compare);
-return _84(_87);
-};
-PlotKit.Layout.prototype._evaluateBarCharts=function(){
-var _88=PlotKit.Base.items;
-var _89=_88(this.datasets).length;
-var _90=10000000;
-var _91=this._uniqueXValues();
-for(var i=1;i<_91.length;i++){
-_90=Math.min(Math.abs(_91[i]-_91[i-1]),_90);
-}
-var _92=0;
-var _93=0;
-var _94=0;
-if(_91.length==1){
-_90=1;
-this.xscale=1;
-this.minxval=_91[0];
-_92=1*this.options.barWidthFillFraction;
-_93=_92/_89;
-_94=(1-this.options.barWidthFillFraction)/2;
-}else{
-if(this.xrange==1){
-this.xscale=0.5;
-}else{
-if(this.xrange==2){
-this.xscale=1/3;
-}else{
-this.xscale=(1-_90/this.xrange)/this.xrange;
-}
-}
-_92=_90*this.xscale*this.options.barWidthFillFraction;
-_93=_92/_89;
-_94=_90*this.xscale*(1-this.options.barWidthFillFraction)/2;
-}
-this.minxdelta=_90;
-this.bars=new Array();
-var i=0;
-for(var _95 in this.datasets){
-var _96=this.datasets[_95];
-if(PlotKit.Base.isFuncLike(_96)){
-continue;
-}
-for(var j=0;j<_96.length;j++){
-var _98=_96[j];
-var _99={x:((parseFloat(_98[0])-this.minxval)*this.xscale)+(i*_93)+_94,y:1-((parseFloat(_98[1])-this.minyval)*this.yscale),w:_93,h:((parseFloat(_98[1])-this.minyval)*this.yscale),xval:parseFloat(_98[0]),yval:parseFloat(_98[1]),name:_95};
-if((_99.x>=0)&&(_99.x<=1)&&(_99.y>=0)&&(_99.y<=1)){
-this.bars.push(_99);
-}
-}
-i++;
-}
-};
-PlotKit.Layout.prototype._evaluateHorizBarCharts=function(){
-var _100=PlotKit.Base.items;
-var _101=_100(this.datasets).length;
-var _102=10000000;
-var _103=this._uniqueXValues();
-for(var i=1;i<_103.length;i++){
-_102=Math.min(Math.abs(_103[i]-_103[i-1]),_102);
-}
-var _104=0;
-var _105=0;
-var _106=0;
-if(_103.length==1){
-_102=1;
-this.xscale=1;
-this.minxval=_103[0];
-_104=1*this.options.barWidthFillFraction;
-_105=_104/_101;
-_106=(1-this.options.barWidthFillFraction)/2;
-}else{
-this.xscale=(1-_102/this.xrange)/this.xrange;
-_104=_102*this.xscale*this.options.barWidthFillFraction;
-_105=_104/_101;
-_106=_102*this.xscale*(1-this.options.barWidthFillFraction)/2;
-}
-this.minxdelta=_102;
-this.bars=new Array();
-var i=0;
-for(var _107 in this.datasets){
-var _108=this.datasets[_107];
-if(PlotKit.Base.isFuncLike(_108)){
-continue;
-}
-for(var j=0;j<_108.length;j++){
-var item=_108[j];
-var rect={y:((parseFloat(item[0])-this.minxval)*this.xscale)+(i*_105)+_106,x:0,h:_105,w:((parseFloat(item[1])-this.minyval)*this.yscale),xval:parseFloat(item[0]),yval:parseFloat(item[1]),name:_107};
-if(rect.y<=0){
-rect.y=0;
-}
-if(rect.y>=1){
-rect.y=1;
-}
-if((rect.x>=0)&&(rect.x<=1)){
-this.bars.push(rect);
-}
-}
-i++;
-}
-};
-PlotKit.Layout.prototype._evaluateLineCharts=function(){
-var _111=PlotKit.Base.items;
-var _112=_111(this.datasets).length;
-this.points=new Array();
-var i=0;
-for(var _113 in this.datasets){
-var _114=this.datasets[_113];
-if(PlotKit.Base.isFuncLike(_114)){
-continue;
-}
-_114.sort(function(a,b){
-return compare(parseFloat(a[0]),parseFloat(b[0]));
-});
-for(var j=0;j<_114.length;j++){
-var item=_114[j];
-var _117={x:((parseFloat(item[0])-this.minxval)*this.xscale),y:1-((parseFloat(item[1])-this.minyval)*this.yscale),xval:parseFloat(item[0]),yval:parseFloat(item[1]),name:_113};
-if(_117.y<=0){
-_117.y=0;
-}
-if(_117.y>=1){
-_117.y=1;
-}
-if((_117.x>=0)&&(_117.x<=1)){
-this.points.push(_117);
-}
-}
-i++;
-}
-};
-PlotKit.Layout.prototype._evaluatePieCharts=function(){
-var _118=PlotKit.Base.items;
-var sum=MochiKit.Iter.sum;
-var _120=MochiKit.Base.itemgetter;
-var _121=_118(this.datasets).length;
-var _122=_118(this.datasets)[0][1];
-var _123=sum(map(_120(1),_122));
-this.slices=new Array();
-var _124=0;
-for(var i=0;i<_122.length;i++){
-var _125=_122[i][1]/_123;
-var _126=_124*Math.PI*2;
-var _127=(_124+_125)*Math.PI*2;
-var _128={fraction:_125,xval:_122[i][0],yval:_122[i][1],startAngle:_126,endAngle:_127};
-if(_122[i][1]!=0){
-this.slices.push(_128);
-}
-_124+=_125;
-}
-};
-PlotKit.Layout.prototype._evaluateLineTicksForXAxis=function(){
-var _129=MochiKit.Base.isUndefinedOrNull;
-if(this.options.xTicks){
-this.xticks=new Array();
-var _130=function(tick){
-var _132=tick.label;
-if(_129(_132)){
-_132=tick.v.toString();
-}
-var pos=this.xscale*(tick.v-this.minxval);
-if((pos>=0)&&(pos<=1)){
-this.xticks.push([pos,_132]);
-}
-};
-MochiKit.Iter.forEach(this.options.xTicks,bind(_130,this));
-}else{
-if(this.options.xNumberOfTicks){
-var _134=this._uniqueXValues();
-var _135=this.xrange/this.options.xNumberOfTicks;
-var _136=0;
-this.xticks=new Array();
-for(var i=0;i<=_134.length;i++){
-if((_134[i]-this.minxval)>=(_136*_135)){
-var pos=this.xscale*(_134[i]-this.minxval);
-if((pos>1)||(pos<0)){
-continue;
-}
-this.xticks.push([pos,_134[i]]);
-_136++;
-}
-if(_136>this.options.xNumberOfTicks){
-break;
-}
-}
-}
-}
-};
-PlotKit.Layout.prototype._evaluateLineTicksForYAxis=function(){
-var _137=MochiKit.Base.isUndefinedOrNull;
-if(this.options.yTicks){
-this.yticks=new Array();
-var _138=function(tick){
-var _139=tick.label;
-if(_137(_139)){
-_139=tick.v.toString();
-}
-var pos=1-(this.yscale*(tick.v-this.minyval));
-if((pos>=0)&&(pos<=1)){
-this.yticks.push([pos,_139]);
-}
-};
-MochiKit.Iter.forEach(this.options.yTicks,bind(_138,this));
-}else{
-if(this.options.yNumberOfTicks){
-this.yticks=new Array();
-var _140=PlotKit.Base.roundInterval;
-var prec=this.options.yTickPrecision;
-var _142=_140(this.yrange,this.options.yNumberOfTicks,prec);
-for(var i=0;i<=this.options.yNumberOfTicks;i++){
-var yval=this.minyval+(i*_142);
-var pos=1-((yval-this.minyval)*this.yscale);
-if((pos>1)||(pos<0)){
-continue;
-}
-this.yticks.push([pos,MochiKit.Format.roundToFixed(yval,prec)]);
-}
-}
-}
-};
-PlotKit.Layout.prototype._evaluateLineTicks=function(){
-this._evaluateLineTicksForXAxis();
-this._evaluateLineTicksForYAxis();
-};
-PlotKit.Layout.prototype._evaluateBarTicks=function(){
-this._evaluateLineTicks();
-var _144=function(tick){
-return [tick[0]+(this.minxdelta*this.xscale)/2,tick[1]];
-};
-this.xticks=MochiKit.Base.map(bind(_144,this),this.xticks);
-if(this.options.barOrientation=="horizontal"){
-var _145=this.xticks;
-this.xticks=this.yticks;
-this.yticks=_145;
-var _146=function(tick){
-return [1-tick[0],tick[1]];
-};
-this.xticks=MochiKit.Base.map(_146,this.xticks);
-}
-};
-PlotKit.Layout.prototype._evaluatePieTicks=function(){
-var _147=MochiKit.Base.isUndefinedOrNull;
-var _148=MochiKit.Format.numberFormatter("#%");
-this.xticks=new Array();
-if(this.options.xTicks){
-var _149=new Array();
-for(var i=0;i<this.slices.length;i++){
-_149[this.slices[i].xval]=this.slices[i];
-}
-for(var i=0;i<this.options.xTicks.length;i++){
-var tick=this.options.xTicks[i];
-var _150=_149[tick.v];
-var _151=tick.label;
-if(_150){
-if(_147(_151)){
-_151=tick.v.toString();
-}
-_151+=" ("+_148(_150.fraction)+")";
-this.xticks.push([tick.v,_151]);
-}
-}
-}else{
-for(var i=0;i<this.slices.length;i++){
-var _150=this.slices[i];
-var _151=_150.xval+" ("+_148(_150.fraction)+")";
-this.xticks.push([_150.xval,_151]);
-}
-}
-};
-PlotKit.Layout.prototype._regenerateHitTestCache=function(){
-this.hitTestCache.xvalues=this._uniqueXValues();
-this.hitTestCache.xlookup=new Array();
-this.hitTestCache.x2maxy=new Array();
-var _152=MochiKit.Base.listMax;
-var _153=MochiKit.Base.itemgetter;
-var map=MochiKit.Base.map;
-var _154=keys(this.datasets);
-for(var i=0;i<_154.length;i++){
-var _155=this.datasets[_154[i]];
-for(var j=0;j<_155.length;j++){
-var xval=_155[j][0];
-var yval=_155[j][1];
-if(this.hitTestCache.xlookup[xval]){
-this.hitTestCache.xlookup[xval].push([yval,_154[i]]);
-}else{
-this.hitTestCache.xlookup[xval]=[[yval,_154[i]]];
-}
-}
-}
-for(var x in this.hitTestCache.xlookup){
-var _157=this.hitTestCache.xlookup[x];
-this.hitTestCache.x2maxy[x]=_152(map(_153(0),_157));
-}
-};
-PlotKit.LayoutModule={};
-PlotKit.LayoutModule.Layout=PlotKit.Layout;
-PlotKit.LayoutModule.EXPORT=["Layout"];
-PlotKit.LayoutModule.EXPORT_OK=[];
-PlotKit.LayoutModule.__new__=function(){
-var m=MochiKit.Base;
-m.nameFunctions(this);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-};
-PlotKit.LayoutModule.__new__();
-MochiKit.Base._exportSymbols(this,PlotKit.LayoutModule);
-try{
-if((typeof (PlotKit.Base)=="undefined")||(typeof (PlotKit.Layout)=="undefined")){
-throw "";
-}
-}
-catch(e){
-throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Base,Layout}";
-}
-if(typeof (PlotKit.CanvasRenderer)=="undefined"){
-PlotKit.CanvasRenderer={};
-}
-PlotKit.CanvasRenderer.NAME="PlotKit.CanvasRenderer";
-PlotKit.CanvasRenderer.VERSION=PlotKit.VERSION;
-PlotKit.CanvasRenderer.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-PlotKit.CanvasRenderer.toString=function(){
-return this.__repr__();
-};
-PlotKit.CanvasRenderer=function(_158,_159,_160){
-if(arguments.length>0){
-this.__init__(_158,_159,_160);
-}
-};
-PlotKit.CanvasRenderer.prototype.__init__=function(_161,_162,_163){
-var _164=MochiKit.Base.isUndefinedOrNull;
-var _165=MochiKit.Color.Color;
-this.options={"drawBackground":true,"backgroundColor":_165.whiteColor(),"padding":{left:30,right:30,top:5,bottom:10},"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),"strokeColor":_165.whiteColor(),"strokeColorTransform":"asStrokeColor","strokeWidth":0.5,"shouldFill":true,"shouldStroke":true,"drawXAxis":true,"drawYAxis":true,"axisLineColor":_165.blackColor(),"axisLineWidth":0.5,"axisTickSize":3,"axisLabelColor":_165.blackColor(),"axisLabelFont":"Arial","axisLabelFontSize":9,"axisLabelWidth":50,"pieRadius":0.4,"enableEvents":true};
-MochiKit.Base.update(this.options,_163?_163:{});
-this.layout=_162;
-this.element=MochiKit.DOM.getElement(_161);
-this.container=this.element.parentNode;
-this.isIE=PlotKit.Base.excanvasSupported();
-if(this.isIE&&!_164(G_vmlCanvasManager)){
-this.IEDelay=0.5;
-this.maxTries=5;
-this.renderDelay=null;
-this.clearDelay=null;
-this.element=G_vmlCanvasManager.initElement(this.element);
-}
-this.height=this.element.height;
-this.width=this.element.width;
-if(_164(this.element)){
-throw "CanvasRenderer() - passed canvas is not found";
-}
-if(!this.isIE&&!(PlotKit.CanvasRenderer.isSupported(this.element))){
-throw "CanvasRenderer() - Canvas is not supported.";
-}
-if(_164(this.container)||(this.container.nodeName.toLowerCase()!="div")){
-throw "CanvasRenderer() - <canvas> needs to be enclosed in <div>";
-}
-this.xlabels=new Array();
-this.ylabels=new Array();
-this.isFirstRender=true;
-this.area={x:this.options.padding.left,y:this.options.padding.top,w:this.width-this.options.padding.left-this.options.padding.right,h:this.height-this.options.padding.top-this.options.padding.bottom};
-MochiKit.DOM.updateNodeAttributes(this.container,{"style":{"position":"relative","width":this.width+"px"}});
-};
-PlotKit.CanvasRenderer.prototype.render=function(){
-if(this.isIE){
-try{
-if(this.renderDelay){
-this.renderDelay.cancel();
-this.renderDelay=null;
-}
-var _166=this.element.getContext("2d");
-}
-catch(e){
-this.isFirstRender=false;
-if(this.maxTries-->0){
-this.renderDelay=MochiKit.Async.wait(this.IEDelay);
-this.renderDelay.addCallback(bind(this.render,this));
-}
-return;
-}
-}
-if(this.options.drawBackground){
-this._renderBackground();
-}
-if(this.layout.style=="bar"){
-this._renderBarChart();
-this._renderBarAxis();
-}else{
-if(this.layout.style=="pie"){
-this._renderPieChart();
-this._renderPieAxis();
-}else{
-if(this.layout.style=="line"){
-this._renderLineChart();
-this._renderLineAxis();
-}
-}
-}
-};
-PlotKit.CanvasRenderer.prototype._renderBarChartWrap=function(data,_168){
-var _169=this.element.getContext("2d");
-var _170=this.options.colorScheme.length;
-var _171=this.options.colorScheme;
-var _172=MochiKit.Base.keys(this.layout.datasets);
-var _173=_172.length;
-for(var i=0;i<_173;i++){
-var _174=_172[i];
-var _175=_171[i%_170];
-_169.save();
-_169.fillStyle=_175.toRGBString();
-if(this.options.strokeColor){
-_169.strokeStyle=this.options.strokeColor.toRGBString();
-}else{
-if(this.options.strokeColorTransform){
-_169.strokeStyle=_175[this.options.strokeColorTransform]().toRGBString();
-}
-}
-_169.lineWidth=this.options.strokeWidth;
-var _176=function(obj){
-if(obj.name==_174){
-_168(_169,obj);
-}
-};
-MochiKit.Iter.forEach(data,bind(_176,this));
-_169.restore();
-}
-};
-PlotKit.CanvasRenderer.prototype._renderBarChart=function(){
-var bind=MochiKit.Base.bind;
-var _178=function(_179,bar){
-var x=this.area.w*bar.x+this.area.x;
-var y=this.area.h*bar.y+this.area.y;
-var w=this.area.w*bar.w;
-var h=this.area.h*bar.h;
-if((w<1)||(h<1)){
-return;
-}
-if(this.options.shouldFill){
-_179.fillRect(x,y,w,h);
-}
-if(this.options.shouldStroke){
-_179.strokeRect(x,y,w,h);
-}
-};
-this._renderBarChartWrap(this.layout.bars,bind(_178,this));
-};
-PlotKit.CanvasRenderer.prototype._renderLineChart=function(){
-var _182=this.element.getContext("2d");
-var _183=this.options.colorScheme.length;
-var _184=this.options.colorScheme;
-var _185=MochiKit.Base.keys(this.layout.datasets);
-var _186=_185.length;
-var bind=MochiKit.Base.bind;
-var _187=MochiKit.Base.partial;
-for(var i=0;i<_186;i++){
-var _188=_185[i];
-var _189=_184[i%_183];
-var _190=this.options.strokeColorTransform;
-_182.save();
-_182.fillStyle=_189.toRGBString();
-if(this.options.strokeColor){
-_182.strokeStyle=this.options.strokeColor.toRGBString();
-}else{
-if(this.options.strokeColorTransform){
-_182.strokeStyle=_189[_190]().toRGBString();
-}
-}
-_182.lineWidth=this.options.strokeWidth;
-var _191=function(ctx){
-ctx.beginPath();
-ctx.moveTo(this.area.x,this.area.y+this.area.h);
-var _193=function(ctx_,_195){
-if(_195.name==_188){
-ctx_.lineTo(this.area.w*_195.x+this.area.x,this.area.h*_195.y+this.area.y);
-}
-};
-MochiKit.Iter.forEach(this.layout.points,_187(_193,ctx),this);
-ctx.lineTo(this.area.w+this.area.x,this.area.h+this.area.y);
-ctx.lineTo(this.area.x,this.area.y+this.area.h);
-ctx.closePath();
-};
-if(this.options.shouldFill){
-bind(_191,this)(_182);
-_182.fill();
-}
-if(this.options.shouldStroke){
-bind(_191,this)(_182);
-_182.stroke();
-}
-_182.restore();
-}
-};
-PlotKit.CanvasRenderer.prototype._renderPieChart=function(){
-var _196=this.element.getContext("2d");
-var _197=this.options.colorScheme.length;
-var _198=this.layout.slices;
-var _199=this.area.x+this.area.w*0.5;
-var _200=this.area.y+this.area.h*0.5;
-var _201=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
-if(this.isIE){
-_199=parseInt(_199);
-_200=parseInt(_200);
-_201=parseInt(_201);
-}
-for(var i=0;i<_198.length;i++){
-var _202=this.options.colorScheme[i%_197];
-_196.save();
-_196.fillStyle=_202.toRGBString();
-var _203=function(){
-_196.beginPath();
-_196.moveTo(_199,_200);
-_196.arc(_199,_200,_201,_198[i].startAngle-Math.PI/2,_198[i].endAngle-Math.PI/2,false);
-_196.lineTo(_199,_200);
-_196.closePath();
-};
-if(Math.abs(_198[i].startAngle-_198[i].endAngle)>0.001){
-if(this.options.shouldFill){
-_203();
-_196.fill();
-}
-if(this.options.shouldStroke){
-_203();
-_196.lineWidth=this.options.strokeWidth;
-if(this.options.strokeColor){
-_196.strokeStyle=this.options.strokeColor.toRGBString();
-}else{
-if(this.options.strokeColorTransform){
-_196.strokeStyle=_202[this.options.strokeColorTransform]().toRGBString();
-}
-}
-_196.stroke();
-}
-}
-_196.restore();
-}
-};
-PlotKit.CanvasRenderer.prototype._renderBarAxis=function(){
-this._renderAxis();
-};
-PlotKit.CanvasRenderer.prototype._renderLineAxis=function(){
-this._renderAxis();
-};
-PlotKit.CanvasRenderer.prototype._renderAxis=function(){
-if(!this.options.drawXAxis&&!this.options.drawYAxis){
-return;
-}
-var _204=this.element.getContext("2d");
-var _205={"style":{"position":"absolute","fontSize":this.options.axisLabelFontSize+"px","zIndex":10,"color":this.options.axisLabelColor.toRGBString(),"width":this.options.axisLabelWidth+"px","overflow":"hidden"}};
-_204.save();
-_204.strokeStyle=this.options.axisLineColor.toRGBString();
-_204.lineWidth=this.options.axisLineWidth;
-if(this.options.drawYAxis){
-if(this.layout.yticks){
-var _206=function(tick){
-if(typeof (tick)=="function"){
-return;
-}
-var x=this.area.x;
-var y=this.area.y+tick[0]*this.area.h;
-_204.beginPath();
-_204.moveTo(x,y);
-_204.lineTo(x-this.options.axisTickSize,y);
-_204.closePath();
-_204.stroke();
-var _207=DIV(_205,tick[1]);
-_207.style.top=(y-this.options.axisLabelFontSize)+"px";
-_207.style.left=(x-this.options.padding.left-this.options.axisTickSize)+"px";
-_207.style.textAlign="right";
-_207.style.width=(this.options.padding.left-this.options.axisTickSize*2)+"px";
-MochiKit.DOM.appendChildNodes(this.container,_207);
-this.ylabels.push(_207);
-};
-MochiKit.Iter.forEach(this.layout.yticks,bind(_206,this));
-}
-_204.beginPath();
-_204.moveTo(this.area.x,this.area.y);
-_204.lineTo(this.area.x,this.area.y+this.area.h);
-_204.closePath();
-_204.stroke();
-}
-if(this.options.drawXAxis){
-if(this.layout.xticks){
-var _206=function(tick){
-if(typeof (dataset)=="function"){
-return;
-}
-var x=this.area.x+tick[0]*this.area.w;
-var y=this.area.y+this.area.h;
-_204.beginPath();
-_204.moveTo(x,y);
-_204.lineTo(x,y+this.options.axisTickSize);
-_204.closePath();
-_204.stroke();
-var _208=DIV(_205,tick[1]);
-_208.style.top=(y+this.options.axisTickSize)+"px";
-_208.style.left=(x-this.options.axisLabelWidth/2)+"px";
-_208.style.textAlign="center";
-_208.style.width=this.options.axisLabelWidth+"px";
-MochiKit.DOM.appendChildNodes(this.container,_208);
-this.xlabels.push(_208);
-};
-MochiKit.Iter.forEach(this.layout.xticks,bind(_206,this));
-}
-_204.beginPath();
-_204.moveTo(this.area.x,this.area.y+this.area.h);
-_204.lineTo(this.area.x+this.area.w,this.area.y+this.area.h);
-_204.closePath();
-_204.stroke();
-}
-_204.restore();
-};
-PlotKit.CanvasRenderer.prototype._renderPieAxis=function(){
-if(!this.options.drawXAxis){
-return;
-}
-if(this.layout.xticks){
-var _209=new Array();
-for(var i=0;i<this.layout.slices.length;i++){
-_209[this.layout.slices[i].xval]=this.layout.slices[i];
-}
-var _210=this.area.x+this.area.w*0.5;
-var _211=this.area.y+this.area.h*0.5;
-var _212=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
-var _213=this.options.axisLabelWidth;
-for(var i=0;i<this.layout.xticks.length;i++){
-var _214=_209[this.layout.xticks[i][0]];
-if(MochiKit.Base.isUndefinedOrNull(_214)){
-continue;
-}
-var _215=(_214.startAngle+_214.endAngle)/2;
-var _216=_215;
-if(_216>Math.PI*2){
-_216=_216-Math.PI*2;
-}else{
-if(_216<0){
-_216=_216+Math.PI*2;
-}
-}
-var _217=_210+Math.sin(_216)*(_212+10);
-var _218=_211-Math.cos(_216)*(_212+10);
-var _219={"position":"absolute","zIndex":11,"width":_213+"px","fontSize":this.options.axisLabelFontSize+"px","overflow":"hidden","color":this.options.axisLabelColor.toHexString()};
-if(_216<=Math.PI*0.5){
-_219["textAlign"]="left";
-_219["verticalAlign"]="top";
-_219["left"]=_217+"px";
-_219["top"]=(_218-this.options.axisLabelFontSize)+"px";
-}else{
-if((_216>Math.PI*0.5)&&(_216<=Math.PI)){
-_219["textAlign"]="left";
-_219["verticalAlign"]="bottom";
-_219["left"]=_217+"px";
-_219["top"]=_218+"px";
-}else{
-if((_216>Math.PI)&&(_216<=Math.PI*1.5)){
-_219["textAlign"]="right";
-_219["verticalAlign"]="bottom";
-_219["left"]=(_217-_213)+"px";
-_219["top"]=_218+"px";
-}else{
-_219["textAlign"]="right";
-_219["verticalAlign"]="bottom";
-_219["left"]=(_217-_213)+"px";
-_219["top"]=(_218-this.options.axisLabelFontSize)+"px";
-}
-}
-}
-var _220=DIV({"style":_219},this.layout.xticks[i][1]);
-this.xlabels.push(_220);
-MochiKit.DOM.appendChildNodes(this.container,_220);
-}
-}
-};
-PlotKit.CanvasRenderer.prototype._renderBackground=function(){
-var _221=this.element.getContext("2d");
-_221.save();
-_221.fillStyle=this.options.backgroundColor.toRGBString();
-_221.fillRect(0,0,this.width,this.height);
-_221.restore();
-};
-PlotKit.CanvasRenderer.prototype.clear=function(){
-if(this.isIE){
-try{
-if(this.clearDelay){
-this.clearDelay.cancel();
-this.clearDelay=null;
-}
-var _222=this.element.getContext("2d");
-}
-catch(e){
-this.isFirstRender=false;
-this.clearDelay=MochiKit.Async.wait(this.IEDelay);
-this.clearDelay.addCallback(bind(this.clear,this));
-return;
-}
-}
-var _222=this.element.getContext("2d");
-_222.clearRect(0,0,this.width,this.height);
-MochiKit.Iter.forEach(this.xlabels,MochiKit.DOM.removeElement);
-MochiKit.Iter.forEach(this.ylabels,MochiKit.DOM.removeElement);
-this.xlabels=new Array();
-this.ylabels=new Array();
-};
-PlotKit.CanvasRenderer.prototype._initialiseEvents=function(){
-var _223=MochiKit.Signal.connect;
-var bind=MochiKit.Base.bind;
-_223(this.element,"onclick",bind(this.onclick,this));
-};
-PlotKit.CanvasRenderer.prototype._resolveObject=function(e){
-var x=(e.mouse().page.x-PlotKit.Base.findPosX(this.element)-this.area.x)/this.area.w;
-var y=(e.mouse().page.y-PlotKit.Base.findPosY(this.element)-this.area.y)/this.area.h;
-var _225=this.layout.hitTest(x,y);
-if(_225){
-return _225;
-}
-return null;
-};
-PlotKit.CanvasRenderer.prototype._createEventObject=function(_226,e){
-if(_226==null){
-return null;
-}
-e.chart=_226;
-return e;
-};
-PlotKit.CanvasRenderer.prototype.onclick=function(e){
-var _227=this._resolveObject(e);
-var _228=this._createEventObject(_227,e);
-if(_228!=null){
-MochiKit.Signal.signal(this,"onclick",_228);
-}
-};
-PlotKit.CanvasRenderer.prototype.onmouseover=function(e){
-var _229=this._resolveObject(e);
-var _230=this._createEventObject(_229,e);
-if(_230!=null){
-signal(this,"onmouseover",_230);
-}
-};
-PlotKit.CanvasRenderer.prototype.onmouseout=function(e){
-var _231=this._resolveObject(e);
-var _232=this._createEventObject(_231,e);
-if(_232==null){
-signal(this,"onmouseout",e);
-}else{
-signal(this,"onmouseout",_232);
-}
-};
-PlotKit.CanvasRenderer.prototype.onmousemove=function(e){
-var _233=this._resolveObject(e);
-var _234=this._createEventObject(_233,e);
-if((_233==null)&&(this.event_isinside==null)){
-return;
-}
-if((_233!=null)&&(this.event_isinside==null)){
-signal(this,"onmouseover",_234);
-}
-if((_233==null)&&(this.event_isinside!=null)){
-signal(this,"onmouseout",_234);
-}
-if((_233!=null)&&(this.event_isinside!=null)){
-signal(this,"onmousemove",_234);
-}
-this.event_isinside=_233;
-};
-PlotKit.CanvasRenderer.isSupported=function(_235){
-var _236=null;
-try{
-if(MochiKit.Base.isUndefinedOrNull(_235)){
-_236=MochiKit.DOM.CANVAS({});
-}else{
-_236=MochiKit.DOM.getElement(_235);
-}
-var _237=_236.getContext("2d");
-}
-catch(e){
-var ie=navigator.appVersion.match(/MSIE (\d\.\d)/);
-var _239=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);
-if((!ie)||(ie[1]<6)||(_239)){
-return false;
-}
-return true;
-}
-return true;
-};
-PlotKit.Canvas={};
-PlotKit.Canvas.CanvasRenderer=PlotKit.CanvasRenderer;
-PlotKit.Canvas.EXPORT=["CanvasRenderer"];
-PlotKit.Canvas.EXPORT_OK=["CanvasRenderer"];
-PlotKit.Canvas.__new__=function(){
-var m=MochiKit.Base;
-m.nameFunctions(this);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-};
-PlotKit.Canvas.__new__();
-MochiKit.Base._exportSymbols(this,PlotKit.Canvas);
-try{
-if(typeof (PlotKit.Layout)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Layout";
-}
-PlotKit.SVGRenderer=function(_240,_241,_242){
-if(arguments.length>0){
-this.__init__(_240,_241,_242);
-}
-};
-PlotKit.SVGRenderer.NAME="PlotKit.SVGRenderer";
-PlotKit.SVGRenderer.VERSION=PlotKit.VERSION;
-PlotKit.SVGRenderer.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-PlotKit.SVGRenderer.toString=function(){
-return this.__repr__();
-};
-PlotKit.SVGRenderer.SVGNS="http://www.w3.org/2000/svg";
-PlotKit.SVGRenderer.prototype.__init__=function(_243,_244,_245){
-var _246=MochiKit.Base.isUndefinedOrNull;
-this.options={"drawBackground":true,"backgroundColor":Color.whiteColor(),"padding":{left:30,right:30,top:5,bottom:10},"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),"strokeColor":Color.whiteColor(),"strokeColorTransform":"asStrokeColor","strokeWidth":0.5,"shouldFill":true,"shouldStroke":true,"drawXAxis":true,"drawYAxis":true,"axisLineColor":Color.blackColor(),"axisLineWidth":0.5,"axisTickSize":3,"axisLabelColor":Color.blackColor(),"axisLabelFont":"Arial","axisLabelFontSize":9,"axisLabelWidth":50,"axisLabelUseDiv":true,"pieRadius":0.4,"enableEvents":true};
-MochiKit.Base.update(this.options,_245?_245:{});
-this.layout=_244;
-this.element=MochiKit.DOM.getElement(_243);
-this.container=this.element.parentNode;
-this.height=parseInt(this.element.getAttribute("height"));
-this.width=parseInt(this.element.getAttribute("width"));
-this.document=document;
-this.root=this.element;
-try{
-this.document=this.element.getSVGDocument();
-this.root=_246(this.document.documentElement)?this.element:this.document.documentElement;
-}
-catch(e){
-}
-this.element.style.zIndex=1;
-if(_246(this.element)){
-throw "SVGRenderer() - passed SVG object is not found";
-}
-if(_246(this.container)||this.container.nodeName.toLowerCase()!="div"){
-throw "SVGRenderer() - No DIV's around the SVG.";
-}
-this.xlabels=new Array();
-this.ylabels=new Array();
-this.defs=this.createSVGElement("defs");
-this.area={x:this.options.padding.left,y:this.options.padding.top,w:this.width-this.options.padding.left-this.options.padding.right,h:this.height-this.options.padding.top-this.options.padding.bottom};
-MochiKit.DOM.updateNodeAttributes(this.container,{"style":{"position":"relative","width":this.width+"px"}});
-};
-PlotKit.SVGRenderer.prototype.render=function(){
-if(this.options.drawBackground){
-this._renderBackground();
-}
-if(this.layout.style=="bar"){
-this._renderBarChart();
-this._renderBarAxis();
-}else{
-if(this.layout.style=="pie"){
-this._renderPieChart();
-this._renderPieAxis();
-}else{
-if(this.layout.style=="line"){
-this._renderLineChart();
-this._renderLineAxis();
-}
-}
-}
-};
-PlotKit.SVGRenderer.prototype._renderBarOrLine=function(data,_247,_248,_249){
-var _250=this.options.colorScheme.length;
-var _251=this.options.colorScheme;
-var _252=MochiKit.Base.keys(this.layout.datasets);
-var _253=_252.length;
-for(var i=0;i<_253;i++){
-var _254=_252[i];
-var _255=new Array();
-var _256=_251[i%_250];
-if(this.options.shouldFill){
-_255["fill"]=_256.toRGBString();
-}else{
-_255["fill"]="none";
-}
-if(this.options.shouldStroke&&(this.options.strokeColor||this.options.strokeColorTransform)){
-if(this.options.strokeColor){
-_255["stroke"]=this.options.strokeColor.toRGBString();
-}else{
-if(this.options.strokeColorTransform){
-_255["stroke"]=_256[this.options.strokeColorTransform]().toRGBString();
-}
-}
-_255["strokeWidth"]=this.options.strokeWidth;
-}
-if(_248){
-_248(_255);
-}
-var _257=function(obj){
-if(obj.name==_254){
-_247(_255,obj);
-}
-};
-MochiKit.Iter.forEach(data,bind(_257,this));
-if(_249){
-_249(_255);
-}
-}
-};
-PlotKit.SVGRenderer.prototype._renderBarChart=function(){
-var bind=MochiKit.Base.bind;
-var _258=function(_259,bar){
-var x=this.area.w*bar.x+this.area.x;
-var y=this.area.h*bar.y+this.area.y;
-var w=this.area.w*bar.w;
-var h=this.area.h*bar.h;
-this._drawRect(x,y,w,h,_259);
-};
-this._renderBarOrLine(this.layout.bars,bind(_258,this));
-};
-PlotKit.SVGRenderer.prototype._renderLineChart=function(){
-var bind=MochiKit.Base.bind;
-var _260=function(_261,_262){
-this._tempPointsBuffer+=(this.area.w*_262.x+this.area.x)+","+(this.area.h*_262.y+this.area.y)+" ";
-};
-var _263=function(_264){
-this._tempPointsBuffer="";
-this._tempPointsBuffer+=(this.area.x)+","+(this.area.y+this.area.h)+" ";
-};
-var _265=function(_266){
-this._tempPointsBuffer+=(this.area.w+this.area.x)+","+(this.area.h+this.area.y);
-_266["points"]=this._tempPointsBuffer;
-var elem=this.createSVGElement("polygon",_266);
-this.root.appendChild(elem);
-};
-this._renderBarOrLine(this.layout.points,bind(_260,this),bind(_263,this),bind(_265,this));
-};
-PlotKit.SVGRenderer.prototype._renderPieChart=function(){
-var _268=this.options.colorScheme.length;
-var _269=this.layout.slices;
-var _270=this.area.x+this.area.w*0.5;
-var _271=this.area.y+this.area.h*0.5;
-var _272=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
-if(_269.length==1&&(Math.abs(_269[0].startAngle)-Math.abs(_269[0].endAngle)<0.1)){
-var _273={"cx":_270,"cy":_271,"r":_272};
-var _274=this.options.colorScheme[0];
-if(this.options.shouldFill){
-_273["fill"]=_274.toRGBString();
-}else{
-_273["fill"]="none";
-}
-if(this.options.shouldStroke&&(this.options.strokeColor||this.options.strokeColorTransform)){
-if(this.options.strokeColor){
-_273["stroke"]=this.options.strokeColor.toRGBString();
-}else{
-if(this.options.strokeColorTransform){
-_273["stroke"]=_274[this.options.strokeColorTransform]().toRGBString();
-}
-}
-_273["style"]="stroke-width: "+this.options.strokeWidth;
-}
-this.root.appendChild(this.createSVGElement("circle",_273));
-return;
-}
-for(var i=0;i<_269.length;i++){
-var _273=new Array();
-var _274=this.options.colorScheme[i%_268];
-if(this.options.shouldFill){
-_273["fill"]=_274.toRGBString();
-}else{
-_273["fill"]="none";
-}
-if(this.options.shouldStroke&&(this.options.strokeColor||this.options.strokeColorTransform)){
-if(this.options.strokeColor){
-_273["stroke"]=this.options.strokeColor.toRGBString();
-}else{
-if(this.options.strokeColorTransform){
-_273["stroke"]=_274[this.options.strokeColorTransform]().toRGBString();
-}
-}
-_273["style"]="stroke-width:"+this.options.strokeWidth;
-}
-var _275=0;
-if(Math.abs(_269[i].endAngle-_269[i].startAngle)>Math.PI){
-_275=1;
-}
-var x1=Math.cos(_269[i].startAngle-Math.PI/2)*_272;
-var y1=Math.sin(_269[i].startAngle-Math.PI/2)*_272;
-var x2=Math.cos(_269[i].endAngle-Math.PI/2)*_272;
-var y2=Math.sin(_269[i].endAngle-Math.PI/2)*_272;
-var rx=x2-x1;
-var ry=y2-y1;
-var _282="M"+_270+","+_271+" ";
-_282+="l"+x1+","+y1+" ";
-_282+="a"+_272+","+_272+" 0 "+_275+",1 "+rx+","+ry+" z";
-_273["d"]=_282;
-var elem=this.createSVGElement("path",_273);
-this.root.appendChild(elem);
-}
-};
-PlotKit.SVGRenderer.prototype._renderBarAxis=function(){
-this._renderAxis();
-};
-PlotKit.SVGRenderer.prototype._renderLineAxis=function(){
-this._renderAxis();
-};
-PlotKit.SVGRenderer.prototype._renderAxis=function(){
-if(!this.options.drawXAxis&&!this.options.drawYAxis){
-return;
-}
-var _283={"style":{"position":"absolute","textAlign":"center","fontSize":this.options.axisLabelFontSize+"px","zIndex":10,"color":this.options.axisLabelColor.toRGBString(),"width":this.options.axisLabelWidth+"px","overflow":"hidden"}};
-var _284={"stroke":this.options.axisLineColor.toRGBString(),"strokeWidth":this.options.axisLineWidth};
-if(this.options.drawYAxis){
-if(this.layout.yticks){
-var _285=function(tick){
-var x=this.area.x;
-var y=this.area.y+tick[0]*this.area.h;
-this._drawLine(x,y,x-3,y,_284);
-if(this.options.axisLabelUseDiv){
-var _286=DIV(_283,tick[1]);
-_286.style.top=(y-this.options.axisLabelFontSize)+"px";
-_286.style.left=(x-this.options.padding.left+this.options.axisTickSize)+"px";
-_286.style.textAlign="left";
-_286.style.width=(this.options.padding.left-3)+"px";
-MochiKit.DOM.appendChildNodes(this.container,_286);
-this.ylabels.push(_286);
-}else{
-var _287={y:y+3,x:(x-this.options.padding.left+3),width:(this.options.padding.left-this.options.axisTickSize)+"px",height:(this.options.axisLabelFontSize+3)+"px",fontFamily:"Arial",fontSize:this.options.axisLabelFontSize+"px",fill:this.options.axisLabelColor.toRGBString()};
-var _286=this.createSVGElement("text",_287);
-_286.appendChild(this.document.createTextNode(tick[1]));
-this.root.appendChild(_286);
-}
-};
-MochiKit.Iter.forEach(this.layout.yticks,bind(_285,this));
-}
-this._drawLine(this.area.x,this.area.y,this.area.x,this.area.y+this.area.h,_284);
-}
-if(this.options.drawXAxis){
-if(this.layout.xticks){
-var _285=function(tick){
-var x=this.area.x+tick[0]*this.area.w;
-var y=this.area.y+this.area.h;
-this._drawLine(x,y,x,y+this.options.axisTickSize,_284);
-if(this.options.axisLabelUseDiv){
-var _288=DIV(_283,tick[1]);
-_288.style.top=(y+this.options.axisTickSize)+"px";
-_288.style.left=(x-this.options.axisLabelWidth/2)+"px";
-_288.style.textAlign="center";
-_288.style.width=this.options.axisLabelWidth+"px";
-MochiKit.DOM.appendChildNodes(this.container,_288);
-this.xlabels.push(_288);
-}else{
-var _289={y:(y+this.options.axisTickSize+this.options.axisLabelFontSize),x:x-3,width:this.options.axisLabelWidth+"px",height:(this.options.axisLabelFontSize+3)+"px",fontFamily:"Arial",fontSize:this.options.axisLabelFontSize+"px",fill:this.options.axisLabelColor.toRGBString(),textAnchor:"middle"};
-var _288=this.createSVGElement("text",_289);
-_288.appendChild(this.document.createTextNode(tick[1]));
-this.root.appendChild(_288);
-}
-};
-MochiKit.Iter.forEach(this.layout.xticks,bind(_285,this));
-}
-this._drawLine(this.area.x,this.area.y+this.area.h,this.area.x+this.area.w,this.area.y+this.area.h,_284);
-}
-};
-PlotKit.SVGRenderer.prototype._renderPieAxis=function(){
-if(this.layout.xticks){
-var _290=new Array();
-for(var i=0;i<this.layout.slices.length;i++){
-_290[this.layout.slices[i].xval]=this.layout.slices[i];
-}
-var _291=this.area.x+this.area.w*0.5;
-var _292=this.area.y+this.area.h*0.5;
-var _293=Math.min(this.area.w*this.options.pieRadius+10,this.area.h*this.options.pieRadius+10);
-var _294=this.options.axisLabelWidth;
-for(var i=0;i<this.layout.xticks.length;i++){
-var _295=_290[this.layout.xticks[i][0]];
-if(MochiKit.Base.isUndefinedOrNull(_295)){
-continue;
-}
-var _296=(_295.startAngle+_295.endAngle)/2;
-var _297=_296;
-if(_297>Math.PI*2){
-_297=_297-Math.PI*2;
-}else{
-if(_297<0){
-_297=_297+Math.PI*2;
-}
-}
-var _298=_291+Math.sin(_297)*(_293+10);
-var _299=_292-Math.cos(_297)*(_293+10);
-var _300={"position":"absolute","zIndex":11,"width":_294+"px","fontSize":this.options.axisLabelFontSize+"px","overflow":"hidden","color":this.options.axisLabelColor.toHexString()};
-var _301={"width":_294+"px","fontSize":this.options.axisLabelFontSize+"px","height":(this.options.axisLabelFontSize+3)+"px","fill":this.options.axisLabelColor.toRGBString()};
-if(_297<=Math.PI*0.5){
-MochiKit.Base.update(_300,{"textAlign":"left","verticalAlign":"top","left":_298+"px","top":(_299-this.options.axisLabelFontSize)+"px"});
-MochiKit.Base.update(_301,{"x":_298,"y":(_299-this.options.axisLabelFontSize),"textAnchor":"left"});
-}else{
-if((_297>Math.PI*0.5)&&(_297<=Math.PI)){
-MochiKit.Base.update(_300,{"textAlign":"left","verticalAlign":"bottom","left":_298+"px","top":_299+"px"});
-MochiKit.Base.update(_301,{"textAnchor":"left","x":_298,"y":_299});
-}else{
-if((_297>Math.PI)&&(_297<=Math.PI*1.5)){
-MochiKit.Base.update(_300,{"textAlign":"right","verticalAlign":"bottom","left":_298+"px","top":_299+"px"});
-MochiKit.Base.update(_301,{"textAnchor":"right","x":_298-_294,"y":_299});
-}else{
-MochiKit.Base.update(_300,{"textAlign":"left","verticalAlign":"bottom","left":_298+"px","top":_299+"px"});
-MochiKit.Base.update(_301,{"textAnchor":"left","x":_298-_294,"y":_299-this.options.axisLabelFontSize});
-}
-}
-}
-if(this.options.axisLabelUseDiv){
-var _302=DIV({"style":_300},this.layout.xticks[i][1]);
-this.xlabels.push(_302);
-MochiKit.DOM.appendChildNodes(this.container,_302);
-}else{
-var _302=this.createSVGElement("text",_301);
-_302.appendChild(this.document.createTextNode(this.layout.xticks[i][1]));
-this.root.appendChild(_302);
-}
-}
-}
-};
-PlotKit.SVGRenderer.prototype._renderBackground=function(){
-var opts={"stroke":"none","fill":this.options.backgroundColor.toRGBString()};
-this._drawRect(0,0,this.width,this.height,opts);
-};
-PlotKit.SVGRenderer.prototype._drawRect=function(x,y,w,h,_304){
-var _305={x:x+"px",y:y+"px",width:w+"px",height:h+"px"};
-if(_304){
-MochiKit.Base.update(_305,_304);
-}
-var elem=this.createSVGElement("rect",_305);
-this.root.appendChild(elem);
-};
-PlotKit.SVGRenderer.prototype._drawLine=function(x1,y1,x2,y2,_306){
-var _307={x1:x1+"px",y1:y1+"px",x2:x2+"px",y2:y2+"px"};
-if(_306){
-MochiKit.Base.update(_307,_306);
-}
-var elem=this.createSVGElement("line",_307);
-this.root.appendChild(elem);
-};
-PlotKit.SVGRenderer.prototype.clear=function(){
-while(this.element.firstChild){
-this.element.removeChild(this.element.firstChild);
-}
-if(this.options.axisLabelUseDiv){
-for(var i=0;i<this.xlabels.length;i++){
-MochiKit.DOM.removeElement(this.xlabels[i]);
-}
-for(var i=0;i<this.ylabels.length;i++){
-MochiKit.DOM.removeElement(this.ylabels[i]);
-}
-}
-this.xlabels=new Array();
-this.ylabels=new Array();
-};
-PlotKit.SVGRenderer.prototype.createSVGElement=function(name,_309){
-var _310=MochiKit.Base.isUndefinedOrNull;
-var elem;
-var doc=_310(this.document)?document:this.document;
-try{
-elem=doc.createElementNS(PlotKit.SVGRenderer.SVGNS,name);
-}
-catch(e){
-elem=doc.createElement(name);
-elem.setAttribute("xmlns",PlotKit.SVGRenderer.SVGNS);
-}
-if(_309){
-MochiKit.DOM.updateNodeAttributes(elem,_309);
-}
-return elem;
-};
-PlotKit.SVGRenderer.SVG=function(_312){
-var ie=navigator.appVersion.match(/MSIE (\d\.\d)/);
-var _313=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);
-if(ie&&(ie[1]>=6)&&(!_313)){
-var _314=_312["width"]?_312["width"]:"100";
-var _315=_312["height"]?_312["height"]:"100";
-var eid=_312["id"]?_312["id"]:"notunique";
-var html="<svg:svg width=\""+_314+"\" height=\""+_315+"\" ";
-html+="id=\""+eid+"\" version=\"1.1\" baseProfile=\"full\" />";
-var _318=document.createElement(html);
-var _319=_318.getSVGDocument().createElementNS(PlotKit.SVGRenderer.SVGNS,"svg");
-_319.setAttribute("width",_314);
-_319.setAttribute("height",_315);
-_318.getSVGDocument().appendChild(_319);
-return _318;
-}else{
-return PlotKit.SVGRenderer.prototype.createSVGElement("svg",_312);
-}
-};
-PlotKit.SVGRenderer.isSupported=function(){
-var _320=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);
-var _321=navigator.appVersion.match(/MSIE (\d\.\d)/);
-var _322=navigator.userAgent.match(/AppleWebKit\/(\d+)/);
-var _323=navigator.userAgent.match(/Opera\/(\d*\.\d*)/);
-var _324=navigator.userAgent.match(/rv:(\d*\.\d*).*Gecko/);
-var _325="http://www.w3.org/TR/SVG11/feature#SVG";
-if(_321&&(_321[1]>=6)&&!_320){
-return document.implementation.hasFeature(_325,"1.1");
-}
-if(_323&&(_323[1]>8.9)){
-return true;
-}
-if(_324&&(_324>1.7)){
-return true;
-}
-return false;
-};
-PlotKit.SVG={};
-PlotKit.SVG.SVGRenderer=PlotKit.SVGRenderer;
-PlotKit.SVG.EXPORT=["SVGRenderer"];
-PlotKit.SVG.EXPORT_OK=["SVGRenderer"];
-PlotKit.SVG.__new__=function(){
-var m=MochiKit.Base;
-m.nameFunctions(this);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-};
-PlotKit.SVG.__new__();
-MochiKit.Base._exportSymbols(this,PlotKit.SVG);
-try{
-if(typeof (PlotKit.CanvasRenderer)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "SweetCanvas depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, Canvas}";
-}
-if(typeof (PlotKit.SweetCanvasRenderer)=="undefined"){
-PlotKit.SweetCanvasRenderer={};
-}
-PlotKit.SweetCanvasRenderer=function(_326,_327,_328){
-if(arguments.length>0){
-this.__init__(_326,_327,_328);
-}
-};
-PlotKit.SweetCanvasRenderer.NAME="PlotKit.SweetCanvasRenderer";
-PlotKit.SweetCanvasRenderer.VERSION=PlotKit.VERSION;
-PlotKit.SweetCanvasRenderer.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-PlotKit.SweetCanvasRenderer.toString=function(){
-return this.__repr__();
-};
-PlotKit.SweetCanvasRenderer.prototype=new PlotKit.CanvasRenderer();
-PlotKit.SweetCanvasRenderer.prototype.constructor=PlotKit.SweetCanvasRenderer;
-PlotKit.SweetCanvasRenderer.__super__=PlotKit.CanvasRenderer.prototype;
-PlotKit.SweetCanvasRenderer.prototype.__init__=function(el,_330,opts){
-var _331=PlotKit.Base.officeBlue();
-MochiKit.Base.update(_331,opts);
-PlotKit.SweetCanvasRenderer.__super__.__init__.call(this,el,_330,_331);
-};
-PlotKit.SweetCanvasRenderer.prototype._renderBarChart=function(){
-var bind=MochiKit.Base.bind;
-var _332=Color.blackColor().colorWithAlpha(0.1).toRGBString();
-var _333=function(_334,x,y,w,h){
-_334.fillStyle=_332;
-_334.fillRect(x-2,y-2,w+4,h+2);
-_334.fillStyle=_332;
-_334.fillRect(x-1,y-1,w+2,h+1);
-};
-var _335=this.options.colorScheme.length;
-var _336=this.options.colorScheme;
-var _337=PlotKit.Base.keys(this.layout.datasets);
-var _338=_337.length;
-var _339=function(name){
-for(var i=0;i<_338;i++){
-if(name==_337[i]){
-return _336[i%_335];
-}
-}
-return _336[0];
-};
-var _340=function(_341,bar){
-var x=this.area.w*bar.x+this.area.x;
-var y=this.area.h*bar.y+this.area.y;
-var w=this.area.w*bar.w;
-var h=this.area.h*bar.h;
-if((w<1)||(h<1)){
-return;
-}
-_341.save();
-_341.shadowBlur=5;
-_341.shadowColor=Color.fromHexString("#888888").toRGBString();
-if(this.isIE){
-_341.save();
-_341.fillStyle="#cccccc";
-_341.fillRect(x-2,y-2,w+4,h+2);
-_341.restore();
-}else{
-_333(_341,x,y,w,h);
-}
-if(this.options.shouldFill){
-_341.fillStyle=_339(bar.name).toRGBString();
-_341.fillRect(x,y,w,h);
-}
-_341.shadowBlur=0;
-_341.strokeStyle=Color.whiteColor().toRGBString();
-_341.lineWidth=2;
-if(this.options.shouldStroke){
-_341.strokeRect(x,y,w,h);
-}
-_341.restore();
-};
-this._renderBarChartWrap(this.layout.bars,bind(_340,this));
-};
-PlotKit.SweetCanvasRenderer.prototype._renderLineChart=function(){
-var _342=this.element.getContext("2d");
-var _343=this.options.colorScheme.length;
-var _344=this.options.colorScheme;
-var _345=PlotKit.Base.keys(this.layout.datasets);
-var _346=_345.length;
-var bind=MochiKit.Base.bind;
-for(var i=0;i<_346;i++){
-var _347=_345[i];
-var _348=_344[i%_343];
-var _349=this.options.strokeColorTransform;
-_342.save();
-var _350=function(ctx){
-ctx.beginPath();
-ctx.moveTo(this.area.x,this.area.y+this.area.h);
-var _351=function(ctx_,_352){
-if(_352.name==_347){
-ctx_.lineTo(this.area.w*_352.x+this.area.x,this.area.h*_352.y+this.area.y);
-}
-};
-MochiKit.Iter.forEach(this.layout.points,partial(_351,ctx),this);
-ctx.lineTo(this.area.w+this.area.x,this.area.h+this.area.y);
-ctx.lineTo(this.area.x,this.area.y+this.area.h);
-ctx.closePath();
-};
-if(this.options.shouldFill){
-_342.save();
-if(this.isIE){
-_342.fillStyle="#cccccc";
-}else{
-_342.fillStyle=Color.blackColor().colorWithAlpha(0.2).toRGBString();
-}
-_342.translate(-1,-2);
-bind(_350,this)(_342);
-if(this.options.shouldFill){
-_342.fill();
-}
-_342.restore();
-}
-_342.shadowBlur=5;
-_342.shadowColor=Color.fromHexString("#888888").toRGBString();
-_342.fillStyle=_348.toRGBString();
-_342.lineWidth=2;
-_342.strokeStyle=Color.whiteColor().toRGBString();
-if(this.options.shouldFill){
-bind(_350,this)(_342);
-_342.fill();
-}
-if(this.options.shouldStroke){
-bind(_350,this)(_342);
-_342.stroke();
-}
-_342.restore();
-}
-};
-PlotKit.SweetCanvasRenderer.prototype._renderPieChart=function(){
-var _353=this.element.getContext("2d");
-var _354=this.options.colorScheme.length;
-var _355=this.layout.slices;
-var _356=this.area.x+this.area.w*0.5;
-var _357=this.area.y+this.area.h*0.5;
-var _358=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
-if(this.isIE){
-_356=parseInt(_356);
-_357=parseInt(_357);
-_358=parseInt(_358);
-}
-if(!this.isIE){
-_353.save();
-var _359=Color.blackColor().colorWithAlpha(0.2);
-_353.fillStyle=_359.toRGBString();
-_353.shadowBlur=5;
-_353.shadowColor=Color.fromHexString("#888888").toRGBString();
-_353.translate(1,1);
-_353.beginPath();
-_353.moveTo(_356,_357);
-_353.arc(_356,_357,_358+2,0,Math.PI*2,false);
-_353.closePath();
-_353.fill();
-_353.restore();
-}
-_353.save();
-_353.strokeStyle=Color.whiteColor().toRGBString();
-_353.lineWidth=2;
-for(var i=0;i<_355.length;i++){
-var _360=this.options.colorScheme[i%_354];
-_353.fillStyle=_360.toRGBString();
-var _361=function(){
-_353.beginPath();
-_353.moveTo(_356,_357);
-_353.arc(_356,_357,_358,_355[i].startAngle-Math.PI/2,_355[i].endAngle-Math.PI/2,false);
-_353.lineTo(_356,_357);
-_353.closePath();
-};
-if(Math.abs(_355[i].startAngle-_355[i].endAngle)>0.0001){
-if(this.options.shouldFill){
-_361();
-_353.fill();
-}
-if(this.options.shouldStroke){
-_361();
-_353.stroke();
-}
-}
-}
-_353.restore();
-};
-PlotKit.SweetCanvasRenderer.prototype._renderBackground=function(){
-var _362=this.element.getContext("2d");
-if(this.layout.style=="bar"||this.layout.style=="line"){
-_362.save();
-_362.fillStyle=this.options.backgroundColor.toRGBString();
-_362.fillRect(this.area.x,this.area.y,this.area.w,this.area.h);
-_362.strokeStyle=this.options.axisLineColor.toRGBString();
-_362.lineWidth=1;
-var _363=this.layout.yticks;
-var _364=false;
-if(this.layout.style=="bar"&&this.layout.options.barOrientation=="horizontal"){
-_363=this.layout.xticks;
-_364=true;
-}
-for(var i=0;i<_363.length;i++){
-var x1=0;
-var y1=0;
-var x2=0;
-var y2=0;
-if(_364){
-x1=_363[i][0]*this.area.w+this.area.x;
-y1=this.area.y;
-x2=x1;
-y2=y1+this.area.h;
-}else{
-x1=this.area.x;
-y1=_363[i][0]*this.area.h+this.area.y;
-x2=x1+this.area.w;
-y2=y1;
-}
-_362.beginPath();
-_362.moveTo(x1,y1);
-_362.lineTo(x2,y2);
-_362.closePath();
-_362.stroke();
-}
-_362.restore();
-}else{
-PlotKit.SweetCanvasRenderer.__super__._renderBackground.call(this);
-}
-};
-PlotKit.SweetCanvas={};
-PlotKit.SweetCanvas.SweetCanvasRenderer=PlotKit.SweetCanvasRenderer;
-PlotKit.SweetCanvas.EXPORT=["SweetCanvasRenderer"];
-PlotKit.SweetCanvas.EXPORT_OK=["SweetCanvasRenderer"];
-PlotKit.SweetCanvas.__new__=function(){
-var m=MochiKit.Base;
-m.nameFunctions(this);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-};
-PlotKit.SweetCanvas.__new__();
-MochiKit.Base._exportSymbols(this,PlotKit.SweetCanvas);
-try{
-if(typeof (PlotKit.SVGRenderer)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "SweetSVG depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, SVG}";
-}
-if(typeof (PlotKit.SweetSVGRenderer)=="undefined"){
-PlotKit.SweetSVGRenderer={};
-}
-PlotKit.SweetSVGRenderer=function(_365,_366,_367){
-if(arguments.length>0){
-this.__init__(_365,_366,_367);
-}
-};
-PlotKit.SweetSVGRenderer.NAME="PlotKit.SweetSVGRenderer";
-PlotKit.SweetSVGRenderer.VERSION=PlotKit.VERSION;
-PlotKit.SweetSVGRenderer.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-PlotKit.SweetSVGRenderer.toString=function(){
-return this.__repr__();
-};
-PlotKit.SweetSVGRenderer.prototype=new PlotKit.SVGRenderer();
-PlotKit.SweetSVGRenderer.prototype.constructor=PlotKit.SweetSVGRenderer;
-PlotKit.SweetSVGRenderer.__super__=PlotKit.SVGRenderer.prototype;
-PlotKit.SweetSVGRenderer.prototype.__init__=function(_368,_369,_370){
-var _371=PlotKit.Base.officeBlue();
-MochiKit.Base.update(_371,_370);
-PlotKit.SweetSVGRenderer.__super__.__init__.call(this,_368,_369,_371);
-};
-PlotKit.SweetSVGRenderer.prototype._addDropShadowFilter=function(){
-var _372=this.createSVGElement("filter",{x:0,y:0,"id":"dropShadow"});
-var _373=this.createSVGElement("feOffset",{"in":"SourceGraphic","dx":0,"dy":0,"result":"topCopy"});
-var blur=this.createSVGElement("feGaussianBlur",{"in":"SourceAlpha","StdDeviation":2,"result":"shadow"});
-var _375=this.createSVGElement("feOffset",{"in":"shadow","dx":-1,"dy":-2,"result":"movedShadow"});
-var _376=this.createSVGElement("feMerge");
-var _377=this.createSVGElement("feMergeNode",{"in":"topCopy"});
-var _378=this.createSVGElement("feMergeNode",{"in":"movedShadow"});
-_376.appendChild(_377);
-_376.appendChild(_378);
-_372.appendChild(_373);
-_372.appendChild(blur);
-_372.appendChild(_375);
-_372.appendChild(_376);
-this.defs.appendChild(_372);
-};
-PlotKit.SweetSVGRenderer.prototype._renderBarChart=function(){
-var bind=MochiKit.Base.bind;
-var _379=Color.blackColor().toRGBString();
-var _380="fill:"+_379+";fill-opacity:0.15";
-var _381="stroke-width: 2.0; stroke:"+Color.whiteColor().toRGBString();
-var _382=function(_383,bar){
-var x=this.area.w*bar.x+this.area.x;
-var y=this.area.h*bar.y+this.area.y;
-var w=this.area.w*bar.w;
-var h=this.area.h*bar.h;
-if((w<1)||(h<1)){
-return;
-}
-_383["style"]=_381;
-this._drawRect(x-2,y-1,w+4,h+2,{"style":_380});
-this._drawRect(x,y,w,h,_383);
-};
-this._renderBarOrLine(this.layout.bars,bind(_382,this));
-};
-PlotKit.SweetSVGRenderer.prototype._renderLineChart=function(){
-var bind=MochiKit.Base.bind;
-var _384=Color.blackColor().toRGBString();
-var _385="fill:"+_384+";fill-opacity:0.15";
-var _386="stroke-width: 2.0; stroke:"+Color.whiteColor().toRGBString();
-var _387=function(_388,_389){
-this._tempPointsBuffer+=(this.area.w*_389.x+this.area.x)+","+(this.area.h*_389.y+this.area.y)+" ";
-};
-var _390=function(_391){
-this._tempPointsBuffer="";
-this._tempPointsBuffer+=(this.area.x)+","+(this.area.y+this.area.h)+" ";
-};
-var _392=function(_393){
-this._tempPointsBuffer+=(this.area.w+this.area.x)+","+(this.area.h+this.area.y);
-_393["points"]=this._tempPointsBuffer;
-_393["stroke"]="none";
-_393["transform"]="translate(-2, -1)";
-_393["style"]=_385;
-var _394=this.createSVGElement("polygon",_393);
-this.root.appendChild(_394);
-_393["transform"]="";
-_393["style"]=_386;
-var elem=this.createSVGElement("polygon",_393);
-this.root.appendChild(elem);
-};
-this._renderBarOrLine(this.layout.points,bind(_387,this),bind(_390,this),bind(_392,this));
-};
-PlotKit.SweetSVGRenderer.prototype._renderPieChart=function(){
-var _395=this.area.x+this.area.w*0.5;
-var _396=this.area.y+this.area.h*0.5;
-var _397=Color.blackColor().toRGBString();
-var _398=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
-var _399="fill:"+_397+";fill-opacity:0.15";
-var _400=this.createSVGElement("circle",{"style":_399,"cx":_395+1,"cy":_396+1,"r":_398+1});
-this.root.appendChild(_400);
-PlotKit.SweetSVGRenderer.__super__._renderPieChart.call(this);
-};
-PlotKit.SweetSVGRenderer.prototype._renderBackground=function(){
-var _401={"fill":this.options.backgroundColor.toRGBString(),"stroke":"none"};
-if(this.layout.style=="bar"||this.layout.style=="line"){
-this._drawRect(this.area.x,this.area.y,this.area.w,this.area.h,_401);
-var _402=this.layout.yticks;
-var _403=false;
-if(this.layout.style=="bar"&&this.layout.options.barOrientation=="horizontal"){
-_402=this.layout.xticks;
-_403=true;
-}
-for(var i=0;i<_402.length;i++){
-var x=0;
-var y=0;
-var w=0;
-var h=0;
-if(_403){
-x=_402[i][0]*this.area.w+this.area.x;
-y=this.area.y;
-w=1;
-h=this.area.w;
-}else{
-x=this.area.x;
-y=_402[i][0]*this.area.h+this.area.y;
-w=this.area.w;
-h=1;
-}
-this._drawRect(x,y,w,h,{"fill":this.options.axisLineColor.toRGBString()});
-}
-}else{
-PlotKit.SweetSVGRenderer.__super__._renderBackground.call(this);
-}
-};
-PlotKit.SweetSVG={};
-PlotKit.SweetSVG.SweetSVGRenderer=PlotKit.SweetSVGRenderer;
-PlotKit.SweetSVG.EXPORT=["SweetSVGRenderer"];
-PlotKit.SweetSVG.EXPORT_OK=["SweetSVGRenderer"];
-PlotKit.SweetSVG.__new__=function(){
-var m=MochiKit.Base;
-m.nameFunctions(this);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-};
-PlotKit.SweetSVG.__new__();
-MochiKit.Base._exportSymbols(this,PlotKit.SweetSVG);
-try{
-if(typeof (PlotKit.CanvasRenderer)=="undefined"){
-throw "";
-}
-}
-catch(e){
-throw "PlotKit.EasyPlot depends on all of PlotKit's components";
-}
-if(typeof (PlotKit.EasyPlot)=="undefined"){
-PlotKit.EasyPlot={};
-}
-PlotKit.EasyPlot.NAME="PlotKit.EasyPlot";
-PlotKit.EasyPlot.VERSION=PlotKit.VERSION;
-PlotKit.EasyPlot.__repr__=function(){
-return "["+this.NAME+" "+this.VERSION+"]";
-};
-PlotKit.EasyPlot.toString=function(){
-return this.__repr__();
-};
-PlotKit.EasyPlot=function(_404,_405,_406,_407){
-this.layout=new Layout(_404,_405);
-this.divElem=_406;
-this.width=parseInt(_406.getAttribute("width"));
-this.height=parseInt(_406.getAttribute("height"));
-this.deferredCount=0;
-if(this.width<1){
-this.width=this.divElem.width?this.divElem.width:300;
-}
-if(this.height<1){
-this.height=this.divElem.height?this.divElem.height:300;
-}
-if(isArrayLike(_407)){
-for(var i=0;i<_407.length;i++){
-if(typeof (_407[i])=="string"){
-this.deferredCount++;
-var d=MochiKit.Async.doSimpleXMLHttpRequest(_407[i]);
-d.addCallback(MochiKit.Base.bind(PlotKit.EasyPlot.onDataLoaded,this));
-}else{
-if(isArrayLike(_407[i])){
-this.layout.addDataset("data-"+i,_407[i]);
-}
-}
-}
-}else{
-if(!isUndefinedOrNull(_407)){
-throw "Passed datasources are not Array like";
-}
-}
-if(CanvasRenderer.isSupported()){
-this.element=CANVAS({"id":this.divElem.getAttribute("id")+"-canvas","width":this.width,"height":this.height},"");
-this.divElem.appendChild(this.element);
-this.renderer=new SweetCanvasRenderer(this.element,this.layout,_405);
-}else{
-if(SVGRenderer.isSupported()){
-this.element=SVGRenderer.SVG({"id":this.divElem.getAttribute("id")+"-svg","width":this.width,"height":this.height,"version":"1.1","baseProfile":"full"},"");
-this.divElem.appendChild(this.element);
-this.renderer=new SweetSVGRenderer(this.element,this.layout,_405);
-}
-}
-if((this.deferredCount==0)&&(PlotKit.Base.keys(this.layout.datasets).length>0)){
-this.layout.evaluate();
-this.renderer.clear();
-this.renderer.render();
-}
-};
-PlotKit.EasyPlot.onDataLoaded=function(_409){
-var _410=new Array();
-var _411=_409.responseText.split("\n");
-for(var i=0;i<_411.length;i++){
-var _412=MochiKit.Format.strip(_411[i]);
-if((_412.length>1)&&(_412.charAt(0)!="#")){
-_410.push(_412.split(","));
-}
-}
-this.layout.addDataset("data-ajax-"+this.deferredCount,_410);
-this.deferredCount--;
-if((this.deferredCount==0)&&(PlotKit.Base.keys(this.layout.datasets).length>0)){
-this.layout.evaluate();
-this.renderer.clear();
-this.renderer.render();
-}
-};
-PlotKit.EasyPlot.prototype.reload=function(){
-this.layout.evaluate();
-this.renderer.clear();
-this.renderer.render();
-};
-PlotKit.EasyPlotModule={};
-PlotKit.EasyPlotModule.EasyPlot=PlotKit.EasyPlot;
-PlotKit.EasyPlotModule.EXPORT=["EasyPlot"];
-PlotKit.EasyPlotModule.EXPORT_OK=[];
-PlotKit.EasyPlotModule.__new__=function(){
-var m=MochiKit.Base;
-m.nameFunctions(this);
-this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
-};
-PlotKit.EasyPlotModule.__new__();
-MochiKit.Base._exportSymbols(this,PlotKit.EasyPlotModule);
-
-
diff --git a/share/web/static/js/PlotKit/SVG.js b/share/web/static/js/PlotKit/SVG.js
deleted file mode 100644
index 3687bc0..0000000
--- a/share/web/static/js/PlotKit/SVG.js
+++ /dev/null
@@ -1,705 +0,0 @@
-/*
-    PlotKit SVG
-    ===========
-    SVG Renderer for PlotKit
-
-    Copyright
-    ---------
-    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
-    For use under the BSD license. <http://www.liquidx.net/plotkit>
-*/
-
-// -------------------------------------------------------------------------
-// NOTES: - If you use XHTML1.1 strict, then you must include each MochiKit
-//          file individuall.
-//        - For IE support, you must include the AdobeSVG object hack.
-//          See tests/svg.html for details.
-// -------------------------------------------------------------------------
-// -------------------------------------------------------------------------
-// Check required components
-// -------------------------------------------------------------------------
-
-try {    
-    if (typeof(PlotKit.Layout) == 'undefined')
-    {
-        throw "";    
-    }
-} 
-catch (e) {    
-    throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Layout"
-}
-
-
-// ---------------------------------------------------------------------------
-//  SVG Renderer
-// ---------------------------------------------------------------------------
-
-PlotKit.SVGRenderer = function(element, layout, options) {
-    if (arguments.length > 0) 
-        this.__init__(element, layout, options);
-};
-
-PlotKit.SVGRenderer.NAME = "PlotKit.SVGRenderer";
-PlotKit.SVGRenderer.VERSION = PlotKit.VERSION;
-
-PlotKit.SVGRenderer.__repr__ = function() {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.SVGRenderer.toString = function() {
-    return this.__repr__();
-}
-
-PlotKit.SVGRenderer.SVGNS = 'http://www.w3.org/2000/svg';
-
-PlotKit.SVGRenderer.prototype.__init__ = function(element, layout, options) {
-    var isNil = MochiKit.Base.isUndefinedOrNull;
-
-    // default options
-    this.options = {
-        "drawBackground": true,
-        "backgroundColor": Color.whiteColor(),
-        "padding": {left: 30, right: 30, top: 5, bottom: 10},
-        "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),
-        "strokeColor": Color.whiteColor(),
-        "strokeColorTransform": "asStrokeColor",
-        "strokeWidth": 0.5,
-        "shouldFill": true,
-        "shouldStroke": true,
-        "drawXAxis": true,
-        "drawYAxis": true,
-        "axisLineColor": Color.blackColor(),
-        "axisLineWidth": 0.5,
-        "axisTickSize": 3,
-        "axisLabelColor": Color.blackColor(),
-        "axisLabelFont": "Arial",
-        "axisLabelFontSize": 9,
-        "axisLabelWidth": 50,
-        "axisLabelUseDiv": true,
-        "pieRadius": 0.4,
-        "enableEvents": true
-    };
-
-    MochiKit.Base.update(this.options, options ? options : {});
-    this.layout = layout;
-    this.element = MochiKit.DOM.getElement(element);
-    this.container = this.element.parentNode;
-    this.height = parseInt(this.element.getAttribute("height"));
-    this.width = parseInt(this.element.getAttribute("width"));
-    this.document = document;
-    this.root = this.element;
-
-    // Adobe SVG Support:
-    // - if an exception is thrown, then no Adobe SVG Plugin support.
-    try {
-        this.document = this.element.getSVGDocument();
-        this.root = isNil(this.document.documentElement) ? this.element : this.document.documentElement;
-    }
-    catch (e) {
-    }
-
-    this.element.style.zIndex = 1;
-
-    if (isNil(this.element))
-        throw "SVGRenderer() - passed SVG object is not found";
-
-    if (isNil(this.container) || this.container.nodeName.toLowerCase() != "div")
-        throw "SVGRenderer() - No DIV's around the SVG.";
-
-    // internal state
-    this.xlabels = new Array();
-    this.ylabels = new Array();
-
-    // initialise some meta structures in SVG
-    this.defs = this.createSVGElement("defs");
-
-    this.area = {
-        x: this.options.padding.left,
-        y: this.options.padding.top,
-        w: this.width - this.options.padding.left - this.options.padding.right,
-        h: this.height - this.options.padding.top - this.options.padding.bottom
-    };
-
-    MochiKit.DOM.updateNodeAttributes(this.container, 
-    {"style":{ "position": "relative", "width": this.width + "px"}});
-
-    
-};
-
-
-PlotKit.SVGRenderer.prototype.render = function() {
-    if (this.options.drawBackground)
-        this._renderBackground();
-
-    if (this.layout.style == "bar") {
-        this._renderBarChart();
-        this._renderBarAxis();
-    }
-    else if (this.layout.style == "pie") {
-        this._renderPieChart();
-        this._renderPieAxis();
-    }
-    else if (this.layout.style == "line") {
-        this._renderLineChart();
-        this._renderLineAxis();
-    }
-};
-
-PlotKit.SVGRenderer.prototype._renderBarOrLine = function(data, plotFunc, startFunc, endFunc) {
-    
-    var colorCount = this.options.colorScheme.length;
-    var colorScheme = this.options.colorScheme;
-    var setNames = MochiKit.Base.keys(this.layout.datasets);
-    var setCount = setNames.length;
-
-    for (var i = 0; i < setCount; i++) {
-        var setName = setNames[i];
-        var attrs = new Array();
-        var color = colorScheme[i%colorCount];
-
-        if (this.options.shouldFill)
-            attrs["fill"] = color.toRGBString();
-        else
-            attrs["fill"] = "none";
-
-        if (this.options.shouldStroke && 
-            (this.options.strokeColor || this.options.strokeColorTransform)) {
-            if (this.options.strokeColor)
-                attrs["stroke"] = this.options.strokeColor.toRGBString();
-            else if (this.options.strokeColorTransform)
-                attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString();
-            attrs["strokeWidth"] = this.options.strokeWidth;
-        }
-
-        if (startFunc)
-            startFunc(attrs);
-
-        var forEachFunc = function(obj) {
-            if (obj.name == setName)
-                plotFunc(attrs, obj);
-        };                
-
-        MochiKit.Iter.forEach(data, bind(forEachFunc, this));
-        if (endFunc)
-            endFunc(attrs);
-    }
-};
-
-PlotKit.SVGRenderer.prototype._renderBarChart = function() {
-    var bind = MochiKit.Base.bind;
-
-    var drawRect = function(attrs, bar) {
-        var x = this.area.w * bar.x + this.area.x;
-        var y = this.area.h * bar.y + this.area.y;
-        var w = this.area.w * bar.w;
-        var h = this.area.h * bar.h;
-        this._drawRect(x, y, w, h, attrs);
-    };
-    this._renderBarOrLine(this.layout.bars, bind(drawRect, this));
-};
-
-PlotKit.SVGRenderer.prototype._renderLineChart = function() {
-    var bind = MochiKit.Base.bind;
-
-    var addPoint = function(attrs, point) {
-        this._tempPointsBuffer += (this.area.w * point.x + this.area.x) + "," +
-                                 (this.area.h * point.y + this.area.y) + " ";
-    };
-
-    var startLine = function(attrs) {
-        this._tempPointsBuffer = "";
-        this._tempPointsBuffer += (this.area.x) + "," + (this.area.y+this.area.h) + " ";
-    };
-
-    var endLine = function(attrs) {
-        this._tempPointsBuffer += (this.area.w + this.area.x) + ","  +(this.area.h + this.area.y);
-        attrs["points"] = this._tempPointsBuffer;
-        var elem = this.createSVGElement("polygon", attrs);
-        this.root.appendChild(elem);
-    };
-
-    this._renderBarOrLine(this.layout.points, 
-                          bind(addPoint, this), 
-                          bind(startLine, this), 
-                          bind(endLine, this));
-};
-
-
-PlotKit.SVGRenderer.prototype._renderPieChart = function() {
-    var colorCount = this.options.colorScheme.length;
-    var slices = this.layout.slices;
-
-    var centerx = this.area.x + this.area.w * 0.5;
-    var centery = this.area.y + this.area.h * 0.5;
-    var radius = Math.min(this.area.w * this.options.pieRadius, 
-                          this.area.h * this.options.pieRadius);
-
-    // NOTE NOTE!! Canvas Tag draws the circle clockwise from the y = 0, x = 1
-    // so we have to subtract 90 degrees to make it start at y = 1, x = 0
-
-	// workaround if we only have 1 slice of 100%
-	if (slices.length == 1 && (Math.abs(slices[0].startAngle) - Math.abs(slices[0].endAngle) < 0.1)) {
-        var attrs = {"cx": centerx , "cy": centery , "r": radius };
-        var color = this.options.colorScheme[0];
-        if (this.options.shouldFill)
-            attrs["fill"] = color.toRGBString();
-        else
-            attrs["fill"] = "none";
-
-        if (this.options.shouldStroke && 
-            (this.options.strokeColor || this.options.strokeColorTransform)) {
-            if (this.options.strokeColor)
-                attrs["stroke"] = this.options.strokeColor.toRGBString();
-            else if (this.options.strokeColorTransform)
-                attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString();
-            attrs["style"] = "stroke-width: " + this.options.strokeWidth;
-        }
-
-        this.root.appendChild(this.createSVGElement("circle", attrs));
-        return;
-	}
-
-    for (var i = 0; i < slices.length; i++) {
-        var attrs = new Array();
-        var color = this.options.colorScheme[i%colorCount];
-        if (this.options.shouldFill)
-            attrs["fill"] = color.toRGBString();
-        else
-            attrs["fill"] = "none";
-
-        if (this.options.shouldStroke &&
-            (this.options.strokeColor || this.options.strokeColorTransform)) {
-            if (this.options.strokeColor)
-                attrs["stroke"] = this.options.strokeColor.toRGBString();
-            else if (this.options.strokeColorTransform)
-                attrs["stroke"] = color[this.options.strokeColorTransform]().toRGBString();
-            attrs["style"] = "stroke-width:" + this.options.strokeWidth;
-        }
-
-        var largearc = 0;
-        if (Math.abs(slices[i].endAngle - slices[i].startAngle) > Math.PI)
-            largearc = 1;
-        var x1 = Math.cos(slices[i].startAngle - Math.PI/2) * radius;
-        var y1 = Math.sin(slices[i].startAngle - Math.PI/2) * radius;
-        var x2 = Math.cos(slices[i].endAngle - Math.PI/2) * radius;
-        var y2 = Math.sin(slices[i].endAngle - Math.PI/2) * radius;
-        var rx = x2 - x1;
-        var ry = y2 - y1;
-
-        var pathString = "M" + centerx + "," + centery + " ";       
-        pathString += "l" + x1 + "," + y1 + " ";
-        pathString += "a" + radius + "," + radius + " 0 " + largearc + ",1 " + rx + "," + ry + " z";
-
-        attrs["d"] = pathString;
-
-        var elem = this.createSVGElement("path", attrs);
-        this.root.appendChild(elem);
-    }
-};
-
-PlotKit.SVGRenderer.prototype._renderBarAxis = function() {
-    this._renderAxis();
-}
-
-PlotKit.SVGRenderer.prototype._renderLineAxis = function() {
-    this._renderAxis();
-};
-
-
-PlotKit.SVGRenderer.prototype._renderAxis = function() {
-
-    if (!this.options.drawXAxis && !this.options.drawYAxis)
-        return;
-
-    var labelStyle = {"style":
-         {"position": "absolute",
-          "textAlign": "center",
-          "fontSize": this.options.axisLabelFontSize + "px",
-          "zIndex": 10,
-          "color": this.options.axisLabelColor.toRGBString(),
-          "width": this.options.axisLabelWidth + "px",
-          "overflow": "hidden"
-         }
-    };
-
-    // axis lines
-    var lineAttrs = {
-        "stroke": this.options.axisLineColor.toRGBString(),
-        "strokeWidth": this.options.axisLineWidth
-    };
-    
-
-    if (this.options.drawYAxis) {
-        if (this.layout.yticks) {
-            var drawTick = function(tick) {
-                var x = this.area.x;
-                var y = this.area.y + tick[0] * this.area.h;
-                this._drawLine(x, y, x - 3, y, lineAttrs);
-                
-                if (this.options.axisLabelUseDiv) {
-                    var label = DIV(labelStyle, tick[1]);
-                    label.style.top = (y - this.options.axisLabelFontSize) + "px";
-                    label.style.left = (x - this.options.padding.left + this.options.axisTickSize) + "px";
-                    label.style.textAlign = "left";
-                    label.style.width = (this.options.padding.left - 3) + "px";
-                    MochiKit.DOM.appendChildNodes(this.container, label);
-                    this.ylabels.push(label);
-                }
-                else {
-                    var attrs = {
-                        y: y + 3,
-                        x: (x - this.options.padding.left + 3),
-                        width: (this.options.padding.left - this.options.axisTickSize) + "px",
-                        height: (this.options.axisLabelFontSize + 3) + "px",
-                        fontFamily: "Arial",
-                        fontSize: this.options.axisLabelFontSize + "px",
-                        fill: this.options.axisLabelColor.toRGBString()
-                    };
-                    
-                    /* we can do clipping just like DIVs
-                    http://www.xml.com/pub/a/2004/06/02/svgtype.html */
-                    /*
-                    var mask = this.createSVGElement("mask", {id: "mask" + tick[0]});
-                    var maskShape = this.createSVGElement("rect",
-                        {y: y + 3,
-                         x: (x - this.options.padding.left + 3),
-                         width: (this.options.padding.left - this.options.axisTickSize) + "px",
-                         height: (this.options.axisLabelFontSize + 3) + "px",
-                         style: {"fill": "#ffffff", "stroke": "#000000"}});
-                    mask.appendChild(maskShape);
-                    this.defs.appendChild(mask);
-                    
-                    attrs["filter"] = "url(#mask" + tick[0] + ")";
-                    */
-                    
-                    var label = this.createSVGElement("text", attrs);
-                    label.appendChild(this.document.createTextNode(tick[1]));
-                    this.root.appendChild(label);
-                }
-            };
-            
-            MochiKit.Iter.forEach(this.layout.yticks, bind(drawTick, this));
-        }
-
-        this._drawLine(this.area.x, this.area.y, this.area.x, this.area.y + this.area.h, lineAttrs);
-    }
-
-    if (this.options.drawXAxis) {
-        if (this.layout.xticks) {
-            var drawTick = function(tick) {
-                var x = this.area.x + tick[0] * this.area.w;
-                var y = this.area.y + this.area.h;
-                this._drawLine(x, y, x, y + this.options.axisTickSize, lineAttrs);
-
-                if (this.options.axisLabelUseDiv) {
-                    var label = DIV(labelStyle, tick[1]);
-                    label.style.top = (y + this.options.axisTickSize) + "px";
-                    label.style.left = (x - this.options.axisLabelWidth/2) + "px";
-                    label.style.textAlign = "center";
-                    label.style.width = this.options.axisLabelWidth + "px";
-                    MochiKit.DOM.appendChildNodes(this.container, label);
-                    this.xlabels.push(label);
-                }
-                else {
-                    var attrs = {
-                        y: (y + this.options.axisTickSize + this.options.axisLabelFontSize),
-                        x: x - 3,
-                        width: this.options.axisLabelWidth + "px",
-                        height: (this.options.axisLabelFontSize + 3) + "px",
-                        fontFamily: "Arial",
-                        fontSize: this.options.axisLabelFontSize + "px",
-                        fill: this.options.axisLabelColor.toRGBString(),
-                        textAnchor: "middle"
-                    };
-                    var label = this.createSVGElement("text", attrs);
-                    label.appendChild(this.document.createTextNode(tick[1]));
-                    this.root.appendChild(label);
-                }
-            };
-            
-            MochiKit.Iter.forEach(this.layout.xticks, bind(drawTick, this));
-        }
-
-        this._drawLine(this.area.x, this.area.y + this.area.h, this.area.x + this.area.w, this.area.y + this.area.h, lineAttrs)
-    }
-};
-
-PlotKit.SVGRenderer.prototype._renderPieAxis = function() {
-
-    if (this.layout.xticks) {
-        // make a lookup dict for x->slice values
-        var lookup = new Array();
-        for (var i = 0; i < this.layout.slices.length; i++) {
-            lookup[this.layout.slices[i].xval] = this.layout.slices[i];
-        }
-        
-        var centerx = this.area.x + this.area.w * 0.5;
-        var centery = this.area.y + this.area.h * 0.5;
-        var radius = Math.min(this.area.w * this.options.pieRadius + 10, 
-                              this.area.h * this.options.pieRadius + 10);
-        var labelWidth = this.options.axisLabelWidth;
-        
-        for (var i = 0; i < this.layout.xticks.length; i++) {
-            var slice = lookup[this.layout.xticks[i][0]];
-            if (MochiKit.Base.isUndefinedOrNull(slice))
-                continue;
-                
-                
-            var angle = (slice.startAngle + slice.endAngle)/2;
-            // normalize the angle
-            var normalisedAngle = angle;
-            if (normalisedAngle > Math.PI * 2)
-                normalisedAngle = normalisedAngle - Math.PI * 2;
-            else if (normalisedAngle < 0)
-                normalisedAngle = normalisedAngle + Math.PI * 2;
-                
-            var labelx = centerx + Math.sin(normalisedAngle) * (radius + 10);
-            var labely = centery - Math.cos(normalisedAngle) * (radius + 10);
-
-            var attrib = {
-                "position": "absolute",
-                 "zIndex": 11,
-                "width": labelWidth + "px",
-                "fontSize": this.options.axisLabelFontSize + "px",
-                "overflow": "hidden",
-                "color": this.options.axisLabelColor.toHexString()
-            };
-
-            var svgattrib = {
-                "width": labelWidth + "px",
-                "fontSize": this.options.axisLabelFontSize + "px",
-                "height": (this.options.axisLabelFontSize + 3) + "px",
-                "fill": this.options.axisLabelColor.toRGBString()
-            };
-
-            if (normalisedAngle <= Math.PI * 0.5) {
-                // text on top and align left
-                MochiKit.Base.update(attrib, {
-                    'textAlign': 'left', 'verticalAlign': 'top',
-                    'left': labelx + 'px',
-                    'top':  (labely - this.options.axisLabelFontSize) + "px"
-                });
-                MochiKit.Base.update(svgattrib, {
-                    "x": labelx,
-                    "y" :(labely - this.options.axisLabelFontSize),
-                    "textAnchor": "left"
-                        });
-            }
-            else if ((normalisedAngle > Math.PI * 0.5) && (normalisedAngle <= Math.PI)) {
-                // text on bottom and align left
-                MochiKit.Base.update(attrib, {
-                    'textAlign': 'left', 'verticalAlign': 'bottom',
-                    'left': labelx + 'px',
-                    'top':  labely + "px"
-                });
-                MochiKit.Base.update(svgattrib, {
-                    'textAnchor': 'left',
-                    'x': labelx,
-                    'y':  labely
-                });
-            }
-            else if ((normalisedAngle > Math.PI) && (normalisedAngle <= Math.PI*1.5)) {
-                // text on bottom and align right
-                MochiKit.Base.update(attrib, {
-                    'textAlign': 'right', 'verticalAlign': 'bottom',
-                    'left': labelx + 'px',
-                    'top':  labely + "px"
-                });
-                MochiKit.Base.update(svgattrib, {
-                    'textAnchor': 'right',
-                    'x': labelx - labelWidth,
-                    'y':  labely
-                });
-            }
-            else {
-                // text on top and align right
-                MochiKit.Base.update(attrib, {
-                    'textAlign': 'left', 'verticalAlign': 'bottom',
-                    'left': labelx + 'px',
-                    'top':  labely + "px"
-                });
-                MochiKit.Base.update(svgattrib, {
-                    'textAnchor': 'left',
-                    'x': labelx - labelWidth,
-                    'y':  labely - this.options.axisLabelFontSize
-                });
-            }
-
-            if (this.options.axisLabelUseDiv) {
-                var label = DIV({'style': attrib}, this.layout.xticks[i][1]);
-                this.xlabels.push(label);
-                MochiKit.DOM.appendChildNodes(this.container, label);
-            }
-            else {
-                var label = this.createSVGElement("text", svgattrib);
-                label.appendChild(this.document.createTextNode(this.layout.xticks[i][1]))
-                this.root.appendChild(label);
-            }
-      }
-        
-    }
-};
-
-PlotKit.SVGRenderer.prototype._renderBackground = function() {
-    var opts = {"stroke": "none",
-                  "fill": this.options.backgroundColor.toRGBString()
-    };
-    this._drawRect(0, 0, this.width, this.height, opts);
-};
-
-PlotKit.SVGRenderer.prototype._drawRect = function(x, y, w, h, moreattrs) {
-    var attrs = {x: x + "px", y: y + "px", width: w + "px", height: h + "px"};
-    if (moreattrs)
-        MochiKit.Base.update(attrs, moreattrs);
-
-    var elem = this.createSVGElement("rect", attrs);
-    this.root.appendChild(elem);
-};
-
-PlotKit.SVGRenderer.prototype._drawLine = function(x1, y1, x2, y2, moreattrs) {
-    var attrs = {x1: x1 + "px", y1: y1 + "px", x2: x2 + "px", y2: y2 + "px"};
-    if (moreattrs)
-        MochiKit.Base.update(attrs, moreattrs);
-
-    var elem = this.createSVGElement("line", attrs);
-    this.root.appendChild(elem);
-}
-
-PlotKit.SVGRenderer.prototype.clear = function() {
-    while(this.element.firstChild) {
-        this.element.removeChild(this.element.firstChild);
-    }
-    
-    if (this.options.axisLabelUseDiv) {
-        for (var i = 0; i < this.xlabels.length; i++) {
-            MochiKit.DOM.removeElement(this.xlabels[i]);
-        }        
-        for (var i = 0; i < this.ylabels.length; i++) {
-            MochiKit.DOM.removeElement(this.ylabels[i]);
-        }            
-    }
-    this.xlabels = new Array();
-    this.ylabels = new Array();
-};
-
-
-PlotKit.SVGRenderer.prototype.createSVGElement = function(name, attrs) {
-    var isNil = MochiKit.Base.isUndefinedOrNull;
-    var elem;
-    var doc = isNil(this.document) ? document : this.document;
-
-    try {
-        elem = doc.createElementNS(PlotKit.SVGRenderer.SVGNS, name);
-    }
-    catch (e) {
-        elem = doc.createElement(name);
-        elem.setAttribute("xmlns", PlotKit.SVGRenderer.SVGNS);
-    }
-
-    if (attrs)
-        MochiKit.DOM.updateNodeAttributes(elem, attrs);
-
-    // TODO: we don't completely emulate the MochiKit.DOM.createElement
-    //       as we don't care about nodes contained. We really should though.
-
-    return elem;
-
-};
-
-
-PlotKit.SVGRenderer.SVG = function(attrs) {
-    // we have to do things differently for IE+AdobeSVG.
-    // My guess this works (via trial and error) is that we need to
-    // have an SVG object in order to use SVGDocument.createElementNS
-    // but IE doesn't allow us to that.
-
-    var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
-    var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
-    if (ie && (ie[1] >= 6) && (!opera)) {
-        var width = attrs["width"] ? attrs["width"] : "100";
-        var height = attrs["height"] ? attrs["height"] : "100";
-        var eid = attrs["id"] ? attrs["id"] : "notunique";
-        
-        var html = '<svg:svg width="' + width + '" height="' + height + '" ';
-        html += 'id="' + eid + '" version="1.1" baseProfile="full" />';
-
-        var canvas = document.createElement(html);
-
-        // create embedded SVG inside SVG.
-        var group = canvas.getSVGDocument().createElementNS(PlotKit.SVGRenderer.SVGNS, "svg");
-        group.setAttribute("width", width);
-        group.setAttribute("height", height);
-        canvas.getSVGDocument().appendChild(group);
-
-        return canvas;
-    }
-    else {
-        return PlotKit.SVGRenderer.prototype.createSVGElement("svg", attrs);
-    }
-};
-
-PlotKit.SVGRenderer.isSupported = function() {
-    var isOpera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
-    var ieVersion = navigator.appVersion.match(/MSIE (\d\.\d)/);
-    var safariVersion = navigator.userAgent.match(/AppleWebKit\/(\d+)/);
-    var operaVersion = navigator.userAgent.match(/Opera\/(\d*\.\d*)/);
-    var mozillaVersion = navigator.userAgent.match(/rv:(\d*\.\d*).*Gecko/);
-    var svgFeature = "http://www.w3.org/TR/SVG11/feature#SVG";
-
-    if (ieVersion && (ieVersion[1] >= 6) && !isOpera) {
-        return document.implementation.hasFeature(svgFeature,"1.1");
-        /*
-        var dummysvg = document.createElement('<svg:svg width="1" height="1" baseProfile="full" version="1.1" id="dummy">');
-        try {
-            dummysvg.getSVGDocument();
-            dummysvg = null;
-            return true;
-        }
-        catch (e) {
-            return false;
-        }
-        */
-        
-    }
-    
-    /* support not really there yet. no text and paths are buggy
-    if (safariVersion && (safariVersion[1] > 419))
-        return true;
-    */
-
-    if (operaVersion && (operaVersion[1] > 8.9))
-        return true
-    
-    if (mozillaVersion && (mozillaVersion > 1.7))
-        return true;
-    
-    return false;
-};
-
-// Namespace Iniitialisation
-
-PlotKit.SVG = {}
-PlotKit.SVG.SVGRenderer = PlotKit.SVGRenderer;
-
-PlotKit.SVG.EXPORT = [
-    "SVGRenderer"
-];
-
-PlotKit.SVG.EXPORT_OK = [
-    "SVGRenderer"
-];
-
-PlotKit.SVG.__new__ = function() {
-    var m = MochiKit.Base;
-    
-    m.nameFunctions(this);
-    
-    this.EXPORT_TAGS = {
-        ":common": this.EXPORT,
-        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
-    };
-};
-
-PlotKit.SVG.__new__();
-MochiKit.Base._exportSymbols(this, PlotKit.SVG);
-
diff --git a/share/web/static/js/PlotKit/SweetCanvas.js b/share/web/static/js/PlotKit/SweetCanvas.js
deleted file mode 100644
index bb6fe5c..0000000
--- a/share/web/static/js/PlotKit/SweetCanvas.js
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
-    PlotKit Sweet Canvas Renderer
-    =============================
-    Canvas Renderer for PlotKit which looks pretty!
-
-    Copyright
-    ---------
-    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
-    For use under the BSD license. <http://www.liquidx.net/plotkit>
-*/
-
-// -------------------------------------------------------------------------
-// Check required components
-// -------------------------------------------------------------------------
-
-try {    
-    if (typeof(PlotKit.CanvasRenderer) == 'undefined')
-    {
-        throw "";    
-    }
-} 
-catch (e) {    
-    throw "SweetCanvas depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, Canvas}"
-}
-
-
-if (typeof(PlotKit.SweetCanvasRenderer) == 'undefined') {
-    PlotKit.SweetCanvasRenderer = {};
-}
-
-PlotKit.SweetCanvasRenderer = function(element, layout, options) {
-    if (arguments.length > 0) {
-        this.__init__(element, layout, options);
-    }
-};
-
-PlotKit.SweetCanvasRenderer.NAME = "PlotKit.SweetCanvasRenderer";
-PlotKit.SweetCanvasRenderer.VERSION = PlotKit.VERSION;
-
-PlotKit.SweetCanvasRenderer.__repr__ = function() {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.SweetCanvasRenderer.toString = function() {
-    return this.__repr__();
-};
-
-// ---------------------------------------------------------------------
-// Subclassing Magic
-// ---------------------------------------------------------------------
-
-PlotKit.SweetCanvasRenderer.prototype = new PlotKit.CanvasRenderer();
-PlotKit.SweetCanvasRenderer.prototype.constructor = PlotKit.SweetCanvasRenderer;
-PlotKit.SweetCanvasRenderer.__super__ = PlotKit.CanvasRenderer.prototype;
-
-// ---------------------------------------------------------------------
-// Constructor
-// ---------------------------------------------------------------------
-
-PlotKit.SweetCanvasRenderer.prototype.__init__ = function(el, layout, opts) { 
-    var moreOpts = PlotKit.Base.officeBlue();
-    MochiKit.Base.update(moreOpts, opts);
-    PlotKit.SweetCanvasRenderer.__super__.__init__.call(this, el, layout, moreOpts);
-};
-
-// ---------------------------------------------------------------------
-// Extended Plotting Functions
-// ---------------------------------------------------------------------
-
-PlotKit.SweetCanvasRenderer.prototype._renderBarChart = function() {
-    var bind = MochiKit.Base.bind;
-    var shadowColor = Color.blackColor().colorWithAlpha(0.1).toRGBString();
-
-    var prepareFakeShadow = function(context, x, y, w, h) {
-        context.fillStyle = shadowColor;
-        context.fillRect(x-2, y-2, w+4, h+2); 
-        context.fillStyle = shadowColor;
-        context.fillRect(x-1, y-1, w+2, h+1); 
-    };
-
-    var colorCount = this.options.colorScheme.length;
-    var colorScheme =  this.options.colorScheme;
-    var setNames = PlotKit.Base.keys(this.layout.datasets);
-    var setCount = setNames.length;
-
-    var chooseColor = function(name) {
-        for (var i = 0; i < setCount; i++) {
-            if (name == setNames[i])
-                return colorScheme[i%colorCount];
-        }
-        return colorScheme[0];
-    };
-
-    var drawRect = function(context, bar) {
-        var x = this.area.w * bar.x + this.area.x;
-        var y = this.area.h * bar.y + this.area.y;
-        var w = this.area.w * bar.w;
-        var h = this.area.h * bar.h;
-
-        if ((w < 1) || (h < 1))
-            return;        
-
-        context.save();
-
-        context.shadowBlur = 5.0;
-        context.shadowColor = Color.fromHexString("#888888").toRGBString();
-
-        if (this.isIE) {
-            context.save();
-            context.fillStyle = "#cccccc";
-            context.fillRect(x-2, y-2, w+4, h+2); 
-            context.restore();
-        }
-        else {
-            prepareFakeShadow(context, x, y, w, h);
-        }
-
-        if (this.options.shouldFill) {
-            context.fillStyle = chooseColor(bar.name).toRGBString();
-            context.fillRect(x, y, w, h);
-        }
-
-        context.shadowBlur = 0;
-        context.strokeStyle = Color.whiteColor().toRGBString();
-        context.lineWidth = 2.0;
-        
-        if (this.options.shouldStroke) {
-            context.strokeRect(x, y, w, h);                
-        }
-
-        context.restore();
-
-    };
-    this._renderBarChartWrap(this.layout.bars, bind(drawRect, this));
-};
-
-PlotKit.SweetCanvasRenderer.prototype._renderLineChart = function() {
-    var context = this.element.getContext("2d");
-    var colorCount = this.options.colorScheme.length;
-    var colorScheme = this.options.colorScheme;
-    var setNames = PlotKit.Base.keys(this.layout.datasets);
-    var setCount = setNames.length;
-    var bind = MochiKit.Base.bind;
-
-
-    for (var i = 0; i < setCount; i++) {
-        var setName = setNames[i];
-        var color = colorScheme[i%colorCount];
-        var strokeX = this.options.strokeColorTransform;
-
-        // setup graphics context
-        context.save();
-        
-        // create paths
-        var makePath = function(ctx) {
-            ctx.beginPath();
-            ctx.moveTo(this.area.x, this.area.y + this.area.h);
-            var addPoint = function(ctx_, point) {
-            if (point.name == setName)
-                ctx_.lineTo(this.area.w * point.x + this.area.x,
-                            this.area.h * point.y + this.area.y);
-            };
-            MochiKit.Iter.forEach(this.layout.points, partial(addPoint, ctx), this);
-            ctx.lineTo(this.area.w + this.area.x,
-                           this.area.h + this.area.y);
-            ctx.lineTo(this.area.x, this.area.y + this.area.h);
-            ctx.closePath();
-        };
-
-        // faux shadow for firefox
-        if (this.options.shouldFill) {
-            context.save();
-            if (this.isIE) {
-                context.fillStyle = "#cccccc";
-            }
-            else {
-                context.fillStyle = Color.blackColor().colorWithAlpha(0.2).toRGBString();
-            }
-            context.translate(-1, -2);
-            bind(makePath, this)(context);
-            if (this.options.shouldFill) {
-                context.fill();
-            }
-            context.restore();
-        }
-
-        context.shadowBlur = 5.0;
-        context.shadowColor = Color.fromHexString("#888888").toRGBString();
-        context.fillStyle = color.toRGBString();
-        context.lineWidth = 2.0;
-        context.strokeStyle = Color.whiteColor().toRGBString();
-
-        if (this.options.shouldFill) {
-            bind(makePath, this)(context);
-            context.fill();
-        }
-        if (this.options.shouldStroke) {
-            bind(makePath, this)(context);
-            context.stroke();
-        }
-        context.restore();
-    }
-};
-
-PlotKit.SweetCanvasRenderer.prototype._renderPieChart = function() {
-    var context = this.element.getContext("2d");
-
-    var colorCount = this.options.colorScheme.length;
-    var slices = this.layout.slices;
-
-    var centerx = this.area.x + this.area.w * 0.5;
-    var centery = this.area.y + this.area.h * 0.5;
-    var radius = Math.min(this.area.w * this.options.pieRadius, 
-                          this.area.h * this.options.pieRadius);
-
-    if (this.isIE) {
-        centerx = parseInt(centerx);
-        centery = parseInt(centery);
-        radius = parseInt(radius);
-    }
-
-	// NOTE NOTE!! Canvas Tag draws the circle clockwise from the y = 0, x = 1
-	// so we have to subtract 90 degrees to make it start at y = 1, x = 0
-
-    if (!this.isIE) {
-        context.save();
-        var shadowColor = Color.blackColor().colorWithAlpha(0.2);
-        context.fillStyle = shadowColor.toRGBString();
-        context.shadowBlur = 5.0;
-        context.shadowColor = Color.fromHexString("#888888").toRGBString();
-        context.translate(1, 1);
-        context.beginPath();
-        context.moveTo(centerx, centery);
-        context.arc(centerx, centery, radius + 2, 0, Math.PI*2, false);
-        context.closePath();
-        context.fill();
-        context.restore();
-    }
-
-    context.save();
-    context.strokeStyle = Color.whiteColor().toRGBString();
-    context.lineWidth = 2.0;    
-    for (var i = 0; i < slices.length; i++) {
-        var color = this.options.colorScheme[i%colorCount];
-        context.fillStyle = color.toRGBString();
-
-        var makePath = function() {
-            context.beginPath();
-            context.moveTo(centerx, centery);
-            context.arc(centerx, centery, radius, 
-                        slices[i].startAngle - Math.PI/2,
-                        slices[i].endAngle - Math.PI/2,
-                        false);
-            context.lineTo(centerx, centery);
-            context.closePath();
-        };
-
-        if (Math.abs(slices[i].startAngle - slices[i].endAngle) > 0.0001) {
-            if (this.options.shouldFill) {
-                makePath();
-                context.fill();
-            }
-            if (this.options.shouldStroke) {
-                makePath();
-                context.stroke();
-            }
-        }
-    }
-    context.restore();
-};
-
-PlotKit.SweetCanvasRenderer.prototype._renderBackground = function() {
-    var context = this.element.getContext("2d");
-   
-    if (this.layout.style == "bar" || this.layout.style == "line") {
-        context.save();
-        context.fillStyle = this.options.backgroundColor.toRGBString();
-        context.fillRect(this.area.x, this.area.y, this.area.w, this.area.h);
-        context.strokeStyle = this.options.axisLineColor.toRGBString();
-        context.lineWidth = 1.0;
-        
-        var ticks = this.layout.yticks;
-        var horiz = false;
-        if (this.layout.style == "bar" && 
-            this.layout.options.barOrientation == "horizontal") {
-                ticks = this.layout.xticks;
-                horiz = true;
-        }
-        
-        for (var i = 0; i < ticks.length; i++) {
-            var x1 = 0;
-            var y1 = 0;
-            var x2 = 0;
-            var y2 = 0;
-            
-            if (horiz) {
-                x1 = ticks[i][0] * this.area.w + this.area.x;
-                y1 = this.area.y;
-                x2 = x1;
-                y2 = y1 + this.area.h;
-            }
-            else {
-                x1 = this.area.x;
-                y1 = ticks[i][0] * this.area.h + this.area.y;
-                x2 = x1 + this.area.w;
-                y2 = y1;
-            }
-            
-            context.beginPath();
-            context.moveTo(x1, y1);
-            context.lineTo(x2, y2);
-            context.closePath();
-            context.stroke();
-        }
-        context.restore();
-    }
-    else {
-        PlotKit.SweetCanvasRenderer.__super__._renderBackground.call(this);
-    }
-};
-
-// Namespace Iniitialisation
-
-PlotKit.SweetCanvas = {}
-PlotKit.SweetCanvas.SweetCanvasRenderer = PlotKit.SweetCanvasRenderer;
-
-PlotKit.SweetCanvas.EXPORT = [
-    "SweetCanvasRenderer"
-];
-
-PlotKit.SweetCanvas.EXPORT_OK = [
-    "SweetCanvasRenderer"
-];
-
-PlotKit.SweetCanvas.__new__ = function() {
-    var m = MochiKit.Base;
-    
-    m.nameFunctions(this);
-    
-    this.EXPORT_TAGS = {
-        ":common": this.EXPORT,
-        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
-    };
-};
-
-PlotKit.SweetCanvas.__new__();
-MochiKit.Base._exportSymbols(this, PlotKit.SweetCanvas);
-
diff --git a/share/web/static/js/PlotKit/SweetSVG.js b/share/web/static/js/PlotKit/SweetSVG.js
deleted file mode 100644
index 4183058..0000000
--- a/share/web/static/js/PlotKit/SweetSVG.js
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
-    PlotKit Sweet SVG Renderer
-    ==========================
-    SVG Renderer for PlotKit which looks pretty!
-
-    Copyright
-    ---------
-    Copyright 2005,2006 (c) Alastair Tse <alastair^liquidx.net>
-    For use under the BSD license. <http://www.liquidx.net/plotkit>
-*/
-
-
-// -------------------------------------------------------------------------
-// Check required components
-// -------------------------------------------------------------------------
-
-try {    
-    if (typeof(PlotKit.SVGRenderer) == 'undefined')
-    {
-        throw "";    
-    }
-} 
-catch (e) {    
-    throw "SweetSVG depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, SVG}"
-}
-
-
-if (typeof(PlotKit.SweetSVGRenderer) == 'undefined') {
-    PlotKit.SweetSVGRenderer = {};
-}
-
-PlotKit.SweetSVGRenderer = function(element, layout, options) {
-    if (arguments.length > 0) {
-        this.__init__(element, layout, options);
-    }
-};
-
-PlotKit.SweetSVGRenderer.NAME = "PlotKit.SweetSVGRenderer";
-PlotKit.SweetSVGRenderer.VERSION = PlotKit.VERSION;
-
-PlotKit.SweetSVGRenderer.__repr__ = function() {
-    return "[" + this.NAME + " " + this.VERSION + "]";
-};
-
-PlotKit.SweetSVGRenderer.toString = function() {
-    return this.__repr__();
-};
-
-// ---------------------------------------------------------------------
-// Subclassing Magic
-// ---------------------------------------------------------------------
-
-PlotKit.SweetSVGRenderer.prototype = new PlotKit.SVGRenderer();
-PlotKit.SweetSVGRenderer.prototype.constructor = PlotKit.SweetSVGRenderer;
-PlotKit.SweetSVGRenderer.__super__ = PlotKit.SVGRenderer.prototype;
-
-// ---------------------------------------------------------------------
-// Constructor
-// ---------------------------------------------------------------------
-
-PlotKit.SweetSVGRenderer.prototype.__init__ = function(element, layout, options) { 
-    var moreOpts = PlotKit.Base.officeBlue();
-    MochiKit.Base.update(moreOpts, options);
-    PlotKit.SweetSVGRenderer.__super__.__init__.call(this, element, layout, moreOpts);
-    //this._addDropShadowFilter();
-};
-
-PlotKit.SweetSVGRenderer.prototype._addDropShadowFilter = function() {
-    var filter = this.createSVGElement("filter", {x: 0, y: 0, "id":"dropShadow"});
-    var goffset = this.createSVGElement("feOffset",
-        {"in": "SourceGraphic", "dx": 0, "dy": 0, "result": "topCopy"});
-    var blur = this.createSVGElement("feGaussianBlur",
-        {"in": "SourceAlpha", "StdDeviation": 2, "result": "shadow"});
-    var soffset = this.createSVGElement("feOffset",
-        {"in": "shadow", "dx": -1, "dy": -2, "result":"movedShadow"});
-    var merge = this.createSVGElement("feMerge");
-    var gmerge = this.createSVGElement("feMergeNode", {"in":"topCopy"});
-    var smerge = this.createSVGElement("feMergeNode", {"in":"movedShadow"});
-    
-    merge.appendChild(gmerge);
-    merge.appendChild(smerge);
-    filter.appendChild(goffset);
-    filter.appendChild(blur);
-    filter.appendChild(soffset);
-    filter.appendChild(merge);
-    this.defs.appendChild(filter);
-};
-
-// ---------------------------------------------------------------------
-// Extended Plotting Functions
-// ---------------------------------------------------------------------
-
-PlotKit.SweetSVGRenderer.prototype._renderBarChart = function() {
-    var bind = MochiKit.Base.bind;
-    var shadowColor = Color.blackColor().toRGBString();
-    var shadowStyle = "fill:" + shadowColor + ";fill-opacity:0.15";
-    var strokeStyle = "stroke-width: 2.0; stroke:" + Color.whiteColor().toRGBString();
-    
-    var drawRect = function(attrs, bar) {
-        var x = this.area.w * bar.x + this.area.x;
-        var y = this.area.h * bar.y + this.area.y;
-        var w = this.area.w * bar.w;
-        var h = this.area.h * bar.h;
-
-        if ((w < 1) || (h < 1))
-            return;        
-
-        //attrs["filter"] = "url(#dropShadow)";
-        attrs["style"] = strokeStyle;
-        this._drawRect(x - 2, y - 1, w+4, h+2, {"style":shadowStyle});
-        this._drawRect(x, y, w, h, attrs);
-    };
-    this._renderBarOrLine(this.layout.bars, bind(drawRect, this));
-
-};
-
-PlotKit.SweetSVGRenderer.prototype._renderLineChart = function() {
-    var bind = MochiKit.Base.bind;
-    var shadowColor = Color.blackColor().toRGBString();
-    var shadowStyle = "fill:" + shadowColor + ";fill-opacity:0.15";
-    var strokeStyle = "stroke-width: 2.0; stroke:" + Color.whiteColor().toRGBString();
-
-    var addPoint = function(attrs, point) {
-        this._tempPointsBuffer += (this.area.w * point.x + this.area.x) + "," +
-                                 (this.area.h * point.y + this.area.y) + " ";
-    };
-
-    var startLine = function(attrs) {
-        this._tempPointsBuffer = "";
-        this._tempPointsBuffer += (this.area.x) + "," + (this.area.y+this.area.h) + " ";
-    };
-
-    var endLine = function(attrs) {
-        this._tempPointsBuffer += (this.area.w + this.area.x) + ","  +(this.area.h + this.area.y);
-        attrs["points"] = this._tempPointsBuffer;    
-            
-        attrs["stroke"] = "none";
-        attrs["transform"] = "translate(-2, -1)";
-        attrs["style"] = shadowStyle;
-        var shadow = this.createSVGElement("polygon", attrs);
-        this.root.appendChild(shadow);
-        
-        attrs["transform"] = "";
-        attrs["style"] = strokeStyle;
-        var elem = this.createSVGElement("polygon", attrs);
-        this.root.appendChild(elem);
-        
-       
-    };
-
-    this._renderBarOrLine(this.layout.points, 
-                             bind(addPoint, this), 
-                             bind(startLine, this), 
-                             bind(endLine, this));
-};
-
-PlotKit.SweetSVGRenderer.prototype._renderPieChart = function() {
-    var centerx = this.area.x + this.area.w * 0.5;
-    var centery = this.area.y + this.area.h * 0.5;
-    var shadowColor = Color.blackColor().toRGBString();
-    var radius = Math.min(this.area.w * this.options.pieRadius, 
-                          this.area.h * this.options.pieRadius);
-    var shadowStyle = "fill:" + shadowColor + ";fill-opacity:0.15";
-    
-    var shadow = this.createSVGElement("circle", 
-        {"style": shadowStyle, "cx": centerx + 1, "cy": centery + 1, "r": radius + 1});
-    this.root.appendChild(shadow);
-                             
-    PlotKit.SweetSVGRenderer.__super__._renderPieChart.call(this);
-};
-    
-
-PlotKit.SweetSVGRenderer.prototype._renderBackground = function() {
-    var attrs = {
-        "fill": this.options.backgroundColor.toRGBString(),
-        "stroke": "none"
-    };
-    
-
-    if (this.layout.style == "bar" || this.layout.style == "line") {
-        this._drawRect(this.area.x, this.area.y, 
-                       this.area.w, this.area.h, attrs);
-                       
-        var ticks = this.layout.yticks;
-        var horiz = false;
-        if (this.layout.style == "bar" && 
-            this.layout.options.barOrientation == "horizontal") {
-                ticks = this.layout.xticks;
-                horiz = true;
-        }
-        
-        for (var i = 0; i < ticks.length; i++) {
-            var x = 0;
-            var y = 0;
-            var w = 0;
-            var h = 0;
-            
-            if (horiz) {
-                x = ticks[i][0] * this.area.w + this.area.x;
-                y = this.area.y;
-                w = 1;
-                h = this.area.w;
-            }
-            else {
-                x = this.area.x;
-                y = ticks[i][0] * this.area.h + this.area.y;
-                w = this.area.w;
-                h = 1;
-            }
-            
-            this._drawRect(x, y, w, h,
-                           {"fill": this.options.axisLineColor.toRGBString()});
-        }
-    }
-    else {
-        PlotKit.SweetSVGRenderer.__super__._renderBackground.call(this);
-        
-    }
-    
-};
-
-// Namespace Iniitialisation
-
-PlotKit.SweetSVG = {}
-PlotKit.SweetSVG.SweetSVGRenderer = PlotKit.SweetSVGRenderer;
-
-PlotKit.SweetSVG.EXPORT = [
-    "SweetSVGRenderer"
-];
-
-PlotKit.SweetSVG.EXPORT_OK = [
-    "SweetSVGRenderer"
-];
-
-PlotKit.SweetSVG.__new__ = function() {
-    var m = MochiKit.Base;
-    
-    m.nameFunctions(this);
-    
-    this.EXPORT_TAGS = {
-        ":common": this.EXPORT,
-        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
-    };
-};
-
-PlotKit.SweetSVG.__new__();
-MochiKit.Base._exportSymbols(this, PlotKit.SweetSVG);
diff --git a/share/web/static/js/PlotKit/dummy.svg b/share/web/static/js/PlotKit/dummy.svg
deleted file mode 100644
index 6a82bd4..0000000
--- a/share/web/static/js/PlotKit/dummy.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://web.resource.org/cc/"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:xlink="http://www.w3.org/1999/xlink">
-</svg>

commit b738d53738aed76f2da47ec929fe2dea32d4ca8b
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Aug 2 06:28:00 2007 +0000

    Don't mess with the data structure if it's already what plotkit expects
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3764 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index 001321f..e56ea34 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -87,7 +87,11 @@ sub _transform_data {
         my @ds;
         for ( my $i = 0; $i < @$dataset; $i++ ) {
             # PlotKit can't deal with undefined values
-            push @ds, [ $i, defined $dataset->[$i] ? $dataset->[$i] : '0' ];
+            if ( not ref $dataset->[$i] ) {
+                push @ds, [ $i, defined $dataset->[$i] ? $dataset->[$i] : '0' ];
+            } else {
+                push @ds, $dataset->[$i];
+            }
         }
         push @data, \@ds;
     }

commit 9f850543a64829a24834a299a038a319d32a7ef6
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Aug 3 16:10:43 2007 +0000

    Making the IMG-based chart renderers capable of handling CSS styling with some added behaviour.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3767 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index a7ba7dd..4cb8441 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -90,6 +90,10 @@ sub init {
         ));
     }
 
+    else {
+        Jifty->web->add_javascript('chart_img_behaviour.js');
+    }
+
     push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
 }
 
diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
index eff4ada..3f2737b 100644
--- a/lib/Jifty/Plugin/Chart/Dispatcher.pm
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -24,11 +24,24 @@ on 'chart/chart/*' => run {
 
     # Unpack the data and then clear it from the session
     my $args = Jifty::YAML::Load( Jifty->web->session->get( $session_id ) );
-    Jifty->web->session->remove( $session_id );
+
+    # XXX if there are a lot of charts, this could asplode
+    #Jifty->web->session->remove( $session_id );
 
     # No data? Act like a 404
     last_rule unless defined $args;
 
+    # Request might override width/height:
+    $args->{width}  = get 'width'  if get 'width';
+    $args->{height} = get 'height' if get 'height';
+
+    # XXX TODO Is there a better way to guess the pixel heights when using CSS
+    # heights initially?
+
+    # Remove 'px' from width/height and set to 400/300 if not in pixels
+    ($args->{width}  =~ s/px$//) or ($args->{width}  = 400);
+    ($args->{height} =~ s/px$//) or ($args->{height} = 300);
+
     # Use the "type" to determine which class to use
     my $class = 'Chart::' . $args->{type};
 
@@ -55,11 +68,24 @@ on 'chart/gd_graph/*' => run {
 
     # Unpack the data and then clear it from the session
     my $args = Jifty::YAML::Load( Jifty->web->session->get( $session_id ) );
-    Jifty->web->session->remove( $session_id );
+
+    # XXX If there are lots of charts, this could asplode
+    #Jifty->web->session->remove( $session_id );
 
     # No data? Act like a 404
     last_rule unless defined $args;
 
+    # Request might override width/height:
+    $args->{width}  = get 'width'  if get 'width';
+    $args->{height} = get 'height' if get 'height';
+
+    # XXX TODO Is there a better way to guess the pixel heights when using CSS
+    # heights initially?
+
+    # Remove 'px' from width/height and set to 400/300 if not in pixels
+    ($args->{width}  =~ s/px$//) or ($args->{width}  = 400);
+    ($args->{height} =~ s/px$//) or ($args->{height} = 300);
+
     # Use the "type" to determine which class to use
     my $class = 'GD::Graph::' . $args->{type};
 
diff --git a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
index 4f79ed5..1664aa8 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
@@ -34,8 +34,21 @@ sub render {
     my $session_id = 'chart_' . $chart_id;
     Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
 
+    # Build up the chart tag
+    my $img;
+    $img  = qq{<img};
+    $img .= qq{ src="/chart/chart/$chart_id"};
+    $img .= qq{ class="@{[ join ' ', @{ $args{class} } ]}"};
+
+    my @styles;
+    push @styles, "width:$args{width}"   if defined $args{width};
+    push @styles, "height:$args{height}" if defined $args{height};
+
+    $img .= qq{ style="@{[ join ';', @styles ]}"} if @styles;
+    $img .= qq{/>};
+    
     # Output the <img> tag and include the chart's configuration key
-    Jifty->web->out(qq{<img src="/chart/chart/$chart_id" width="$args{width}" height="$args{height}"/>});
+    Jifty->web->out($img);
 
     # Make sure we don't return anything that will get output
     return;
diff --git a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
index 34d3599..3b9160e 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
@@ -40,8 +40,21 @@ sub render {
     my $session_id = 'chart_' . $chart_id;
     Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
 
+    # Build up the chart tag
+    my $img;
+    $img  = qq{<img};
+    $img .= qq{ src="/chart/gd_graph/$chart_id"};
+    $img .= qq{ class="@{[ join ' ', @{ $args{class} } ]}"};
+
+    my @styles;
+    push @styles, "width:$args{width}"   if defined $args{width};
+    push @styles, "height:$args{height}" if defined $args{height};
+
+    $img .= qq{ style="@{[ join ';', @styles ]}"} if @styles;
+    $img .= qq{/>};
+
     # Output the <img> tag and include the chart's configuration key
-    Jifty->web->out(qq{<img src="/chart/gd_graph/$chart_id" width="$args{width}" height="$args{height}"/>});
+    Jifty->web->out($img);
 
     # Make sure we don't return anything that will get output
     return;
diff --git a/lib/Jifty/Plugin/Chart/Web.pm b/lib/Jifty/Plugin/Chart/Web.pm
index ac10e55..b3efb6b 100644
--- a/lib/Jifty/Plugin/Chart/Web.pm
+++ b/lib/Jifty/Plugin/Chart/Web.pm
@@ -3,6 +3,8 @@ use warnings;
 
 package Jifty::Plugin::Chart::Web;
 
+use Scalar::Util qw/ looks_like_number /;
+
 =head1 NAME
 
 Jifty::Plugin::Chart::Web - Base class to add to Jifty::Web's ISA
@@ -55,11 +57,15 @@ A bar chart turned sideways.
 
 =item width
 
-The width, in pixels, the chart should take on the page. Defaults to 400.
+This is the width the chart should take when rendered. This may be a number, indicating the width in pixels. It may also be any value that would be appropriate for the C<width> CSS property.
+
+Defaults to C<undef>, which indicates that the chart will take on whatever size the box it is in will be. See L</CSS FOR CHARTS>.
 
 =item height
 
-The height, in pixels, the chart should take on the page. Defaults to 300.
+This is the height the chart should take when rendered. This may be a number, indicating the height in pixels. It may also be any value that would be appropriate for the C<height> CSS property.
+
+Defaults to C<undef>, which indicates that the chart will take on whatever size the box it is in will be. See L</CSS FOR CHARTS>.
 
 =item data
 
@@ -67,20 +73,25 @@ An array of arrays containing the data. The first array in the parent array is a
 
 Defaults to no data (i.e., it must be given if anything useful is to happen).
 
+=item class
+
+This allows you to associated an additional class or classes to the element containing the chart. This can be a string containing on or more class names separated by spaces or an array of class names.
+
 =back
 
 Here's an example:
 
   <% Jifty->web->chart(
       type   => 'Pie',
-      width  => 400,
-      height => 300,
+      width  => '100%',
+      height => '300px',
       data   => sub {
           [
               [ 2004, 2005, 2006, 2007 ],
               [ 26, 37, 12, 42 ]
           ];
       },
+      class => 'visualizeronimicon',
   ) %>
 
 Be sure to output anything returned by the method (unless it returns undef).
@@ -94,13 +105,26 @@ sub chart {
     # TODO It might be a good idea to make this config.yml-able
     # Setup the defaults
     my %args = (
-        type   => 'points',
-        width  => 400,
-        height => 300,
-        data   => [],
+        type       => 'points',
+        width      => undef,
+        height     => undef,
+        data       => [],
+        class      => [],
         @_,
     );
 
+    # canonicalize the width/height
+    $args{width}  .= 'px' if looks_like_number($args{width});
+    $args{height} .= 'px' if looks_like_number($args{height});
+
+    # canonicalize the class argument
+    if (not ref $args{class}) {
+        $args{class} = defined $args{class} ? [ $args{class} ] : [];
+    }
+
+    # Add the chart class, which is always present
+    push @{ $args{class} }, 'chart';
+
     # Turn any subs into values returned
     for my $key (keys %args) {
         $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
@@ -110,6 +134,18 @@ sub chart {
     return $plugin->renderer->render(%args);
 }
 
+=head1 CSS FOR CHARTS
+
+The chart API allows you to build the charts without explicit pixel widths and heights. In fact, you can not specify C<width> and C<height> and perform the styling in your regular CSS stylesheets by using the "chart" class associated with every chart or by using custom classes with the C<class> argument.
+
+See your renderer class documentation for further details.
+
+=head1 JAVASCRIPT FOR CHARTS
+
+Charts typically require JavaScript to render properly. If the client does not have JavaScript available, the chart may not work or could look very bad. 
+
+If you are using one of the image based renderers like L<Jifty::Plugin::Chart::Renderer::Chart>, it is recommended that you stick with pixel widths if you expect clients with limited or no JavaScript support. 
+
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>
diff --git a/share/web/static/js/chart_img_behaviour.js b/share/web/static/js/chart_img_behaviour.js
new file mode 100644
index 0000000..c0e974c
--- /dev/null
+++ b/share/web/static/js/chart_img_behaviour.js
@@ -0,0 +1,16 @@
+/*
+ * chart_behaviour.js
+ *
+ * Helper to make charts more designer friendly.
+ */
+
+Behaviour.register({
+    'img.chart': function(e) {
+        var dim = Element.getDimensions(e);
+        var url = e.src;
+        url += url.indexOf('?') >= 0 ? '&' : '?';
+        url += 'width=' + dim.width + 'px';
+        url += '&height=' + dim.height + 'px';
+        e.src = url;
+    },
+});

commit 85059879f86689b0ed70b9d4e71de18e8567ede3
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Aug 3 16:40:37 2007 +0000

    Added a renderer parameter to the chart() method and add per-renderer initialization.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3768 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 4cb8441..9080b20 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -5,8 +5,9 @@ package Jifty::Plugin::Chart;
 use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 
 use Jifty::Plugin::Chart::Web;
+use Scalar::Util qw/ blessed /;
 
-__PACKAGE__->mk_accessors(qw/ renderer /);
+__PACKAGE__->mk_accessors(qw/ renderer renderers /);
 
 =head1 NAME
 
@@ -71,30 +72,52 @@ sub init {
         @_,
     );
 
-    if ( $args{renderer} !~ /::/ ) {
-        $args{renderer} = __PACKAGE__.'::Renderer::'.$args{renderer};
-    }
+    # Create the empty renderers list
+    $self->renderers({});
+
+    # Load the default renderer
+    $self->renderer( $self->init_renderer($args{renderer}) );
+
+    push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
+}
+
+=head2 init_renderer
+
+  my $renderer = $chart_plugin->init_renderer($renderer_class)
+
+This is a helper method that is used by the API to initialize the renderer class. This is handled automatically so you probably shouldn't use this.
 
-    eval "use $args{renderer}";
-    warn $@ if $@;
-    $self->renderer( $args{renderer} );
-
-    if ( $self->renderer =~ 'PlotKit' ) {
-        Jifty->web->add_external_javascript(qw(
-            /static/js/mochikit.noexport.js
-            /static/js/MochiKit/MochiKit.js
-        ));
-        Jifty->web->add_javascript(qw(
-            PlotKit/excanvas.js
-            PlotKit/PlotKit_Packed-20060807-custom.js
-        ));
+=cut
+
+sub init_renderer {
+    my ($self, $renderer_class) = @_;
+
+    # If it's already an object, just return that
+    if ( blessed($renderer_class)
+            and $renderer_class->isa(__PACKAGE__.'::Renderer') ) {
+        return $renderer_class;
     }
 
-    else {
-        Jifty->web->add_javascript('chart_img_behaviour.js');
+    # Prepend Jifty::Plugin::Chart::Renderer:: if we think we need to
+    if ( $renderer_class !~ /::/ ) {
+        $renderer_class = __PACKAGE__.'::Renderer::'.$renderer_class;
     }
 
-    push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
+    # Check to see if we already loaded this one
+    my $renderer = $self->renderers->{ $renderer_class };
+    return $renderer if defined $renderer;
+
+    # Tell perl to load the class
+    $renderer_class->require;
+
+    # Initialize the renderer
+    $renderer = $renderer_class->new;
+
+    # Remember it
+    $self->renderers->{ $renderer_class } = $renderer;
+
+    # Return it!
+    return $renderer;
 }
 
 =head1 SEE ALSO
diff --git a/lib/Jifty/Plugin/Chart/Renderer.pm b/lib/Jifty/Plugin/Chart/Renderer.pm
index 1386fa6..a116443 100644
--- a/lib/Jifty/Plugin/Chart/Renderer.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer.pm
@@ -20,15 +20,15 @@ In F<lib/MyApp/Renderer.pm>:
   package MyApp::Renderer;
   use base qw/ Jifty::Plugin::Chart::Renderer /;
 
+  sub init {
+      my $self = shift;
+
+      # Handle any required initialization, like required CSS, JS, etc.
+  }
+
   sub render {
       my $self = shift;
-      my %args = (
-          type   => 'points',
-          width  => 400,
-          height => 300,
-          data   => [],
-          @_,
-      );
+      my %args = @_;
 
       # Output your chart
       Jifty->web->out( #{ Output your chart here... } );
@@ -41,6 +41,29 @@ In F<lib/MyApp/Renderer.pm>:
 
 Your renderer implementation must subclass this package and implement the following methods:
 
+=head2 new
+
+This is the constructor. Don't override this directly. Instead implement L</init>.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {}, $class;
+    $self->init;
+    return $self;
+}
+
+=head2 init
+
+  $renderer->init();
+
+This is called by C<new> immediately after constructing the object. Subclasses should implement this method to do any required initialization such as letting Jifty know about required CSS files, JS files, etc.
+
+=cut
+
+sub init {}
+
 =head2 render
 
   Jifty->web->out($renderer->render(%args));
@@ -49,6 +72,10 @@ See L<Jifty::Plugin::Chart::Web> for the arguments. It must (at least) accept th
 
 The C<render> method may either return it's output or print it out using L<Jifty::Web::out>.
 
+=cut
+
+sub render {}
+
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart::Web>, L<Jifty::Plugin::Chart::Renderer::Chart>
diff --git a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
index 1664aa8..07f28e8 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
@@ -16,6 +16,16 @@ This is the default chart renderer used by the L<Jifty::Plugin::Chart> plugin. I
 
 =head1 METHODS
 
+=head2 init
+
+Adds the F<chart_img_behaviour.js> script to those loaded.
+
+=cut
+
+sub init {
+    Jifty->web->add_javascript('chart_img_behaviour.js');
+}
+
 =head2 render
 
 Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
diff --git a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
index 3b9160e..7b8b7ac 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
@@ -22,6 +22,16 @@ This is a chart renderer that uses L<GD::Graph> to build charts.
 
 =head1 METHODS
 
+=head2 init
+
+Adds the F<chart_img_behaviour.js> script to those loaded.
+
+=cut
+
+sub init {
+    Jifty->web->add_javascript('chart_img_behaviour.js');
+}
+
 =head2 render
 
 Renders an IMG tag referring to the L<GD::Graph> image view.
diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index e56ea34..e2c2694 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -16,6 +16,23 @@ This is an alternate chart renderer used by the L<Jifty::Plugin::Chart> plugin.
 
 =head1 METHODS
 
+=head2 init
+
+Adds the various JavaScript files required to use PlotKit.
+
+=cut
+
+sub init {
+    Jifty->web->add_external_javascript(qw(
+        /static/js/mochikit.noexport.js
+        /static/js/MochiKit/MochiKit.js
+    ));
+    Jifty->web->add_javascript(qw(
+        PlotKit/excanvas.js
+        PlotKit/PlotKit_Packed-20060807-custom.js
+    ));
+}
+
 =head2 render
 
 Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
diff --git a/lib/Jifty/Plugin/Chart/Web.pm b/lib/Jifty/Plugin/Chart/Web.pm
index b3efb6b..0b5e70f 100644
--- a/lib/Jifty/Plugin/Chart/Web.pm
+++ b/lib/Jifty/Plugin/Chart/Web.pm
@@ -77,6 +77,10 @@ Defaults to no data (i.e., it must be given if anything useful is to happen).
 
 This allows you to associated an additional class or classes to the element containing the chart. This can be a string containing on or more class names separated by spaces or an array of class names.
 
+=item renderer
+
+This allows you to use a different renderer than the one configured in F<config.yml>. Give the renderer as a class name, which will be initialized for you.
+
 =back
 
 Here's an example:
@@ -105,14 +109,18 @@ sub chart {
     # TODO It might be a good idea to make this config.yml-able
     # Setup the defaults
     my %args = (
-        type       => 'points',
-        width      => undef,
-        height     => undef,
-        data       => [],
-        class      => [],
+        renderer => $plugin->renderer,
+        type     => 'points',
+        width    => undef,
+        height   => undef,
+        data     => [],
+        class    => [],
         @_,
     );
 
+    # load the renderer
+    $args{renderer} = $plugin->init_renderer($args{renderer});
+
     # canonicalize the width/height
     $args{width}  .= 'px' if looks_like_number($args{width});
     $args{height} .= 'px' if looks_like_number($args{height});
@@ -130,8 +138,8 @@ sub chart {
         $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
     }
 
-    # Call the rendering plugin's render method
-    return $plugin->renderer->render(%args);
+    # Call the rendering class' render method
+    return $args{renderer}->render(%args);
 }
 
 =head1 CSS FOR CHARTS

commit 97d5a5c4c152c2c5517ddde16ca8968374137cd3
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Aug 3 21:32:01 2007 +0000

    Updated the behaviour script used by IMG chart renderers to make it more URI aware.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3782 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/share/web/static/js/chart_img_behaviour.js b/share/web/static/js/chart_img_behaviour.js
index c0e974c..ce8f3a8 100644
--- a/share/web/static/js/chart_img_behaviour.js
+++ b/share/web/static/js/chart_img_behaviour.js
@@ -8,9 +8,26 @@ Behaviour.register({
     'img.chart': function(e) {
         var dim = Element.getDimensions(e);
         var url = e.src;
-        url += url.indexOf('?') >= 0 ? '&' : '?';
-        url += 'width=' + dim.width + 'px';
-        url += '&height=' + dim.height + 'px';
+
+        var path  = url;
+        var query = $H();
+
+        if (url.indexOf('?') >= 0) {
+            var path_and_query = url.split('?');
+            path = path_and_query[0];
+
+            var query_params = path_and_query[1].split('&');
+            for (var query_param in query_params) {
+                var key_and_value = query_param.split('=');
+                query[ key_and_value[0] ] = key_and_value[1];
+            }
+        }
+
+        query['width']  = dim.width + 'px';
+        query['height'] = dim.height + 'px';
+
+        url = path + '?' + query.toQueryString();
+
         e.src = url;
     },
 });

commit 88716d2f14293078367df4b9f7e2d4b18584d730
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sat Aug 4 22:30:52 2007 +0000

    Standardizing the chart types across the three current renderers.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3787 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
index 07f28e8..078a99a 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
@@ -36,8 +36,25 @@ sub render {
     my $self = shift;
     my %args = @_;
 
-    # Make sure the type is ready to be used as a class name
-    $args{type} = ucfirst lc $args{type};
+    # Conversion from generic types to Chart types
+    my %types = (
+        'bars'           => 'Bars',
+        'composite'      => 'Composite', # non-standard
+        'direction'      => 'Direction', # non-standard
+        'errorbars'      => 'ErrorBars', # non-standard
+        'horizontalbars' => 'HorizontalBars',
+        'lines'          => 'Lines',
+        'linespoints'    => 'LinesPoints',
+        'mountain'       => 'Mountain',  # non-standard
+        'pareto'         => 'Pareto',    # non-standard
+        'pie'            => 'Pie',
+        'points'         => 'Points',
+        'split'          => 'Split',     # non-standard
+        'stackedbars'    => 'StackedBars',
+    );
+
+    # Make sure the type is ready to be used as a Chart class name
+    $args{type} = $types{ $args{type} } || undef;
 
     # Save the data for retrieval from the session later
     my $chart_id   = Jifty->web->serial;
diff --git a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
index 7b8b7ac..d7f0c9a 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
@@ -42,8 +42,20 @@ sub render {
     my $self = shift;
     my %args = @_;
 
-    # Convert the type to lowercase
-    $args{type} = lc $args{type};
+    # GD::Graph types from generic types
+    my %types = (
+        lines          => 'lines',
+        bars           => 'bars',
+        horizontalbars => 'hbars',
+        points         => 'points',
+        linespoints    => 'linespoints', # non-standart
+        area           => 'area',
+        pie            => 'pie',
+        mixed          => 'mixed', # non-standard
+    );
+
+    # Convert the generic type to a GD::Graph type
+    $args{type} = $types{ $args{type} } || undef;
 
     # Save the data for retrieval from the session later
     my $chart_id   = Jifty->web->serial;
diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index e2c2694..226c7f3 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -49,13 +49,16 @@ sub render {
     }
 
     my %types = (
-        Lines   => 'line',
-        Bars    => 'bar',
-        Pie     => 'pie',
+        lines          => { type => 'line' },
+        bars           => { type => 'bar', orientation => 'vertical' },
+        pie            => { type => 'pie' },
+        horizontalbars => { type => 'bar', orientation => 'horizontal' },
     );
 
     # Make sure the type is ready to be used
-    $args{type} = $types{ ucfirst lc $args{type} } || undef;
+    my $options = $types{ $args{type} } || {};
+    $args{type} = delete $options{type};
+    $args{options}{$_} = $options{$_} foreach keys %$options;
 
     if ( not defined $args{type} ) {
         Jifty->log->warn("Unsupported chart type: $args{type}!");
diff --git a/lib/Jifty/Plugin/Chart/Web.pm b/lib/Jifty/Plugin/Chart/Web.pm
index 0b5e70f..9eafab1 100644
--- a/lib/Jifty/Plugin/Chart/Web.pm
+++ b/lib/Jifty/Plugin/Chart/Web.pm
@@ -25,34 +25,38 @@ The arguments passed in C<%args> may include:
 
 =item type
 
-This will be one of the following scalar values indicating the kind of chart:
+This will be one of the following scalar values indicating the kind of chart. A given renderer may not support every type listed here. A renderer might support others in addition to these, but if it supports these it should use these names.
 
 =over
 
-=item Points
+=item points
 
 This is the default value. A scatter plot with each dataset represented using differnet dot styles.
 
-=item Lines
+=item lines
 
 A line plot with each dataset presented as separate line.
 
-=item Bars
+=item bars
 
 A bar chart with each dataset set side-by-side.
 
-=item StackedBars
+=item stackedbars
 
 A bar chart with each dataset stacked on top of each other.
 
-=item Pie
+=item pie
 
 A pie chart with a single dataset representing the values for different pieces of the pie.
 
-=item HorizontalBars
+=item horizontalbars
 
 A bar chart turned sideways.
 
+=item area
+
+An area chart uses lines to represent each dataset, but the lines are stacked on top of each other with filled areas underneath.
+
 =back
 
 =item width
@@ -81,6 +85,10 @@ This allows you to associated an additional class or classes to the element cont
 
 This allows you to use a different renderer than the one configured in F<config.yml>. Give the renderer as a class name, which will be initialized for you.
 
+=item options
+
+This is a hash containing additional options to pass to the renderer and are renderer specific. This may include anything that is not otherwise set by one of the other options above.
+
 =back
 
 Here's an example:
@@ -125,6 +133,9 @@ sub chart {
     $args{width}  .= 'px' if looks_like_number($args{width});
     $args{height} .= 'px' if looks_like_number($args{height});
 
+    # canonicalize the type argument (always lowercase)
+    $args{type} = lc $args{type};
+
     # canonicalize the class argument
     if (not ref $args{class}) {
         $args{class} = defined $args{class} ? [ $args{class} ] : [];

commit f93fbea09bd74aa0cb5b3b13abadd2d0e3435442
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sat Aug 4 22:31:00 2007 +0000

    Added better error handling on renderer require.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3788 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 9080b20..d2834d3 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -108,7 +108,8 @@ sub init_renderer {
     return $renderer if defined $renderer;
 
     # Tell perl to load the class
-    $renderer_class->require;
+    $renderer_class->require
+        or warn $@;
 
     # Initialize the renderer
     $renderer = $renderer_class->new;

commit 5607fbfcca9705a74f6a426b83fb155ad93ff631
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sat Aug 4 22:31:06 2007 +0000

    Fixed typos in the new type handling code and fixed error handling.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3789 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index 226c7f3..b174b15 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -55,13 +55,16 @@ sub render {
         horizontalbars => { type => 'bar', orientation => 'horizontal' },
     );
 
+    # save it for error reporting
+    my $orig_type = $args{type};
+
     # Make sure the type is ready to be used
     my $options = $types{ $args{type} } || {};
-    $args{type} = delete $options{type};
-    $args{options}{$_} = $options{$_} foreach keys %$options;
+    $args{type} = delete $types{type};
+    $args{options}{$_} = $types{$_} foreach keys %types;
 
     if ( not defined $args{type} ) {
-        Jifty->log->warn("Unsupported chart type: $args{type}!");
+        Jifty->log->warn("Unsupported chart type: $orig_type!");
         return;
     }
 

commit 3a54048a0bf8d94627021769036c0c88dc80c04e
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sat Aug 4 22:31:18 2007 +0000

    Fixing my previous brain damage and additional typos.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3790 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index b174b15..6af21bf 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -50,9 +50,9 @@ sub render {
 
     my %types = (
         lines          => { type => 'line' },
-        bars           => { type => 'bar', orientation => 'vertical' },
+        bars           => { type => 'bar', barOrientation => 'vertical' },
         pie            => { type => 'pie' },
-        horizontalbars => { type => 'bar', orientation => 'horizontal' },
+        horizontalbars => { type => 'bar', barOrientation => 'horizontal' },
     );
 
     # save it for error reporting
@@ -60,8 +60,8 @@ sub render {
 
     # Make sure the type is ready to be used
     my $options = $types{ $args{type} } || {};
-    $args{type} = delete $types{type};
-    $args{options}{$_} = $types{$_} foreach keys %types;
+    $args{type} = delete $options->{type};
+    $args{options}{$_} = $options->{$_} foreach keys %$options;
 
     if ( not defined $args{type} ) {
         Jifty->log->warn("Unsupported chart type: $orig_type!");

commit 74f477d6c13d4675a5cc20c6bcc6f3c5a1234003
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sat Aug 4 22:31:26 2007 +0000

    Improved the way the DIV tag is generated for PlotKit.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3791 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index 6af21bf..0107751 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -74,8 +74,15 @@ sub render {
     my $chart_id   = 'chart_' . Jifty->web->serial;
 
     # Output the <canvas> tag and include the chart's JS
+    my $div;
+    $div  = qq{<div id="$chart_id"};
+    $div .= qq{ class="@{[ join ' ', @{ $args{class} } ]}"};
+    $div .= qq{ height="$args{height}"} if $args{height};
+    $div .= qq{ width="$args{width}"}   if $args{width};
+    $div .= qq{></div>};
+
     Jifty->web->out(<<"    END_OF_HTML");
-<div id="$chart_id" height="$args{height}" width="$args{width}"></div>
+$div
 
 <script type="text/javascript">
 var plot = function() {

commit 8bddb6294d6a61b49a5d3939d96cbd805bee6704
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sat Aug 4 22:31:35 2007 +0000

    Removed some redundant code from the PlotKit renderer and added support for options to the GD::Graph and Chart renderers.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3792 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index 0107751..9c0a570 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -43,11 +43,7 @@ sub render {
     my $self = shift;
     my %args = ( options => {}, @_ );
 
-    # Turn any subs into values returned
-    for my $key (keys %args) {
-        $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
-    }
-
+    # translations from generic type to PlotKit types
     my %types = (
         lines          => { type => 'line' },
         bars           => { type => 'bar', barOrientation => 'vertical' },
@@ -63,6 +59,7 @@ sub render {
     $args{type} = delete $options->{type};
     $args{options}{$_} = $options->{$_} foreach keys %$options;
 
+    # Bad stuff, not a supported type
     if ( not defined $args{type} ) {
         Jifty->log->warn("Unsupported chart type: $orig_type!");
         return;
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index d1e0959..1ef7cb1 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -28,6 +28,7 @@ template 'chart/chart' => sub {
     # Render the chart and output the PNG file generated
     eval {
         my $chart = $args->{class}->new( $args->{width}, $args->{height} );
+        $chart->set(%{ $args->{options} }) if $args->{options};
         # XXX scalar_png() is undocumented!!! Might bad to rely upon.
         outs_raw($chart->scalar_png($args->{data}));
     };
@@ -57,6 +58,7 @@ template 'chart/gd_graph' => sub {
     # Render the chart and output the PNG file generated
     eval {
         my $graph = $args->{class}->new( $args->{width}, $args->{height} );
+        $graph->set(%{ $args->{options} }) if $args->{options};
         my $gd    = $graph->plot($args->{data})
             or die $graph->error;
         outs_raw($gd->png);

commit 0b374a035231cb29cce95e56ff39fdd913139590
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sun Aug 5 21:24:28 2007 +0000

    Adding the SimpleBars renderer as a decent, dead-simple HTML-based renderer for HorizontalBars and a prototype for using tables for client-side chart configuration.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3794 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm b/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
new file mode 100644
index 0000000..0189043
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
@@ -0,0 +1,109 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::SimpleBars;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::SimpleBars - a simple horizontal bar chart
+
+=head1 DESCRIPTION
+
+This is a simple renderer for charts created both as a dead simple way of rendering horizontal bar charts, which can be a very simple way of rendering data, and as a prototype for some other work I'm thinking of doing with the chart plugin.
+
+=head1 OPTIONS
+
+Of the rendering API, this only uses the first dataset given and ignores any others. It also fails if used for any type other than the only one it supports "horizontalbars".
+
+It takes the following options:
+
+=over
+
+=item summary
+
+To maximize the accessibility of your chart, set this to describe the data. This will set the table's summary attribute.
+
+=back
+
+=head1 STYLING
+
+Please be aware that when using this object, you must add background  color to the application CSS file to see the bars.
+
+  div.simple_bars span.bar {
+      background-color: black;
+  }
+
+=head1 METHODS
+
+=head2 init
+
+Tell Jifty about the CSS and JS files SimpleBars needs.
+
+=cut
+
+sub init {
+    Jifty->web->add_javascript('simple_bars.js');
+    Jifty->web->add_css('simple_bars.css');
+}
+
+=head2 render
+
+Renders a horizontal bar chart. This is done by rendering a table of HTML values, which is then converted to a bar chart by the Javascript added to the response during L</init>.
+
+If JavaScript is not supported by the browser, all the data is presented ina table. They can still read the data, but just not in the most readable form.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = @_;
+
+    # We only handle horizontalbars, fail on all else
+    if ($args{type} ne 'horizontalbars') {
+        die 'Sorry, SimpleBars charts only handle horizontalbars chart types.';
+    }
+
+    # Create a fresh ID for the chart
+    my $chart_id = 'chart_' . Jifty->web->serial;
+
+    # Add the simple_bars class for the JavaScript to find
+    push @{ $args{class} }, 'simple_bars';
+
+    # Build the table
+    my $table;
+    $table  = qq{<table id="$chart_id"};
+    $table .= qq{ class="@{[ join ' ', @{ $args{class} } ]}"};
+    $table .= qq{ summary="$args{summary}"} if $args{summary};
+    $table .= qq{/><tbody>};
+
+    for my $index (0 .. $#{ $args{data}[0] }) {
+        my $label = $args{data}[0][$index];
+        my $point = $args{data}[1][$index];
+
+        $table .= '<tr>';
+        $table .= "<td>@{[ Jifty->web->escape($label) ]}</td>";
+        $table .= "<td>@{[ Jifty->web->escape($point) ]}</td>";
+        $table .= '</tr>';
+    }
+
+    $table .= '</tbody></table>';
+
+    Jifty->web->out($table);
+
+    return;
+}
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software. You may modify and redistribute it under the same terms as Perl itself.
+
+=cut
+
+1
diff --git a/share/web/static/css/simple_bars.css b/share/web/static/css/simple_bars.css
new file mode 100644
index 0000000..811f9f9
--- /dev/null
+++ b/share/web/static/css/simple_bars.css
@@ -0,0 +1,31 @@
+div.simple_bars div.row {
+    position: relative;
+    padding: 3px 1%;
+    width: 100%;
+    overflow: auto;
+}
+
+div.simple_bars div.row span.label {
+    display: block;
+    float: left;
+    width: 30%;
+    overflow: hidden;
+}
+
+div.simple_bars div.row span.barArea {
+    display: block;
+    float: left;
+    width: 60%;
+    overflow: hidden;
+}
+
+div.simple_bars div.row span.barArea span.bar {
+    display: block;
+}
+
+div.simple_bars div.row span.value {
+    display: block;
+    float: left;
+    width: 8%;
+    text-align: right;
+}
diff --git a/share/web/static/js/simple_bars.js b/share/web/static/js/simple_bars.js
new file mode 100644
index 0000000..32cc88b
--- /dev/null
+++ b/share/web/static/js/simple_bars.js
@@ -0,0 +1,69 @@
+/*
+ * $Id$
+ * simple_bars.js
+ * by Andrew Sterling Hanenkamp
+ *
+ * Copyright 2007 Boomer Consulting, Inc.
+ *
+ * A custom and extremely simple way of rendering a horizontal bar chart. This
+ * code was custom built for use with Jifty, but could be reused elsewhere.
+ *
+ * This is free software. You may modify or redistribute this code under the
+ * terms of the GNU General Public License or the Artistic license.
+ */
+
+function SimpleBars(table) {
+    var dataset = $H();
+
+    for (var i = 0; i < table.tBodies[0].rows.length; i++) {
+        var table_row = table.tBodies[0].rows[i];
+        dataset[table_row.cells[0].innerHTML] = table_row.cells[1].innerHTML;
+    }
+
+    var max_value = 0;
+    dataset.values().each(function(v,i){max_value=Math.max(max_value, v);});
+
+    var simple_bars = document.createElement('div');
+    simple_bars.id = table.id;
+    simple_bars.className = table.className;
+
+    dataset.keys().each(function(k, i) {
+        var v = dataset[k];
+
+        var row = document.createElement('div');
+        row.className = 'row';
+
+        var row_label = document.createElement('span');
+        row_label.className = 'label';
+        row_label.innerHTML = k;
+        row.appendChild(row_label);
+
+        var row_bar_area = document.createElement('span');
+        row_bar_area.className = 'barArea';
+        row.appendChild(row_bar_area);
+
+        var row_bar = document.createElement('span');
+        row_bar.className = 'bar';
+        row_bar.style.width = Math.round( 100 * v / max_value ) + '%';
+        row_bar.innerHTML = '&nbsp;';
+        row_bar_area.appendChild(row_bar);
+
+        var row_value = document.createElement('span');
+        row_value.className = 'value';
+        row_value.innerHTML = v;
+        row.appendChild(row_value);
+
+        simple_bars.appendChild(row);
+    });
+
+    table.parentNode.insertBefore(simple_bars, table);
+    table.parentNode.removeChild(table);
+
+    return this;
+}
+
+Behaviour.register({
+    'table.simple_bars': function(table) {
+        new SimpleBars(table);
+    }
+});

commit fbbb48c94ce37539c661899c6118c33ae07cdaf1
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Sun Aug 5 23:07:44 2007 +0000

    Make sure we do not attempt to render 0 pixel values no matter what.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3795 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
index 3f2737b..946bc82 100644
--- a/lib/Jifty/Plugin/Chart/Dispatcher.pm
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -42,6 +42,10 @@ on 'chart/chart/*' => run {
     ($args->{width}  =~ s/px$//) or ($args->{width}  = 400);
     ($args->{height} =~ s/px$//) or ($args->{height} = 300);
 
+    # No zeroes! Ba Ba Blacksheep.
+    $args->{width}  ||= 400;
+    $args->{height} ||= 300;
+
     # Use the "type" to determine which class to use
     my $class = 'Chart::' . $args->{type};
 
@@ -86,6 +90,10 @@ on 'chart/gd_graph/*' => run {
     ($args->{width}  =~ s/px$//) or ($args->{width}  = 400);
     ($args->{height} =~ s/px$//) or ($args->{height} = 300);
 
+    # No zeroes! Ba Ba Blacksheep.
+    $args->{width}  ||= 400;
+    $args->{height} ||= 300;
+
     # Use the "type" to determine which class to use
     my $class = 'GD::Graph::' . $args->{type};
 

commit 79f52e5ce4efac79c4bccd8a29f8fadbdb082647
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 6 00:34:44 2007 +0000

    Render onAvailable instead of on window load so that we work in regions
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3796 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index 9c0a570..43e1d5b 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -90,7 +90,7 @@ var plot = function() {
         @{[Jifty::JSON::objToJson( $args{data} )]}
     );
 };
-YAHOO.util.Event.addListener( window, "load", plot );
+YAHOO.util.Event.onAvailable( "$chart_id", plot );
 </script>
     END_OF_HTML
 

commit 4017a939a0d069f8715e802d373183a0069f900a
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 6 01:35:49 2007 +0000

    Use PlotKit.Base.map explicitly
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3797 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/share/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js b/share/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
index 00c2199..4e484fd 100644
--- a/share/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
+++ b/share/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
@@ -590,7 +590,7 @@ var sum=MochiKit.Iter.sum;
 var _118=MochiKit.Base.itemgetter;
 var _119=_116(this.datasets).length;
 var _120=_116(this.datasets)[0][1];
-var _121=sum(map(_118(1),_120));
+var _121=sum(PlotKit.Base.map(_118(1),_120));
 this.slices=new Array();
 var _122=0;
 for(var i=0;i<_120.length;i++){
@@ -680,7 +680,7 @@ this._evaluateLineTicks();
 var _142=function(tick){
 return [tick[0]+(this.minxdelta*this.xscale)/2,tick[1]];
 };
-this.xticks=MochiKit.Base.map(MochiKit.Base.bind(_142,this),this.xticks);
+this.xticks=PlotKit.Base.map(MochiKit.Base.bind(_142,this),this.xticks);
 if(this.options.barOrientation=="horizontal"){
 var _143=this.xticks;
 this.xticks=this.yticks;
@@ -688,7 +688,7 @@ this.yticks=_143;
 var _144=function(tick){
 return [1-tick[0],tick[1]];
 };
-this.xticks=MochiKit.Base.map(_144,this.xticks);
+this.xticks=PlotKit.Base.map(_144,this.xticks);
 }
 };
 PlotKit.Layout.prototype._evaluatePieTicks=function(){

commit f8a9dcf7cd70827aef77af64a72970893ff736f4
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Aug 8 04:54:59 2007 +0000

    Bunch of updates to the chart plugin
    
        - Refactored dispatcher
        - Added XML SWF renderer
        - Renderers are now passed the configuration hash when init'd
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3822 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index d2834d3..038cc25 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -76,7 +76,7 @@ sub init {
     $self->renderers({});
 
     # Load the default renderer
-    $self->renderer( $self->init_renderer($args{renderer}) );
+    $self->renderer( $self->init_renderer( $args{renderer}, %args ) );
 
     push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
 }
@@ -90,7 +90,7 @@ This is a helper method that is used by the API to initialize the renderer class
 =cut
 
 sub init_renderer {
-    my ($self, $renderer_class) = @_;
+    my ( $self, $renderer_class ) = ( shift, shift );
 
     # If it's already an object, just return that
     if ( blessed($renderer_class)
@@ -112,7 +112,7 @@ sub init_renderer {
         or warn $@;
 
     # Initialize the renderer
-    $renderer = $renderer_class->new;
+    $renderer = $renderer_class->new( @_ );
 
     # Remember it
     $self->renderers->{ $renderer_class } = $renderer;
diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
index 946bc82..f9a2233 100644
--- a/lib/Jifty/Plugin/Chart/Dispatcher.pm
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -10,70 +10,35 @@ use Jifty::YAML;
 
 Jifty::Plugin::Chart::Dispatcher - Dispatcher for the chart API plugin
 
-=head1 RULES
-
-=head2 chart/chart/*
-
-Grabs the chart configuration stored in the key indicated in C<$1> and unpacks it using L<YAML>. It then passes it to the L<Jifty::Plugin::Chart::View/chart> template.
-
 =cut
 
-on 'chart/chart/*' => run {
-    # Create a session ID to lookup the chart configuration
-    my $session_id = 'chart_' . $1;
-
-    # Unpack the data and then clear it from the session
-    my $args = Jifty::YAML::Load( Jifty->web->session->get( $session_id ) );
-
-    # XXX if there are a lot of charts, this could asplode
-    #Jifty->web->session->remove( $session_id );
-
-    # No data? Act like a 404
-    last_rule unless defined $args;
-
-    # Request might override width/height:
-    $args->{width}  = get 'width'  if get 'width';
-    $args->{height} = get 'height' if get 'height';
-
-    # XXX TODO Is there a better way to guess the pixel heights when using CSS
-    # heights initially?
-
-    # Remove 'px' from width/height and set to 400/300 if not in pixels
-    ($args->{width}  =~ s/px$//) or ($args->{width}  = 400);
-    ($args->{height} =~ s/px$//) or ($args->{height} = 300);
+my %classes = (
+    chart       => 'Chart::$TYPE',
+    gd_graph    => 'GD::Graph::$TYPE',
+    xmlswf      => 'XML::Simple',
+);
 
-    # No zeroes! Ba Ba Blacksheep.
-    $args->{width}  ||= 400;
-    $args->{height} ||= 300;
-
-    # Use the "type" to determine which class to use
-    my $class = 'Chart::' . $args->{type};
-
-    # Load that class or die if it does not exist
-    $class->require;
+=head1 RULES
 
-    # Remember the class name for the view
-    $args->{class} = $class;
+=head2 chart/*/*
 
-    # Send them on to chart the chart
-    set 'args' => $args;
-    show 'chart/chart'
-};
+Grabs the chart configuration stored in the key indicated in C<$1> and unpacks it using L<YAML>. It then passes it to the correct L<Jifty::Plugin::Chart::View> template.
 
-=head2 chart/gd_graph/*
+=cut
 
-Grabs the chart configuration stored in the key indicated in C<$1> and unpacks it using L<YAML>. It then passes it to the L<Jifty::Plugin::Chart::View/chart> template.
+on 'chart/*/*' => run {
+    my $renderer = $1;
 
-=cut
+    # No renderer?  Act like a 404.
+    last_rule if not defined $classes{$renderer};
 
-on 'chart/gd_graph/*' => run {
     # Create a session ID to lookup the chart configuration
-    my $session_id = 'chart_' . $1;
+    my $session_id = 'chart_' . $2;
 
     # Unpack the data and then clear it from the session
     my $args = Jifty::YAML::Load( Jifty->web->session->get( $session_id ) );
 
-    # XXX If there are lots of charts, this could asplode
+    # XXX if there are a lot of charts, this could asplode
     #Jifty->web->session->remove( $session_id );
 
     # No data? Act like a 404
@@ -94,8 +59,10 @@ on 'chart/gd_graph/*' => run {
     $args->{width}  ||= 400;
     $args->{height} ||= 300;
 
+    my $class = $classes{$renderer};
+    
     # Use the "type" to determine which class to use
-    my $class = 'GD::Graph::' . $args->{type};
+    $class =~ s/\$TYPE/$args->{type}/g;
 
     # Load that class or die if it does not exist
     $class->require;
@@ -105,7 +72,7 @@ on 'chart/gd_graph/*' => run {
 
     # Send them on to chart the chart
     set 'args' => $args;
-    show 'chart/gd_graph'
+    show "chart/$renderer";
 };
 
 =head1 SEE ALSO
diff --git a/lib/Jifty/Plugin/Chart/Renderer.pm b/lib/Jifty/Plugin/Chart/Renderer.pm
index a116443..cbf86eb 100644
--- a/lib/Jifty/Plugin/Chart/Renderer.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer.pm
@@ -50,7 +50,7 @@ This is the constructor. Don't override this directly. Instead implement L</init
 sub new {
     my $class = shift;
     my $self = bless {}, $class;
-    $self->init;
+    $self->init( @_ );
     return $self;
 }
 
@@ -58,7 +58,7 @@ sub new {
 
   $renderer->init();
 
-This is called by C<new> immediately after constructing the object. Subclasses should implement this method to do any required initialization such as letting Jifty know about required CSS files, JS files, etc.
+This is called by C<new> immediately after constructing the object.  It is passed a param hash from the config file.  Subclasses should implement this method to do any required initialization such as letting Jifty know about required CSS files, JS files, etc.
 
 =cut
 
diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
new file mode 100644
index 0000000..b6d2a54
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -0,0 +1,155 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::XMLSWF;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+use Jifty::YAML;
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::XMLSWF - A chart renderer using XML SWF charts
+
+=head1 DESCRIPTION
+
+This chart renderer uses the XML SWF charting tools to render charts.
+
+  Plugins:
+    - Chart:
+        renderer: XMLSWF
+        license_key: YOUR_OPTIONAL_LICENSE_KEY
+
+=head1 METHODS
+
+=head2 init
+
+Save the license key, if any
+
+=cut
+
+our $LICENSE = "";
+
+sub init {
+    my $self = shift;
+    my %args = ( @_ );
+
+    if ( defined $args{license_key} ) {
+        $LICENSE = $args{license_key};
+    }
+}
+
+=head2 render
+
+Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = (
+        bgcolor => '#ffffff',
+        align   => 'left',
+        wmode   => 'transparent',
+        @_
+    );
+
+    # Conversion from generic types to XML SWF types -- incomplete
+    my %types = (
+        'bars'           => 'column',
+        'stackedbars'    => 'stacked column',
+        'horizontalbars' => 'bar',
+        'lines'          => 'line',
+        'pie'            => '3d pie',
+        'points'         => 'scatter',
+    );
+
+    # Make sure the type is ready to be used
+    $args{type} = $types{ $args{type} } || undef;
+
+    # Save the data for retrieval from the session later
+    my $chart_id   = Jifty->web->serial;
+    my $session_id = 'chart_' . $chart_id;
+    Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
+
+    # Build up the chart tag
+    my $src = '/static/flash/xmlswf/charts.swf?';
+
+    my $query = Jifty->web->query_string(
+                    library_path => '/static/flash/xmlswf/charts_library',
+                    xml_source   => "/chart/xmlswf/$chart_id",
+                    license      => $LICENSE
+                );
+    $query =~ s/;/&/g;
+    $src .= $query;
+
+    my $tags = {
+        embed => {
+            src             => $src,
+            quality         => 'high',
+            bgcolor         => $args{bgcolor},
+            width           => $args{width},
+            height          => $args{height},
+            name            => $session_id,
+            align           => $args{align},
+            wmode           => $args{wmode},
+            type            => 'application/x-shockwave-flash',
+            swLiveConnect   => 'true',
+            pluginspage     => 'http://www.macromedia.com/go/getflashplayer',
+        },
+        object => {
+            classid     => 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000',
+            codebase    => 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0',
+            width       => $args{width},
+            height      => $args{height},
+            id          => $session_id,
+            class       => join( ' ', @{$args{class}} ),
+            align       => $args{align},
+        },
+        params  => {
+            movie   => $src,
+            quality => 'high',
+            bgcolor => $args{bgcolor},
+            wmode   => $args{wmode},
+        },
+    };
+
+    my $html = "<div>\n";
+    $html .= "<object";
+    $html .= qq[ $_="@{[$tags->{object}{$_}]}"]
+        for keys %{ $tags->{object} };
+    $html .= ">\n";
+
+    $html .= qq[<param name="$_" value="@{[$tags->{params}{$_}]}" />\n] # /damn vim
+        for keys %{ $tags->{params} };
+
+    $html .= "<embed";
+    $html .= qq[ $_="@{[$tags->{embed}{$_}]}"]
+        for keys %{ $tags->{embed} };
+    $html .= "></embed>\n";
+    $html .= "</object>\n";
+    $html .= "</div>\n";
+
+    # Output the HTML and include the chart's configuration key
+    Jifty->web->out($html);
+
+    # Make sure we don't return anything that will get output
+    return;
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>
+
+=head1 AUTHOR
+
+Thomas Sibley
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Best Practical Solutions, LLC
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 1ef7cb1..4e69cfb 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -71,6 +71,56 @@ template 'chart/gd_graph' => sub {
     }
 };
 
+=head2 chart/xmlswf
+
+This shows a chart using XML SWF. It expects to find the arguments in the C<args> parameter, which is setup for it in L<Jifty::Plugin::Chart::Dispatcher>.
+
+This will output an XML source file unless there is an error building the chart.
+
+=cut
+
+template 'chart/xmlswf' => sub {
+    # Load the arguments
+    my $args = get 'args';
+
+    # Set the output type to the XML file type
+    Jifty->handler->apache->content_type('application/xml');
+
+    # The KeyAttr thing is a bloody hack to get ordering right
+    my $xml = $args->{class}->new(
+        RootName => 'chart',
+        KeyAttr  => { row => '+string' }
+    );
+
+    my $labels = shift @{ $args->{data} };
+
+    # Base chart options
+    my %chart = (
+        chart_type       => { content => $args->{type} },
+        axis_category    => { size => '11', color => '808080' },
+        axis_value       => { size => '11', color => '808080' },
+        axis_ticks       => { major_color => '808080' },
+        legend_label     => { size => '11' },
+        chart_value      => { position => 'cursor', size => '11', color => '666666' },
+        %{ $args->{options} },
+        chart_data       => {
+            row => [
+                {
+                    string => [ {}, @$labels ],
+                },
+            ],
+        },
+    );
+
+    for my $i ( 0 .. $#{ $args->{data} } ) {
+        push @{$chart{'chart_data'}{'row'}}, {
+            string => [ $args->{legend}[$i] || {} ],
+            number => $args->{data}[$i],
+        };
+    }
+
+    outs_raw( $xml->XMLout( \%chart ) );
+};
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart::Dispatcher>

commit fc35a892ce5a4d3162d0050f051d8ef025894961
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Aug 8 05:01:40 2007 +0000

    - Treat the width and height appropriately
    - Add the XML::Simple dep
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3823 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index 43e1d5b..c0d084c 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -65,6 +65,10 @@ sub render {
         return;
     }
 
+    # Kill the "px" unit
+    $args{width} =~ s/px$//;
+    $args{height} =~ s/px$//;
+
     $self->_transform_data( \%args );
 
     # Save the data for retrieval from the session later
diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index b6d2a54..d36c0cf 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -66,6 +66,10 @@ sub render {
     # Make sure the type is ready to be used
     $args{type} = $types{ $args{type} } || undef;
 
+    # Kill the "px" unit
+    $args{width} =~ s/px$//;
+    $args{height} =~ s/px$//;
+
     # Save the data for retrieval from the session later
     my $chart_id   = Jifty->web->serial;
     my $session_id = 'chart_' . $chart_id;

commit bfe1a3c2c4d7ea0362312339118f80f0f70f1c6a
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Aug 8 05:12:55 2007 +0000

    Don't specify an align attribute
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3824 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index d36c0cf..05e21e0 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -48,7 +48,6 @@ sub render {
     my $self = shift;
     my %args = (
         bgcolor => '#ffffff',
-        align   => 'left',
         wmode   => 'transparent',
         @_
     );
@@ -94,7 +93,6 @@ sub render {
             width           => $args{width},
             height          => $args{height},
             name            => $session_id,
-            align           => $args{align},
             wmode           => $args{wmode},
             type            => 'application/x-shockwave-flash',
             swLiveConnect   => 'true',
@@ -107,7 +105,6 @@ sub render {
             height      => $args{height},
             id          => $session_id,
             class       => join( ' ', @{$args{class}} ),
-            align       => $args{align},
         },
         params  => {
             movie   => $src,

commit 6eedb90480eaa72af31ba8d1aca32c075106fe17
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Aug 8 05:46:05 2007 +0000

    Whoops.  Forgot to check in the actual XML SWF library.  This is version 4.6.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3825 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/share/web/static/flash/xmlswf/charts.swf b/share/web/static/flash/xmlswf/charts.swf
new file mode 100644
index 0000000..b4ddc29
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/arno.swf b/share/web/static/flash/xmlswf/charts_library/arno.swf
new file mode 100644
index 0000000..8e5fc18
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/arno.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/arst.swf b/share/web/static/flash/xmlswf/charts_library/arst.swf
new file mode 100644
index 0000000..bbe113b
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/arst.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/brfl.swf b/share/web/static/flash/xmlswf/charts_library/brfl.swf
new file mode 100644
index 0000000..4abc116
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/brfl.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/brno.swf b/share/web/static/flash/xmlswf/charts_library/brno.swf
new file mode 100644
index 0000000..c5fb27a
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/brno.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/brst.swf b/share/web/static/flash/xmlswf/charts_library/brst.swf
new file mode 100644
index 0000000..ebf0995
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/brst.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/cl3d.swf b/share/web/static/flash/xmlswf/charts_library/cl3d.swf
new file mode 100644
index 0000000..663644a
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/cl3d.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/clfl.swf b/share/web/static/flash/xmlswf/charts_library/clfl.swf
new file mode 100644
index 0000000..9839c51
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/clfl.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/clno.swf b/share/web/static/flash/xmlswf/charts_library/clno.swf
new file mode 100644
index 0000000..cbbbc77
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/clno.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/clp3.swf b/share/web/static/flash/xmlswf/charts_library/clp3.swf
new file mode 100644
index 0000000..4e28ea4
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/clp3.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/cls3.swf b/share/web/static/flash/xmlswf/charts_library/cls3.swf
new file mode 100644
index 0000000..1f97c55
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/cls3.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/clst.swf b/share/web/static/flash/xmlswf/charts_library/clst.swf
new file mode 100644
index 0000000..fc69365
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/clst.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/cnno.swf b/share/web/static/flash/xmlswf/charts_library/cnno.swf
new file mode 100644
index 0000000..f8ec7ee
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/cnno.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/lnno.swf b/share/web/static/flash/xmlswf/charts_library/lnno.swf
new file mode 100644
index 0000000..3a1d6f9
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/lnno.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/mxno.swf b/share/web/static/flash/xmlswf/charts_library/mxno.swf
new file mode 100644
index 0000000..44d71e6
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/mxno.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/pi3d.swf b/share/web/static/flash/xmlswf/charts_library/pi3d.swf
new file mode 100644
index 0000000..8ed95df
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/pi3d.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/pino.swf b/share/web/static/flash/xmlswf/charts_library/pino.swf
new file mode 100644
index 0000000..a80831e
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/pino.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/pono.swf b/share/web/static/flash/xmlswf/charts_library/pono.swf
new file mode 100644
index 0000000..0f2a2fe
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/pono.swf differ
diff --git a/share/web/static/flash/xmlswf/charts_library/scno.swf b/share/web/static/flash/xmlswf/charts_library/scno.swf
new file mode 100644
index 0000000..ec98ec1
Binary files /dev/null and b/share/web/static/flash/xmlswf/charts_library/scno.swf differ

commit 3cda9bf2057b0a9e7dde32a84d47ceb042017012
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Aug 10 13:57:59 2007 +0000

    Removing extra comma from JavaScript list.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3842 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/share/web/static/js/chart_img_behaviour.js b/share/web/static/js/chart_img_behaviour.js
index ce8f3a8..77300a7 100644
--- a/share/web/static/js/chart_img_behaviour.js
+++ b/share/web/static/js/chart_img_behaviour.js
@@ -29,5 +29,5 @@ Behaviour.register({
         url = path + '?' + query.toQueryString();
 
         e.src = url;
-    },
+    }
 });

commit 739ce7d41cb90d608a06ca1901d1d28c1f18487d
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Aug 10 14:19:16 2007 +0000

    Chart plugin configuration updates:
     * Deprecating the renderer option.
     * Adding the DefaultRenderer option to replace renderer.
     * Adding the PreloadRenderers option to allow additional renderers to be preloaded.
     * Updated the documentation.
     * Made sure that the configuration is always passed to the renderer constructor, even if they are loaded late.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3843 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 038cc25..2b1c184 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -7,7 +7,7 @@ use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 use Jifty::Plugin::Chart::Web;
 use Scalar::Util qw/ blessed /;
 
-__PACKAGE__->mk_accessors(qw/ renderer renderers /);
+__PACKAGE__->mk_accessors(qw/ renderer renderers plugin_args /);
 
 =head1 NAME
 
@@ -49,13 +49,29 @@ This method is described in L<Jifty::Plugin::Chart::Web> and an example is shown
 
 =head1 CONFIGURATION
 
-The plugin takes a single configuration option called C<renderer>. This may be set to a chart renderer class, which is just an implementation of L<Jifty::Plugin::Chart::Renderer>. The default, L<Jifty::Plugin::Chart::Renderer::Chart>, uses L<Chart> to render charts as PNG files which are then included in your pages for you.
-
 Here is an example configuration for F<config.yml>:
 
   Plugins:
     - Chart:
-        renderer: Chart
+        DefaultRenderer: PlotKit
+        PreloadRenderers:
+         - XMLSWF
+         - SimpleBars
+         - App::Renderer::Custom
+
+The available options are:
+
+=over
+
+=item DefaultRenderer
+
+This is the name of the class to use as the default renderer. L<Jifty::Plugin::Chart::Renderer::Chart> is the current default, but that could change in the future. It's recommended that you set this to your preference.
+
+=item PreloadRenderers
+
+This is a list of other render classes to load during initialization. If they are not loaded during initialization some renderers may not work correctly the first time they are run because they are not able to inform Jifty of the CSS or JS files they need before that part of the page is already rendered. If you use the "renderer" option of L<Jifty::Plugin::Chart::Web/chart>, then you should make sure any value you use is set here in the configuration to make sure it works properly.
+
+=back
 
 =head1 METHODS
 
@@ -68,15 +84,35 @@ Adds the L<Jifty::Plugin::Chart::Web/chart> method to L<Jifty::Web>.
 sub init {
     my $self = shift;
     my %args = (
-        renderer => 'Chart',
+        DefaultRenderer => 'Chart',
         @_,
     );
 
+    # Save the arguments for use in init_renderer() later
+    $self->plugin_args( \%args );
+
+    # Deprecating the old form
+    if (defined $args{renderer}) {
+        warn 'DEPRECATED: renderer argument to Chart plugin is deprecated.'
+            .' Use DefaultRenderer instead.';
+        $args{DefaultRenderer} = delete $args{renderer};
+    }
+
     # Create the empty renderers list
     $self->renderers({});
 
+    # Pre-load any renderers they plan to use
+    if (defined $args{PreloadRenderers}) {
+        $args{PreloadRenderers} = [ $args{PreloadRenderers} ]
+            unless ref $args{PreloadRenderers};
+
+        for my $renderer (@{ $args{PreloadRenderers} }) {
+            $self->init_renderer( $renderer );
+        }
+    }
+
     # Load the default renderer
-    $self->renderer( $self->init_renderer( $args{renderer}, %args ) );
+    $self->renderer( $self->init_renderer( $args{DefaultRenderer}, %args ) );
 
     push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
 }
@@ -112,7 +148,7 @@ sub init_renderer {
         or warn $@;
 
     # Initialize the renderer
-    $renderer = $renderer_class->new( @_ );
+    $renderer = $renderer_class->new( %{ $self->plugin_args } );
 
     # Remember it
     $self->renderers->{ $renderer_class } = $renderer;

commit 0e1e58bea717c242ecdfaa9e13afc5091eb85573
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Aug 10 20:50:53 2007 +0000

    Fix to a JavaScript bug performing the rerendering of IMG graphs.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@3846 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/share/web/static/js/chart_img_behaviour.js b/share/web/static/js/chart_img_behaviour.js
index 77300a7..2d69b94 100644
--- a/share/web/static/js/chart_img_behaviour.js
+++ b/share/web/static/js/chart_img_behaviour.js
@@ -26,7 +26,7 @@ Behaviour.register({
         query['width']  = dim.width + 'px';
         query['height'] = dim.height + 'px';
 
-        url = path + '?' + query.toQueryString();
+        url = path + '?' + $H(query).toQueryString();
 
         e.src = url;
     }

commit 930db803f0203626650fe82aa59b9cf851255154
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Nov 13 19:31:50 2007 +0000

     * <embed> isn't a tag which gets a close on it
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@4430 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index 05e21e0..4a8114a 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -126,7 +126,7 @@ sub render {
     $html .= "<embed";
     $html .= qq[ $_="@{[$tags->{embed}{$_}]}"]
         for keys %{ $tags->{embed} };
-    $html .= "></embed>\n";
+    $html .= " />\n";
     $html .= "</object>\n";
     $html .= "</div>\n";
 

commit 14f38351b114c81b31f0886cb9a46db216499b57
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Jan 11 04:39:42 2008 +0000

    Updating so that simple bars work again (broke with Prototype 1.6) and also ported to use jQuery.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@4818 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/share/web/static/js/simple_bars.js b/share/web/static/js/simple_bars.js
index 32cc88b..fc3f383 100644
--- a/share/web/static/js/simple_bars.js
+++ b/share/web/static/js/simple_bars.js
@@ -13,57 +13,52 @@
  */
 
 function SimpleBars(table) {
-    var dataset = $H();
+    var dataSet = {};
 
-    for (var i = 0; i < table.tBodies[0].rows.length; i++) {
-        var table_row = table.tBodies[0].rows[i];
-        dataset[table_row.cells[0].innerHTML] = table_row.cells[1].innerHTML;
-    }
-
-    var max_value = 0;
-    dataset.values().each(function(v,i){max_value=Math.max(max_value, v);});
-
-    var simple_bars = document.createElement('div');
-    simple_bars.id = table.id;
-    simple_bars.className = table.className;
+    jQuery('tr', table).each(function() {
+        dataSet[ jQuery(this.cells[0]).text() ] = jQuery(this.cells[1]).text();
+    });
 
-    dataset.keys().each(function(k, i) {
-        var v = dataset[k];
+    var maxValue = 0;
+    for (var name in dataSet) {
+        maxValue = Math.max(maxValue, dataSet[name]);
+    }
 
-        var row = document.createElement('div');
-        row.className = 'row';
+    var simpleBars 
+        = jQuery("<div/>")
+            .attr('id', table.attr('id'))
+            .attr('class', table.attr('class'));
 
-        var row_label = document.createElement('span');
-        row_label.className = 'label';
-        row_label.innerHTML = k;
-        row.appendChild(row_label);
+    for (var k in dataSet) {
+        var v = dataSet[k];
 
-        var row_bar_area = document.createElement('span');
-        row_bar_area.className = 'barArea';
-        row.appendChild(row_bar_area);
+        var row = jQuery('<div class="row"/>');
+        jQuery('<span class="label"/>')
+            .text(k)
+            .appendTo(row);
 
-        var row_bar = document.createElement('span');
-        row_bar.className = 'bar';
-        row_bar.style.width = Math.round( 100 * v / max_value ) + '%';
-        row_bar.innerHTML = '&nbsp;';
-        row_bar_area.appendChild(row_bar);
+        var rowBarArea = jQuery('<span class="barArea"/>');
+        jQuery('<span class="bar"/>')
+            .css('width', Math.round( 100 * v / maxValue ) + '%')
+            .html('&nbsp;')
+            .appendTo(rowBarArea);
+        rowBarArea.appendTo(row);
 
-        var row_value = document.createElement('span');
-        row_value.className = 'value';
-        row_value.innerHTML = v;
-        row.appendChild(row_value);
+        jQuery('<span class="value"/>')
+            .text(v)
+            .appendTo(row);
 
-        simple_bars.appendChild(row);
-    });
+        simpleBars.append(row);
+    }
 
-    table.parentNode.insertBefore(simple_bars, table);
-    table.parentNode.removeChild(table);
+    table.before(simpleBars);
+    table.remove();
 
     return this;
 }
 
 Behaviour.register({
     'table.simple_bars': function(table) {
-        new SimpleBars(table);
+        new SimpleBars(jQuery(table));
     }
 });

commit 0b11e42f157776ebd37b12989b4e28eb2a38853a
Author: Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
Date:   Fri Jan 11 04:47:53 2008 +0000

    Updated the chart image behaviour JavaScript since it broke with the Prototype 1.6 update.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@4819 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/share/web/static/js/chart_img_behaviour.js b/share/web/static/js/chart_img_behaviour.js
index 2d69b94..46ec316 100644
--- a/share/web/static/js/chart_img_behaviour.js
+++ b/share/web/static/js/chart_img_behaviour.js
@@ -10,7 +10,7 @@ Behaviour.register({
         var url = e.src;
 
         var path  = url;
-        var query = $H();
+        var query = new Hash();
 
         if (url.indexOf('?') >= 0) {
             var path_and_query = url.split('?');
@@ -19,14 +19,14 @@ Behaviour.register({
             var query_params = path_and_query[1].split('&');
             for (var query_param in query_params) {
                 var key_and_value = query_param.split('=');
-                query[ key_and_value[0] ] = key_and_value[1];
+                query.set(key_and_value[0], key_and_value[1]);
             }
         }
 
-        query['width']  = dim.width + 'px';
-        query['height'] = dim.height + 'px';
+        query.set('width', dim.width + 'px');
+        query.set('height', dim.height + 'px');
 
-        url = path + '?' + $H(query).toQueryString();
+        url = path + '?' + query.toQueryString();
 
         e.src = url;
     }

commit 9ffbc531fc69059e2a880f24fa948dc5229883d6
Author: Tomohiro Hosaka <bokutin at bokut.in>
Date:   Mon Jan 28 07:23:05 2008 +0000

    fix pods.
    WARN - DEPRECATED: renderer argument to Chart plugin is deprecated. Use DefaultRenderer instead. at trunk/lib/Jifty/Plugin/Chart.pm line 96.
    
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@4952 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer.pm b/lib/Jifty/Plugin/Chart/Renderer.pm
index cbf86eb..af950c7 100644
--- a/lib/Jifty/Plugin/Chart/Renderer.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer.pm
@@ -13,7 +13,7 @@ In your F<config.yml>:
 
   Plugins:
     - Chart:
-        renderer: MyApp::Renderer;
+        DefaultRenderer: MyApp::Renderer
 
 In F<lib/MyApp/Renderer.pm>:
 
diff --git a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
index d7f0c9a..a2cb8d5 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
@@ -14,7 +14,7 @@ In F<config.yml>:
 
   Plugins:
     - Chart:
-        renderer: Jifty::Plugin::Chart::Renderer::GD::Graph
+        DefaultRenderer: Jifty::Plugin::Chart::Renderer::GD::Graph
 
 =head1 DESCRIPTION
 
diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index 4a8114a..88661f4 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -16,7 +16,7 @@ This chart renderer uses the XML SWF charting tools to render charts.
 
   Plugins:
     - Chart:
-        renderer: XMLSWF
+        DefaultRenderer: XMLSWF
         license_key: YOUR_OPTIONAL_LICENSE_KEY
 
 =head1 METHODS

commit bec403cc7aa4ac9aa8977eb12af2ebfbe1b9f03d
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Mar 28 01:38:42 2008 +0000

    tiny fix, sometimes options are undef
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5244 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 4e69cfb..4b983ed 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -102,7 +102,7 @@ template 'chart/xmlswf' => sub {
         axis_ticks       => { major_color => '808080' },
         legend_label     => { size => '11' },
         chart_value      => { position => 'cursor', size => '11', color => '666666' },
-        %{ $args->{options} },
+        %{ $args->{options} || {} },
         chart_data       => {
             row => [
                 {

commit 14fcd8809a57f32152b3841ae1cc1845b0e083e2
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 24 02:37:55 2008 +0000

    Set a default color for the SimpleBars chart renderer
    
    Add a Google charts renderer to the Chart plugin
    
    Some type updates
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5576 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
new file mode 100644
index 0000000..73b73ff
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -0,0 +1,205 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::Google;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+use URI::Escape qw(uri_escape);
+use List::Util qw(max min);
+use Scalar::Util qw(looks_like_number);
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::Google - A chart renderer using Google Charts
+
+=head1 DESCRIPTION
+
+This is an alternate chart renderer used by the L<Jifty::Plugin::Chart> plugin. It works by rendering an <img> tag in the HTML output.
+
+=head1 METHODS
+
+=head2 render
+
+Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = (
+        width     => 200,
+        height    => 100,
+        labels    => [],
+        geography => 'world',
+        min_minus => 0,
+        max_plus  => 0,
+        @_
+    );
+
+    # Translations from generic type to Google charts types (incomplete)
+    my %types = (
+        trend                   => 'lc',
+        lines                   => 'lxy',
+        line                    => 'lxy',
+        sparkline               => 'ls',
+        horizontalbars          => 'bhg',
+        bars                    => 'bvg',
+        bar                     => 'bvg',
+        stackedhorizontalbars   => 'bhs',
+        stackedbars             => 'bvs',
+        pie                     => 'p',
+        pie3d                   => 'p3',
+        venn                    => 'v',
+        scatter                 => 's',
+        points                  => 's',
+        point                   => 's',
+        map                     => 't',
+        geo                     => 't',
+    );
+
+    # Make sure the type is ready to be used
+    my $type = $types{ $args{type} } || undef;
+
+    # Not a supported type
+    if ( not defined $type ) {
+        Jifty->log->warn("Unsupported chart type: $args{'type'}!");
+        return;
+    }
+
+    # Kill the "px" unit
+    $args{'width'} =~ s/px$//;
+    $args{'height'} =~ s/px$//;
+
+    if ( $args{'type'} eq 'map' ) {
+        $args{'codes'} = shift @{ $args{'data'} };
+        
+        # Light blue for water
+        $args{'bgcolor'} = "EAF7FE" if not defined $args{'bgcolor'};
+    }
+
+    # Set max/min value if we don't have one
+    if ( not defined $args{'max_value'} or not defined $args{'min_value'} ) {
+        my $max = 0;
+        my $min = 0;
+
+        for my $dataset ( @{ $args{'data'} } ) {
+            if ( not defined $args{'max_value'} ) {
+                my $lmax = max @$dataset;
+                $max = $lmax if $lmax > $max;
+            }
+            if ( not defined $args{'min_value'} ) {
+                my $lmin = min @$dataset;
+                $min = $lmin if $lmin < $min;
+            }
+        }
+        
+        $args{'max_value'} = $max if not defined $args{'max_value'};
+        $args{'min_value'} = $min if not defined $args{'min_value'};
+    }
+
+    # Build the base chart URL
+    my $url = 'http://chart.apis.google.com/chart?';
+    
+    # Add the type
+    $url .= "cht=$type";
+
+    # Add the width - XXX TODO: we don't validate these yet
+    $url .= "&chs=$args{'width'}x$args{'height'}";
+
+    # Add the data (encoding it first)
+    if ( $args{'type'} eq 'map' ) {
+        $url .= "&chtm=$args{'geographical'}";           # Geo. area
+        $url .= "&chld=" . join '', @{ $args{'codes'} }; # Codes
+        
+        # We need to do simple encoding
+        $url .= "&chd=t:" . $self->_simple_encode_data( $args{'max_value'}, $args{'data'} );
+    }
+    else {
+        my $min = $args{'min_value'} - $args{'min_minus'};
+        my $max = $args{'max_value'} + $args{'max_plus'};
+
+        # If it's a number, pass it through, otherwise replace it with a
+        # number out of range to mark it as undefined
+        my @data;
+        for my $data ( @{$args{'data'}} ) {
+            push @data, [map { looks_like_number($_) ? $_ : $max+42 } @$data];
+        }
+
+        # Let's do text encoding with data scaling
+        $url .= "&chd=t:" . join '|', map { join ',', @$_ } @data;
+        $url .= "&chds=$min,$max";
+    }
+
+    # Add the legend
+    if ( $args{'legend'} ) {
+        $url .= "&chdl="  . join '|', map { uri_escape($_) } @{ $args{'legend'} };
+        $url .= "&chdlp=" . substr $args{'legend_position'}, 0, 1
+            if $args{'legend_position'};
+    }
+
+    # Add any axes
+    if ( $args{'axes'} ) {
+        $url .= "&chxt=" . $args{'axes'};
+        
+        my $labels;
+        my $index = 0;
+        for my $labelset ( @{ $args{'labels'} } ) {
+            $labels .= "$index:|" . join '|', map { uri_escape($_) } @$labelset
+                if @$labelset;
+            $index++;
+        }
+        $url .= "&chxl=$labels" if defined $labels;
+    }
+
+    # Add colors since Google::Chart sucks at it
+    if ( defined $args{'colors'} ) {
+        $url .= "&chco=" . join ',', @{ $args{'colors'} };
+    }
+    if ( defined $args{'bgcolor'} ) {
+        $url .= "&chf=bg,s,$args{'bgcolor'}";
+    }
+
+    Jifty->web->out( qq{<img src="$url" />} );
+
+    # Make sure we don't return anything that will get output
+    return;
+}
+
+# Borrowed with slight modifications from Google::Chart::Data::SimpleEncoding
+sub _simple_encode_data {
+    my $self = shift;
+    my $max  = shift;
+    my $data = shift;
+
+    my $result = '';
+    my @map = ('A'..'Z', 'a'..'z', 0..9);
+    for my $value ( @$data ) {
+        if ( looks_like_number($value) ) {
+            my $index = int($value / $max * (@map - 1));
+            $index = 0 if $index < 0;
+            $index = @map if $index > @map;
+            $result .= $map[$index];
+        } else {
+            $result .= '_';
+        }
+    }
+    return $result;
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>
+
+=head1 AUTHOR
+
+Thomas Sibley
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 Best Practical Solutions, LLC
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
diff --git a/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm b/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
index 0189043..e981ee2 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
@@ -28,7 +28,8 @@ To maximize the accessibility of your chart, set this to describe the data. This
 
 =head1 STYLING
 
-Please be aware that when using this object, you must add background  color to the application CSS file to see the bars.
+Please be aware that when using this object, you can change the bar color
+using CSS like so:
 
   div.simple_bars span.bar {
       background-color: black;
diff --git a/share/web/static/css/simple_bars.css b/share/web/static/css/simple_bars.css
index 811f9f9..9fdfb22 100644
--- a/share/web/static/css/simple_bars.css
+++ b/share/web/static/css/simple_bars.css
@@ -23,6 +23,10 @@ div.simple_bars div.row span.barArea span.bar {
     display: block;
 }
 
+div.simple_bars span.bar {
+    background-color: #4d89f9;
+}
+
 div.simple_bars div.row span.value {
     display: block;
     float: left;

commit 947e858c37fd11e57c4c995508a3b75e21a4b413
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 24 03:01:39 2008 +0000

    A few fixes for map charts
    
    Some more fixes for maps
    
    More fixes
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5577 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 73b73ff..6bfdff4 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -30,7 +30,7 @@ sub render {
         width     => 200,
         height    => 100,
         labels    => [],
-        geography => 'world',
+        geoarea   => 'world',
         min_minus => 0,
         max_plus  => 0,
         @_
@@ -70,7 +70,21 @@ sub render {
     $args{'width'} =~ s/px$//;
     $args{'height'} =~ s/px$//;
 
-    if ( $args{'type'} eq 'map' ) {
+    # Check size and die if wrong
+    for ( qw(width height) ) {
+        if ( $type eq 't' ) {
+            my $max = $_ eq 'width' ? 440 : 220;
+            die "$_ over ${max}px" if $args{$_} > $max;
+        } else {
+            die "$_ over 1000px" if $args{$_} > 1000;
+        }
+    }
+
+    # Check chart area
+    die "Chart area over maximum allowed (300,000 for charts, 96,800 for maps)"
+        if $args{'width'} * $args{'height'} > ( $type eq 't' ? 96800 : 300000 );
+
+    if ( $type eq 't' ) {
         $args{'codes'} = shift @{ $args{'data'} };
         
         # Light blue for water
@@ -107,12 +121,13 @@ sub render {
     $url .= "&chs=$args{'width'}x$args{'height'}";
 
     # Add the data (encoding it first)
-    if ( $args{'type'} eq 'map' ) {
-        $url .= "&chtm=$args{'geographical'}";           # Geo. area
-        $url .= "&chld=" . join '', @{ $args{'codes'} }; # Codes
+    if ( $type eq 't' ) {
+        # Map!
+        $url .= "&chtm=$args{'geoarea'}";
+        $url .= "&chld=" . join '', @{ $args{'codes'} };
         
         # We need to do simple encoding
-        $url .= "&chd=t:" . $self->_simple_encode_data( $args{'max_value'}, $args{'data'} );
+        $url .= "&chd=s:" . $self->_simple_encode_data( $args{'max_value'}, @{$args{'data'}} );
     }
     else {
         my $min = $args{'min_value'} - $args{'min_minus'};

commit 3fc15e7ee9eba3187c88d2f3541418eb58f49ca2
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 24 21:14:47 2008 +0000

    Pie charts require a different parameter for labels/legends
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5578 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 6bfdff4..689f00b 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -147,7 +147,9 @@ sub render {
 
     # Add the legend
     if ( $args{'legend'} ) {
-        $url .= "&chdl="  . join '|', map { uri_escape($_) } @{ $args{'legend'} };
+        my $key = $args{'type'} =~ /pie/i ? 'chl' : 'chdl';
+
+        $url .= "&$key="  . join '|', map { uri_escape($_) } @{ $args{'legend'} };
         $url .= "&chdlp=" . substr $args{'legend_position'}, 0, 1
             if $args{'legend_position'};
     }

commit c35b738878bcbc523c75a004717ce62df9107646
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 24 21:14:55 2008 +0000

    * We need to calculate the max/min for stacked bar charts differently (they were broken before this)
    * Support add/subtracting percentages of the max/min
    * Support setting bar width
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5579 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 689f00b..99c3cae 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -5,7 +5,7 @@ package Jifty::Plugin::Chart::Renderer::Google;
 use base qw/ Jifty::Plugin::Chart::Renderer /;
 
 use URI::Escape qw(uri_escape);
-use List::Util qw(max min);
+use List::Util qw(max min sum);
 use Scalar::Util qw(looks_like_number);
 
 =head1 NAME
@@ -58,7 +58,7 @@ sub render {
     );
 
     # Make sure the type is ready to be used
-    my $type = $types{ $args{type} } || undef;
+    my $type = $types{ lc $args{type} } || undef;
 
     # Not a supported type
     if ( not defined $type ) {
@@ -96,14 +96,37 @@ sub render {
         my $max = 0;
         my $min = 0;
 
-        for my $dataset ( @{ $args{'data'} } ) {
-            if ( not defined $args{'max_value'} ) {
-                my $lmax = max @$dataset;
-                $max = $lmax if $lmax > $max;
+        if ( $args{'type'} =~ /stacked/i ) {
+            # Stacked bar charts are additive, so max / min take a little
+            # more work to calculate
+            my $size = @{ $args{'data'}->[0] } - 1;
+            for my $index ( 0 .. $size ) {
+                my @stack = grep { defined } map { $_->[$index] } @{ $args{'data'} };
+
+                if ( not defined $args{'max_value'} ) {
+                    # Add all of the positive numbers
+                    my $lmax = sum grep { $_ > 0 } @stack;
+                    $max = $lmax if defined $lmax and $lmax > $max;
+                }
+                if ( not defined $args{'min_value'} ) {
+                    # Add all of the negative numbers
+                    my $lmin = sum grep { $_ < 0 } @stack;
+                    $min = $lmin if defined $lmin and $lmin < $min;
+                }
             }
-            if ( not defined $args{'min_value'} ) {
-                my $lmin = min @$dataset;
-                $min = $lmin if $lmin < $min;
+        }
+        else {
+            # Everything else, simply find the largest and smallest value in
+            # any of the datasets
+            for my $dataset ( @{ $args{'data'} } ) {
+                if ( not defined $args{'max_value'} ) {
+                    my $lmax = max grep { defined } @$dataset;
+                    $max = $lmax if $lmax > $max;
+                }
+                if ( not defined $args{'min_value'} ) {
+                    my $lmin = min grep { defined } @$dataset;
+                    $min = $lmin if $lmin < $min;
+                }
             }
         }
         
@@ -130,6 +153,15 @@ sub render {
         $url .= "&chd=s:" . $self->_simple_encode_data( $args{'max_value'}, @{$args{'data'}} );
     }
     else {
+        # If we want to add/subtract a percentage of the max/min, then
+        # calculate it now
+        for my $limit (qw( min max )) {
+            my $key = $limit . "_" . ($limit eq 'min' ? 'minus' : 'plus');
+            if ( $args{$key} =~ s/%$// ) {
+                $args{$key} = int( ($args{$key} / 100) * abs($args{ $limit ."_value" }) );
+            }
+        }
+
         my $min = $args{'min_value'} - $args{'min_minus'};
         my $max = $args{'max_value'} + $args{'max_plus'};
 
@@ -137,7 +169,7 @@ sub render {
         # number out of range to mark it as undefined
         my @data;
         for my $data ( @{$args{'data'}} ) {
-            push @data, [map { looks_like_number($_) ? $_ : $max+42 } @$data];
+            push @data, [map { looks_like_number($_) ? $_ : $min-42 } @$data];
         }
 
         # Let's do text encoding with data scaling
@@ -168,7 +200,7 @@ sub render {
         $url .= "&chxl=$labels" if defined $labels;
     }
 
-    # Add colors since Google::Chart sucks at it
+    # Add colors
     if ( defined $args{'colors'} ) {
         $url .= "&chco=" . join ',', @{ $args{'colors'} };
     }
@@ -176,6 +208,11 @@ sub render {
         $url .= "&chf=bg,s,$args{'bgcolor'}";
     }
 
+    # Add bar widths for bar charts
+    if ( $args{'type'} =~ /bar/i ) {
+        $url .= "&chbh=" . join ',', @{ $args{'bar_width'} };
+    }
+
     Jifty->web->out( qq{<img src="$url" />} );
 
     # Make sure we don't return anything that will get output

commit 08e42995cf0c3588db3aef9455c8c24f2c10cec2
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jul 25 15:22:32 2008 +0000

    * Format data values to not waste URL and domain/range space
    * Support setting range labels
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5580 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 99c3cae..52ceaa3 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -33,6 +33,7 @@ sub render {
         geoarea   => 'world',
         min_minus => 0,
         max_plus  => 0,
+        format    => '%0.1f',
         @_
     );
 
@@ -140,9 +141,20 @@ sub render {
     # Add the type
     $url .= "cht=$type";
 
-    # Add the width - XXX TODO: we don't validate these yet
+    # Add the width
     $url .= "&chs=$args{'width'}x$args{'height'}";
 
+    # Format the data
+    unless ( not defined $args{'format'} ) {
+        for my $set ( @{$args{'data'}} ) {
+            @$set = map {
+                        looks_like_number($_)
+                            ? sprintf $args{'format'}, $_
+                            : $_
+                    } @$set;
+        }
+    }
+
     # Add the data (encoding it first)
     if ( $type eq 't' ) {
         # Map!
@@ -165,6 +177,14 @@ sub render {
         my $min = $args{'min_value'} - $args{'min_minus'};
         my $max = $args{'max_value'} + $args{'max_plus'};
 
+        unless ( not defined $args{'format'} ) {
+            $min = sprintf $args{'format'}, $min;
+            $max = sprintf $args{'format'}, $max;
+        }
+
+        $args{'calculated_min'} = $min;
+        $args{'calculated_max'} = $max;
+
         # If it's a number, pass it through, otherwise replace it with a
         # number out of range to mark it as undefined
         my @data;
@@ -189,15 +209,23 @@ sub render {
     # Add any axes
     if ( $args{'axes'} ) {
         $url .= "&chxt=" . $args{'axes'};
-        
-        my $labels;
-        my $index = 0;
-        for my $labelset ( @{ $args{'labels'} } ) {
-            $labels .= "$index:|" . join '|', map { uri_escape($_) } @$labelset
-                if @$labelset;
-            $index++;
+
+        if ( defined $args{'labels'} ) {
+            my @labels;
+            my @ranges;
+            my $index = 0;
+            for my $labelset ( @{ $args{'labels'} } ) {
+                if ( ref $labelset eq 'ARRAY' and @$labelset ) {
+                    push @labels, "$index:|" . join '|', map { uri_escape($_) } @$labelset;
+                }
+                elsif ( not ref $labelset and $labelset eq 'RANGE' ) {
+                    push @ranges, "$index,$args{'calculated_min'},$args{'calculated_max'}";
+                }
+                $index++;
+            }
+            $url .= "&chxl=" . join '|', @labels if @labels;
+            $url .= "&chxr=" . join '|', @ranges if @ranges;
         }
-        $url .= "&chxl=$labels" if defined $labels;
     }
 
     # Add colors

commit 904f5874e60250d5a584603532acdefc0d3d23da
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jul 25 15:41:23 2008 +0000

    Don't floor the percentage to add to max/min
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5581 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 52ceaa3..5494af4 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -169,8 +169,8 @@ sub render {
         # calculate it now
         for my $limit (qw( min max )) {
             my $key = $limit . "_" . ($limit eq 'min' ? 'minus' : 'plus');
-            if ( $args{$key} =~ s/%$// ) {
-                $args{$key} = int( ($args{$key} / 100) * abs($args{ $limit ."_value" }) );
+            if ( $args{$key} =~ s/\%$// ) {
+                $args{$key} = ($args{$key} / 100) * abs($args{ $limit."_value" });
             }
         }
 

commit c2fbc057f9285f5c88a69dbeddb01665e3c232e3
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jul 25 18:33:54 2008 +0000

    Support Google chart titles and support XML/SWF stacked horizontal bar charts
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5582 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 5494af4..9092a51 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -197,6 +197,12 @@ sub render {
         $url .= "&chds=$min,$max";
     }
 
+    # Add a title
+    if ( defined $args{'title'} ) {
+        $args{'title'} =~ tr/\n/|/;
+        $url .= "&chtt=" . uri_escape( $args{'title'} );
+    }
+
     # Add the legend
     if ( $args{'legend'} ) {
         my $key = $args{'type'} =~ /pie/i ? 'chl' : 'chdl';
diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index 88661f4..9fd4b12 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -57,6 +57,7 @@ sub render {
         'bars'           => 'column',
         'stackedbars'    => 'stacked column',
         'horizontalbars' => 'bar',
+        'stackedhorizontalbars' => 'stacked bar',
         'lines'          => 'line',
         'pie'            => '3d pie',
         'points'         => 'scatter',

commit 7c9379bc21052fdea4da11731f523a443a7bd385
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Aug 6 19:10:18 2008 +0000

    Let the Google Chart's renderer set axis styles
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5673 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 9092a51..c0bb490 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -34,6 +34,7 @@ sub render {
         min_minus => 0,
         max_plus  => 0,
         format    => '%0.1f',
+        axis_styles => [],
         @_
     );
 
@@ -229,8 +230,19 @@ sub render {
                 }
                 $index++;
             }
+            
+            my @styles;
+            $index = 0;
+            for my $style ( @{ $args{'axis_styles'} } ) {
+                if ( ref $style eq 'ARRAY' and @$style ) {
+                    push @styles, join ',', $index, @$style;
+                }
+                $index++;
+            }
+
             $url .= "&chxl=" . join '|', @labels if @labels;
             $url .= "&chxr=" . join '|', @ranges if @ranges;
+            $url .= "&chxs=" . join '|', @styles if @styles;
         }
     }
 

commit c34cdee95ba2fa909950e7a6b53202f7c3e2d23b
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Aug 14 19:03:15 2008 +0000

    Add support for shape markers
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5719 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index c0bb490..30ebfb5 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -33,7 +33,8 @@ sub render {
         geoarea   => 'world',
         min_minus => 0,
         max_plus  => 0,
-        format    => '%0.1f',
+        format    => '%0.2f',
+        markers   => [],
         axis_styles => [],
         @_
     );
@@ -166,6 +167,17 @@ sub render {
         $url .= "&chd=s:" . $self->_simple_encode_data( $args{'max_value'}, @{$args{'data'}} );
     }
     else {
+        # Deal with out of range horizontal markers here by fixing our range
+        if ( @{ $args{'markers'} } ) {
+            for my $marker ( grep { $_->{'type'} eq 'h' } @{$args{'markers'}} ) {
+                $args{'max_value'} = $marker->{'position'}
+                    if $marker->{'position'} > $args{'max_value'};
+                
+                $args{'min_value'} = $marker->{'position'}
+                    if $marker->{'position'} < $args{'min_value'};
+            }
+        }
+
         # If we want to add/subtract a percentage of the max/min, then
         # calculate it now
         for my $limit (qw( min max )) {
@@ -177,15 +189,16 @@ sub render {
 
         my $min = $args{'min_value'} - $args{'min_minus'};
         my $max = $args{'max_value'} + $args{'max_plus'};
+        
+        $args{'calculated_min'} = $min;
+        $args{'calculated_max'} = $max;
 
+        # Format the min and max for use a few lines down
         unless ( not defined $args{'format'} ) {
             $min = sprintf $args{'format'}, $min;
             $max = sprintf $args{'format'}, $max;
         }
 
-        $args{'calculated_min'} = $min;
-        $args{'calculated_max'} = $max;
-
         # If it's a number, pass it through, otherwise replace it with a
         # number out of range to mark it as undefined
         my @data;
@@ -226,7 +239,8 @@ sub render {
                     push @labels, "$index:|" . join '|', map { uri_escape($_) } @$labelset;
                 }
                 elsif ( not ref $labelset and $labelset eq 'RANGE' ) {
-                    push @ranges, "$index,$args{'calculated_min'},$args{'calculated_max'}";
+                    push @ranges, sprintf "%d,$args{'format'},$args{'format'}",
+                                           $index, $args{'calculated_min'}, $args{'calculated_max'};
                 }
                 $index++;
             }
@@ -259,6 +273,37 @@ sub render {
         $url .= "&chbh=" . join ',', @{ $args{'bar_width'} };
     }
 
+    # Add shape markers
+    if ( @{ $args{'markers'} } ) {
+        my @markers;
+        for my $data ( @{$args{'markers'}} ) {
+            my %marker = (
+                type     => 'x',
+                color    => '000000',
+                dataset  => 0,
+                position => 0,
+                size     => 5,
+                priority => 0,
+                %$data,
+            );
+
+            # Calculate where the position should be for horizontal lines
+            if ( $marker{'type'} eq 'h' ) {
+                $marker{'position'} /= abs( $args{'calculated_max'} - $args{'calculated_min'} );
+            }
+            # Fix text type
+            elsif ( $marker{'type'} eq 't' ) {
+                $marker{'type'} .= uri_escape( $marker{'text'} );
+            }
+
+            # Format the position
+            $marker{'position'} = sprintf $args{'format'}, $marker{'position'};
+
+            push @markers, join(',', @marker{qw( type color dataset position size priority )});
+        }
+        $url .= "&chm=" . join '|', @markers if @markers;
+    }
+
     Jifty->web->out( qq{<img src="$url" />} );
 
     # Make sure we don't return anything that will get output

commit 80c405afc86953af7ebb0d7fccc86a06318aa4d0
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Aug 15 02:19:06 2008 +0000

    Support range markers and actually calculate the position with the chart range correctly for all markers
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5725 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 30ebfb5..bac350c 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -273,7 +273,7 @@ sub render {
         $url .= "&chbh=" . join ',', @{ $args{'bar_width'} };
     }
 
-    # Add shape markers
+    # Add shape/range markers
     if ( @{ $args{'markers'} } ) {
         my @markers;
         for my $data ( @{$args{'markers'}} ) {
@@ -289,15 +289,34 @@ sub render {
 
             # Calculate where the position should be for horizontal lines
             if ( $marker{'type'} eq 'h' ) {
-                $marker{'position'} /= abs( $args{'calculated_max'} - $args{'calculated_min'} );
+                $marker{'position'} = $self->_position_in_range( $marker{'position'},
+                                                                 $args{'calculated_min'},
+                                                                 $args{'calculated_max'} );
+            }
+            # Calculate where the position should be for ranges
+            elsif ( lc($marker{'type'}) eq 'r' ) {
+                for (qw( start end )) {
+                    $marker{$_} = $args{'calculated_min'} if $marker{$_} eq 'MIN';
+                    $marker{$_} = $args{'calculated_max'} if $marker{$_} eq 'MAX';
+
+                    $marker{$_} = $self->_position_in_range( $marker{$_},
+                                                             $args{'calculated_min'},
+                                                             $args{'calculated_max'} );
+                }
             }
             # Fix text type
             elsif ( $marker{'type'} eq 't' ) {
                 $marker{'type'} .= uri_escape( $marker{'text'} );
             }
 
-            # Format the position
-            $marker{'position'} = sprintf $args{'format'}, $marker{'position'};
+            if ( lc($marker{'type'}) eq 'r' ) {
+                $marker{'position'} = sprintf $args{'format'}, $marker{'start'};
+                $marker{'size'}     = sprintf $args{'format'}, $marker{'end'};
+            }
+            else {
+                # Format the position
+                $marker{'position'} = sprintf $args{'format'}, $marker{'position'};
+            }
 
             push @markers, join(',', @marker{qw( type color dataset position size priority )});
         }
@@ -310,6 +329,11 @@ sub render {
     return;
 }
 
+sub _position_in_range {
+    my ( $self, $point, $min, $max ) = @_;
+    return ($point - $min) / ($max - $min);
+}
+
 # Borrowed with slight modifications from Google::Chart::Data::SimpleEncoding
 sub _simple_encode_data {
     my $self = shift;

commit 1d11e4401a68c9941b803ea4d804e681075851e4
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 18 15:02:27 2008 +0000

    We need some error handling here so we don't try to divide by zero and (somehow) get hilarious errors like:
    
    Can't locate object method "message" via package "Illegal division by zero at /var/home/perl/5.8.8/lib/site_perl/5.8.8/Jifty/Plugin/Chart/Renderer/Google.pm line 334." (perhaps you forgot to load "Illegal division by zero at /var/home/perl/5.8.8/lib/site_perl/5.8.8/Jifty/Plugin/Chart/Renderer/Google.pm line 334."?)
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5746 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index bac350c..8099bbf 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -331,6 +331,13 @@ sub render {
 
 sub _position_in_range {
     my ( $self, $point, $min, $max ) = @_;
+
+    return 0 if not defined $point
+             or not defined $min
+             or not defined $max;
+
+    return $min if $max == $min;
+
     return ($point - $min) / ($max - $min);
 }
 

commit b750dddd66347eaa00e43493cc36e2a84ae09ee7
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Mon Sep 8 19:12:09 2008 +0000

    Support for allowing different min/max for each dataset in the Google renderer
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5811 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 8099bbf..eaf76f9 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -6,6 +6,7 @@ use base qw/ Jifty::Plugin::Chart::Renderer /;
 
 use URI::Escape qw(uri_escape);
 use List::Util qw(max min sum);
+use List::MoreUtils qw(mesh);
 use Scalar::Util qw(looks_like_number);
 
 =head1 NAME
@@ -73,6 +74,10 @@ sub render {
     $args{'width'} =~ s/px$//;
     $args{'height'} =~ s/px$//;
 
+    # a bit of dwim
+    $args{'min_value'} ||= delete $args{'min_values'};
+    $args{'max_value'} ||= delete $args{'max_values'};
+
     # Check size and die if wrong
     for ( qw(width height) ) {
         if ( $type eq 't' ) {
@@ -187,28 +192,43 @@ sub render {
             }
         }
 
-        my $min = $args{'min_value'} - $args{'min_minus'};
-        my $max = $args{'max_value'} + $args{'max_plus'};
-        
-        $args{'calculated_min'} = $min;
-        $args{'calculated_max'} = $max;
+        for ('min_value', 'max_value') {
+            $args{$_} = [ $args{$_} ] if !ref($args{$_});
+        }
+
+        my @min = map { $_ - $args{'min_minus'} } @{ $args{'min_value'} };
+        my @max = map { $_ - $args{'max_plus'}  } @{ $args{'max_value'} };
+
+        # repeat if necessary
+        #push @min, ($min[-1]) x (@data - @min);
+        #push @max, ($max[-1]) x (@data - @max);
+
+        $args{'calculated_min'} = \@min;
+        $args{'calculated_max'} = \@max;
 
         # Format the min and max for use a few lines down
         unless ( not defined $args{'format'} ) {
-            $min = sprintf $args{'format'}, $min;
-            $max = sprintf $args{'format'}, $max;
+            @min = map { sprintf $args{'format'}, $_ } @min;
+            @max = map { sprintf $args{'format'}, $_ } @max;
         }
 
         # If it's a number, pass it through, otherwise replace it with a
         # number out of range to mark it as undefined
         my @data;
-        for my $data ( @{$args{'data'}} ) {
-            push @data, [map { looks_like_number($_) ? $_ : $min-42 } @$data];
+        for my $data_idx ( 0 .. @{$args{'data'}}-1 ) {
+            push @data, [
+                map {
+                    looks_like_number($_)
+                    ? $_
+                    : $min[$data_idx] - 42
+                } @{ $args{'data'}[$data_idx] }
+            ];
         }
 
         # Let's do text encoding with data scaling
         $url .= "&chd=t:" . join '|', map { join ',', @$_ } @data;
-        $url .= "&chds=$min,$max";
+
+        $url .= "&chds=" . join(',', mesh @min, @max);
     }
 
     # Add a title
@@ -240,7 +260,9 @@ sub render {
                 }
                 elsif ( not ref $labelset and $labelset eq 'RANGE' ) {
                     push @ranges, sprintf "%d,$args{'format'},$args{'format'}",
-                                           $index, $args{'calculated_min'}, $args{'calculated_max'};
+                                           $index,
+                                           $args{'calculated_min'}[$index],
+                                           $args{'calculated_max'}[$index];
                 }
                 $index++;
             }
@@ -276,6 +298,7 @@ sub render {
     # Add shape/range markers
     if ( @{ $args{'markers'} } ) {
         my @markers;
+        my $index = 0;
         for my $data ( @{$args{'markers'}} ) {
             my %marker = (
                 type     => 'x',
@@ -290,18 +313,18 @@ sub render {
             # Calculate where the position should be for horizontal lines
             if ( $marker{'type'} eq 'h' ) {
                 $marker{'position'} = $self->_position_in_range( $marker{'position'},
-                                                                 $args{'calculated_min'},
-                                                                 $args{'calculated_max'} );
+                                                                 $args{'calculated_min'}[$index],
+                                                                 $args{'calculated_max'}[$index] );
             }
             # Calculate where the position should be for ranges
             elsif ( lc($marker{'type'}) eq 'r' ) {
                 for (qw( start end )) {
-                    $marker{$_} = $args{'calculated_min'} if $marker{$_} eq 'MIN';
-                    $marker{$_} = $args{'calculated_max'} if $marker{$_} eq 'MAX';
+                    $marker{$_} = $args{'calculated_min'}[$index] if $marker{$_} eq 'MIN';
+                    $marker{$_} = $args{'calculated_max'}[$index] if $marker{$_} eq 'MAX';
 
                     $marker{$_} = $self->_position_in_range( $marker{$_},
-                                                             $args{'calculated_min'},
-                                                             $args{'calculated_max'} );
+                                                             $args{'calculated_min'}[$index],
+                                                             $args{'calculated_max'}[$index] );
                 }
             }
             # Fix text type
@@ -321,6 +344,7 @@ sub render {
             push @markers, join(',', @marker{qw( type color dataset position size priority )});
         }
         $url .= "&chm=" . join '|', @markers if @markers;
+        ++$index;
     }
 
     Jifty->web->out( qq{<img src="$url" />} );
@@ -343,21 +367,23 @@ sub _position_in_range {
 
 # Borrowed with slight modifications from Google::Chart::Data::SimpleEncoding
 sub _simple_encode_data {
-    my $self = shift;
-    my $max  = shift;
-    my $data = shift;
+    my $self  = shift;
+    my $maxes = shift;
+    my $data  = shift;
 
+    my $i = 0;
     my $result = '';
     my @map = ('A'..'Z', 'a'..'z', 0..9);
     for my $value ( @$data ) {
         if ( looks_like_number($value) ) {
-            my $index = int($value / $max * (@map - 1));
+            my $index = int($value / $maxes->[$i] * (@map - 1));
             $index = 0 if $index < 0;
             $index = @map if $index > @map;
             $result .= $map[$index];
         } else {
             $result .= '_';
         }
+        ++$i;
     }
     return $result;
 }

commit eede144a3df9f83d690f84d430a17d6a264b1964
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Mon Sep 8 19:25:41 2008 +0000

    We do need to duplicate the min/max values for the rest of the datasets
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5812 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index eaf76f9..b3e962f 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -200,8 +200,8 @@ sub render {
         my @max = map { $_ - $args{'max_plus'}  } @{ $args{'max_value'} };
 
         # repeat if necessary
-        #push @min, ($min[-1]) x (@data - @min);
-        #push @max, ($max[-1]) x (@data - @max);
+        push @min, ($min[-1]) x (@{ $args{'data'} } - @min);
+        push @max, ($max[-1]) x (@{ $args{'data'} } - @max);
 
         $args{'calculated_min'} = \@min;
         $args{'calculated_max'} = \@max;

commit cac25ed80787daf4e266371180291aeffe85714e
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Mon Sep 8 20:41:18 2008 +0000

    Allow area graphs in XML/SWF
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5813 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index 9fd4b12..859741d 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -61,6 +61,7 @@ sub render {
         'lines'          => 'line',
         'pie'            => '3d pie',
         'points'         => 'scatter',
+        'area'           => 'area',
     );
 
     # Make sure the type is ready to be used

commit 3404b59b5ae6b81f951711f85b86660bb5ab81c4
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Mon Sep 8 20:41:26 2008 +0000

    Support for composite graphs in XML/SWF
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5814 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index 859741d..c3d3323 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -65,7 +65,22 @@ sub render {
     );
 
     # Make sure the type is ready to be used
-    $args{type} = $types{ $args{type} } || undef;
+
+    if ($args{type} eq 'composite') {
+        for (@{ $args{types} }) {
+            if (!$types{$_}) {
+                Jifty->log->warn("Unsupported chart type: $_!");
+                return;
+            }
+            $_ = $types{$_};
+        }
+    }
+    else {
+        $args{type} = $types{ $args{type} } or do {
+            Jifty->log->warn("Unsupported chart type: $args{type}!");
+            return;
+        };
+    }
 
     # Kill the "px" unit
     $args{width} =~ s/px$//;
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 4b983ed..7908883 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -112,6 +112,10 @@ template 'chart/xmlswf' => sub {
         },
     );
 
+    if ($args->{type} eq 'composite') {
+        $chart{chart_type} = { string => $args->{types} };
+    }
+
     for my $i ( 0 .. $#{ $args->{data} } ) {
         push @{$chart{'chart_data'}{'row'}}, {
             string => [ $args->{legend}[$i] || {} ],

commit 2185ec30f1b9e6e243c9b273f5a24679ae7099c7
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Sep 16 07:33:21 2008 +0000

    (empty commit message)
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5845 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index b3e962f..8099bbf 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -6,7 +6,6 @@ use base qw/ Jifty::Plugin::Chart::Renderer /;
 
 use URI::Escape qw(uri_escape);
 use List::Util qw(max min sum);
-use List::MoreUtils qw(mesh);
 use Scalar::Util qw(looks_like_number);
 
 =head1 NAME
@@ -74,10 +73,6 @@ sub render {
     $args{'width'} =~ s/px$//;
     $args{'height'} =~ s/px$//;
 
-    # a bit of dwim
-    $args{'min_value'} ||= delete $args{'min_values'};
-    $args{'max_value'} ||= delete $args{'max_values'};
-
     # Check size and die if wrong
     for ( qw(width height) ) {
         if ( $type eq 't' ) {
@@ -192,43 +187,28 @@ sub render {
             }
         }
 
-        for ('min_value', 'max_value') {
-            $args{$_} = [ $args{$_} ] if !ref($args{$_});
-        }
-
-        my @min = map { $_ - $args{'min_minus'} } @{ $args{'min_value'} };
-        my @max = map { $_ - $args{'max_plus'}  } @{ $args{'max_value'} };
-
-        # repeat if necessary
-        push @min, ($min[-1]) x (@{ $args{'data'} } - @min);
-        push @max, ($max[-1]) x (@{ $args{'data'} } - @max);
-
-        $args{'calculated_min'} = \@min;
-        $args{'calculated_max'} = \@max;
+        my $min = $args{'min_value'} - $args{'min_minus'};
+        my $max = $args{'max_value'} + $args{'max_plus'};
+        
+        $args{'calculated_min'} = $min;
+        $args{'calculated_max'} = $max;
 
         # Format the min and max for use a few lines down
         unless ( not defined $args{'format'} ) {
-            @min = map { sprintf $args{'format'}, $_ } @min;
-            @max = map { sprintf $args{'format'}, $_ } @max;
+            $min = sprintf $args{'format'}, $min;
+            $max = sprintf $args{'format'}, $max;
         }
 
         # If it's a number, pass it through, otherwise replace it with a
         # number out of range to mark it as undefined
         my @data;
-        for my $data_idx ( 0 .. @{$args{'data'}}-1 ) {
-            push @data, [
-                map {
-                    looks_like_number($_)
-                    ? $_
-                    : $min[$data_idx] - 42
-                } @{ $args{'data'}[$data_idx] }
-            ];
+        for my $data ( @{$args{'data'}} ) {
+            push @data, [map { looks_like_number($_) ? $_ : $min-42 } @$data];
         }
 
         # Let's do text encoding with data scaling
         $url .= "&chd=t:" . join '|', map { join ',', @$_ } @data;
-
-        $url .= "&chds=" . join(',', mesh @min, @max);
+        $url .= "&chds=$min,$max";
     }
 
     # Add a title
@@ -260,9 +240,7 @@ sub render {
                 }
                 elsif ( not ref $labelset and $labelset eq 'RANGE' ) {
                     push @ranges, sprintf "%d,$args{'format'},$args{'format'}",
-                                           $index,
-                                           $args{'calculated_min'}[$index],
-                                           $args{'calculated_max'}[$index];
+                                           $index, $args{'calculated_min'}, $args{'calculated_max'};
                 }
                 $index++;
             }
@@ -298,7 +276,6 @@ sub render {
     # Add shape/range markers
     if ( @{ $args{'markers'} } ) {
         my @markers;
-        my $index = 0;
         for my $data ( @{$args{'markers'}} ) {
             my %marker = (
                 type     => 'x',
@@ -313,18 +290,18 @@ sub render {
             # Calculate where the position should be for horizontal lines
             if ( $marker{'type'} eq 'h' ) {
                 $marker{'position'} = $self->_position_in_range( $marker{'position'},
-                                                                 $args{'calculated_min'}[$index],
-                                                                 $args{'calculated_max'}[$index] );
+                                                                 $args{'calculated_min'},
+                                                                 $args{'calculated_max'} );
             }
             # Calculate where the position should be for ranges
             elsif ( lc($marker{'type'}) eq 'r' ) {
                 for (qw( start end )) {
-                    $marker{$_} = $args{'calculated_min'}[$index] if $marker{$_} eq 'MIN';
-                    $marker{$_} = $args{'calculated_max'}[$index] if $marker{$_} eq 'MAX';
+                    $marker{$_} = $args{'calculated_min'} if $marker{$_} eq 'MIN';
+                    $marker{$_} = $args{'calculated_max'} if $marker{$_} eq 'MAX';
 
                     $marker{$_} = $self->_position_in_range( $marker{$_},
-                                                             $args{'calculated_min'}[$index],
-                                                             $args{'calculated_max'}[$index] );
+                                                             $args{'calculated_min'},
+                                                             $args{'calculated_max'} );
                 }
             }
             # Fix text type
@@ -344,7 +321,6 @@ sub render {
             push @markers, join(',', @marker{qw( type color dataset position size priority )});
         }
         $url .= "&chm=" . join '|', @markers if @markers;
-        ++$index;
     }
 
     Jifty->web->out( qq{<img src="$url" />} );
@@ -367,23 +343,21 @@ sub _position_in_range {
 
 # Borrowed with slight modifications from Google::Chart::Data::SimpleEncoding
 sub _simple_encode_data {
-    my $self  = shift;
-    my $maxes = shift;
-    my $data  = shift;
+    my $self = shift;
+    my $max  = shift;
+    my $data = shift;
 
-    my $i = 0;
     my $result = '';
     my @map = ('A'..'Z', 'a'..'z', 0..9);
     for my $value ( @$data ) {
         if ( looks_like_number($value) ) {
-            my $index = int($value / $maxes->[$i] * (@map - 1));
+            my $index = int($value / $max * (@map - 1));
             $index = 0 if $index < 0;
             $index = @map if $index > @map;
             $result .= $map[$index];
         } else {
             $result .= '_';
         }
-        ++$i;
     }
     return $result;
 }
diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index c3d3323..9fd4b12 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -61,26 +61,10 @@ sub render {
         'lines'          => 'line',
         'pie'            => '3d pie',
         'points'         => 'scatter',
-        'area'           => 'area',
     );
 
     # Make sure the type is ready to be used
-
-    if ($args{type} eq 'composite') {
-        for (@{ $args{types} }) {
-            if (!$types{$_}) {
-                Jifty->log->warn("Unsupported chart type: $_!");
-                return;
-            }
-            $_ = $types{$_};
-        }
-    }
-    else {
-        $args{type} = $types{ $args{type} } or do {
-            Jifty->log->warn("Unsupported chart type: $args{type}!");
-            return;
-        };
-    }
+    $args{type} = $types{ $args{type} } || undef;
 
     # Kill the "px" unit
     $args{width} =~ s/px$//;
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 7908883..4b983ed 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -112,10 +112,6 @@ template 'chart/xmlswf' => sub {
         },
     );
 
-    if ($args->{type} eq 'composite') {
-        $chart{chart_type} = { string => $args->{types} };
-    }
-
     for my $i ( 0 .. $#{ $args->{data} } ) {
         push @{$chart{'chart_data'}{'row'}}, {
             string => [ $args->{legend}[$i] || {} ],

commit 5d21f6f7c828feae719f3aa635d5cad99009b205
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Sep 16 07:33:34 2008 +0000

    Support for allowing different min/max for each dataset in the Google renderer
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5846 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 8099bbf..eaf76f9 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -6,6 +6,7 @@ use base qw/ Jifty::Plugin::Chart::Renderer /;
 
 use URI::Escape qw(uri_escape);
 use List::Util qw(max min sum);
+use List::MoreUtils qw(mesh);
 use Scalar::Util qw(looks_like_number);
 
 =head1 NAME
@@ -73,6 +74,10 @@ sub render {
     $args{'width'} =~ s/px$//;
     $args{'height'} =~ s/px$//;
 
+    # a bit of dwim
+    $args{'min_value'} ||= delete $args{'min_values'};
+    $args{'max_value'} ||= delete $args{'max_values'};
+
     # Check size and die if wrong
     for ( qw(width height) ) {
         if ( $type eq 't' ) {
@@ -187,28 +192,43 @@ sub render {
             }
         }
 
-        my $min = $args{'min_value'} - $args{'min_minus'};
-        my $max = $args{'max_value'} + $args{'max_plus'};
-        
-        $args{'calculated_min'} = $min;
-        $args{'calculated_max'} = $max;
+        for ('min_value', 'max_value') {
+            $args{$_} = [ $args{$_} ] if !ref($args{$_});
+        }
+
+        my @min = map { $_ - $args{'min_minus'} } @{ $args{'min_value'} };
+        my @max = map { $_ - $args{'max_plus'}  } @{ $args{'max_value'} };
+
+        # repeat if necessary
+        #push @min, ($min[-1]) x (@data - @min);
+        #push @max, ($max[-1]) x (@data - @max);
+
+        $args{'calculated_min'} = \@min;
+        $args{'calculated_max'} = \@max;
 
         # Format the min and max for use a few lines down
         unless ( not defined $args{'format'} ) {
-            $min = sprintf $args{'format'}, $min;
-            $max = sprintf $args{'format'}, $max;
+            @min = map { sprintf $args{'format'}, $_ } @min;
+            @max = map { sprintf $args{'format'}, $_ } @max;
         }
 
         # If it's a number, pass it through, otherwise replace it with a
         # number out of range to mark it as undefined
         my @data;
-        for my $data ( @{$args{'data'}} ) {
-            push @data, [map { looks_like_number($_) ? $_ : $min-42 } @$data];
+        for my $data_idx ( 0 .. @{$args{'data'}}-1 ) {
+            push @data, [
+                map {
+                    looks_like_number($_)
+                    ? $_
+                    : $min[$data_idx] - 42
+                } @{ $args{'data'}[$data_idx] }
+            ];
         }
 
         # Let's do text encoding with data scaling
         $url .= "&chd=t:" . join '|', map { join ',', @$_ } @data;
-        $url .= "&chds=$min,$max";
+
+        $url .= "&chds=" . join(',', mesh @min, @max);
     }
 
     # Add a title
@@ -240,7 +260,9 @@ sub render {
                 }
                 elsif ( not ref $labelset and $labelset eq 'RANGE' ) {
                     push @ranges, sprintf "%d,$args{'format'},$args{'format'}",
-                                           $index, $args{'calculated_min'}, $args{'calculated_max'};
+                                           $index,
+                                           $args{'calculated_min'}[$index],
+                                           $args{'calculated_max'}[$index];
                 }
                 $index++;
             }
@@ -276,6 +298,7 @@ sub render {
     # Add shape/range markers
     if ( @{ $args{'markers'} } ) {
         my @markers;
+        my $index = 0;
         for my $data ( @{$args{'markers'}} ) {
             my %marker = (
                 type     => 'x',
@@ -290,18 +313,18 @@ sub render {
             # Calculate where the position should be for horizontal lines
             if ( $marker{'type'} eq 'h' ) {
                 $marker{'position'} = $self->_position_in_range( $marker{'position'},
-                                                                 $args{'calculated_min'},
-                                                                 $args{'calculated_max'} );
+                                                                 $args{'calculated_min'}[$index],
+                                                                 $args{'calculated_max'}[$index] );
             }
             # Calculate where the position should be for ranges
             elsif ( lc($marker{'type'}) eq 'r' ) {
                 for (qw( start end )) {
-                    $marker{$_} = $args{'calculated_min'} if $marker{$_} eq 'MIN';
-                    $marker{$_} = $args{'calculated_max'} if $marker{$_} eq 'MAX';
+                    $marker{$_} = $args{'calculated_min'}[$index] if $marker{$_} eq 'MIN';
+                    $marker{$_} = $args{'calculated_max'}[$index] if $marker{$_} eq 'MAX';
 
                     $marker{$_} = $self->_position_in_range( $marker{$_},
-                                                             $args{'calculated_min'},
-                                                             $args{'calculated_max'} );
+                                                             $args{'calculated_min'}[$index],
+                                                             $args{'calculated_max'}[$index] );
                 }
             }
             # Fix text type
@@ -321,6 +344,7 @@ sub render {
             push @markers, join(',', @marker{qw( type color dataset position size priority )});
         }
         $url .= "&chm=" . join '|', @markers if @markers;
+        ++$index;
     }
 
     Jifty->web->out( qq{<img src="$url" />} );
@@ -343,21 +367,23 @@ sub _position_in_range {
 
 # Borrowed with slight modifications from Google::Chart::Data::SimpleEncoding
 sub _simple_encode_data {
-    my $self = shift;
-    my $max  = shift;
-    my $data = shift;
+    my $self  = shift;
+    my $maxes = shift;
+    my $data  = shift;
 
+    my $i = 0;
     my $result = '';
     my @map = ('A'..'Z', 'a'..'z', 0..9);
     for my $value ( @$data ) {
         if ( looks_like_number($value) ) {
-            my $index = int($value / $max * (@map - 1));
+            my $index = int($value / $maxes->[$i] * (@map - 1));
             $index = 0 if $index < 0;
             $index = @map if $index > @map;
             $result .= $map[$index];
         } else {
             $result .= '_';
         }
+        ++$i;
     }
     return $result;
 }

commit 1b93263549534f2023739b5d2eb7143ec4fca3ef
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Sep 16 07:33:49 2008 +0000

    We do need to duplicate the min/max values for the rest of the datasets
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5847 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index eaf76f9..b3e962f 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -200,8 +200,8 @@ sub render {
         my @max = map { $_ - $args{'max_plus'}  } @{ $args{'max_value'} };
 
         # repeat if necessary
-        #push @min, ($min[-1]) x (@data - @min);
-        #push @max, ($max[-1]) x (@data - @max);
+        push @min, ($min[-1]) x (@{ $args{'data'} } - @min);
+        push @max, ($max[-1]) x (@{ $args{'data'} } - @max);
 
         $args{'calculated_min'} = \@min;
         $args{'calculated_max'} = \@max;

commit 0677fba6d556b91466879adbe174cf679e7bc860
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Sep 16 07:34:02 2008 +0000

    Allow area graphs in XML/SWF
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5848 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index 9fd4b12..859741d 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -61,6 +61,7 @@ sub render {
         'lines'          => 'line',
         'pie'            => '3d pie',
         'points'         => 'scatter',
+        'area'           => 'area',
     );
 
     # Make sure the type is ready to be used

commit 89c7c9c41b22b88209733e189b67293b3c4dbfeb
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Sep 16 07:34:14 2008 +0000

    Support for composite graphs in XML/SWF
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5849 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index 859741d..c3d3323 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -65,7 +65,22 @@ sub render {
     );
 
     # Make sure the type is ready to be used
-    $args{type} = $types{ $args{type} } || undef;
+
+    if ($args{type} eq 'composite') {
+        for (@{ $args{types} }) {
+            if (!$types{$_}) {
+                Jifty->log->warn("Unsupported chart type: $_!");
+                return;
+            }
+            $_ = $types{$_};
+        }
+    }
+    else {
+        $args{type} = $types{ $args{type} } or do {
+            Jifty->log->warn("Unsupported chart type: $args{type}!");
+            return;
+        };
+    }
 
     # Kill the "px" unit
     $args{width} =~ s/px$//;
diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 4b983ed..7908883 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -112,6 +112,10 @@ template 'chart/xmlswf' => sub {
         },
     );
 
+    if ($args->{type} eq 'composite') {
+        $chart{chart_type} = { string => $args->{types} };
+    }
+
     for my $i ( 0 .. $#{ $args->{data} } ) {
         push @{$chart{'chart_data'}{'row'}}, {
             string => [ $args->{legend}[$i] || {} ],

commit 9c678f1be458ebae7ca5f13e6dd827b6eb3a80d7
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Sep 16 07:34:34 2008 +0000

    Try to support legendless graphs
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5850 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 7908883..43ab93e 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -117,9 +117,11 @@ template 'chart/xmlswf' => sub {
     }
 
     for my $i ( 0 .. $#{ $args->{data} } ) {
+        my $label = $args->{legend}[$i];
+
         push @{$chart{'chart_data'}{'row'}}, {
-            string => [ $args->{legend}[$i] || {} ],
             number => $args->{data}[$i],
+            defined($label) ? (string => [ $label ]) : (),
         };
     }
 

commit 51b5cc0d8ca468592d50975c539f87f072eb903d
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Wed Oct 15 09:13:35 2008 +0000

    Add a reasonable default for bar width based on number of bars and chart width
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5940 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index b3e962f..2b26027 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -292,6 +292,8 @@ sub render {
 
     # Add bar widths for bar charts
     if ( $args{'type'} =~ /bar/i ) {
+        @{ $args{'bar_width'} } = $self->_calculate_bar_width(\%args)
+            if @{ $args{'bar_width'} } == 0;
         $url .= "&chbh=" . join ',', @{ $args{'bar_width'} };
     }
 
@@ -388,6 +390,20 @@ sub _simple_encode_data {
     return $result;
 }
 
+sub _calculate_bar_width {
+    my $self = shift;
+    my $args = shift;
+
+    my $bars = @{ $args->{data}[0] };
+    my $bar_width = $args->{width};
+
+    $bar_width -= 10;         # chart margins
+    $bar_width -= 3 * $bars;  # bar margins
+    $bar_width /= $bars;      # each bar's width
+
+    return int($bar_width), 3;
+}
+
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>

commit 38bbee4ec0c7345b8d15541ca857bdead6134010
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Thu Oct 16 04:38:11 2008 +0000

    Account for undef bar_width
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5941 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 2b26027..8ccdf7b 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -293,7 +293,7 @@ sub render {
     # Add bar widths for bar charts
     if ( $args{'type'} =~ /bar/i ) {
         @{ $args{'bar_width'} } = $self->_calculate_bar_width(\%args)
-            if @{ $args{'bar_width'} } == 0;
+            if @{ $args{'bar_width'} || [] } == 0;
         $url .= "&chbh=" . join ',', @{ $args{'bar_width'} };
     }
 

commit 82d89e0e70daab6ca32d8672621c62be2f5343a5
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Thu Oct 16 05:55:12 2008 +0000

    zero_line for bar charts (for moving the bars further up or down the graph)
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5943 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 8ccdf7b..a1c62fe 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -290,11 +290,14 @@ sub render {
         $url .= "&chf=bg,s,$args{'bgcolor'}";
     }
 
-    # Add bar widths for bar charts
+    # Add bar widths and zero line for bar charts
     if ( $args{'type'} =~ /bar/i ) {
         @{ $args{'bar_width'} } = $self->_calculate_bar_width(\%args)
             if @{ $args{'bar_width'} || [] } == 0;
         $url .= "&chbh=" . join ',', @{ $args{'bar_width'} };
+
+        $url .= "&chp=" . $args{'zero_line'}
+            if defined $args{'zero_line'};
     }
 
     # Add shape/range markers

commit 3ec93ddf60cf0d328425b78a0ee51bc8bf00cc94
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Thu Oct 16 07:08:50 2008 +0000

    label positions (chxp)
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5944 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index a1c62fe..94050f2 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -279,6 +279,10 @@ sub render {
             $url .= "&chxl=" . join '|', @labels if @labels;
             $url .= "&chxr=" . join '|', @ranges if @ranges;
             $url .= "&chxs=" . join '|', @styles if @styles;
+
+            # label positions
+            $url .= "&chxp=" . join '|', @{ $args{'positions'} }
+                if defined @{ $args{'positions'} };
         }
     }
 

commit ab281205127732e7e30a1739b1a22af595a3ed2a
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Thu Oct 16 08:21:54 2008 +0000

    Fixes for label position
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5945 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 94050f2..ed68440 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -281,8 +281,8 @@ sub render {
             $url .= "&chxs=" . join '|', @styles if @styles;
 
             # label positions
-            $url .= "&chxp=" . join '|', @{ $args{'positions'} }
-                if defined @{ $args{'positions'} };
+            $url .= "&chxp=" . join ',', @{ $args{'positions'} }
+                if defined $args{'positions'};
         }
     }
 

commit 9ef4ec0cedb3a8a026a446df5c3653f710db21a7
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Thu Oct 16 09:57:58 2008 +0000

    Add a want_url argument to the Google renderer for just getting the resulting URL instead of the img tag
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5946 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index ed68440..8ac7f52 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -356,6 +356,8 @@ sub render {
         ++$index;
     }
 
+    return $url if $args{'want_url'};
+
     Jifty->web->out( qq{<img src="$url" />} );
 
     # Make sure we don't return anything that will get output

commit edb378cdccc31ae0501bc4a2cc71c8507eec935c
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Thu Oct 16 10:09:58 2008 +0000

    Add a redirect option to Google charts
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5947 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 8ac7f52..9f89289 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -357,6 +357,7 @@ sub render {
     }
 
     return $url if $args{'want_url'};
+    Jifty->web->_redirect($url) if $args{'redirect'};
 
     Jifty->web->out( qq{<img src="$url" />} );
 

commit d883a0fb29bd3df1924e6b8fe4340911f0cded15
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Nov 14 22:34:58 2008 +0000

     * Move log messages, whenever possible, to $self->log rather than Jifty->log
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5990 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer.pm b/lib/Jifty/Plugin/Chart/Renderer.pm
index af950c7..cba1d74 100644
--- a/lib/Jifty/Plugin/Chart/Renderer.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer.pm
@@ -3,6 +3,8 @@ use warnings;
 
 package Jifty::Plugin::Chart::Renderer;
 
+use base qw/Jifty::Object/;
+
 =head1 NAME
 
 Jifty::Plugin::Chart::Renderer - Base class for chart rendering classes
diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 9f89289..edccd5d 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -66,7 +66,7 @@ sub render {
 
     # Not a supported type
     if ( not defined $type ) {
-        Jifty->log->warn("Unsupported chart type: $args{'type'}!");
+        $self->log->warn("Unsupported chart type: $args{'type'}!");
         return;
     }
 
diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index c0d084c..a6fec91 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -61,7 +61,7 @@ sub render {
 
     # Bad stuff, not a supported type
     if ( not defined $args{type} ) {
-        Jifty->log->warn("Unsupported chart type: $orig_type!");
+        $self->log->warn("Unsupported chart type: $orig_type!");
         return;
     }
 
diff --git a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
index c3d3323..0366a44 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
@@ -69,7 +69,7 @@ sub render {
     if ($args{type} eq 'composite') {
         for (@{ $args{types} }) {
             if (!$types{$_}) {
-                Jifty->log->warn("Unsupported chart type: $_!");
+                $self->log->warn("Unsupported chart type: $_!");
                 return;
             }
             $_ = $types{$_};
@@ -77,7 +77,7 @@ sub render {
     }
     else {
         $args{type} = $types{ $args{type} } or do {
-            Jifty->log->warn("Unsupported chart type: $args{type}!");
+            $self->log->warn("Unsupported chart type: $args{type}!");
             return;
         };
     }

commit 22ddb254b0ab6e9a029a6ab59416eb9c2edb0652
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Mon Nov 17 20:31:12 2008 +0000

    If we have only one max, duplicate it before encoding
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6001 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index edccd5d..d506e1c 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -383,6 +383,8 @@ sub _simple_encode_data {
     my $maxes = shift;
     my $data  = shift;
 
+    $maxes = [ ($maxes) x @$data ] if !ref($maxes);
+
     my $i = 0;
     my $result = '';
     my @map = ('A'..'Z', 'a'..'z', 0..9);

commit def38665658e146fa7ddf4c8ec0fadaff1816082
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Mon Nov 17 22:56:35 2008 +0000

    Begin working on Google Vizualization "charts", specifically the annotated timeline
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6005 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
new file mode 100644
index 0000000..e828886
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -0,0 +1,8 @@
+package Jifty::Plugin::Chart::Renderer::GoogleViz;
+use strict;
+use warnings;
+use base 'Jifty::Plugin::Chart::Renderer';
+
+
+1;
+
diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm
new file mode 100644
index 0000000..65ae493
--- /dev/null
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm
@@ -0,0 +1,8 @@
+package Jifty::Plugin::Chart::Renderer::GoogleViz::AnnotatedTimeline;
+use strict;
+use warnings;
+use base 'Jifty::Plugin::Chart::Renderer::GoogleViz';
+
+
+1;
+

commit 3bcfe7e21ee921810289149c092eee33f658f10c
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Mon Nov 17 23:05:40 2008 +0000

    Superclass init and render
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6006 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index e828886..9304699 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -3,6 +3,31 @@ use strict;
 use warnings;
 use base 'Jifty::Plugin::Chart::Renderer';
 
+=head2 init
+
+We need to load Google's JS.
+
+=cut
+
+sub init {
+    my $self = shift;
+
+    Jifty->web->add_external_javascript("http://www.google.com/jsapi");
+}
+
+=head2 render
+
+=cut
+
+sub render {
+    my $self = shift;
+    if (ref($self) eq __PACKAGE) {
+        Carp::croak("You must use a subclass of GoogleViz, such as GoogleViz::AnnotatedTimeLine");
+    }
+    else {
+        Carp::croak(ref($self) . " does not implement render.");
+    }
+}
 
 1;
 

commit 3a81ba27d10965e398f27441bf6d3ed55b9b835e
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Mon Nov 17 23:19:44 2008 +0000

    Don't require that there be a special class for renderers to load
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6007 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Dispatcher.pm b/lib/Jifty/Plugin/Chart/Dispatcher.pm
index f9a2233..1d3561e 100644
--- a/lib/Jifty/Plugin/Chart/Dispatcher.pm
+++ b/lib/Jifty/Plugin/Chart/Dispatcher.pm
@@ -59,16 +59,16 @@ on 'chart/*/*' => run {
     $args->{width}  ||= 400;
     $args->{height} ||= 300;
 
-    my $class = $classes{$renderer};
-    
-    # Use the "type" to determine which class to use
-    $class =~ s/\$TYPE/$args->{type}/g;
+    if (my $class = $classes{$renderer}) {
+        # Use the "type" to determine which class to use
+        $class =~ s/\$TYPE/$args->{type}/g;
 
-    # Load that class or die if it does not exist
-    $class->require;
+        # Load that class or die if it does not exist
+        $class->require;
 
-    # Remember the class name for the view
-    $args->{class} = $class;
+        # Remember the class name for the view
+        $args->{class} = $class;
+    }
 
     # Send them on to chart the chart
     set 'args' => $args;

commit f1d7cfacce9efffb34c871ae2623353e3f78f4fb
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 00:19:00 2008 +0000

    skeleton of GoogleViz->render
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6008 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 9304699..5222f03 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -3,6 +3,8 @@ use strict;
 use warnings;
 use base 'Jifty::Plugin::Chart::Renderer';
 
+use Jifty::JSON;
+
 =head2 init
 
 We need to load Google's JS.
@@ -21,12 +23,32 @@ sub init {
 
 sub render {
     my $self = shift;
-    if (ref($self) eq __PACKAGE) {
-        Carp::croak("You must use a subclass of GoogleViz, such as GoogleViz::AnnotatedTimeLine");
-    }
-    else {
-        Carp::croak(ref($self) . " does not implement render.");
-    }
+    my $chart_id = 'chart_' . Jifty->web->serial;
+    my $chart_class = $self->chart_class;
+    my $load_params = objToJson {
+        packages => [ $self->packages_to_load ],
+    };
+    my $draw_params = objToJson($self->draw_params);
+
+    Jifty->web->out(<< "JS_HEADER");
+        <script type="text/javascript">
+            google.load('visualization', 1, $load_params);
+            google.setOnLoadCallback(function () {
+                var data = new google.visualization.DataTable();
+JS_HEADER
+
+    $self->render_data;
+
+    Jifty->web->out(<< "JS_FOOTER");
+                var chart = new $chart_class(document.getElementById('$chart_id'));
+                chart.draw(data, $draw_params);
+            });
+        </script>
+JS_FOOTER
+
+    Jifty->web->out(qq{<div id="$chart_id"></div>});
+
+    return;
 }
 
 1;

commit a441f5228dcec451bd70770dfd4472b1c3b3b586
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 00:22:24 2008 +0000

    Factor out load_params
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6009 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 5222f03..c2e96a3 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -25,9 +25,7 @@ sub render {
     my $self = shift;
     my $chart_id = 'chart_' . Jifty->web->serial;
     my $chart_class = $self->chart_class;
-    my $load_params = objToJson {
-        packages => [ $self->packages_to_load ],
-    };
+    my $load_params = objToJson($self->load_params);
     my $draw_params = objToJson($self->draw_params);
 
     Jifty->web->out(<< "JS_HEADER");
@@ -51,5 +49,20 @@ JS_FOOTER
     return;
 }
 
+=head2 load_params
+
+Load the "packages" required for the visualization; define a
+C<packages_to_load> method which returns a list of them.
+
+=cut
+
+sub load_params {
+    my $self = shift;
+
+    return {
+        packages => [ $self->packages_to_load ],
+    };
+}
+
 1;
 

commit 711565cb715597c5285d5cd6b69c7b36c9f203b7
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 00:43:24 2008 +0000

    Allow renderers with :: in their name
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6011 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 2b1c184..9ba5475 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -135,26 +135,31 @@ sub init_renderer {
     }
 
     # Prepend Jifty::Plugin::Chart::Renderer:: if we think we need to
-    if ( $renderer_class !~ /::/ ) {
-        $renderer_class = __PACKAGE__.'::Renderer::'.$renderer_class;
-    }
+    my @renderer_classes = (
+        $renderer_class,
+        __PACKAGE__ . '::Renderer::' . $renderer_class,
+    );
 
-    # Check to see if we already loaded this one
-    my $renderer = $self->renderers->{ $renderer_class };
-    return $renderer if defined $renderer;
+    for my $renderer_class (@renderer_classes) {
+        # Check to see if we already loaded this one
+        my $renderer = $self->renderers->{ $renderer_class };
+        return $renderer if defined $renderer;
 
-    # Tell perl to load the class
-    $renderer_class->require
-        or warn $@;
+        # Tell perl to load the class
+        unless ($renderer_class->require) {
+           warn $@ unless $@ =~ /^Can't locate /;
+           next;
+        }
 
-    # Initialize the renderer
-    $renderer = $renderer_class->new( %{ $self->plugin_args } );
+        # Initialize the renderer
+        $renderer = $renderer_class->new( %{ $self->plugin_args } );
 
-    # Remember it
-    $self->renderers->{ $renderer_class } = $renderer;
+        # Remember it
+        $self->renderers->{ $renderer_class } = $renderer;
 
-    # Return it!
-    return $renderer;
+        # Return it!
+        return $renderer;
+    }
 }
 
 =head1 SEE ALSO

commit 78aaf4943ae46fa19c452a9581da63d64af5d2f2
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 00:56:58 2008 +0000

    Minor fixes
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6013 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index c2e96a3..27b0233 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use base 'Jifty::Plugin::Chart::Renderer';
 
-use Jifty::JSON;
+use Jifty::JSON 'objToJson';
 
 =head2 init
 
@@ -27,20 +27,22 @@ sub render {
     my $chart_class = $self->chart_class;
     my $load_params = objToJson($self->load_params);
     my $draw_params = objToJson($self->draw_params);
+    my $callback_name = 'callback_' . Jifty->web->serial;
 
     Jifty->web->out(<< "JS_HEADER");
         <script type="text/javascript">
             google.load('visualization', 1, $load_params);
-            google.setOnLoadCallback(function () {
+            google.setOnLoadCallback($callback_name);
+            function $callback_name() {
                 var data = new google.visualization.DataTable();
 JS_HEADER
 
-    $self->render_data;
+    $self->render_data(@_);
 
     Jifty->web->out(<< "JS_FOOTER");
                 var chart = new $chart_class(document.getElementById('$chart_id'));
                 chart.draw(data, $draw_params);
-            });
+            }
         </script>
 JS_FOOTER
 

commit 3bc3aab02a25ebd659da3a83e80f496c15fceba3
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 19:53:40 2008 +0000

    Need to set the height and width of the viz div
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6017 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 27b0233..7378544 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -23,6 +23,8 @@ sub init {
 
 sub render {
     my $self = shift;
+    my %args = @_;
+
     my $chart_id = 'chart_' . Jifty->web->serial;
     my $chart_class = $self->chart_class;
     my $load_params = objToJson($self->load_params);
@@ -37,7 +39,7 @@ sub render {
                 var data = new google.visualization.DataTable();
 JS_HEADER
 
-    $self->render_data(@_);
+    $self->render_data(%args));
 
     Jifty->web->out(<< "JS_FOOTER");
                 var chart = new $chart_class(document.getElementById('$chart_id'));
@@ -46,7 +48,12 @@ JS_HEADER
         </script>
 JS_FOOTER
 
-    Jifty->web->out(qq{<div id="$chart_id"></div>});
+    Jifty->web->out(qq{
+        <div
+            style="width: $args{width}; height: $args{height};"
+            id="$chart_id"
+        ></div>
+    });
 
     return;
 }

commit 5c1646fb4319441902d5221fde8c294fdd51fb76
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 19:54:39 2008 +0000

    Typo
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6018 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 7378544..241d7db 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -39,7 +39,7 @@ sub render {
                 var data = new google.visualization.DataTable();
 JS_HEADER
 
-    $self->render_data(%args));
+    $self->render_data(%args);
 
     Jifty->web->out(<< "JS_FOOTER");
                 var chart = new $chart_class(document.getElementById('$chart_id'));

commit 95eb4b02cc9eb27bf224821e11ca24c8c2c32f87
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 20:57:46 2008 +0000

    Render the columns and data of a google viz
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6019 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 241d7db..47e88a1 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -73,5 +73,80 @@ sub load_params {
     };
 }
 
+=head2 render_data
+
+Renders the columns and the data.
+
+=cut
+
+sub render_data {
+    my $self = shift;
+    my %args = @_;
+
+    $self->add_columns(%args);
+    $self->add_data(%args);
+}
+
+=head2 add_columns
+
+Adds the columns to the visualization. Each column is a key-value pair; the key
+is the column's C<id> and the value is either a string (the C<type>) or a
+hashref. The hashref may specify C<type> and C<label>. If no C<label> is given,
+the C<id> is used.
+
+=cut
+
+sub add_columns {
+    my $self = shift;
+    my %args = @_;
+
+    my %cols = %{ $args{columns} };
+
+    for my $id (keys %cols) {
+        my $column = $cols{$id};
+
+        my ($type, $label);
+        if (ref($column)) {
+            $type  = $column->{type};
+            $label = $column->{label};
+        }
+        else {
+            $type = $column;
+        }
+
+        $label ||= $id;
+
+        Jifty->web->out("data.addColumn('$type', '$label', '$id');\n");
+    }
+}
+
+=head2 add_data
+
+Adds the data to the chart. Each data point should be a hash reference of
+column id to value.
+
+=cut
+
+sub add_data {
+    my $self = shift;
+    my %args = @_;
+
+    my @data = @{ $args{data} };
+
+    Jifty->web->out('data.addRows(' . scalar(@data) . ");\n");
+
+    my $row = 0;
+    for my $datapoint (@data) {
+        for my $column (keys %$datapoint) {
+            my $value = $datapoint->{$column};
+            my $encoded = objToJson($value);
+
+            Jifty->web->out("data.setValue($row, '$column', $encoded);\n");
+        }
+
+        ++$row;
+    }
+}
+
 1;
 

commit 2e8449029708e9949f990f2ddf5b9aa522fb6950
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 20:59:27 2008 +0000

    AnnotatedTimeline parameters
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6020 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm
index 65ae493..b52ad80 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm
@@ -3,6 +3,11 @@ use strict;
 use warnings;
 use base 'Jifty::Plugin::Chart::Renderer::GoogleViz';
 
+use constant packages_to_load => 'annotatedtimeline';
+use constant chart_class => 'google.visualization.AnnotatedTimeLine';
+use constant draw_params => {
+    displayAnnotations => "true",
+};
 
 1;
 

commit a8266dca22849eb972d37156feb1a2da7f29e2ce
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 21:15:10 2008 +0000

    Better column data structure
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6021 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 47e88a1..cccfda1 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -94,16 +94,19 @@ is the column's C<id> and the value is either a string (the C<type>) or a
 hashref. The hashref may specify C<type> and C<label>. If no C<label> is given,
 the C<id> is used.
 
+It also canonicalizes the columns so that each is a hashref with C<type> and
+C<label> set.
+
 =cut
 
 sub add_columns {
     my $self = shift;
     my %args = @_;
 
-    my %cols = %{ $args{columns} };
+    my $cols = $args{columns};
 
-    for my $id (keys %cols) {
-        my $column = $cols{$id};
+    for my $id (keys %$cols) {
+        my $column = $cols->{$id};
 
         my ($type, $label);
         if (ref($column)) {
@@ -116,6 +119,11 @@ sub add_columns {
 
         $label ||= $id;
 
+        $cols->{$id} = {
+            type  => $type,
+            label => $label,
+        };
+
         Jifty->web->out("data.addColumn('$type', '$label', '$id');\n");
     }
 }

commit 16d3528a62341156606a5b856a80af95ba5eb2d9
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 21:17:28 2008 +0000

    Encode values with the context of a column; DateTime encoding
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6022 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index cccfda1..8b71dc1 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -140,13 +140,18 @@ sub add_data {
     my %args = @_;
 
     my @data = @{ $args{data} };
+    my %cols = %{ $args{columns} };
 
     Jifty->web->out('data.addRows(' . scalar(@data) . ");\n");
 
     my $row = 0;
     for my $datapoint (@data) {
         for my $column (keys %$datapoint) {
-            my $value = $datapoint->{$column};
+            my $value = $self->encode_value(
+                value  => $datapoint->{$column},
+                column => $cols{$column},
+            );
+
             my $encoded = objToJson($value);
 
             Jifty->web->out("data.setValue($row, '$column', $encoded);\n");
@@ -156,5 +161,45 @@ sub add_data {
     }
 }
 
+=head2 encode_value
+
+=cut
+
+sub encode_value {
+    my $self = shift;
+    my %args = @_;
+
+    my $value  = $args{value};
+    my $column = $args{column};
+
+    if ($column->{type} eq 'date') {
+        if (!ref($value)) {
+            $value = Jifty::DateTime->new_from_string($value);
+        }
+
+        if (ref($value)) {
+            if ($value->isa('Jifty::DateTime') && $value->is_date) {
+                return sprintf 'new Date(%d, %d, %d)',
+                    $value->year,
+                    $value->month,
+                    $value->day;
+            }
+            elsif ($value->isa('DateTime')) {
+                return sprintf 'new Date(%d, %d, %d, %d, %d, %d)',
+                    $value->year,
+                    $value->month,
+                    $value->day,
+                    $value->hour,
+                    $value->minute,
+                    $value->second;
+            }
+        }
+
+        die "Can't handle the date '$value'";
+    }
+
+    return objToJson($value);
+}
+
 1;
 

commit 44fd7fb7283d20f863653ef4c0ad3b59f001edb9
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 21:19:01 2008 +0000

    Avoid doubly encoding values
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6023 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 8b71dc1..56c95ba 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -152,9 +152,7 @@ sub add_data {
                 column => $cols{$column},
             );
 
-            my $encoded = objToJson($value);
-
-            Jifty->web->out("data.setValue($row, '$column', $encoded);\n");
+            Jifty->web->out("data.setValue($row, '$column', $value);\n");
         }
 
         ++$row;

commit 81f1f65a7d820cec4ac0c59c9aba01608d239e21
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Tue Nov 18 21:31:53 2008 +0000

    Columns must be ordered, but we don't need to care about that for render_data
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6024 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 56c95ba..b9b41a5 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -83,8 +83,8 @@ sub render_data {
     my $self = shift;
     my %args = @_;
 
-    $self->add_columns(%args);
-    $self->add_data(%args);
+    my $cols = $self->add_columns(%args);
+    $self->add_data(%args, columns => $cols);
 }
 
 =head2 add_columns
@@ -94,8 +94,7 @@ is the column's C<id> and the value is either a string (the C<type>) or a
 hashref. The hashref may specify C<type> and C<label>. If no C<label> is given,
 the C<id> is used.
 
-It also canonicalizes the columns so that each is a hashref with C<type> and
-C<label> set.
+It will return a hashref of canonicalized columns.
 
 =cut
 
@@ -103,11 +102,11 @@ sub add_columns {
     my $self = shift;
     my %args = @_;
 
-    my $cols = $args{columns};
-
-    for my $id (keys %$cols) {
-        my $column = $cols->{$id};
+    my $index = 0;
+    my @cols = @{ $args{columns} };
+    my %canonicalized_columns;
 
+    while (my ($name, $column) = splice @cols, 0, 2) {
         my ($type, $label);
         if (ref($column)) {
             $type  = $column->{type};
@@ -117,15 +116,18 @@ sub add_columns {
             $type = $column;
         }
 
-        $label ||= $id;
+        $label ||= $name;
 
-        $cols->{$id} = {
+        $canonicalized_columns{$name} = {
             type  => $type,
             label => $label,
+            index => $index++,
         };
 
-        Jifty->web->out("data.addColumn('$type', '$label', '$id');\n");
+        Jifty->web->out("data.addColumn('$type', '$label', '$name');\n");
     }
+
+    return \%canonicalized_columns;
 }
 
 =head2 add_data
@@ -140,7 +142,7 @@ sub add_data {
     my %args = @_;
 
     my @data = @{ $args{data} };
-    my %cols = %{ $args{columns} };
+    my $cols = $args{columns};
 
     Jifty->web->out('data.addRows(' . scalar(@data) . ");\n");
 
@@ -149,10 +151,11 @@ sub add_data {
         for my $column (keys %$datapoint) {
             my $value = $self->encode_value(
                 value  => $datapoint->{$column},
-                column => $cols{$column},
+                column => $cols->{$column},
             );
+            my $cid = $cols->{$column}{index};
 
-            Jifty->web->out("data.setValue($row, '$column', $value);\n");
+            Jifty->web->out("data.setValue($row, $cid, $value);\n");
         }
 
         ++$row;

commit 2d45f679195ccb8cec3cd8b160026340cf44a847
Author: Shawn Moore <sartak at bestpractical.com>
Date:   Wed Nov 19 17:29:01 2008 +0000

    If the column isn't known, throw an error
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6025 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index b9b41a5..b20379d 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -149,11 +149,14 @@ sub add_data {
     my $row = 0;
     for my $datapoint (@data) {
         for my $column (keys %$datapoint) {
+            my $col = $cols->{$column}
+                or die "Invalid column id '$column'";
+
             my $value = $self->encode_value(
                 value  => $datapoint->{$column},
-                column => $cols->{$column},
+                column => $col,
             );
-            my $cid = $cols->{$column}{index};
+            my $cid = $col->{index};
 
             Jifty->web->out("data.setValue($row, $cid, $value);\n");
         }

commit b37e6793e47d1367ac0c3565bb7197afd7ac6936
Author: Chia-liang Kao <clkao at bestpractical.com>
Date:   Thu Nov 27 18:01:43 2008 +0000

    support legends for gd::graph::lines charts.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6032 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 43ab93e..27010ea 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -59,6 +59,7 @@ template 'chart/gd_graph' => sub {
     eval {
         my $graph = $args->{class}->new( $args->{width}, $args->{height} );
         $graph->set(%{ $args->{options} }) if $args->{options};
+        $graph->set_legend(@{ $args->{legend} } ) if $args->{legend};
         my $gd    = $graph->plot($args->{data})
             or die $graph->error;
         outs_raw($gd->png);

commit 4f15343678b5536c104fd10bf9589f0a8f3389c7
Author: Cornelius Lin <c9s at aiink.com>
Date:   Wed Jan 7 03:27:57 2009 +0000

     - load chart renderer plugin pre-require plugins for chart plugin
     - plotkit plugin should load prototypism plugin
    
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6204 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 9ba5475..3ce68c4 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -7,7 +7,7 @@ use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 use Jifty::Plugin::Chart::Web;
 use Scalar::Util qw/ blessed /;
 
-__PACKAGE__->mk_accessors(qw/ renderer renderers plugin_args /);
+__PACKAGE__->mk_accessors(qw/ renderer renderers plugin_args _prereq_plugins/);
 
 =head1 NAME
 
@@ -154,6 +154,11 @@ sub init_renderer {
         # Initialize the renderer
         $renderer = $renderer_class->new( %{ $self->plugin_args } );
 
+        if( $renderer->can('prereq_plugins') ) {
+          my @prereq_plugins =  $renderer->prereq_plugins ;
+          push @{ $self->{_prereq_plugins} } , @prereq_plugins ;
+        }
+
         # Remember it
         $self->renderers->{ $renderer_class } = $renderer;
 
@@ -162,6 +167,12 @@ sub init_renderer {
     }
 }
 
+sub prereq_plugins {
+  my $self = shift;
+  return @{ $self->_prereq_plugins };
+}
+
+
 =head1 SEE ALSO
 
 L<Jifty::Plugin>, L<Jifty::Web>, L<Jifty::Plugin::Chart::Renderer>, L<Jifty::Plugin::Chart::Renderer::Chart>, L<Jifty::Plugin::Chart::View>
diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index a6fec91..191c2b4 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 package Jifty::Plugin::Chart::Renderer::PlotKit;
-use base qw/ Jifty::Plugin::Chart::Renderer /;
+use base qw/Jifty::Plugin::Chart::Renderer /;
 
 use Jifty::YAML;
 
@@ -23,6 +23,7 @@ Adds the various JavaScript files required to use PlotKit.
 =cut
 
 sub init {
+
     Jifty->web->add_external_javascript(qw(
         /static/js/mochikit.noexport.js
         /static/js/MochiKit/MochiKit.js
@@ -130,6 +131,10 @@ sub _transform_data {
     $args->{data} = \@data;
 }
 
+sub prereq_plugins {
+  return ("Prototypism");
+}
+
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>

commit cada0dab938d9fbb3cb22ca00bbe60508d8cf3b1
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Jan 8 23:18:12 2009 +0000

     * Fix dereferencing error in tests
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6210 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 3ce68c4..b9551cd 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -90,6 +90,7 @@ sub init {
 
     # Save the arguments for use in init_renderer() later
     $self->plugin_args( \%args );
+    $self->_prereq_plugins( [] );
 
     # Deprecating the old form
     if (defined $args{renderer}) {
@@ -168,8 +169,8 @@ sub init_renderer {
 }
 
 sub prereq_plugins {
-  my $self = shift;
-  return @{ $self->_prereq_plugins };
+    my $self = shift;
+    return @{ $self->_prereq_plugins };
 }
 
 

commit 0ef0e6456c38be169b939215df8d6391160ab641
Author: Cornelius Lin <c9s at aiink.com>
Date:   Thu Jan 15 03:07:06 2009 +0000

     - fixed date of googleviz, month start from zero.
    
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6243 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index b20379d..662c93e 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -44,6 +44,7 @@ JS_HEADER
     Jifty->web->out(<< "JS_FOOTER");
                 var chart = new $chart_class(document.getElementById('$chart_id'));
                 chart.draw(data, $draw_params);
+
             }
         </script>
 JS_FOOTER
@@ -185,13 +186,13 @@ sub encode_value {
             if ($value->isa('Jifty::DateTime') && $value->is_date) {
                 return sprintf 'new Date(%d, %d, %d)',
                     $value->year,
-                    $value->month,
+                    $value->month - 1,
                     $value->day;
             }
             elsif ($value->isa('DateTime')) {
                 return sprintf 'new Date(%d, %d, %d, %d, %d, %d)',
                     $value->year,
-                    $value->month,
+                    $value->month - 1,
                     $value->day,
                     $value->hour,
                     $value->minute,

commit 5bd406726ecf9d8fe1ec09753443b3c013505675
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 25 20:35:34 2009 +0000

    Move appropriate tests into the right dists, and split out Makefile.PL's
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@6464 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..d6ec551
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,15 @@
+use inc::Module::Install 0.46;
+name('Jifty-Plugin-Chart');
+version_from('lib/Jifty/Plugin/Chart.pm');
+
+requires('Jifty');
+requires('Chart::Base');
+requires('GD');          # for a testing hack
+requires('GD::Graph');
+requires('XML::Simple');
+requires('Image::Info'); # for testing
+
+auto_install();
+tests(qw( t/*/t/*.t ));
+
+WriteAll;
diff --git a/t/TestApp-Plugin-Chart/Makefile.PL b/t/TestApp-Plugin-Chart/Makefile.PL
new file mode 100644
index 0000000..68d738b
--- /dev/null
+++ b/t/TestApp-Plugin-Chart/Makefile.PL
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp-Plugin-Chart';
+version     '0.01';
+requires    'Jifty' => '0.70129';
+
+WriteAll;
diff --git a/t/TestApp-Plugin-Chart/bin/jifty b/t/TestApp-Plugin-Chart/bin/jifty
new file mode 100644
index 0000000..59debb2
--- /dev/null
+++ b/t/TestApp-Plugin-Chart/bin/jifty
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();
diff --git a/t/TestApp-Plugin-Chart/etc/config.yml b/t/TestApp-Plugin-Chart/etc/config.yml
new file mode 100644
index 0000000..3a2f9a2
--- /dev/null
+++ b/t/TestApp-Plugin-Chart/etc/config.yml
@@ -0,0 +1,55 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::Chart
+  ApplicationName: TestApp-Plugin-Chart
+  ApplicationUUID: D16D885C-3E10-11DC-ABE9-A583E6FF98E1
+  ConfigFileVersion: 2
+  Database: 
+    CheckSchema: 1
+    Database: testapp_plugin_chart
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    RecordUUIDs: active
+    User: ''
+    Version: 0.0.1
+  DevelMode: 0
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - LetMe: {}
+    - SkeletonApp: {}
+    - REST: {}
+    - Halo: {}
+    - ErrorTemplates: {}
+    - OnlineDocs: {}
+    - CompressedCSSandJS: {}
+    - AdminUI: {}
+
+    - Chart: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::Chart::View
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates
diff --git a/t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm b/t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm
new file mode 100644
index 0000000..3ba5523
--- /dev/null
+++ b/t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::Chart::View;
+use Jifty::View::Declare -base;
+
+template '/graphit' => page {
+    Jifty->web->chart(
+        type   => 'Pie',
+        width  => '100%',
+        height => 500,
+        data   => sub {
+            [
+                [ 2004, 2005, 2006, 2007 ],
+                [ 26, 37, 12, 42 ]
+            ];
+        },
+    );
+};
+
+1;
diff --git a/t/TestApp-Plugin-Chart/t/chart.t b/t/TestApp-Plugin-Chart/t/chart.t
new file mode 100644
index 0000000..3a5640b
--- /dev/null
+++ b/t/TestApp-Plugin-Chart/t/chart.t
@@ -0,0 +1,56 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+# XXX FIXME This is here to prevent a segfault on my machine during testing.
+#   -- sterling
+use Test::More;
+eval "use GD; use Chart::pie; 1";
+if ($@) {
+    plan skip_all => 'Chart is not installed.';
+}
+else {
+    plan tests => 9;
+}
+
+use Jifty::Test::Dist;
+use Jifty::Test::WWW::Mechanize;
+
+use Jifty::Plugin::Chart::Renderer::Chart;
+
+my $chart_plugin = (Jifty->find_plugin('Jifty::Plugin::Chart'))[0];
+$chart_plugin->renderer(
+    $chart_plugin->init_renderer('Jifty::Plugin::Chart::Renderer::Chart')
+);
+
+my $server = Jifty::Test->make_server;
+ok($server, 'got a server');
+
+my $url = $server->started_ok;
+
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+$mech->get_ok($url . '/graphit', 'try getting /graphit');
+my $img_match = qr{<img src="(/chart/chart/S\d+)" };
+$mech->content_like($img_match, 'has an img tag');
+my ($chart_path) = $mech->content =~ $img_match;
+
+$mech->get_ok($url . $chart_path, 'try getting ' . $chart_path);
+
+my $response = $mech->response;
+is($response->header('Content-type'), 'image/png', 'content type set to png');
+
+SKIP: {
+    eval "use Image::Info qw/ image_info /";
+    skip "Image::Info is not installed", 3 if $@;
+
+    my $imgdata = $mech->content;
+    my $info = image_info(\$imgdata);
+
+    diag($info->{error}) if $info->{error};
+
+    is($info->{file_ext}, 'png', 'it is a png file');
+    is($info->{width}, 400, 'it is 400 pixels wide');
+    is($info->{height}, 500, 'it is 500 pixels tall');
+};
+
diff --git a/t/TestApp-Plugin-Chart/t/gd_graph.t b/t/TestApp-Plugin-Chart/t/gd_graph.t
new file mode 100644
index 0000000..eb254fc
--- /dev/null
+++ b/t/TestApp-Plugin-Chart/t/gd_graph.t
@@ -0,0 +1,56 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+# XXX FIXME This is here to prevent a segfault on my machine during testing.
+#   -- sterling
+use Test::More;
+eval "use GD; use GD::Graph::pie; 1";
+if ($@) {
+    plan skip_all => 'GD::Graph is not installed.';
+}
+else {
+    plan tests => 9;
+}
+
+use Jifty::Test::Dist;
+use Jifty::Test::WWW::Mechanize;
+
+use Jifty::Plugin::Chart::Renderer::GD::Graph;
+
+my $chart_plugin = (Jifty->find_plugin('Jifty::Plugin::Chart'))[0];
+$chart_plugin->renderer(
+    $chart_plugin->init_renderer('Jifty::Plugin::Chart::Renderer::GD::Graph')
+);
+
+my $server = Jifty::Test->make_server;
+ok($server, 'got a server');
+
+my $url = $server->started_ok;
+
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+$mech->get_ok($url . '/graphit', 'try getting /graphit');
+my $img_match = qr{<img src="(/chart/gd_graph/S\d+)" };
+$mech->content_like($img_match, 'has an img tag');
+my ($chart_path) = $mech->content =~ $img_match;
+
+$mech->get_ok($url . $chart_path, 'try getting ' . $chart_path);
+
+my $response = $mech->response;
+is($response->header('Content-type'), 'image/png', 'content type set to png');
+
+SKIP: {
+    eval "use Image::Info qw/ image_info /";
+    skip "Image::Info is not installed", 3 if $@;
+
+    my $imgdata = $mech->content;
+    my $info = image_info(\$imgdata);
+
+    diag($info->{error}) if $info->{error};
+
+    is($info->{file_ext}, 'png', 'it is a png file');
+    is($info->{width}, 400, 'it is 400 pixels wide');
+    is($info->{height}, 500, 'it is 500 pixels tall');
+};
+

commit 637c8b98d4c8f3ba3be8af3c0cc46a0edfd6678c
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Feb 26 20:30:11 2009 +0000

    Move Chart plugin out of core
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@6485 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/share/web/static/js/PlotKit/excanvas.js b/share/web/static/js/PlotKit/excanvas.js
old mode 100644
new mode 100755
diff --git a/t/TestApp-Plugin-Chart/bin/jifty b/t/TestApp-Plugin-Chart/bin/jifty
old mode 100644
new mode 100755

commit f056fe4300918e14a4e5fa2480f06a047bae32f7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 27 20:34:18 2009 +0000

    Add versions, inc/, META.yml to dists, and various other dist housecleaning
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@6524 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/META.yml b/META.yml
new file mode 100644
index 0000000..b6580fd
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,23 @@
+---
+author: ~
+distribution_type: module
+generated_by: 'Module::Install version 0.79'
+license: unknown
+meta-spec:
+  url: http://module-build.sourceforge.net/META-spec-v1.4.html
+  version: 1.4
+name: Jifty-Plugin-Chart
+no_index:
+  directory:
+    - inc
+    - share
+    - t
+recommends:
+  Chart::Base: 0
+  GD: 0
+  GD::Graph: 0
+  Image::Info: 0
+  XML::Simple: 0
+requires:
+  Jifty: 0
+version: undef
diff --git a/Makefile.PL b/Makefile.PL
index d6ec551..700c32c 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -3,11 +3,11 @@ name('Jifty-Plugin-Chart');
 version_from('lib/Jifty/Plugin/Chart.pm');
 
 requires('Jifty');
-requires('Chart::Base');
-requires('GD');          # for a testing hack
-requires('GD::Graph');
-requires('XML::Simple');
-requires('Image::Info'); # for testing
+recommends('Chart::Base');
+recommends('GD');          # for a testing hack
+recommends('GD::Graph');
+recommends('XML::Simple');
+recommends('Image::Info'); # for testing
 
 auto_install();
 tests(qw( t/*/t/*.t ));
diff --git a/inc/Module/AutoInstall.pm b/inc/Module/AutoInstall.pm
new file mode 100644
index 0000000..7efc552
--- /dev/null
+++ b/inc/Module/AutoInstall.pm
@@ -0,0 +1,768 @@
+#line 1
+package Module::AutoInstall;
+
+use strict;
+use Cwd                 ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION};
+BEGIN {
+	$VERSION = '1.03';
+}
+
+# special map on pre-defined feature sets
+my %FeatureMap = (
+    ''      => 'Core Features',    # XXX: deprecated
+    '-core' => 'Core Features',
+);
+
+# various lexical flags
+my ( @Missing, @Existing,  %DisabledTests, $UnderCPAN,     $HasCPANPLUS );
+my ( $Config,  $CheckOnly, $SkipInstall,   $AcceptDefault, $TestOnly );
+my ( $PostambleActions, $PostambleUsed );
+
+# See if it's a testing or non-interactive session
+_accept_default( $ENV{AUTOMATED_TESTING} or ! -t STDIN ); 
+_init();
+
+sub _accept_default {
+    $AcceptDefault = shift;
+}
+
+sub missing_modules {
+    return @Missing;
+}
+
+sub do_install {
+    __PACKAGE__->install(
+        [
+            $Config
+            ? ( UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+            : ()
+        ],
+        @Missing,
+    );
+}
+
+# initialize various flags, and/or perform install
+sub _init {
+    foreach my $arg (
+        @ARGV,
+        split(
+            /[\s\t]+/,
+            $ENV{PERL_AUTOINSTALL} || $ENV{PERL_EXTUTILS_AUTOINSTALL} || ''
+        )
+      )
+    {
+        if ( $arg =~ /^--config=(.*)$/ ) {
+            $Config = [ split( ',', $1 ) ];
+        }
+        elsif ( $arg =~ /^--installdeps=(.*)$/ ) {
+            __PACKAGE__->install( $Config, @Missing = split( /,/, $1 ) );
+            exit 0;
+        }
+        elsif ( $arg =~ /^--default(?:deps)?$/ ) {
+            $AcceptDefault = 1;
+        }
+        elsif ( $arg =~ /^--check(?:deps)?$/ ) {
+            $CheckOnly = 1;
+        }
+        elsif ( $arg =~ /^--skip(?:deps)?$/ ) {
+            $SkipInstall = 1;
+        }
+        elsif ( $arg =~ /^--test(?:only)?$/ ) {
+            $TestOnly = 1;
+        }
+    }
+}
+
+# overrides MakeMaker's prompt() to automatically accept the default choice
+sub _prompt {
+    goto &ExtUtils::MakeMaker::prompt unless $AcceptDefault;
+
+    my ( $prompt, $default ) = @_;
+    my $y = ( $default =~ /^[Yy]/ );
+
+    print $prompt, ' [', ( $y ? 'Y' : 'y' ), '/', ( $y ? 'n' : 'N' ), '] ';
+    print "$default\n";
+    return $default;
+}
+
+# the workhorse
+sub import {
+    my $class = shift;
+    my @args  = @_ or return;
+    my $core_all;
+
+    print "*** $class version " . $class->VERSION . "\n";
+    print "*** Checking for Perl dependencies...\n";
+
+    my $cwd = Cwd::cwd();
+
+    $Config = [];
+
+    my $maxlen = length(
+        (
+            sort   { length($b) <=> length($a) }
+              grep { /^[^\-]/ }
+              map  {
+                ref($_)
+                  ? ( ( ref($_) eq 'HASH' ) ? keys(%$_) : @{$_} )
+                  : ''
+              }
+              map { +{@args}->{$_} }
+              grep { /^[^\-]/ or /^-core$/i } keys %{ +{@args} }
+        )[0]
+    );
+
+    while ( my ( $feature, $modules ) = splice( @args, 0, 2 ) ) {
+        my ( @required, @tests, @skiptests );
+        my $default  = 1;
+        my $conflict = 0;
+
+        if ( $feature =~ m/^-(\w+)$/ ) {
+            my $option = lc($1);
+
+            # check for a newer version of myself
+            _update_to( $modules, @_ ) and return if $option eq 'version';
+
+            # sets CPAN configuration options
+            $Config = $modules if $option eq 'config';
+
+            # promote every features to core status
+            $core_all = ( $modules =~ /^all$/i ) and next
+              if $option eq 'core';
+
+            next unless $option eq 'core';
+        }
+
+        print "[" . ( $FeatureMap{ lc($feature) } || $feature ) . "]\n";
+
+        $modules = [ %{$modules} ] if UNIVERSAL::isa( $modules, 'HASH' );
+
+        unshift @$modules, -default => &{ shift(@$modules) }
+          if ( ref( $modules->[0] ) eq 'CODE' );    # XXX: bugward combatability
+
+        while ( my ( $mod, $arg ) = splice( @$modules, 0, 2 ) ) {
+            if ( $mod =~ m/^-(\w+)$/ ) {
+                my $option = lc($1);
+
+                $default   = $arg    if ( $option eq 'default' );
+                $conflict  = $arg    if ( $option eq 'conflict' );
+                @tests     = @{$arg} if ( $option eq 'tests' );
+                @skiptests = @{$arg} if ( $option eq 'skiptests' );
+
+                next;
+            }
+
+            printf( "- %-${maxlen}s ...", $mod );
+
+            if ( $arg and $arg =~ /^\D/ ) {
+                unshift @$modules, $arg;
+                $arg = 0;
+            }
+
+            # XXX: check for conflicts and uninstalls(!) them.
+            if (
+                defined( my $cur = _version_check( _load($mod), $arg ||= 0 ) ) )
+            {
+                print "loaded. ($cur" . ( $arg ? " >= $arg" : '' ) . ")\n";
+                push @Existing, $mod => $arg;
+                $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+            }
+            else {
+                print "missing." . ( $arg ? " (would need $arg)" : '' ) . "\n";
+                push @required, $mod => $arg;
+            }
+        }
+
+        next unless @required;
+
+        my $mandatory = ( $feature eq '-core' or $core_all );
+
+        if (
+            !$SkipInstall
+            and (
+                $CheckOnly
+                or _prompt(
+                    qq{==> Auto-install the }
+                      . ( @required / 2 )
+                      . ( $mandatory ? ' mandatory' : ' optional' )
+                      . qq{ module(s) from CPAN?},
+                    $default ? 'y' : 'n',
+                ) =~ /^[Yy]/
+            )
+          )
+        {
+            push( @Missing, @required );
+            $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+        }
+
+        elsif ( !$SkipInstall
+            and $default
+            and $mandatory
+            and
+            _prompt( qq{==> The module(s) are mandatory! Really skip?}, 'n', )
+            =~ /^[Nn]/ )
+        {
+            push( @Missing, @required );
+            $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
+        }
+
+        else {
+            $DisabledTests{$_} = 1 for map { glob($_) } @tests;
+        }
+    }
+
+    $UnderCPAN = _check_lock();    # check for $UnderCPAN
+
+    if ( @Missing and not( $CheckOnly or $UnderCPAN ) ) {
+        require Config;
+        print
+"*** Dependencies will be installed the next time you type '$Config::Config{make}'.\n";
+
+        # make an educated guess of whether we'll need root permission.
+        print "    (You may need to do that as the 'root' user.)\n"
+          if eval '$>';
+    }
+    print "*** $class configuration finished.\n";
+
+    chdir $cwd;
+
+    # import to main::
+    no strict 'refs';
+    *{'main::WriteMakefile'} = \&Write if caller(0) eq 'main';
+}
+
+# Check to see if we are currently running under CPAN.pm and/or CPANPLUS;
+# if we are, then we simply let it taking care of our dependencies
+sub _check_lock {
+    return unless @Missing;
+
+    if ($ENV{PERL5_CPANPLUS_IS_RUNNING}) {
+        print <<'END_MESSAGE';
+
+*** Since we're running under CPANPLUS, I'll just let it take care
+    of the dependency's installation later.
+END_MESSAGE
+        return 1;
+    }
+
+    _load_cpan();
+
+    # Find the CPAN lock-file
+    my $lock = MM->catfile( $CPAN::Config->{cpan_home}, ".lock" );
+    return unless -f $lock;
+
+    # Check the lock
+    local *LOCK;
+    return unless open(LOCK, $lock);
+
+    if (
+            ( $^O eq 'MSWin32' ? _under_cpan() : <LOCK> == getppid() )
+        and ( $CPAN::Config->{prerequisites_policy} || '' ) ne 'ignore'
+    ) {
+        print <<'END_MESSAGE';
+
+*** Since we're running under CPAN, I'll just let it take care
+    of the dependency's installation later.
+END_MESSAGE
+        return 1;
+    }
+
+    close LOCK;
+    return;
+}
+
+sub install {
+    my $class = shift;
+
+    my $i;    # used below to strip leading '-' from config keys
+    my @config = ( map { s/^-// if ++$i; $_ } @{ +shift } );
+
+    my ( @modules, @installed );
+    while ( my ( $pkg, $ver ) = splice( @_, 0, 2 ) ) {
+
+        # grep out those already installed
+        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
+            push @installed, $pkg;
+        }
+        else {
+            push @modules, $pkg, $ver;
+        }
+    }
+
+    return @installed unless @modules;  # nothing to do
+    return @installed if _check_lock(); # defer to the CPAN shell
+
+    print "*** Installing dependencies...\n";
+
+    return unless _connected_to('cpan.org');
+
+    my %args = @config;
+    my %failed;
+    local *FAILED;
+    if ( $args{do_once} and open( FAILED, '.#autoinstall.failed' ) ) {
+        while (<FAILED>) { chomp; $failed{$_}++ }
+        close FAILED;
+
+        my @newmod;
+        while ( my ( $k, $v ) = splice( @modules, 0, 2 ) ) {
+            push @newmod, ( $k => $v ) unless $failed{$k};
+        }
+        @modules = @newmod;
+    }
+
+    if ( _has_cpanplus() ) {
+        _install_cpanplus( \@modules, \@config );
+    } else {
+        _install_cpan( \@modules, \@config );
+    }
+
+    print "*** $class installation finished.\n";
+
+    # see if we have successfully installed them
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
+            push @installed, $pkg;
+        }
+        elsif ( $args{do_once} and open( FAILED, '>> .#autoinstall.failed' ) ) {
+            print FAILED "$pkg\n";
+        }
+    }
+
+    close FAILED if $args{do_once};
+
+    return @installed;
+}
+
+sub _install_cpanplus {
+    my @modules   = @{ +shift };
+    my @config    = _cpanplus_config( @{ +shift } );
+    my $installed = 0;
+
+    require CPANPLUS::Backend;
+    my $cp   = CPANPLUS::Backend->new;
+    my $conf = $cp->configure_object;
+
+    return unless $conf->can('conf') # 0.05x+ with "sudo" support
+               or _can_write($conf->_get_build('base'));  # 0.04x
+
+    # if we're root, set UNINST=1 to avoid trouble unless user asked for it.
+    my $makeflags = $conf->get_conf('makeflags') || '';
+    if ( UNIVERSAL::isa( $makeflags, 'HASH' ) ) {
+        # 0.03+ uses a hashref here
+        $makeflags->{UNINST} = 1 unless exists $makeflags->{UNINST};
+
+    } else {
+        # 0.02 and below uses a scalar
+        $makeflags = join( ' ', split( ' ', $makeflags ), 'UNINST=1' )
+          if ( $makeflags !~ /\bUNINST\b/ and eval qq{ $> eq '0' } );
+
+    }
+    $conf->set_conf( makeflags => $makeflags );
+    $conf->set_conf( prereqs   => 1 );
+
+    
+
+    while ( my ( $key, $val ) = splice( @config, 0, 2 ) ) {
+        $conf->set_conf( $key, $val );
+    }
+
+    my $modtree = $cp->module_tree;
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        print "*** Installing $pkg...\n";
+
+        MY::preinstall( $pkg, $ver ) or next if defined &MY::preinstall;
+
+        my $success;
+        my $obj = $modtree->{$pkg};
+
+        if ( $obj and defined( _version_check( $obj->{version}, $ver ) ) ) {
+            my $pathname = $pkg;
+            $pathname =~ s/::/\\W/;
+
+            foreach my $inc ( grep { m/$pathname.pm/i } keys(%INC) ) {
+                delete $INC{$inc};
+            }
+
+            my $rv = $cp->install( modules => [ $obj->{module} ] );
+
+            if ( $rv and ( $rv->{ $obj->{module} } or $rv->{ok} ) ) {
+                print "*** $pkg successfully installed.\n";
+                $success = 1;
+            } else {
+                print "*** $pkg installation cancelled.\n";
+                $success = 0;
+            }
+
+            $installed += $success;
+        } else {
+            print << ".";
+*** Could not find a version $ver or above for $pkg; skipping.
+.
+        }
+
+        MY::postinstall( $pkg, $ver, $success ) if defined &MY::postinstall;
+    }
+
+    return $installed;
+}
+
+sub _cpanplus_config {
+	my @config = ();
+	while ( @_ ) {
+		my ($key, $value) = (shift(), shift());
+		if ( $key eq 'prerequisites_policy' ) {
+			if ( $value eq 'follow' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_INSTALL();
+			} elsif ( $value eq 'ask' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_ASK();
+			} elsif ( $value eq 'ignore' ) {
+				$value = CPANPLUS::Internals::Constants::PREREQ_IGNORE();
+			} else {
+				die "*** Cannot convert option $key = '$value' to CPANPLUS version.\n";
+			}
+		} else {
+			die "*** Cannot convert option $key to CPANPLUS version.\n";
+		}
+	}
+	return @config;
+}
+
+sub _install_cpan {
+    my @modules   = @{ +shift };
+    my @config    = @{ +shift };
+    my $installed = 0;
+    my %args;
+
+    _load_cpan();
+    require Config;
+
+    if (CPAN->VERSION < 1.80) {
+        # no "sudo" support, probe for writableness
+        return unless _can_write( MM->catfile( $CPAN::Config->{cpan_home}, 'sources' ) )
+                  and _can_write( $Config::Config{sitelib} );
+    }
+
+    # if we're root, set UNINST=1 to avoid trouble unless user asked for it.
+    my $makeflags = $CPAN::Config->{make_install_arg} || '';
+    $CPAN::Config->{make_install_arg} =
+      join( ' ', split( ' ', $makeflags ), 'UNINST=1' )
+      if ( $makeflags !~ /\bUNINST\b/ and eval qq{ $> eq '0' } );
+
+    # don't show start-up info
+    $CPAN::Config->{inhibit_startup_message} = 1;
+
+    # set additional options
+    while ( my ( $opt, $arg ) = splice( @config, 0, 2 ) ) {
+        ( $args{$opt} = $arg, next )
+          if $opt =~ /^force$/;    # pseudo-option
+        $CPAN::Config->{$opt} = $arg;
+    }
+
+    local $CPAN::Config->{prerequisites_policy} = 'follow';
+
+    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
+        MY::preinstall( $pkg, $ver ) or next if defined &MY::preinstall;
+
+        print "*** Installing $pkg...\n";
+
+        my $obj     = CPAN::Shell->expand( Module => $pkg );
+        my $success = 0;
+
+        if ( $obj and defined( _version_check( $obj->cpan_version, $ver ) ) ) {
+            my $pathname = $pkg;
+            $pathname =~ s/::/\\W/;
+
+            foreach my $inc ( grep { m/$pathname.pm/i } keys(%INC) ) {
+                delete $INC{$inc};
+            }
+
+            my $rv = $args{force} ? CPAN::Shell->force( install => $pkg )
+                                  : CPAN::Shell->install($pkg);
+            $rv ||= eval {
+                $CPAN::META->instance( 'CPAN::Distribution', $obj->cpan_file, )
+                  ->{install}
+                  if $CPAN::META;
+            };
+
+            if ( $rv eq 'YES' ) {
+                print "*** $pkg successfully installed.\n";
+                $success = 1;
+            }
+            else {
+                print "*** $pkg installation failed.\n";
+                $success = 0;
+            }
+
+            $installed += $success;
+        }
+        else {
+            print << ".";
+*** Could not find a version $ver or above for $pkg; skipping.
+.
+        }
+
+        MY::postinstall( $pkg, $ver, $success ) if defined &MY::postinstall;
+    }
+
+    return $installed;
+}
+
+sub _has_cpanplus {
+    return (
+        $HasCPANPLUS = (
+            $INC{'CPANPLUS/Config.pm'}
+              or _load('CPANPLUS::Shell::Default')
+        )
+    );
+}
+
+# make guesses on whether we're under the CPAN installation directory
+sub _under_cpan {
+    require Cwd;
+    require File::Spec;
+
+    my $cwd  = File::Spec->canonpath( Cwd::cwd() );
+    my $cpan = File::Spec->canonpath( $CPAN::Config->{cpan_home} );
+
+    return ( index( $cwd, $cpan ) > -1 );
+}
+
+sub _update_to {
+    my $class = __PACKAGE__;
+    my $ver   = shift;
+
+    return
+      if defined( _version_check( _load($class), $ver ) );  # no need to upgrade
+
+    if (
+        _prompt( "==> A newer version of $class ($ver) is required. Install?",
+            'y' ) =~ /^[Nn]/
+      )
+    {
+        die "*** Please install $class $ver manually.\n";
+    }
+
+    print << ".";
+*** Trying to fetch it from CPAN...
+.
+
+    # install ourselves
+    _load($class) and return $class->import(@_)
+      if $class->install( [], $class, $ver );
+
+    print << '.'; exit 1;
+
+*** Cannot bootstrap myself. :-( Installation terminated.
+.
+}
+
+# check if we're connected to some host, using inet_aton
+sub _connected_to {
+    my $site = shift;
+
+    return (
+        ( _load('Socket') and Socket::inet_aton($site) ) or _prompt(
+            qq(
+*** Your host cannot resolve the domain name '$site', which
+    probably means the Internet connections are unavailable.
+==> Should we try to install the required module(s) anyway?), 'n'
+          ) =~ /^[Yy]/
+    );
+}
+
+# check if a directory is writable; may create it on demand
+sub _can_write {
+    my $path = shift;
+    mkdir( $path, 0755 ) unless -e $path;
+
+    return 1 if -w $path;
+
+    print << ".";
+*** You are not allowed to write to the directory '$path';
+    the installation may fail due to insufficient permissions.
+.
+
+    if (
+        eval '$>' and lc(`sudo -V`) =~ /version/ and _prompt(
+            qq(
+==> Should we try to re-execute the autoinstall process with 'sudo'?),
+            ((-t STDIN) ? 'y' : 'n')
+        ) =~ /^[Yy]/
+      )
+    {
+
+        # try to bootstrap ourselves from sudo
+        print << ".";
+*** Trying to re-execute the autoinstall process with 'sudo'...
+.
+        my $missing = join( ',', @Missing );
+        my $config = join( ',',
+            UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+          if $Config;
+
+        return
+          unless system( 'sudo', $^X, $0, "--config=$config",
+            "--installdeps=$missing" );
+
+        print << ".";
+*** The 'sudo' command exited with error!  Resuming...
+.
+    }
+
+    return _prompt(
+        qq(
+==> Should we try to install the required module(s) anyway?), 'n'
+    ) =~ /^[Yy]/;
+}
+
+# load a module and return the version it reports
+sub _load {
+    my $mod  = pop;    # class/instance doesn't matter
+    my $file = $mod;
+
+    $file =~ s|::|/|g;
+    $file .= '.pm';
+
+    local $@;
+    return eval { require $file; $mod->VERSION } || ( $@ ? undef: 0 );
+}
+
+# Load CPAN.pm and it's configuration
+sub _load_cpan {
+    return if $CPAN::VERSION;
+    require CPAN;
+    if ( $CPAN::HandleConfig::VERSION ) {
+        # Newer versions of CPAN have a HandleConfig module
+        CPAN::HandleConfig->load;
+    } else {
+    	# Older versions had the load method in Config directly
+        CPAN::Config->load;
+    }
+}
+
+# compare two versions, either use Sort::Versions or plain comparison
+sub _version_check {
+    my ( $cur, $min ) = @_;
+    return unless defined $cur;
+
+    $cur =~ s/\s+$//;
+
+    # check for version numbers that are not in decimal format
+    if ( ref($cur) or ref($min) or $cur =~ /v|\..*\./ or $min =~ /v|\..*\./ ) {
+        if ( ( $version::VERSION or defined( _load('version') )) and
+             version->can('new') 
+            ) {
+
+            # use version.pm if it is installed.
+            return (
+                ( version->new($cur) >= version->new($min) ) ? $cur : undef );
+        }
+        elsif ( $Sort::Versions::VERSION or defined( _load('Sort::Versions') ) )
+        {
+
+            # use Sort::Versions as the sorting algorithm for a.b.c versions
+            return ( ( Sort::Versions::versioncmp( $cur, $min ) != -1 )
+                ? $cur
+                : undef );
+        }
+
+        warn "Cannot reliably compare non-decimal formatted versions.\n"
+          . "Please install version.pm or Sort::Versions.\n";
+    }
+
+    # plain comparison
+    local $^W = 0;    # shuts off 'not numeric' bugs
+    return ( $cur >= $min ? $cur : undef );
+}
+
+# nothing; this usage is deprecated.
+sub main::PREREQ_PM { return {}; }
+
+sub _make_args {
+    my %args = @_;
+
+    $args{PREREQ_PM} = { %{ $args{PREREQ_PM} || {} }, @Existing, @Missing }
+      if $UnderCPAN or $TestOnly;
+
+    if ( $args{EXE_FILES} and -e 'MANIFEST' ) {
+        require ExtUtils::Manifest;
+        my $manifest = ExtUtils::Manifest::maniread('MANIFEST');
+
+        $args{EXE_FILES} =
+          [ grep { exists $manifest->{$_} } @{ $args{EXE_FILES} } ];
+    }
+
+    $args{test}{TESTS} ||= 't/*.t';
+    $args{test}{TESTS} = join( ' ',
+        grep { !exists( $DisabledTests{$_} ) }
+          map { glob($_) } split( /\s+/, $args{test}{TESTS} ) );
+
+    my $missing = join( ',', @Missing );
+    my $config =
+      join( ',', UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
+      if $Config;
+
+    $PostambleActions = (
+        $missing
+        ? "\$(PERL) $0 --config=$config --installdeps=$missing"
+        : "\$(NOECHO) \$(NOOP)"
+    );
+
+    return %args;
+}
+
+# a wrapper to ExtUtils::MakeMaker::WriteMakefile
+sub Write {
+    require Carp;
+    Carp::croak "WriteMakefile: Need even number of args" if @_ % 2;
+
+    if ($CheckOnly) {
+        print << ".";
+*** Makefile not written in check-only mode.
+.
+        return;
+    }
+
+    my %args = _make_args(@_);
+
+    no strict 'refs';
+
+    $PostambleUsed = 0;
+    local *MY::postamble = \&postamble unless defined &MY::postamble;
+    ExtUtils::MakeMaker::WriteMakefile(%args);
+
+    print << "." unless $PostambleUsed;
+*** WARNING: Makefile written with customized MY::postamble() without
+    including contents from Module::AutoInstall::postamble() --
+    auto installation features disabled.  Please contact the author.
+.
+
+    return 1;
+}
+
+sub postamble {
+    $PostambleUsed = 1;
+
+    return << ".";
+
+config :: installdeps
+\t\$(NOECHO) \$(NOOP)
+
+checkdeps ::
+\t\$(PERL) $0 --checkdeps
+
+installdeps ::
+\t$PostambleActions
+
+.
+
+}
+
+1;
+
+__END__
+
+#line 1003
diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
new file mode 100644
index 0000000..b46be99
--- /dev/null
+++ b/inc/Module/Install.pm
@@ -0,0 +1,369 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+#     3. The installed version of inc::Module::Install loads
+#     4. inc::Module::Install calls "require Module::Install"
+#     5. The ./inc/ version of Module::Install loads
+# } ELSE {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+#     3. The ./inc/ version of Module::Install loads
+# }
+
+BEGIN {
+	require 5.004;
+}
+use strict 'vars';
+
+use vars qw{$VERSION};
+BEGIN {
+	# All Module::Install core packages now require synchronised versions.
+	# This will be used to ensure we don't accidentally load old or
+	# different versions of modules.
+	# This is not enforced yet, but will be some time in the next few
+	# releases once we can make sure it won't clash with custom
+	# Module::Install extensions.
+	$VERSION = '0.79';
+
+	*inc::Module::Install::VERSION = *VERSION;
+	@inc::Module::Install::ISA     = __PACKAGE__;
+
+}
+
+
+
+
+
+# Whether or not inc::Module::Install is actually loaded, the
+# $INC{inc/Module/Install.pm} is what will still get set as long as
+# the caller loaded module this in the documented manner.
+# If not set, the caller may NOT have loaded the bundled version, and thus
+# they may not have a MI version that works with the Makefile.PL. This would
+# result in false errors or unexpected behaviour. And we don't want that.
+my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+unless ( $INC{$file} ) { die <<"END_DIE" }
+
+Please invoke ${\__PACKAGE__} with:
+
+	use inc::${\__PACKAGE__};
+
+not:
+
+	use ${\__PACKAGE__};
+
+END_DIE
+
+
+
+
+
+# If the script that is loading Module::Install is from the future,
+# then make will detect this and cause it to re-run over and over
+# again. This is bad. Rather than taking action to touch it (which
+# is unreliable on some platforms and requires write permissions)
+# for now we should catch this and refuse to run.
+if ( -f $0 and (stat($0))[9] > time ) { die <<"END_DIE" }
+
+Your installer $0 has a modification time in the future.
+
+This is known to create infinite loops in make.
+
+Please correct this, then run $0 again.
+
+END_DIE
+
+
+
+
+
+# Build.PL was formerly supported, but no longer is due to excessive
+# difficulty in implementing every single feature twice.
+if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" }
+
+Module::Install no longer supports Build.PL.
+
+It was impossible to maintain duel backends, and has been deprecated.
+
+Please remove all Build.PL files and only use the Makefile.PL installer.
+
+END_DIE
+
+
+
+
+
+# To save some more typing in Module::Install installers, every...
+# use inc::Module::Install
+# ...also acts as an implicit use strict.
+$^H |= strict::bits(qw(refs subs vars));
+
+
+
+
+
+use Cwd        ();
+use File::Find ();
+use File::Path ();
+use FindBin;
+
+sub autoload {
+	my $self = shift;
+	my $who  = $self->_caller;
+	my $cwd  = Cwd::cwd();
+	my $sym  = "${who}::AUTOLOAD";
+	$sym->{$cwd} = sub {
+		my $pwd = Cwd::cwd();
+		if ( my $code = $sym->{$pwd} ) {
+			# delegate back to parent dirs
+			goto &$code unless $cwd eq $pwd;
+		}
+		$$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+		unless ( uc($1) eq $1 ) {
+			unshift @_, ( $self, $1 );
+			goto &{$self->can('call')};
+		}
+	};
+}
+
+sub import {
+	my $class = shift;
+	my $self  = $class->new(@_);
+	my $who   = $self->_caller;
+
+	unless ( -f $self->{file} ) {
+		require "$self->{path}/$self->{dispatch}.pm";
+		File::Path::mkpath("$self->{prefix}/$self->{author}");
+		$self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+		$self->{admin}->init;
+		@_ = ($class, _self => $self);
+		goto &{"$self->{name}::import"};
+	}
+
+	*{"${who}::AUTOLOAD"} = $self->autoload;
+	$self->preload;
+
+	# Unregister loader and worker packages so subdirs can use them again
+	delete $INC{"$self->{file}"};
+	delete $INC{"$self->{path}.pm"};
+
+	return 1;
+}
+
+sub preload {
+	my $self = shift;
+	unless ( $self->{extensions} ) {
+		$self->load_extensions(
+			"$self->{prefix}/$self->{path}", $self
+		);
+	}
+
+	my @exts = @{$self->{extensions}};
+	unless ( @exts ) {
+		my $admin = $self->{admin};
+		@exts = $admin->load_all_extensions;
+	}
+
+	my %seen;
+	foreach my $obj ( @exts ) {
+		while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+			next unless $obj->can($method);
+			next if $method =~ /^_/;
+			next if $method eq uc($method);
+			$seen{$method}++;
+		}
+	}
+
+	my $who = $self->_caller;
+	foreach my $name ( sort keys %seen ) {
+		*{"${who}::$name"} = sub {
+			${"${who}::AUTOLOAD"} = "${who}::$name";
+			goto &{"${who}::AUTOLOAD"};
+		};
+	}
+}
+
+sub new {
+	my ($class, %args) = @_;
+
+	# ignore the prefix on extension modules built from top level.
+	my $base_path = Cwd::abs_path($FindBin::Bin);
+	unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+		delete $args{prefix};
+	}
+
+	return $args{_self} if $args{_self};
+
+	$args{dispatch} ||= 'Admin';
+	$args{prefix}   ||= 'inc';
+	$args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
+	$args{bundle}   ||= 'inc/BUNDLES';
+	$args{base}     ||= $base_path;
+	$class =~ s/^\Q$args{prefix}\E:://;
+	$args{name}     ||= $class;
+	$args{version}  ||= $class->VERSION;
+	unless ( $args{path} ) {
+		$args{path}  = $args{name};
+		$args{path}  =~ s!::!/!g;
+	}
+	$args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+	$args{wrote}      = 0;
+
+	bless( \%args, $class );
+}
+
+sub call {
+	my ($self, $method) = @_;
+	my $obj = $self->load($method) or return;
+        splice(@_, 0, 2, $obj);
+	goto &{$obj->can($method)};
+}
+
+sub load {
+	my ($self, $method) = @_;
+
+	$self->load_extensions(
+		"$self->{prefix}/$self->{path}", $self
+	) unless $self->{extensions};
+
+	foreach my $obj (@{$self->{extensions}}) {
+		return $obj if $obj->can($method);
+	}
+
+	my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+	my $obj = $admin->load($method, 1);
+	push @{$self->{extensions}}, $obj;
+
+	$obj;
+}
+
+sub load_extensions {
+	my ($self, $path, $top) = @_;
+
+	unless ( grep { !ref $_ and lc $_ eq lc $self->{prefix} } @INC ) {
+		unshift @INC, $self->{prefix};
+	}
+
+	foreach my $rv ( $self->find_extensions($path) ) {
+		my ($file, $pkg) = @{$rv};
+		next if $self->{pathnames}{$pkg};
+
+		local $@;
+		my $new = eval { require $file; $pkg->can('new') };
+		unless ( $new ) {
+			warn $@ if $@;
+			next;
+		}
+		$self->{pathnames}{$pkg} = delete $INC{$file};
+		push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+	}
+
+	$self->{extensions} ||= [];
+}
+
+sub find_extensions {
+	my ($self, $path) = @_;
+
+	my @found;
+	File::Find::find( sub {
+		my $file = $File::Find::name;
+		return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+		my $subpath = $1;
+		return if lc($subpath) eq lc($self->{dispatch});
+
+		$file = "$self->{path}/$subpath.pm";
+		my $pkg = "$self->{name}::$subpath";
+		$pkg =~ s!/!::!g;
+
+		# If we have a mixed-case package name, assume case has been preserved
+		# correctly.  Otherwise, root through the file to locate the case-preserved
+		# version of the package name.
+		if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+			my $content = Module::Install::_read($subpath . '.pm');
+			my $in_pod  = 0;
+			foreach ( split //, $content ) {
+				$in_pod = 1 if /^=\w/;
+				$in_pod = 0 if /^=cut/;
+				next if ($in_pod || /^=cut/);  # skip pod text
+				next if /^\s*#/;               # and comments
+				if ( m/^\s*package\s+($pkg)\s*;/i ) {
+					$pkg = $1;
+					last;
+				}
+			}
+		}
+
+		push @found, [ $file, $pkg ];
+	}, $path ) if -d $path;
+
+	@found;
+}
+
+
+
+
+
+#####################################################################
+# Utility Functions
+
+sub _caller {
+	my $depth = 0;
+	my $call  = caller($depth);
+	while ( $call eq __PACKAGE__ ) {
+		$depth++;
+		$call = caller($depth);
+	}
+	return $call;
+}
+
+sub _read {
+	local *FH;
+	open FH, "< $_[0]" or die "open($_[0]): $!";
+	my $str = do { local $/; <FH> };
+	close FH or die "close($_[0]): $!";
+	return $str;
+}
+
+sub _write {
+	local *FH;
+	open FH, "> $_[0]" or die "open($_[0]): $!";
+	foreach ( 1 .. $#_ ) { print FH $_[$_] or die "print($_[0]): $!" }
+	close FH or die "close($_[0]): $!";
+}
+
+# _version is for processing module versions (eg, 1.03_05) not
+# Perl versions (eg, 5.8.1).
+
+sub _version ($) {
+	my $s = shift || 0;
+	   $s =~ s/^(\d+)\.?//;
+	my $l = $1 || 0;
+	my @v = map { $_ . '0' x (3 - length $_) } $s =~ /(\d{1,3})\D?/g;
+	   $l = $l . '.' . join '', @v if @v;
+	return $l + 0;
+}
+
+# Cloned from Params::Util::_CLASS
+sub _CLASS ($) {
+	(
+		defined $_[0]
+		and
+		! ref $_[0]
+		and
+		$_[0] =~ m/^[^\W\d]\w*(?:::\w+)*$/s
+	) ? $_[0] : undef;
+}
+
+1;
+
+# Copyright 2008 - 2009 Adam Kennedy.
diff --git a/inc/Module/Install/AutoInstall.pm b/inc/Module/Install/AutoInstall.pm
new file mode 100644
index 0000000..343738e
--- /dev/null
+++ b/inc/Module/Install/AutoInstall.pm
@@ -0,0 +1,61 @@
+#line 1
+package Module::Install::AutoInstall;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.79';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub AutoInstall { $_[0] }
+
+sub run {
+    my $self = shift;
+    $self->auto_install_now(@_);
+}
+
+sub write {
+    my $self = shift;
+    $self->auto_install(@_);
+}
+
+sub auto_install {
+    my $self = shift;
+    return if $self->{done}++;
+
+    # Flatten array of arrays into a single array
+    my @core = map @$_, map @$_, grep ref,
+               $self->build_requires, $self->requires;
+
+    my @config = @_;
+
+    # We'll need Module::AutoInstall
+    $self->include('Module::AutoInstall');
+    require Module::AutoInstall;
+
+    Module::AutoInstall->import(
+        (@config ? (-config => \@config) : ()),
+        (@core   ? (-core   => \@core)   : ()),
+        $self->features,
+    );
+
+    $self->makemaker_args( Module::AutoInstall::_make_args() );
+
+    my $class = ref($self);
+    $self->postamble(
+        "# --- $class section:\n" .
+        Module::AutoInstall::postamble()
+    );
+}
+
+sub auto_install_now {
+    my $self = shift;
+    $self->auto_install(@_);
+    Module::AutoInstall::do_install();
+}
+
+1;
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
new file mode 100644
index 0000000..1145fe4
--- /dev/null
+++ b/inc/Module/Install/Base.pm
@@ -0,0 +1,72 @@
+#line 1
+package Module::Install::Base;
+
+$VERSION = '0.79';
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+	my $w = $SIG{__WARN__};
+	$SIG{__WARN__} = sub { $w };
+}
+
+### This is the ONLY module that shouldn't have strict on
+# use strict;
+
+#line 41
+
+sub new {
+    my ($class, %args) = @_;
+
+    foreach my $method ( qw(call load) ) {
+        *{"$class\::$method"} = sub {
+            shift()->_top->$method(@_);
+        } unless defined &{"$class\::$method"};
+    }
+
+    bless( \%args, $class );
+}
+
+#line 61
+
+sub AUTOLOAD {
+    my $self = shift;
+    local $@;
+    my $autoload = eval { $self->_top->autoload } or return;
+    goto &$autoload;
+}
+
+#line 76
+
+sub _top { $_[0]->{_top} }
+
+#line 89
+
+sub admin {
+    $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
+}
+
+#line 101
+
+sub is_admin {
+    $_[0]->admin->VERSION;
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+my $Fake;
+sub new { $Fake ||= bless(\@_, $_[0]) }
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+	$SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 146
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
new file mode 100644
index 0000000..ac81dec
--- /dev/null
+++ b/inc/Module/Install/Can.pm
@@ -0,0 +1,83 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Module::Install::Base;
+use Config ();
+### This adds a 5.005 Perl version dependency.
+### This is a bug and will be fixed.
+use File::Spec ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.79';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+	my ($self, $mod, $ver) = @_;
+	$mod =~ s{::|\\}{/}g;
+	$mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+	my $pkg = $mod;
+	$pkg =~ s{/}{::}g;
+	$pkg =~ s{\.pm$}{}i;
+
+	local $@;
+	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# check if we can run some command
+sub can_run {
+	my ($self, $cmd) = @_;
+
+	my $_cmd = $cmd;
+	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+		next if $dir eq '';
+		my $abs = File::Spec->catfile($dir, $_[1]);
+		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+	}
+
+	return;
+}
+
+# can we locate a (the) C compiler
+sub can_cc {
+	my $self   = shift;
+	my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+	# $Config{cc} may contain args; try to find out the program part
+	while (@chunks) {
+		return $self->can_run("@chunks") || (pop(@chunks), next);
+	}
+
+	return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+	require ExtUtils::MM_Cygwin;
+	require ExtUtils::MM_Win32;
+	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+		*ExtUtils::MM_Cygwin::maybe_command = sub {
+			my ($self, $file) = @_;
+			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+				ExtUtils::MM_Win32->maybe_command($file);
+			} else {
+				ExtUtils::MM_Unix->maybe_command($file);
+			}
+		}
+	}
+}
+
+1;
+
+__END__
+
+#line 158
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
new file mode 100644
index 0000000..41d9569
--- /dev/null
+++ b/inc/Module/Install/Fetch.pm
@@ -0,0 +1,93 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.79';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub get_file {
+    my ($self, %args) = @_;
+    my ($scheme, $host, $path, $file) =
+        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+        $args{url} = $args{ftp_url}
+            or (warn("LWP support unavailable!\n"), return);
+        ($scheme, $host, $path, $file) =
+            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+    }
+
+    $|++;
+    print "Fetching '$file' from $host... ";
+
+    unless (eval { require Socket; Socket::inet_aton($host) }) {
+        warn "'$host' resolve failed!\n";
+        return;
+    }
+
+    return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+    require Cwd;
+    my $dir = Cwd::getcwd();
+    chdir $args{local_dir} or return if exists $args{local_dir};
+
+    if (eval { require LWP::Simple; 1 }) {
+        LWP::Simple::mirror($args{url}, $file);
+    }
+    elsif (eval { require Net::FTP; 1 }) { eval {
+        # use Net::FTP to get past firewall
+        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+        $ftp->login("anonymous", 'anonymous at example.com');
+        $ftp->cwd($path);
+        $ftp->binary;
+        $ftp->get($file) or (warn("$!\n"), return);
+        $ftp->quit;
+    } }
+    elsif (my $ftp = $self->can_run('ftp')) { eval {
+        # no Net::FTP, fallback to ftp.exe
+        require FileHandle;
+        my $fh = FileHandle->new;
+
+        local $SIG{CHLD} = 'IGNORE';
+        unless ($fh->open("|$ftp -n")) {
+            warn "Couldn't open ftp: $!\n";
+            chdir $dir; return;
+        }
+
+        my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+        foreach (@dialog) { $fh->print("$_\n") }
+        $fh->close;
+    } }
+    else {
+        warn "No working 'ftp' program available!\n";
+        chdir $dir; return;
+    }
+
+    unless (-f $file) {
+        warn "Fetching failed: $@\n";
+        chdir $dir; return;
+    }
+
+    return if exists $args{size} and -s $file != $args{size};
+    system($args{run}) if exists $args{run};
+    unlink($file) if $args{remove};
+
+    print(((!exists $args{check_for} or -e $args{check_for})
+        ? "done!" : "failed! ($!)"), "\n");
+    chdir $dir; return !$?;
+}
+
+1;
diff --git a/inc/Module/Install/Include.pm b/inc/Module/Install/Include.pm
new file mode 100644
index 0000000..742121a
--- /dev/null
+++ b/inc/Module/Install/Include.pm
@@ -0,0 +1,34 @@
+#line 1
+package Module::Install::Include;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.79';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub include {
+	shift()->admin->include(@_);
+}
+
+sub include_deps {
+	shift()->admin->include_deps(@_);
+}
+
+sub auto_include {
+	shift()->admin->auto_include(@_);
+}
+
+sub auto_include_deps {
+	shift()->admin->auto_include_deps(@_);
+}
+
+sub auto_include_dependent_dists {
+	shift()->admin->auto_include_dependent_dists(@_);
+}
+
+1;
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
new file mode 100644
index 0000000..689a4b7
--- /dev/null
+++ b/inc/Module/Install/Makefile.pm
@@ -0,0 +1,253 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use Module::Install::Base;
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.79';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+	shift;
+
+	# Infinite loop protection
+	my @c = caller();
+	if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+		die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+	}
+
+	# In automated testing, always use defaults
+	if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+		local $ENV{PERL_MM_USE_DEFAULT} = 1;
+		goto &ExtUtils::MakeMaker::prompt;
+	} else {
+		goto &ExtUtils::MakeMaker::prompt;
+	}
+}
+
+sub makemaker_args {
+	my $self = shift;
+	my $args = ( $self->{makemaker_args} ||= {} );
+	%$args = ( %$args, @_ );
+	return $args;
+}
+
+# For mm args that take multiple space-seperated args,
+# append an argument to the current list.
+sub makemaker_append {
+	my $self = sShift;
+	my $name = shift;
+	my $args = $self->makemaker_args;
+	$args->{name} = defined $args->{$name}
+		? join( ' ', $args->{name}, @_ )
+		: join( ' ', @_ );
+}
+
+sub build_subdirs {
+	my $self    = shift;
+	my $subdirs = $self->makemaker_args->{DIR} ||= [];
+	for my $subdir (@_) {
+		push @$subdirs, $subdir;
+	}
+}
+
+sub clean_files {
+	my $self  = shift;
+	my $clean = $self->makemaker_args->{clean} ||= {};
+	  %$clean = (
+		%$clean,
+		FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_),
+	);
+}
+
+sub realclean_files {
+	my $self      = shift;
+	my $realclean = $self->makemaker_args->{realclean} ||= {};
+	  %$realclean = (
+		%$realclean,
+		FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_),
+	);
+}
+
+sub libs {
+	my $self = shift;
+	my $libs = ref $_[0] ? shift : [ shift ];
+	$self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+	my $self = shift;
+	$self->makemaker_args( INC => shift );
+}
+
+my %test_dir = ();
+
+sub _wanted_t {
+	/\.t$/ and -f $_ and $test_dir{$File::Find::dir} = 1;
+}
+
+sub tests_recursive {
+	my $self = shift;
+	if ( $self->tests ) {
+		die "tests_recursive will not work if tests are already defined";
+	}
+	my $dir = shift || 't';
+	unless ( -d $dir ) {
+		die "tests_recursive dir '$dir' does not exist";
+	}
+	%test_dir = ();
+	require File::Find;
+	File::Find::find( \&_wanted_t, $dir );
+	$self->tests( join ' ', map { "$_/*.t" } sort keys %test_dir );
+}
+
+sub write {
+	my $self = shift;
+	die "&Makefile->write() takes no arguments\n" if @_;
+
+	# Make sure we have a new enough
+	require ExtUtils::MakeMaker;
+
+	# MakeMaker can complain about module versions that include
+	# an underscore, even though its own version may contain one!
+	# Hence the funny regexp to get rid of it.  See RT #35800
+	# for details.
+
+	$self->configure_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/ );
+
+	# Generate the
+	my $args = $self->makemaker_args;
+	$args->{DISTNAME} = $self->name;
+	$args->{NAME}     = $self->module_name || $self->name;
+	$args->{VERSION}  = $self->version;
+	$args->{NAME}     =~ s/-/::/g;
+	if ( $self->tests ) {
+		$args->{test} = { TESTS => $self->tests };
+	}
+	if ($] >= 5.005) {
+		$args->{ABSTRACT} = $self->abstract;
+		$args->{AUTHOR}   = $self->author;
+	}
+	if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
+		$args->{NO_META} = 1;
+	}
+	if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
+		$args->{SIGN} = 1;
+	}
+	unless ( $self->is_admin ) {
+		delete $args->{SIGN};
+	}
+
+	# merge both kinds of requires into prereq_pm
+	my $prereq = ($args->{PREREQ_PM} ||= {});
+	%$prereq = ( %$prereq,
+		map { @$_ }
+		map { @$_ }
+		grep $_,
+		($self->configure_requires, $self->build_requires, $self->requires)
+	);
+
+	# Remove any reference to perl, PREREQ_PM doesn't support it
+	delete $args->{PREREQ_PM}->{perl};
+
+	# merge both kinds of requires into prereq_pm
+	my $subdirs = ($args->{DIR} ||= []);
+	if ($self->bundles) {
+		foreach my $bundle (@{ $self->bundles }) {
+			my ($file, $dir) = @$bundle;
+			push @$subdirs, $dir if -d $dir;
+			delete $prereq->{$file};
+		}
+	}
+
+	if ( my $perl_version = $self->perl_version ) {
+		eval "use $perl_version; 1"
+			or die "ERROR: perl: Version $] is installed, "
+			. "but we need version >= $perl_version";
+	}
+
+	$args->{INSTALLDIRS} = $self->installdirs;
+
+	my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
+
+	my $user_preop = delete $args{dist}->{PREOP};
+	if (my $preop = $self->admin->preop($user_preop)) {
+		foreach my $key ( keys %$preop ) {
+			$args{dist}->{$key} = $preop->{$key};
+		}
+	}
+
+	my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+	$self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+	my $self          = shift;
+	my $makefile_name = shift;
+	my $top_class     = ref($self->_top) || '';
+	my $top_version   = $self->_top->VERSION || '';
+
+	my $preamble = $self->preamble
+		? "# Preamble by $top_class $top_version\n"
+			. $self->preamble
+		: '';
+	my $postamble = "# Postamble by $top_class $top_version\n"
+		. ($self->postamble || '');
+
+	local *MAKEFILE;
+	open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+	my $makefile = do { local $/; <MAKEFILE> };
+	close MAKEFILE or die $!;
+
+	$makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+	$makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+	$makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+	$makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+	$makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+	# Module::Install will never be used to build the Core Perl
+	# Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
+	# PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
+	$makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
+	#$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
+
+	# Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
+	$makefile =~ s/(\"?)-I\$\(PERL_LIB\)\1//g;
+
+	# XXX - This is currently unused; not sure if it breaks other MM-users
+	# $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
+
+	open  MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+	print MAKEFILE  "$preamble$makefile$postamble" or die $!;
+	close MAKEFILE  or die $!;
+
+	1;
+}
+
+sub preamble {
+	my ($self, $text) = @_;
+	$self->{preamble} = $text . $self->{preamble} if defined $text;
+	$self->{preamble};
+}
+
+sub postamble {
+	my ($self, $text) = @_;
+	$self->{postamble} ||= $self->admin->postamble;
+	$self->{postamble} .= $text if defined $text;
+	$self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 379
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
new file mode 100644
index 0000000..37d5eff
--- /dev/null
+++ b/inc/Module/Install/Metadata.pm
@@ -0,0 +1,510 @@
+#line 1
+package Module::Install::Metadata;
+
+use strict 'vars';
+use Module::Install::Base;
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+	$VERSION = '0.79';
+	$ISCORE  = 1;
+	@ISA     = qw{Module::Install::Base};
+}
+
+my @scalar_keys = qw{
+	name
+	module_name
+	abstract
+	author
+	version
+	distribution_type
+	tests
+	installdirs
+};
+
+my @tuple_keys = qw{
+	configure_requires
+	build_requires
+	requires
+	recommends
+	bundles
+	resources
+};
+
+my @resource_keys = qw{
+	homepage
+	bugtracker
+	repository
+};
+
+sub Meta              { shift          }
+sub Meta_ScalarKeys   { @scalar_keys   }
+sub Meta_TupleKeys    { @tuple_keys    }
+sub Meta_ResourceKeys { @resource_keys }
+
+foreach my $key ( @scalar_keys ) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}{$key} if defined wantarray and !@_;
+		$self->{values}{$key} = shift;
+		return $self;
+	};
+}
+
+foreach my $key ( @resource_keys ) {
+	*$key = sub {
+		my $self = shift;
+		unless ( @_ ) {
+			return () unless $self->{values}{resources};
+			return map  { $_->[1] }
+			       grep { $_->[0] eq $key }
+			       @{ $self->{values}{resources} };
+		}
+		return $self->{values}{resources}{$key} unless @_;
+		my $uri = shift or die(
+			"Did not provide a value to $key()"
+		);
+		$self->resources( $key => $uri );
+		return 1;
+	};
+}
+
+foreach my $key ( grep {$_ ne "resources"} @tuple_keys) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}{$key} unless @_;
+		my @added;
+		while ( @_ ) {
+			my $module  = shift or last;
+			my $version = shift || 0;
+			push @added, [ $module, $version ];
+		}
+		push @{ $self->{values}{$key} }, @added;
+		return map {@$_} @added;
+	};
+}
+
+# Resource handling
+my %lc_resource = map { $_ => 1 } qw{
+	homepage
+	license
+	bugtracker
+	repository
+};
+
+sub resources {
+	my $self = shift;
+	while ( @_ ) {
+		my $name  = shift or last;
+		my $value = shift or next;
+		if ( $name eq lc $name and ! $lc_resource{$name} ) {
+			die("Unsupported reserved lowercase resource '$name'");
+		}
+		$self->{values}{resources} ||= [];
+		push @{ $self->{values}{resources} }, [ $name, $value ];
+	}
+	$self->{values}{resources};
+}
+
+# Aliases for build_requires that will have alternative
+# meanings in some future version of META.yml.
+sub test_requires      { shift->build_requires(@_) }
+sub install_requires   { shift->build_requires(@_) }
+
+# Aliases for installdirs options
+sub install_as_core    { $_[0]->installdirs('perl')   }
+sub install_as_cpan    { $_[0]->installdirs('site')   }
+sub install_as_site    { $_[0]->installdirs('site')   }
+sub install_as_vendor  { $_[0]->installdirs('vendor') }
+
+sub sign {
+	my $self = shift;
+	return $self->{values}{sign} if defined wantarray and ! @_;
+	$self->{values}{sign} = ( @_ ? $_[0] : 1 );
+	return $self;
+}
+
+sub dynamic_config {
+	my $self = shift;
+	unless ( @_ ) {
+		warn "You MUST provide an explicit true/false value to dynamic_config\n";
+		return $self;
+	}
+	$self->{values}{dynamic_config} = $_[0] ? 1 : 0;
+	return 1;
+}
+
+sub perl_version {
+	my $self = shift;
+	return $self->{values}{perl_version} unless @_;
+	my $version = shift or die(
+		"Did not provide a value to perl_version()"
+	);
+
+	# Normalize the version
+	$version = $self->_perl_version($version);
+
+	# We don't support the reall old versions
+	unless ( $version >= 5.005 ) {
+		die "Module::Install only supports 5.005 or newer (use ExtUtils::MakeMaker)\n";
+	}
+
+	$self->{values}{perl_version} = $version;
+}
+
+sub license {
+	my $self = shift;
+	return $self->{values}{license} unless @_;
+	my $license = shift or die(
+		'Did not provide a value to license()'
+	);
+	$self->{values}{license} = $license;
+
+	# Automatically fill in license URLs
+	if ( $license eq 'perl' ) {
+		$self->resources( license => 'http://dev.perl.org/licenses/' );
+	}
+
+	return 1;
+}
+
+sub all_from {
+	my ( $self, $file ) = @_;
+
+	unless ( defined($file) ) {
+		my $name = $self->name or die(
+			"all_from called with no args without setting name() first"
+		);
+		$file = join('/', 'lib', split(/-/, $name)) . '.pm';
+		$file =~ s{.*/}{} unless -e $file;
+		unless ( -e $file ) {
+			die("all_from cannot find $file from $name");
+		}
+	}
+	unless ( -f $file ) {
+		die("The path '$file' does not exist, or is not a file");
+	}
+
+	# Some methods pull from POD instead of code.
+	# If there is a matching .pod, use that instead
+	my $pod = $file;
+	$pod =~ s/\.pm$/.pod/i;
+	$pod = $file unless -e $pod;
+
+	# Pull the different values
+	$self->name_from($file)         unless $self->name;
+	$self->version_from($file)      unless $self->version;
+	$self->perl_version_from($file) unless $self->perl_version;
+	$self->author_from($pod)        unless $self->author;
+	$self->license_from($pod)       unless $self->license;
+	$self->abstract_from($pod)      unless $self->abstract;
+
+	return 1;
+}
+
+sub provides {
+	my $self     = shift;
+	my $provides = ( $self->{values}{provides} ||= {} );
+	%$provides = (%$provides, @_) if @_;
+	return $provides;
+}
+
+sub auto_provides {
+	my $self = shift;
+	return $self unless $self->is_admin;
+	unless (-e 'MANIFEST') {
+		warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+		return $self;
+	}
+	# Avoid spurious warnings as we are not checking manifest here.
+	local $SIG{__WARN__} = sub {1};
+	require ExtUtils::Manifest;
+	local *ExtUtils::Manifest::manicheck = sub { return };
+
+	require Module::Build;
+	my $build = Module::Build->new(
+		dist_name    => $self->name,
+		dist_version => $self->version,
+		license      => $self->license,
+	);
+	$self->provides( %{ $build->find_dist_packages || {} } );
+}
+
+sub feature {
+	my $self     = shift;
+	my $name     = shift;
+	my $features = ( $self->{values}{features} ||= [] );
+	my $mods;
+
+	if ( @_ == 1 and ref( $_[0] ) ) {
+		# The user used ->feature like ->features by passing in the second
+		# argument as a reference.  Accomodate for that.
+		$mods = $_[0];
+	} else {
+		$mods = \@_;
+	}
+
+	my $count = 0;
+	push @$features, (
+		$name => [
+			map {
+				ref($_) ? ( ref($_) eq 'HASH' ) ? %$_ : @$_ : $_
+			} @$mods
+		]
+	);
+
+	return @$features;
+}
+
+sub features {
+	my $self = shift;
+	while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+		$self->feature( $name, @$mods );
+	}
+	return $self->{values}{features}
+		? @{ $self->{values}{features} }
+		: ();
+}
+
+sub no_index {
+	my $self = shift;
+	my $type = shift;
+	push @{ $self->{values}{no_index}{$type} }, @_ if $type;
+	return $self->{values}{no_index};
+}
+
+sub read {
+	my $self = shift;
+	$self->include_deps( 'YAML::Tiny', 0 );
+
+	require YAML::Tiny;
+	my $data = YAML::Tiny::LoadFile('META.yml');
+
+	# Call methods explicitly in case user has already set some values.
+	while ( my ( $key, $value ) = each %$data ) {
+		next unless $self->can($key);
+		if ( ref $value eq 'HASH' ) {
+			while ( my ( $module, $version ) = each %$value ) {
+				$self->can($key)->($self, $module => $version );
+			}
+		} else {
+			$self->can($key)->($self, $value);
+		}
+	}
+	return $self;
+}
+
+sub write {
+	my $self = shift;
+	return $self unless $self->is_admin;
+	$self->admin->write_meta;
+	return $self;
+}
+
+sub version_from {
+	require ExtUtils::MM_Unix;
+	my ( $self, $file ) = @_;
+	$self->version( ExtUtils::MM_Unix->parse_version($file) );
+}
+
+sub abstract_from {
+	require ExtUtils::MM_Unix;
+	my ( $self, $file ) = @_;
+	$self->abstract(
+		bless(
+			{ DISTNAME => $self->name },
+			'ExtUtils::MM_Unix'
+		)->parse_abstract($file)
+	 );
+}
+
+# Add both distribution and module name
+sub name_from {
+	my ($self, $file) = @_;
+	if (
+		Module::Install::_read($file) =~ m/
+		^ \s*
+		package \s*
+		([\w:]+)
+		\s* ;
+		/ixms
+	) {
+		my ($name, $module_name) = ($1, $1);
+		$name =~ s{::}{-}g;
+		$self->name($name);
+		unless ( $self->module_name ) {
+			$self->module_name($module_name);
+		}
+	} else {
+		die("Cannot determine name from $file\n");
+	}
+}
+
+sub perl_version_from {
+	my $self = shift;
+	if (
+		Module::Install::_read($_[0]) =~ m/
+		^
+		(?:use|require) \s*
+		v?
+		([\d_\.]+)
+		\s* ;
+		/ixms
+	) {
+		my $perl_version = $1;
+		$perl_version =~ s{_}{}g;
+		$self->perl_version($perl_version);
+	} else {
+		warn "Cannot determine perl version info from $_[0]\n";
+		return;
+	}
+}
+
+sub author_from {
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
+	if ($content =~ m/
+		=head \d \s+ (?:authors?)\b \s*
+		([^\n]*)
+		|
+		=head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+		.*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+		([^\n]*)
+	/ixms) {
+		my $author = $1 || $2;
+		$author =~ s{E<lt>}{<}g;
+		$author =~ s{E<gt>}{>}g;
+		$self->author($author);
+	} else {
+		warn "Cannot determine author info from $_[0]\n";
+	}
+}
+
+sub license_from {
+	my $self = shift;
+	if (
+		Module::Install::_read($_[0]) =~ m/
+		(
+			=head \d \s+
+			(?:licen[cs]e|licensing|copyright|legal)\b
+			.*?
+		)
+		(=head\\d.*|=cut.*|)
+		\z
+	/ixms ) {
+		my $license_text = $1;
+		my @phrases      = (
+			'under the same (?:terms|license) as perl itself' => 'perl',        1,
+			'GNU general public license'                      => 'gpl',         1,
+			'GNU public license'                              => 'gpl',         1,
+			'GNU lesser general public license'               => 'lgpl',        1,
+			'GNU lesser public license'                       => 'lgpl',        1,
+			'GNU library general public license'              => 'lgpl',        1,
+			'GNU library public license'                      => 'lgpl',        1,
+			'BSD license'                                     => 'bsd',         1,
+			'Artistic license'                                => 'artistic',    1,
+			'GPL'                                             => 'gpl',         1,
+			'LGPL'                                            => 'lgpl',        1,
+			'BSD'                                             => 'bsd',         1,
+			'Artistic'                                        => 'artistic',    1,
+			'MIT'                                             => 'mit',         1,
+			'proprietary'                                     => 'proprietary', 0,
+		);
+		while ( my ($pattern, $license, $osi) = splice(@phrases, 0, 3) ) {
+			$pattern =~ s{\s+}{\\s+}g;
+			if ( $license_text =~ /\b$pattern\b/i ) {
+				$self->license($license);
+				return 1;
+			}
+		}
+	}
+
+	warn "Cannot determine license info from $_[0]\n";
+	return 'unknown';
+}
+
+sub bugtracker_from {
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
+	my @links   = $content =~ m/L\<(http\:\/\/rt\.cpan\.org\/[^>]+)\>/g;
+	unless ( @links ) {
+		warn "Cannot determine bugtracker info from $_[0]\n";
+		return 0;
+	}
+	if ( @links > 1 ) {
+		warn "Found more than on rt.cpan.org link in $_[0]\n";
+		return 0;
+	}
+
+	# Set the bugtracker
+	bugtracker( $links[0] );
+	return 1;
+}
+
+# Convert triple-part versions (eg, 5.6.1 or 5.8.9) to
+# numbers (eg, 5.006001 or 5.008009).
+# Also, convert double-part versions (eg, 5.8)
+sub _perl_version {
+	my $v = $_[-1];
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)$/sprintf("%d.%03d",$1,$2)/e;	
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)\.(0|[1-9]\d?\d?)$/sprintf("%d.%03d%03d",$1,$2,$3 || 0)/e;
+	$v =~ s/(\.\d\d\d)000$/$1/;
+	$v =~ s/_.+$//;
+	if ( ref($v) ) {
+		$v = $v + 0; # Numify
+	}
+	return $v;
+}
+
+
+
+
+
+######################################################################
+# MYMETA.yml Support
+
+sub WriteMyMeta {
+	$_[0]->write_mymeta;
+}
+
+sub write_mymeta {
+	my $self = shift;
+	
+	# If there's no existing META.yml there is nothing we can do
+	return unless -f 'META.yml';
+
+	# Merge the perl version into the dependencies
+	my $val  = $self->Meta->{values};
+	my $perl = delete $val->{perl_version};
+	if ( $perl ) {
+		$val->{requires} ||= [];
+		my $requires = $val->{requires};
+
+		# Canonize to three-dot version after Perl 5.6
+		if ( $perl >= 5.006 ) {
+			$perl =~ s{^(\d+)\.(\d\d\d)(\d*)}{join('.', $1, int($2||0), int($3||0))}e
+		}
+		unshift @$requires, [ perl => $perl ];
+	}
+
+	# Load the advisory META.yml file
+	require YAML::Tiny;
+	my @yaml = YAML::Tiny::LoadFile('META.yml');
+	my $meta = $yaml[0];
+
+	# Overwrite the non-configure dependency hashs
+	delete $meta->{requires};
+	delete $meta->{build_requires};
+	delete $meta->{recommends};
+	if ( exists $val->{requires} ) {
+		$meta->{requires} = { map { @$_ } @{ $val->{requires} } };
+	}
+	if ( exists $val->{build_requires} ) {
+		$meta->{build_requires} = { map { @$_ } @{ $val->{build_requires} } };
+	}
+
+	# Save as the MYMETA.yml file
+	YAML::Tiny::DumpFile('MYMETA.yml', $meta);
+}
+
+1;
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
new file mode 100644
index 0000000..2bd721a
--- /dev/null
+++ b/inc/Module/Install/Win32.pm
@@ -0,0 +1,64 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.79';
+	@ISA     = qw{Module::Install::Base};
+	$ISCORE  = 1;
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+	my $self = shift;
+	$self->load('can_run');
+	$self->load('get_file');
+
+	require Config;
+	return unless (
+		$^O eq 'MSWin32'                     and
+		$Config::Config{make}                and
+		$Config::Config{make} =~ /^nmake\b/i and
+		! $self->can_run('nmake')
+	);
+
+	print "The required 'nmake' executable not found, fetching it...\n";
+
+	require File::Basename;
+	my $rv = $self->get_file(
+		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+		local_dir => File::Basename::dirname($^X),
+		size      => 51928,
+		run       => 'Nmake15.exe /o > nul',
+		check_for => 'Nmake.exe',
+		remove    => 1,
+	);
+
+	die <<'END_MESSAGE' unless $rv;
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+      or
+  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+
+}
+
+1;
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
new file mode 100644
index 0000000..3819d78
--- /dev/null
+++ b/inc/Module/Install/WriteAll.pm
@@ -0,0 +1,40 @@
+#line 1
+package Module::Install::WriteAll;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.79';
+	@ISA     = qw{Module::Install::Base};
+	$ISCORE  = 1;
+}
+
+sub WriteAll {
+	my $self = shift;
+	my %args = (
+		meta        => 1,
+		sign        => 0,
+		inline      => 0,
+		check_nmake => 1,
+		@_,
+	);
+
+	$self->sign(1)                if $args{sign};
+	$self->Meta->write            if $args{meta};
+	$self->admin->WriteAll(%args) if $self->is_admin;
+
+	$self->check_nmake if $args{check_nmake};
+	unless ( $self->makemaker_args->{PL_FILES} ) {
+		$self->makemaker_args( PL_FILES => {} );
+	}
+
+	if ( $args{inline} ) {
+		$self->Inline->write;
+	} else {
+		$self->Makefile->write;
+	}
+}
+
+1;
diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index b9551cd..2107352 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -4,6 +4,8 @@ use warnings;
 package Jifty::Plugin::Chart;
 use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 
+our $VERSION = '0.01';
+
 use Jifty::Plugin::Chart::Web;
 use Scalar::Util qw/ blessed /;
 

commit e0f08faae1d13867c78860f82c1475275d20d440
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 27 20:45:01 2009 +0000

    Don't include inc/ or META.yml; svn:ignore them instead
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@6525 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/META.yml b/META.yml
deleted file mode 100644
index b6580fd..0000000
--- a/META.yml
+++ /dev/null
@@ -1,23 +0,0 @@
----
-author: ~
-distribution_type: module
-generated_by: 'Module::Install version 0.79'
-license: unknown
-meta-spec:
-  url: http://module-build.sourceforge.net/META-spec-v1.4.html
-  version: 1.4
-name: Jifty-Plugin-Chart
-no_index:
-  directory:
-    - inc
-    - share
-    - t
-recommends:
-  Chart::Base: 0
-  GD: 0
-  GD::Graph: 0
-  Image::Info: 0
-  XML::Simple: 0
-requires:
-  Jifty: 0
-version: undef
diff --git a/inc/Module/AutoInstall.pm b/inc/Module/AutoInstall.pm
deleted file mode 100644
index 7efc552..0000000
--- a/inc/Module/AutoInstall.pm
+++ /dev/null
@@ -1,768 +0,0 @@
-#line 1
-package Module::AutoInstall;
-
-use strict;
-use Cwd                 ();
-use ExtUtils::MakeMaker ();
-
-use vars qw{$VERSION};
-BEGIN {
-	$VERSION = '1.03';
-}
-
-# special map on pre-defined feature sets
-my %FeatureMap = (
-    ''      => 'Core Features',    # XXX: deprecated
-    '-core' => 'Core Features',
-);
-
-# various lexical flags
-my ( @Missing, @Existing,  %DisabledTests, $UnderCPAN,     $HasCPANPLUS );
-my ( $Config,  $CheckOnly, $SkipInstall,   $AcceptDefault, $TestOnly );
-my ( $PostambleActions, $PostambleUsed );
-
-# See if it's a testing or non-interactive session
-_accept_default( $ENV{AUTOMATED_TESTING} or ! -t STDIN ); 
-_init();
-
-sub _accept_default {
-    $AcceptDefault = shift;
-}
-
-sub missing_modules {
-    return @Missing;
-}
-
-sub do_install {
-    __PACKAGE__->install(
-        [
-            $Config
-            ? ( UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
-            : ()
-        ],
-        @Missing,
-    );
-}
-
-# initialize various flags, and/or perform install
-sub _init {
-    foreach my $arg (
-        @ARGV,
-        split(
-            /[\s\t]+/,
-            $ENV{PERL_AUTOINSTALL} || $ENV{PERL_EXTUTILS_AUTOINSTALL} || ''
-        )
-      )
-    {
-        if ( $arg =~ /^--config=(.*)$/ ) {
-            $Config = [ split( ',', $1 ) ];
-        }
-        elsif ( $arg =~ /^--installdeps=(.*)$/ ) {
-            __PACKAGE__->install( $Config, @Missing = split( /,/, $1 ) );
-            exit 0;
-        }
-        elsif ( $arg =~ /^--default(?:deps)?$/ ) {
-            $AcceptDefault = 1;
-        }
-        elsif ( $arg =~ /^--check(?:deps)?$/ ) {
-            $CheckOnly = 1;
-        }
-        elsif ( $arg =~ /^--skip(?:deps)?$/ ) {
-            $SkipInstall = 1;
-        }
-        elsif ( $arg =~ /^--test(?:only)?$/ ) {
-            $TestOnly = 1;
-        }
-    }
-}
-
-# overrides MakeMaker's prompt() to automatically accept the default choice
-sub _prompt {
-    goto &ExtUtils::MakeMaker::prompt unless $AcceptDefault;
-
-    my ( $prompt, $default ) = @_;
-    my $y = ( $default =~ /^[Yy]/ );
-
-    print $prompt, ' [', ( $y ? 'Y' : 'y' ), '/', ( $y ? 'n' : 'N' ), '] ';
-    print "$default\n";
-    return $default;
-}
-
-# the workhorse
-sub import {
-    my $class = shift;
-    my @args  = @_ or return;
-    my $core_all;
-
-    print "*** $class version " . $class->VERSION . "\n";
-    print "*** Checking for Perl dependencies...\n";
-
-    my $cwd = Cwd::cwd();
-
-    $Config = [];
-
-    my $maxlen = length(
-        (
-            sort   { length($b) <=> length($a) }
-              grep { /^[^\-]/ }
-              map  {
-                ref($_)
-                  ? ( ( ref($_) eq 'HASH' ) ? keys(%$_) : @{$_} )
-                  : ''
-              }
-              map { +{@args}->{$_} }
-              grep { /^[^\-]/ or /^-core$/i } keys %{ +{@args} }
-        )[0]
-    );
-
-    while ( my ( $feature, $modules ) = splice( @args, 0, 2 ) ) {
-        my ( @required, @tests, @skiptests );
-        my $default  = 1;
-        my $conflict = 0;
-
-        if ( $feature =~ m/^-(\w+)$/ ) {
-            my $option = lc($1);
-
-            # check for a newer version of myself
-            _update_to( $modules, @_ ) and return if $option eq 'version';
-
-            # sets CPAN configuration options
-            $Config = $modules if $option eq 'config';
-
-            # promote every features to core status
-            $core_all = ( $modules =~ /^all$/i ) and next
-              if $option eq 'core';
-
-            next unless $option eq 'core';
-        }
-
-        print "[" . ( $FeatureMap{ lc($feature) } || $feature ) . "]\n";
-
-        $modules = [ %{$modules} ] if UNIVERSAL::isa( $modules, 'HASH' );
-
-        unshift @$modules, -default => &{ shift(@$modules) }
-          if ( ref( $modules->[0] ) eq 'CODE' );    # XXX: bugward combatability
-
-        while ( my ( $mod, $arg ) = splice( @$modules, 0, 2 ) ) {
-            if ( $mod =~ m/^-(\w+)$/ ) {
-                my $option = lc($1);
-
-                $default   = $arg    if ( $option eq 'default' );
-                $conflict  = $arg    if ( $option eq 'conflict' );
-                @tests     = @{$arg} if ( $option eq 'tests' );
-                @skiptests = @{$arg} if ( $option eq 'skiptests' );
-
-                next;
-            }
-
-            printf( "- %-${maxlen}s ...", $mod );
-
-            if ( $arg and $arg =~ /^\D/ ) {
-                unshift @$modules, $arg;
-                $arg = 0;
-            }
-
-            # XXX: check for conflicts and uninstalls(!) them.
-            if (
-                defined( my $cur = _version_check( _load($mod), $arg ||= 0 ) ) )
-            {
-                print "loaded. ($cur" . ( $arg ? " >= $arg" : '' ) . ")\n";
-                push @Existing, $mod => $arg;
-                $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
-            }
-            else {
-                print "missing." . ( $arg ? " (would need $arg)" : '' ) . "\n";
-                push @required, $mod => $arg;
-            }
-        }
-
-        next unless @required;
-
-        my $mandatory = ( $feature eq '-core' or $core_all );
-
-        if (
-            !$SkipInstall
-            and (
-                $CheckOnly
-                or _prompt(
-                    qq{==> Auto-install the }
-                      . ( @required / 2 )
-                      . ( $mandatory ? ' mandatory' : ' optional' )
-                      . qq{ module(s) from CPAN?},
-                    $default ? 'y' : 'n',
-                ) =~ /^[Yy]/
-            )
-          )
-        {
-            push( @Missing, @required );
-            $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
-        }
-
-        elsif ( !$SkipInstall
-            and $default
-            and $mandatory
-            and
-            _prompt( qq{==> The module(s) are mandatory! Really skip?}, 'n', )
-            =~ /^[Nn]/ )
-        {
-            push( @Missing, @required );
-            $DisabledTests{$_} = 1 for map { glob($_) } @skiptests;
-        }
-
-        else {
-            $DisabledTests{$_} = 1 for map { glob($_) } @tests;
-        }
-    }
-
-    $UnderCPAN = _check_lock();    # check for $UnderCPAN
-
-    if ( @Missing and not( $CheckOnly or $UnderCPAN ) ) {
-        require Config;
-        print
-"*** Dependencies will be installed the next time you type '$Config::Config{make}'.\n";
-
-        # make an educated guess of whether we'll need root permission.
-        print "    (You may need to do that as the 'root' user.)\n"
-          if eval '$>';
-    }
-    print "*** $class configuration finished.\n";
-
-    chdir $cwd;
-
-    # import to main::
-    no strict 'refs';
-    *{'main::WriteMakefile'} = \&Write if caller(0) eq 'main';
-}
-
-# Check to see if we are currently running under CPAN.pm and/or CPANPLUS;
-# if we are, then we simply let it taking care of our dependencies
-sub _check_lock {
-    return unless @Missing;
-
-    if ($ENV{PERL5_CPANPLUS_IS_RUNNING}) {
-        print <<'END_MESSAGE';
-
-*** Since we're running under CPANPLUS, I'll just let it take care
-    of the dependency's installation later.
-END_MESSAGE
-        return 1;
-    }
-
-    _load_cpan();
-
-    # Find the CPAN lock-file
-    my $lock = MM->catfile( $CPAN::Config->{cpan_home}, ".lock" );
-    return unless -f $lock;
-
-    # Check the lock
-    local *LOCK;
-    return unless open(LOCK, $lock);
-
-    if (
-            ( $^O eq 'MSWin32' ? _under_cpan() : <LOCK> == getppid() )
-        and ( $CPAN::Config->{prerequisites_policy} || '' ) ne 'ignore'
-    ) {
-        print <<'END_MESSAGE';
-
-*** Since we're running under CPAN, I'll just let it take care
-    of the dependency's installation later.
-END_MESSAGE
-        return 1;
-    }
-
-    close LOCK;
-    return;
-}
-
-sub install {
-    my $class = shift;
-
-    my $i;    # used below to strip leading '-' from config keys
-    my @config = ( map { s/^-// if ++$i; $_ } @{ +shift } );
-
-    my ( @modules, @installed );
-    while ( my ( $pkg, $ver ) = splice( @_, 0, 2 ) ) {
-
-        # grep out those already installed
-        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
-            push @installed, $pkg;
-        }
-        else {
-            push @modules, $pkg, $ver;
-        }
-    }
-
-    return @installed unless @modules;  # nothing to do
-    return @installed if _check_lock(); # defer to the CPAN shell
-
-    print "*** Installing dependencies...\n";
-
-    return unless _connected_to('cpan.org');
-
-    my %args = @config;
-    my %failed;
-    local *FAILED;
-    if ( $args{do_once} and open( FAILED, '.#autoinstall.failed' ) ) {
-        while (<FAILED>) { chomp; $failed{$_}++ }
-        close FAILED;
-
-        my @newmod;
-        while ( my ( $k, $v ) = splice( @modules, 0, 2 ) ) {
-            push @newmod, ( $k => $v ) unless $failed{$k};
-        }
-        @modules = @newmod;
-    }
-
-    if ( _has_cpanplus() ) {
-        _install_cpanplus( \@modules, \@config );
-    } else {
-        _install_cpan( \@modules, \@config );
-    }
-
-    print "*** $class installation finished.\n";
-
-    # see if we have successfully installed them
-    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
-        if ( defined( _version_check( _load($pkg), $ver ) ) ) {
-            push @installed, $pkg;
-        }
-        elsif ( $args{do_once} and open( FAILED, '>> .#autoinstall.failed' ) ) {
-            print FAILED "$pkg\n";
-        }
-    }
-
-    close FAILED if $args{do_once};
-
-    return @installed;
-}
-
-sub _install_cpanplus {
-    my @modules   = @{ +shift };
-    my @config    = _cpanplus_config( @{ +shift } );
-    my $installed = 0;
-
-    require CPANPLUS::Backend;
-    my $cp   = CPANPLUS::Backend->new;
-    my $conf = $cp->configure_object;
-
-    return unless $conf->can('conf') # 0.05x+ with "sudo" support
-               or _can_write($conf->_get_build('base'));  # 0.04x
-
-    # if we're root, set UNINST=1 to avoid trouble unless user asked for it.
-    my $makeflags = $conf->get_conf('makeflags') || '';
-    if ( UNIVERSAL::isa( $makeflags, 'HASH' ) ) {
-        # 0.03+ uses a hashref here
-        $makeflags->{UNINST} = 1 unless exists $makeflags->{UNINST};
-
-    } else {
-        # 0.02 and below uses a scalar
-        $makeflags = join( ' ', split( ' ', $makeflags ), 'UNINST=1' )
-          if ( $makeflags !~ /\bUNINST\b/ and eval qq{ $> eq '0' } );
-
-    }
-    $conf->set_conf( makeflags => $makeflags );
-    $conf->set_conf( prereqs   => 1 );
-
-    
-
-    while ( my ( $key, $val ) = splice( @config, 0, 2 ) ) {
-        $conf->set_conf( $key, $val );
-    }
-
-    my $modtree = $cp->module_tree;
-    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
-        print "*** Installing $pkg...\n";
-
-        MY::preinstall( $pkg, $ver ) or next if defined &MY::preinstall;
-
-        my $success;
-        my $obj = $modtree->{$pkg};
-
-        if ( $obj and defined( _version_check( $obj->{version}, $ver ) ) ) {
-            my $pathname = $pkg;
-            $pathname =~ s/::/\\W/;
-
-            foreach my $inc ( grep { m/$pathname.pm/i } keys(%INC) ) {
-                delete $INC{$inc};
-            }
-
-            my $rv = $cp->install( modules => [ $obj->{module} ] );
-
-            if ( $rv and ( $rv->{ $obj->{module} } or $rv->{ok} ) ) {
-                print "*** $pkg successfully installed.\n";
-                $success = 1;
-            } else {
-                print "*** $pkg installation cancelled.\n";
-                $success = 0;
-            }
-
-            $installed += $success;
-        } else {
-            print << ".";
-*** Could not find a version $ver or above for $pkg; skipping.
-.
-        }
-
-        MY::postinstall( $pkg, $ver, $success ) if defined &MY::postinstall;
-    }
-
-    return $installed;
-}
-
-sub _cpanplus_config {
-	my @config = ();
-	while ( @_ ) {
-		my ($key, $value) = (shift(), shift());
-		if ( $key eq 'prerequisites_policy' ) {
-			if ( $value eq 'follow' ) {
-				$value = CPANPLUS::Internals::Constants::PREREQ_INSTALL();
-			} elsif ( $value eq 'ask' ) {
-				$value = CPANPLUS::Internals::Constants::PREREQ_ASK();
-			} elsif ( $value eq 'ignore' ) {
-				$value = CPANPLUS::Internals::Constants::PREREQ_IGNORE();
-			} else {
-				die "*** Cannot convert option $key = '$value' to CPANPLUS version.\n";
-			}
-		} else {
-			die "*** Cannot convert option $key to CPANPLUS version.\n";
-		}
-	}
-	return @config;
-}
-
-sub _install_cpan {
-    my @modules   = @{ +shift };
-    my @config    = @{ +shift };
-    my $installed = 0;
-    my %args;
-
-    _load_cpan();
-    require Config;
-
-    if (CPAN->VERSION < 1.80) {
-        # no "sudo" support, probe for writableness
-        return unless _can_write( MM->catfile( $CPAN::Config->{cpan_home}, 'sources' ) )
-                  and _can_write( $Config::Config{sitelib} );
-    }
-
-    # if we're root, set UNINST=1 to avoid trouble unless user asked for it.
-    my $makeflags = $CPAN::Config->{make_install_arg} || '';
-    $CPAN::Config->{make_install_arg} =
-      join( ' ', split( ' ', $makeflags ), 'UNINST=1' )
-      if ( $makeflags !~ /\bUNINST\b/ and eval qq{ $> eq '0' } );
-
-    # don't show start-up info
-    $CPAN::Config->{inhibit_startup_message} = 1;
-
-    # set additional options
-    while ( my ( $opt, $arg ) = splice( @config, 0, 2 ) ) {
-        ( $args{$opt} = $arg, next )
-          if $opt =~ /^force$/;    # pseudo-option
-        $CPAN::Config->{$opt} = $arg;
-    }
-
-    local $CPAN::Config->{prerequisites_policy} = 'follow';
-
-    while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) {
-        MY::preinstall( $pkg, $ver ) or next if defined &MY::preinstall;
-
-        print "*** Installing $pkg...\n";
-
-        my $obj     = CPAN::Shell->expand( Module => $pkg );
-        my $success = 0;
-
-        if ( $obj and defined( _version_check( $obj->cpan_version, $ver ) ) ) {
-            my $pathname = $pkg;
-            $pathname =~ s/::/\\W/;
-
-            foreach my $inc ( grep { m/$pathname.pm/i } keys(%INC) ) {
-                delete $INC{$inc};
-            }
-
-            my $rv = $args{force} ? CPAN::Shell->force( install => $pkg )
-                                  : CPAN::Shell->install($pkg);
-            $rv ||= eval {
-                $CPAN::META->instance( 'CPAN::Distribution', $obj->cpan_file, )
-                  ->{install}
-                  if $CPAN::META;
-            };
-
-            if ( $rv eq 'YES' ) {
-                print "*** $pkg successfully installed.\n";
-                $success = 1;
-            }
-            else {
-                print "*** $pkg installation failed.\n";
-                $success = 0;
-            }
-
-            $installed += $success;
-        }
-        else {
-            print << ".";
-*** Could not find a version $ver or above for $pkg; skipping.
-.
-        }
-
-        MY::postinstall( $pkg, $ver, $success ) if defined &MY::postinstall;
-    }
-
-    return $installed;
-}
-
-sub _has_cpanplus {
-    return (
-        $HasCPANPLUS = (
-            $INC{'CPANPLUS/Config.pm'}
-              or _load('CPANPLUS::Shell::Default')
-        )
-    );
-}
-
-# make guesses on whether we're under the CPAN installation directory
-sub _under_cpan {
-    require Cwd;
-    require File::Spec;
-
-    my $cwd  = File::Spec->canonpath( Cwd::cwd() );
-    my $cpan = File::Spec->canonpath( $CPAN::Config->{cpan_home} );
-
-    return ( index( $cwd, $cpan ) > -1 );
-}
-
-sub _update_to {
-    my $class = __PACKAGE__;
-    my $ver   = shift;
-
-    return
-      if defined( _version_check( _load($class), $ver ) );  # no need to upgrade
-
-    if (
-        _prompt( "==> A newer version of $class ($ver) is required. Install?",
-            'y' ) =~ /^[Nn]/
-      )
-    {
-        die "*** Please install $class $ver manually.\n";
-    }
-
-    print << ".";
-*** Trying to fetch it from CPAN...
-.
-
-    # install ourselves
-    _load($class) and return $class->import(@_)
-      if $class->install( [], $class, $ver );
-
-    print << '.'; exit 1;
-
-*** Cannot bootstrap myself. :-( Installation terminated.
-.
-}
-
-# check if we're connected to some host, using inet_aton
-sub _connected_to {
-    my $site = shift;
-
-    return (
-        ( _load('Socket') and Socket::inet_aton($site) ) or _prompt(
-            qq(
-*** Your host cannot resolve the domain name '$site', which
-    probably means the Internet connections are unavailable.
-==> Should we try to install the required module(s) anyway?), 'n'
-          ) =~ /^[Yy]/
-    );
-}
-
-# check if a directory is writable; may create it on demand
-sub _can_write {
-    my $path = shift;
-    mkdir( $path, 0755 ) unless -e $path;
-
-    return 1 if -w $path;
-
-    print << ".";
-*** You are not allowed to write to the directory '$path';
-    the installation may fail due to insufficient permissions.
-.
-
-    if (
-        eval '$>' and lc(`sudo -V`) =~ /version/ and _prompt(
-            qq(
-==> Should we try to re-execute the autoinstall process with 'sudo'?),
-            ((-t STDIN) ? 'y' : 'n')
-        ) =~ /^[Yy]/
-      )
-    {
-
-        # try to bootstrap ourselves from sudo
-        print << ".";
-*** Trying to re-execute the autoinstall process with 'sudo'...
-.
-        my $missing = join( ',', @Missing );
-        my $config = join( ',',
-            UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
-          if $Config;
-
-        return
-          unless system( 'sudo', $^X, $0, "--config=$config",
-            "--installdeps=$missing" );
-
-        print << ".";
-*** The 'sudo' command exited with error!  Resuming...
-.
-    }
-
-    return _prompt(
-        qq(
-==> Should we try to install the required module(s) anyway?), 'n'
-    ) =~ /^[Yy]/;
-}
-
-# load a module and return the version it reports
-sub _load {
-    my $mod  = pop;    # class/instance doesn't matter
-    my $file = $mod;
-
-    $file =~ s|::|/|g;
-    $file .= '.pm';
-
-    local $@;
-    return eval { require $file; $mod->VERSION } || ( $@ ? undef: 0 );
-}
-
-# Load CPAN.pm and it's configuration
-sub _load_cpan {
-    return if $CPAN::VERSION;
-    require CPAN;
-    if ( $CPAN::HandleConfig::VERSION ) {
-        # Newer versions of CPAN have a HandleConfig module
-        CPAN::HandleConfig->load;
-    } else {
-    	# Older versions had the load method in Config directly
-        CPAN::Config->load;
-    }
-}
-
-# compare two versions, either use Sort::Versions or plain comparison
-sub _version_check {
-    my ( $cur, $min ) = @_;
-    return unless defined $cur;
-
-    $cur =~ s/\s+$//;
-
-    # check for version numbers that are not in decimal format
-    if ( ref($cur) or ref($min) or $cur =~ /v|\..*\./ or $min =~ /v|\..*\./ ) {
-        if ( ( $version::VERSION or defined( _load('version') )) and
-             version->can('new') 
-            ) {
-
-            # use version.pm if it is installed.
-            return (
-                ( version->new($cur) >= version->new($min) ) ? $cur : undef );
-        }
-        elsif ( $Sort::Versions::VERSION or defined( _load('Sort::Versions') ) )
-        {
-
-            # use Sort::Versions as the sorting algorithm for a.b.c versions
-            return ( ( Sort::Versions::versioncmp( $cur, $min ) != -1 )
-                ? $cur
-                : undef );
-        }
-
-        warn "Cannot reliably compare non-decimal formatted versions.\n"
-          . "Please install version.pm or Sort::Versions.\n";
-    }
-
-    # plain comparison
-    local $^W = 0;    # shuts off 'not numeric' bugs
-    return ( $cur >= $min ? $cur : undef );
-}
-
-# nothing; this usage is deprecated.
-sub main::PREREQ_PM { return {}; }
-
-sub _make_args {
-    my %args = @_;
-
-    $args{PREREQ_PM} = { %{ $args{PREREQ_PM} || {} }, @Existing, @Missing }
-      if $UnderCPAN or $TestOnly;
-
-    if ( $args{EXE_FILES} and -e 'MANIFEST' ) {
-        require ExtUtils::Manifest;
-        my $manifest = ExtUtils::Manifest::maniread('MANIFEST');
-
-        $args{EXE_FILES} =
-          [ grep { exists $manifest->{$_} } @{ $args{EXE_FILES} } ];
-    }
-
-    $args{test}{TESTS} ||= 't/*.t';
-    $args{test}{TESTS} = join( ' ',
-        grep { !exists( $DisabledTests{$_} ) }
-          map { glob($_) } split( /\s+/, $args{test}{TESTS} ) );
-
-    my $missing = join( ',', @Missing );
-    my $config =
-      join( ',', UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config} )
-      if $Config;
-
-    $PostambleActions = (
-        $missing
-        ? "\$(PERL) $0 --config=$config --installdeps=$missing"
-        : "\$(NOECHO) \$(NOOP)"
-    );
-
-    return %args;
-}
-
-# a wrapper to ExtUtils::MakeMaker::WriteMakefile
-sub Write {
-    require Carp;
-    Carp::croak "WriteMakefile: Need even number of args" if @_ % 2;
-
-    if ($CheckOnly) {
-        print << ".";
-*** Makefile not written in check-only mode.
-.
-        return;
-    }
-
-    my %args = _make_args(@_);
-
-    no strict 'refs';
-
-    $PostambleUsed = 0;
-    local *MY::postamble = \&postamble unless defined &MY::postamble;
-    ExtUtils::MakeMaker::WriteMakefile(%args);
-
-    print << "." unless $PostambleUsed;
-*** WARNING: Makefile written with customized MY::postamble() without
-    including contents from Module::AutoInstall::postamble() --
-    auto installation features disabled.  Please contact the author.
-.
-
-    return 1;
-}
-
-sub postamble {
-    $PostambleUsed = 1;
-
-    return << ".";
-
-config :: installdeps
-\t\$(NOECHO) \$(NOOP)
-
-checkdeps ::
-\t\$(PERL) $0 --checkdeps
-
-installdeps ::
-\t$PostambleActions
-
-.
-
-}
-
-1;
-
-__END__
-
-#line 1003
diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
deleted file mode 100644
index b46be99..0000000
--- a/inc/Module/Install.pm
+++ /dev/null
@@ -1,369 +0,0 @@
-#line 1
-package Module::Install;
-
-# For any maintainers:
-# The load order for Module::Install is a bit magic.
-# It goes something like this...
-#
-# IF ( host has Module::Install installed, creating author mode ) {
-#     1. Makefile.PL calls "use inc::Module::Install"
-#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
-#     3. The installed version of inc::Module::Install loads
-#     4. inc::Module::Install calls "require Module::Install"
-#     5. The ./inc/ version of Module::Install loads
-# } ELSE {
-#     1. Makefile.PL calls "use inc::Module::Install"
-#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
-#     3. The ./inc/ version of Module::Install loads
-# }
-
-BEGIN {
-	require 5.004;
-}
-use strict 'vars';
-
-use vars qw{$VERSION};
-BEGIN {
-	# All Module::Install core packages now require synchronised versions.
-	# This will be used to ensure we don't accidentally load old or
-	# different versions of modules.
-	# This is not enforced yet, but will be some time in the next few
-	# releases once we can make sure it won't clash with custom
-	# Module::Install extensions.
-	$VERSION = '0.79';
-
-	*inc::Module::Install::VERSION = *VERSION;
-	@inc::Module::Install::ISA     = __PACKAGE__;
-
-}
-
-
-
-
-
-# Whether or not inc::Module::Install is actually loaded, the
-# $INC{inc/Module/Install.pm} is what will still get set as long as
-# the caller loaded module this in the documented manner.
-# If not set, the caller may NOT have loaded the bundled version, and thus
-# they may not have a MI version that works with the Makefile.PL. This would
-# result in false errors or unexpected behaviour. And we don't want that.
-my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
-unless ( $INC{$file} ) { die <<"END_DIE" }
-
-Please invoke ${\__PACKAGE__} with:
-
-	use inc::${\__PACKAGE__};
-
-not:
-
-	use ${\__PACKAGE__};
-
-END_DIE
-
-
-
-
-
-# If the script that is loading Module::Install is from the future,
-# then make will detect this and cause it to re-run over and over
-# again. This is bad. Rather than taking action to touch it (which
-# is unreliable on some platforms and requires write permissions)
-# for now we should catch this and refuse to run.
-if ( -f $0 and (stat($0))[9] > time ) { die <<"END_DIE" }
-
-Your installer $0 has a modification time in the future.
-
-This is known to create infinite loops in make.
-
-Please correct this, then run $0 again.
-
-END_DIE
-
-
-
-
-
-# Build.PL was formerly supported, but no longer is due to excessive
-# difficulty in implementing every single feature twice.
-if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" }
-
-Module::Install no longer supports Build.PL.
-
-It was impossible to maintain duel backends, and has been deprecated.
-
-Please remove all Build.PL files and only use the Makefile.PL installer.
-
-END_DIE
-
-
-
-
-
-# To save some more typing in Module::Install installers, every...
-# use inc::Module::Install
-# ...also acts as an implicit use strict.
-$^H |= strict::bits(qw(refs subs vars));
-
-
-
-
-
-use Cwd        ();
-use File::Find ();
-use File::Path ();
-use FindBin;
-
-sub autoload {
-	my $self = shift;
-	my $who  = $self->_caller;
-	my $cwd  = Cwd::cwd();
-	my $sym  = "${who}::AUTOLOAD";
-	$sym->{$cwd} = sub {
-		my $pwd = Cwd::cwd();
-		if ( my $code = $sym->{$pwd} ) {
-			# delegate back to parent dirs
-			goto &$code unless $cwd eq $pwd;
-		}
-		$$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
-		unless ( uc($1) eq $1 ) {
-			unshift @_, ( $self, $1 );
-			goto &{$self->can('call')};
-		}
-	};
-}
-
-sub import {
-	my $class = shift;
-	my $self  = $class->new(@_);
-	my $who   = $self->_caller;
-
-	unless ( -f $self->{file} ) {
-		require "$self->{path}/$self->{dispatch}.pm";
-		File::Path::mkpath("$self->{prefix}/$self->{author}");
-		$self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
-		$self->{admin}->init;
-		@_ = ($class, _self => $self);
-		goto &{"$self->{name}::import"};
-	}
-
-	*{"${who}::AUTOLOAD"} = $self->autoload;
-	$self->preload;
-
-	# Unregister loader and worker packages so subdirs can use them again
-	delete $INC{"$self->{file}"};
-	delete $INC{"$self->{path}.pm"};
-
-	return 1;
-}
-
-sub preload {
-	my $self = shift;
-	unless ( $self->{extensions} ) {
-		$self->load_extensions(
-			"$self->{prefix}/$self->{path}", $self
-		);
-	}
-
-	my @exts = @{$self->{extensions}};
-	unless ( @exts ) {
-		my $admin = $self->{admin};
-		@exts = $admin->load_all_extensions;
-	}
-
-	my %seen;
-	foreach my $obj ( @exts ) {
-		while (my ($method, $glob) = each %{ref($obj) . '::'}) {
-			next unless $obj->can($method);
-			next if $method =~ /^_/;
-			next if $method eq uc($method);
-			$seen{$method}++;
-		}
-	}
-
-	my $who = $self->_caller;
-	foreach my $name ( sort keys %seen ) {
-		*{"${who}::$name"} = sub {
-			${"${who}::AUTOLOAD"} = "${who}::$name";
-			goto &{"${who}::AUTOLOAD"};
-		};
-	}
-}
-
-sub new {
-	my ($class, %args) = @_;
-
-	# ignore the prefix on extension modules built from top level.
-	my $base_path = Cwd::abs_path($FindBin::Bin);
-	unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
-		delete $args{prefix};
-	}
-
-	return $args{_self} if $args{_self};
-
-	$args{dispatch} ||= 'Admin';
-	$args{prefix}   ||= 'inc';
-	$args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
-	$args{bundle}   ||= 'inc/BUNDLES';
-	$args{base}     ||= $base_path;
-	$class =~ s/^\Q$args{prefix}\E:://;
-	$args{name}     ||= $class;
-	$args{version}  ||= $class->VERSION;
-	unless ( $args{path} ) {
-		$args{path}  = $args{name};
-		$args{path}  =~ s!::!/!g;
-	}
-	$args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
-	$args{wrote}      = 0;
-
-	bless( \%args, $class );
-}
-
-sub call {
-	my ($self, $method) = @_;
-	my $obj = $self->load($method) or return;
-        splice(@_, 0, 2, $obj);
-	goto &{$obj->can($method)};
-}
-
-sub load {
-	my ($self, $method) = @_;
-
-	$self->load_extensions(
-		"$self->{prefix}/$self->{path}", $self
-	) unless $self->{extensions};
-
-	foreach my $obj (@{$self->{extensions}}) {
-		return $obj if $obj->can($method);
-	}
-
-	my $admin = $self->{admin} or die <<"END_DIE";
-The '$method' method does not exist in the '$self->{prefix}' path!
-Please remove the '$self->{prefix}' directory and run $0 again to load it.
-END_DIE
-
-	my $obj = $admin->load($method, 1);
-	push @{$self->{extensions}}, $obj;
-
-	$obj;
-}
-
-sub load_extensions {
-	my ($self, $path, $top) = @_;
-
-	unless ( grep { !ref $_ and lc $_ eq lc $self->{prefix} } @INC ) {
-		unshift @INC, $self->{prefix};
-	}
-
-	foreach my $rv ( $self->find_extensions($path) ) {
-		my ($file, $pkg) = @{$rv};
-		next if $self->{pathnames}{$pkg};
-
-		local $@;
-		my $new = eval { require $file; $pkg->can('new') };
-		unless ( $new ) {
-			warn $@ if $@;
-			next;
-		}
-		$self->{pathnames}{$pkg} = delete $INC{$file};
-		push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
-	}
-
-	$self->{extensions} ||= [];
-}
-
-sub find_extensions {
-	my ($self, $path) = @_;
-
-	my @found;
-	File::Find::find( sub {
-		my $file = $File::Find::name;
-		return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
-		my $subpath = $1;
-		return if lc($subpath) eq lc($self->{dispatch});
-
-		$file = "$self->{path}/$subpath.pm";
-		my $pkg = "$self->{name}::$subpath";
-		$pkg =~ s!/!::!g;
-
-		# If we have a mixed-case package name, assume case has been preserved
-		# correctly.  Otherwise, root through the file to locate the case-preserved
-		# version of the package name.
-		if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
-			my $content = Module::Install::_read($subpath . '.pm');
-			my $in_pod  = 0;
-			foreach ( split //, $content ) {
-				$in_pod = 1 if /^=\w/;
-				$in_pod = 0 if /^=cut/;
-				next if ($in_pod || /^=cut/);  # skip pod text
-				next if /^\s*#/;               # and comments
-				if ( m/^\s*package\s+($pkg)\s*;/i ) {
-					$pkg = $1;
-					last;
-				}
-			}
-		}
-
-		push @found, [ $file, $pkg ];
-	}, $path ) if -d $path;
-
-	@found;
-}
-
-
-
-
-
-#####################################################################
-# Utility Functions
-
-sub _caller {
-	my $depth = 0;
-	my $call  = caller($depth);
-	while ( $call eq __PACKAGE__ ) {
-		$depth++;
-		$call = caller($depth);
-	}
-	return $call;
-}
-
-sub _read {
-	local *FH;
-	open FH, "< $_[0]" or die "open($_[0]): $!";
-	my $str = do { local $/; <FH> };
-	close FH or die "close($_[0]): $!";
-	return $str;
-}
-
-sub _write {
-	local *FH;
-	open FH, "> $_[0]" or die "open($_[0]): $!";
-	foreach ( 1 .. $#_ ) { print FH $_[$_] or die "print($_[0]): $!" }
-	close FH or die "close($_[0]): $!";
-}
-
-# _version is for processing module versions (eg, 1.03_05) not
-# Perl versions (eg, 5.8.1).
-
-sub _version ($) {
-	my $s = shift || 0;
-	   $s =~ s/^(\d+)\.?//;
-	my $l = $1 || 0;
-	my @v = map { $_ . '0' x (3 - length $_) } $s =~ /(\d{1,3})\D?/g;
-	   $l = $l . '.' . join '', @v if @v;
-	return $l + 0;
-}
-
-# Cloned from Params::Util::_CLASS
-sub _CLASS ($) {
-	(
-		defined $_[0]
-		and
-		! ref $_[0]
-		and
-		$_[0] =~ m/^[^\W\d]\w*(?:::\w+)*$/s
-	) ? $_[0] : undef;
-}
-
-1;
-
-# Copyright 2008 - 2009 Adam Kennedy.
diff --git a/inc/Module/Install/AutoInstall.pm b/inc/Module/Install/AutoInstall.pm
deleted file mode 100644
index 343738e..0000000
--- a/inc/Module/Install/AutoInstall.pm
+++ /dev/null
@@ -1,61 +0,0 @@
-#line 1
-package Module::Install::AutoInstall;
-
-use strict;
-use Module::Install::Base;
-
-use vars qw{$VERSION $ISCORE @ISA};
-BEGIN {
-	$VERSION = '0.79';
-	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
-}
-
-sub AutoInstall { $_[0] }
-
-sub run {
-    my $self = shift;
-    $self->auto_install_now(@_);
-}
-
-sub write {
-    my $self = shift;
-    $self->auto_install(@_);
-}
-
-sub auto_install {
-    my $self = shift;
-    return if $self->{done}++;
-
-    # Flatten array of arrays into a single array
-    my @core = map @$_, map @$_, grep ref,
-               $self->build_requires, $self->requires;
-
-    my @config = @_;
-
-    # We'll need Module::AutoInstall
-    $self->include('Module::AutoInstall');
-    require Module::AutoInstall;
-
-    Module::AutoInstall->import(
-        (@config ? (-config => \@config) : ()),
-        (@core   ? (-core   => \@core)   : ()),
-        $self->features,
-    );
-
-    $self->makemaker_args( Module::AutoInstall::_make_args() );
-
-    my $class = ref($self);
-    $self->postamble(
-        "# --- $class section:\n" .
-        Module::AutoInstall::postamble()
-    );
-}
-
-sub auto_install_now {
-    my $self = shift;
-    $self->auto_install(@_);
-    Module::AutoInstall::do_install();
-}
-
-1;
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
deleted file mode 100644
index 1145fe4..0000000
--- a/inc/Module/Install/Base.pm
+++ /dev/null
@@ -1,72 +0,0 @@
-#line 1
-package Module::Install::Base;
-
-$VERSION = '0.79';
-
-# Suspend handler for "redefined" warnings
-BEGIN {
-	my $w = $SIG{__WARN__};
-	$SIG{__WARN__} = sub { $w };
-}
-
-### This is the ONLY module that shouldn't have strict on
-# use strict;
-
-#line 41
-
-sub new {
-    my ($class, %args) = @_;
-
-    foreach my $method ( qw(call load) ) {
-        *{"$class\::$method"} = sub {
-            shift()->_top->$method(@_);
-        } unless defined &{"$class\::$method"};
-    }
-
-    bless( \%args, $class );
-}
-
-#line 61
-
-sub AUTOLOAD {
-    my $self = shift;
-    local $@;
-    my $autoload = eval { $self->_top->autoload } or return;
-    goto &$autoload;
-}
-
-#line 76
-
-sub _top { $_[0]->{_top} }
-
-#line 89
-
-sub admin {
-    $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
-}
-
-#line 101
-
-sub is_admin {
-    $_[0]->admin->VERSION;
-}
-
-sub DESTROY {}
-
-package Module::Install::Base::FakeAdmin;
-
-my $Fake;
-sub new { $Fake ||= bless(\@_, $_[0]) }
-
-sub AUTOLOAD {}
-
-sub DESTROY {}
-
-# Restore warning handler
-BEGIN {
-	$SIG{__WARN__} = $SIG{__WARN__}->();
-}
-
-1;
-
-#line 146
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
deleted file mode 100644
index ac81dec..0000000
--- a/inc/Module/Install/Can.pm
+++ /dev/null
@@ -1,83 +0,0 @@
-#line 1
-package Module::Install::Can;
-
-use strict;
-use Module::Install::Base;
-use Config ();
-### This adds a 5.005 Perl version dependency.
-### This is a bug and will be fixed.
-use File::Spec ();
-use ExtUtils::MakeMaker ();
-
-use vars qw{$VERSION $ISCORE @ISA};
-BEGIN {
-	$VERSION = '0.79';
-	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
-}
-
-# check if we can load some module
-### Upgrade this to not have to load the module if possible
-sub can_use {
-	my ($self, $mod, $ver) = @_;
-	$mod =~ s{::|\\}{/}g;
-	$mod .= '.pm' unless $mod =~ /\.pm$/i;
-
-	my $pkg = $mod;
-	$pkg =~ s{/}{::}g;
-	$pkg =~ s{\.pm$}{}i;
-
-	local $@;
-	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
-}
-
-# check if we can run some command
-sub can_run {
-	my ($self, $cmd) = @_;
-
-	my $_cmd = $cmd;
-	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
-
-	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
-		next if $dir eq '';
-		my $abs = File::Spec->catfile($dir, $_[1]);
-		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
-	}
-
-	return;
-}
-
-# can we locate a (the) C compiler
-sub can_cc {
-	my $self   = shift;
-	my @chunks = split(/ /, $Config::Config{cc}) or return;
-
-	# $Config{cc} may contain args; try to find out the program part
-	while (@chunks) {
-		return $self->can_run("@chunks") || (pop(@chunks), next);
-	}
-
-	return;
-}
-
-# Fix Cygwin bug on maybe_command();
-if ( $^O eq 'cygwin' ) {
-	require ExtUtils::MM_Cygwin;
-	require ExtUtils::MM_Win32;
-	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
-		*ExtUtils::MM_Cygwin::maybe_command = sub {
-			my ($self, $file) = @_;
-			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
-				ExtUtils::MM_Win32->maybe_command($file);
-			} else {
-				ExtUtils::MM_Unix->maybe_command($file);
-			}
-		}
-	}
-}
-
-1;
-
-__END__
-
-#line 158
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
deleted file mode 100644
index 41d9569..0000000
--- a/inc/Module/Install/Fetch.pm
+++ /dev/null
@@ -1,93 +0,0 @@
-#line 1
-package Module::Install::Fetch;
-
-use strict;
-use Module::Install::Base;
-
-use vars qw{$VERSION $ISCORE @ISA};
-BEGIN {
-	$VERSION = '0.79';
-	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
-}
-
-sub get_file {
-    my ($self, %args) = @_;
-    my ($scheme, $host, $path, $file) =
-        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
-
-    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
-        $args{url} = $args{ftp_url}
-            or (warn("LWP support unavailable!\n"), return);
-        ($scheme, $host, $path, $file) =
-            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
-    }
-
-    $|++;
-    print "Fetching '$file' from $host... ";
-
-    unless (eval { require Socket; Socket::inet_aton($host) }) {
-        warn "'$host' resolve failed!\n";
-        return;
-    }
-
-    return unless $scheme eq 'ftp' or $scheme eq 'http';
-
-    require Cwd;
-    my $dir = Cwd::getcwd();
-    chdir $args{local_dir} or return if exists $args{local_dir};
-
-    if (eval { require LWP::Simple; 1 }) {
-        LWP::Simple::mirror($args{url}, $file);
-    }
-    elsif (eval { require Net::FTP; 1 }) { eval {
-        # use Net::FTP to get past firewall
-        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
-        $ftp->login("anonymous", 'anonymous at example.com');
-        $ftp->cwd($path);
-        $ftp->binary;
-        $ftp->get($file) or (warn("$!\n"), return);
-        $ftp->quit;
-    } }
-    elsif (my $ftp = $self->can_run('ftp')) { eval {
-        # no Net::FTP, fallback to ftp.exe
-        require FileHandle;
-        my $fh = FileHandle->new;
-
-        local $SIG{CHLD} = 'IGNORE';
-        unless ($fh->open("|$ftp -n")) {
-            warn "Couldn't open ftp: $!\n";
-            chdir $dir; return;
-        }
-
-        my @dialog = split(/\n/, <<"END_FTP");
-open $host
-user anonymous anonymous\@example.com
-cd $path
-binary
-get $file $file
-quit
-END_FTP
-        foreach (@dialog) { $fh->print("$_\n") }
-        $fh->close;
-    } }
-    else {
-        warn "No working 'ftp' program available!\n";
-        chdir $dir; return;
-    }
-
-    unless (-f $file) {
-        warn "Fetching failed: $@\n";
-        chdir $dir; return;
-    }
-
-    return if exists $args{size} and -s $file != $args{size};
-    system($args{run}) if exists $args{run};
-    unlink($file) if $args{remove};
-
-    print(((!exists $args{check_for} or -e $args{check_for})
-        ? "done!" : "failed! ($!)"), "\n");
-    chdir $dir; return !$?;
-}
-
-1;
diff --git a/inc/Module/Install/Include.pm b/inc/Module/Install/Include.pm
deleted file mode 100644
index 742121a..0000000
--- a/inc/Module/Install/Include.pm
+++ /dev/null
@@ -1,34 +0,0 @@
-#line 1
-package Module::Install::Include;
-
-use strict;
-use Module::Install::Base;
-
-use vars qw{$VERSION $ISCORE @ISA};
-BEGIN {
-	$VERSION = '0.79';
-	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
-}
-
-sub include {
-	shift()->admin->include(@_);
-}
-
-sub include_deps {
-	shift()->admin->include_deps(@_);
-}
-
-sub auto_include {
-	shift()->admin->auto_include(@_);
-}
-
-sub auto_include_deps {
-	shift()->admin->auto_include_deps(@_);
-}
-
-sub auto_include_dependent_dists {
-	shift()->admin->auto_include_dependent_dists(@_);
-}
-
-1;
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
deleted file mode 100644
index 689a4b7..0000000
--- a/inc/Module/Install/Makefile.pm
+++ /dev/null
@@ -1,253 +0,0 @@
-#line 1
-package Module::Install::Makefile;
-
-use strict 'vars';
-use Module::Install::Base;
-use ExtUtils::MakeMaker ();
-
-use vars qw{$VERSION $ISCORE @ISA};
-BEGIN {
-	$VERSION = '0.79';
-	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
-}
-
-sub Makefile { $_[0] }
-
-my %seen = ();
-
-sub prompt {
-	shift;
-
-	# Infinite loop protection
-	my @c = caller();
-	if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
-		die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
-	}
-
-	# In automated testing, always use defaults
-	if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
-		local $ENV{PERL_MM_USE_DEFAULT} = 1;
-		goto &ExtUtils::MakeMaker::prompt;
-	} else {
-		goto &ExtUtils::MakeMaker::prompt;
-	}
-}
-
-sub makemaker_args {
-	my $self = shift;
-	my $args = ( $self->{makemaker_args} ||= {} );
-	%$args = ( %$args, @_ );
-	return $args;
-}
-
-# For mm args that take multiple space-seperated args,
-# append an argument to the current list.
-sub makemaker_append {
-	my $self = sShift;
-	my $name = shift;
-	my $args = $self->makemaker_args;
-	$args->{name} = defined $args->{$name}
-		? join( ' ', $args->{name}, @_ )
-		: join( ' ', @_ );
-}
-
-sub build_subdirs {
-	my $self    = shift;
-	my $subdirs = $self->makemaker_args->{DIR} ||= [];
-	for my $subdir (@_) {
-		push @$subdirs, $subdir;
-	}
-}
-
-sub clean_files {
-	my $self  = shift;
-	my $clean = $self->makemaker_args->{clean} ||= {};
-	  %$clean = (
-		%$clean,
-		FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_),
-	);
-}
-
-sub realclean_files {
-	my $self      = shift;
-	my $realclean = $self->makemaker_args->{realclean} ||= {};
-	  %$realclean = (
-		%$realclean,
-		FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_),
-	);
-}
-
-sub libs {
-	my $self = shift;
-	my $libs = ref $_[0] ? shift : [ shift ];
-	$self->makemaker_args( LIBS => $libs );
-}
-
-sub inc {
-	my $self = shift;
-	$self->makemaker_args( INC => shift );
-}
-
-my %test_dir = ();
-
-sub _wanted_t {
-	/\.t$/ and -f $_ and $test_dir{$File::Find::dir} = 1;
-}
-
-sub tests_recursive {
-	my $self = shift;
-	if ( $self->tests ) {
-		die "tests_recursive will not work if tests are already defined";
-	}
-	my $dir = shift || 't';
-	unless ( -d $dir ) {
-		die "tests_recursive dir '$dir' does not exist";
-	}
-	%test_dir = ();
-	require File::Find;
-	File::Find::find( \&_wanted_t, $dir );
-	$self->tests( join ' ', map { "$_/*.t" } sort keys %test_dir );
-}
-
-sub write {
-	my $self = shift;
-	die "&Makefile->write() takes no arguments\n" if @_;
-
-	# Make sure we have a new enough
-	require ExtUtils::MakeMaker;
-
-	# MakeMaker can complain about module versions that include
-	# an underscore, even though its own version may contain one!
-	# Hence the funny regexp to get rid of it.  See RT #35800
-	# for details.
-
-	$self->configure_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/ );
-
-	# Generate the
-	my $args = $self->makemaker_args;
-	$args->{DISTNAME} = $self->name;
-	$args->{NAME}     = $self->module_name || $self->name;
-	$args->{VERSION}  = $self->version;
-	$args->{NAME}     =~ s/-/::/g;
-	if ( $self->tests ) {
-		$args->{test} = { TESTS => $self->tests };
-	}
-	if ($] >= 5.005) {
-		$args->{ABSTRACT} = $self->abstract;
-		$args->{AUTHOR}   = $self->author;
-	}
-	if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
-		$args->{NO_META} = 1;
-	}
-	if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
-		$args->{SIGN} = 1;
-	}
-	unless ( $self->is_admin ) {
-		delete $args->{SIGN};
-	}
-
-	# merge both kinds of requires into prereq_pm
-	my $prereq = ($args->{PREREQ_PM} ||= {});
-	%$prereq = ( %$prereq,
-		map { @$_ }
-		map { @$_ }
-		grep $_,
-		($self->configure_requires, $self->build_requires, $self->requires)
-	);
-
-	# Remove any reference to perl, PREREQ_PM doesn't support it
-	delete $args->{PREREQ_PM}->{perl};
-
-	# merge both kinds of requires into prereq_pm
-	my $subdirs = ($args->{DIR} ||= []);
-	if ($self->bundles) {
-		foreach my $bundle (@{ $self->bundles }) {
-			my ($file, $dir) = @$bundle;
-			push @$subdirs, $dir if -d $dir;
-			delete $prereq->{$file};
-		}
-	}
-
-	if ( my $perl_version = $self->perl_version ) {
-		eval "use $perl_version; 1"
-			or die "ERROR: perl: Version $] is installed, "
-			. "but we need version >= $perl_version";
-	}
-
-	$args->{INSTALLDIRS} = $self->installdirs;
-
-	my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
-
-	my $user_preop = delete $args{dist}->{PREOP};
-	if (my $preop = $self->admin->preop($user_preop)) {
-		foreach my $key ( keys %$preop ) {
-			$args{dist}->{$key} = $preop->{$key};
-		}
-	}
-
-	my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
-	$self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
-}
-
-sub fix_up_makefile {
-	my $self          = shift;
-	my $makefile_name = shift;
-	my $top_class     = ref($self->_top) || '';
-	my $top_version   = $self->_top->VERSION || '';
-
-	my $preamble = $self->preamble
-		? "# Preamble by $top_class $top_version\n"
-			. $self->preamble
-		: '';
-	my $postamble = "# Postamble by $top_class $top_version\n"
-		. ($self->postamble || '');
-
-	local *MAKEFILE;
-	open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
-	my $makefile = do { local $/; <MAKEFILE> };
-	close MAKEFILE or die $!;
-
-	$makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
-	$makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
-	$makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
-	$makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
-	$makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
-
-	# Module::Install will never be used to build the Core Perl
-	# Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
-	# PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
-	$makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
-	#$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
-
-	# Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
-	$makefile =~ s/(\"?)-I\$\(PERL_LIB\)\1//g;
-
-	# XXX - This is currently unused; not sure if it breaks other MM-users
-	# $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
-
-	open  MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
-	print MAKEFILE  "$preamble$makefile$postamble" or die $!;
-	close MAKEFILE  or die $!;
-
-	1;
-}
-
-sub preamble {
-	my ($self, $text) = @_;
-	$self->{preamble} = $text . $self->{preamble} if defined $text;
-	$self->{preamble};
-}
-
-sub postamble {
-	my ($self, $text) = @_;
-	$self->{postamble} ||= $self->admin->postamble;
-	$self->{postamble} .= $text if defined $text;
-	$self->{postamble}
-}
-
-1;
-
-__END__
-
-#line 379
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
deleted file mode 100644
index 37d5eff..0000000
--- a/inc/Module/Install/Metadata.pm
+++ /dev/null
@@ -1,510 +0,0 @@
-#line 1
-package Module::Install::Metadata;
-
-use strict 'vars';
-use Module::Install::Base;
-
-use vars qw{$VERSION $ISCORE @ISA};
-BEGIN {
-	$VERSION = '0.79';
-	$ISCORE  = 1;
-	@ISA     = qw{Module::Install::Base};
-}
-
-my @scalar_keys = qw{
-	name
-	module_name
-	abstract
-	author
-	version
-	distribution_type
-	tests
-	installdirs
-};
-
-my @tuple_keys = qw{
-	configure_requires
-	build_requires
-	requires
-	recommends
-	bundles
-	resources
-};
-
-my @resource_keys = qw{
-	homepage
-	bugtracker
-	repository
-};
-
-sub Meta              { shift          }
-sub Meta_ScalarKeys   { @scalar_keys   }
-sub Meta_TupleKeys    { @tuple_keys    }
-sub Meta_ResourceKeys { @resource_keys }
-
-foreach my $key ( @scalar_keys ) {
-	*$key = sub {
-		my $self = shift;
-		return $self->{values}{$key} if defined wantarray and !@_;
-		$self->{values}{$key} = shift;
-		return $self;
-	};
-}
-
-foreach my $key ( @resource_keys ) {
-	*$key = sub {
-		my $self = shift;
-		unless ( @_ ) {
-			return () unless $self->{values}{resources};
-			return map  { $_->[1] }
-			       grep { $_->[0] eq $key }
-			       @{ $self->{values}{resources} };
-		}
-		return $self->{values}{resources}{$key} unless @_;
-		my $uri = shift or die(
-			"Did not provide a value to $key()"
-		);
-		$self->resources( $key => $uri );
-		return 1;
-	};
-}
-
-foreach my $key ( grep {$_ ne "resources"} @tuple_keys) {
-	*$key = sub {
-		my $self = shift;
-		return $self->{values}{$key} unless @_;
-		my @added;
-		while ( @_ ) {
-			my $module  = shift or last;
-			my $version = shift || 0;
-			push @added, [ $module, $version ];
-		}
-		push @{ $self->{values}{$key} }, @added;
-		return map {@$_} @added;
-	};
-}
-
-# Resource handling
-my %lc_resource = map { $_ => 1 } qw{
-	homepage
-	license
-	bugtracker
-	repository
-};
-
-sub resources {
-	my $self = shift;
-	while ( @_ ) {
-		my $name  = shift or last;
-		my $value = shift or next;
-		if ( $name eq lc $name and ! $lc_resource{$name} ) {
-			die("Unsupported reserved lowercase resource '$name'");
-		}
-		$self->{values}{resources} ||= [];
-		push @{ $self->{values}{resources} }, [ $name, $value ];
-	}
-	$self->{values}{resources};
-}
-
-# Aliases for build_requires that will have alternative
-# meanings in some future version of META.yml.
-sub test_requires      { shift->build_requires(@_) }
-sub install_requires   { shift->build_requires(@_) }
-
-# Aliases for installdirs options
-sub install_as_core    { $_[0]->installdirs('perl')   }
-sub install_as_cpan    { $_[0]->installdirs('site')   }
-sub install_as_site    { $_[0]->installdirs('site')   }
-sub install_as_vendor  { $_[0]->installdirs('vendor') }
-
-sub sign {
-	my $self = shift;
-	return $self->{values}{sign} if defined wantarray and ! @_;
-	$self->{values}{sign} = ( @_ ? $_[0] : 1 );
-	return $self;
-}
-
-sub dynamic_config {
-	my $self = shift;
-	unless ( @_ ) {
-		warn "You MUST provide an explicit true/false value to dynamic_config\n";
-		return $self;
-	}
-	$self->{values}{dynamic_config} = $_[0] ? 1 : 0;
-	return 1;
-}
-
-sub perl_version {
-	my $self = shift;
-	return $self->{values}{perl_version} unless @_;
-	my $version = shift or die(
-		"Did not provide a value to perl_version()"
-	);
-
-	# Normalize the version
-	$version = $self->_perl_version($version);
-
-	# We don't support the reall old versions
-	unless ( $version >= 5.005 ) {
-		die "Module::Install only supports 5.005 or newer (use ExtUtils::MakeMaker)\n";
-	}
-
-	$self->{values}{perl_version} = $version;
-}
-
-sub license {
-	my $self = shift;
-	return $self->{values}{license} unless @_;
-	my $license = shift or die(
-		'Did not provide a value to license()'
-	);
-	$self->{values}{license} = $license;
-
-	# Automatically fill in license URLs
-	if ( $license eq 'perl' ) {
-		$self->resources( license => 'http://dev.perl.org/licenses/' );
-	}
-
-	return 1;
-}
-
-sub all_from {
-	my ( $self, $file ) = @_;
-
-	unless ( defined($file) ) {
-		my $name = $self->name or die(
-			"all_from called with no args without setting name() first"
-		);
-		$file = join('/', 'lib', split(/-/, $name)) . '.pm';
-		$file =~ s{.*/}{} unless -e $file;
-		unless ( -e $file ) {
-			die("all_from cannot find $file from $name");
-		}
-	}
-	unless ( -f $file ) {
-		die("The path '$file' does not exist, or is not a file");
-	}
-
-	# Some methods pull from POD instead of code.
-	# If there is a matching .pod, use that instead
-	my $pod = $file;
-	$pod =~ s/\.pm$/.pod/i;
-	$pod = $file unless -e $pod;
-
-	# Pull the different values
-	$self->name_from($file)         unless $self->name;
-	$self->version_from($file)      unless $self->version;
-	$self->perl_version_from($file) unless $self->perl_version;
-	$self->author_from($pod)        unless $self->author;
-	$self->license_from($pod)       unless $self->license;
-	$self->abstract_from($pod)      unless $self->abstract;
-
-	return 1;
-}
-
-sub provides {
-	my $self     = shift;
-	my $provides = ( $self->{values}{provides} ||= {} );
-	%$provides = (%$provides, @_) if @_;
-	return $provides;
-}
-
-sub auto_provides {
-	my $self = shift;
-	return $self unless $self->is_admin;
-	unless (-e 'MANIFEST') {
-		warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
-		return $self;
-	}
-	# Avoid spurious warnings as we are not checking manifest here.
-	local $SIG{__WARN__} = sub {1};
-	require ExtUtils::Manifest;
-	local *ExtUtils::Manifest::manicheck = sub { return };
-
-	require Module::Build;
-	my $build = Module::Build->new(
-		dist_name    => $self->name,
-		dist_version => $self->version,
-		license      => $self->license,
-	);
-	$self->provides( %{ $build->find_dist_packages || {} } );
-}
-
-sub feature {
-	my $self     = shift;
-	my $name     = shift;
-	my $features = ( $self->{values}{features} ||= [] );
-	my $mods;
-
-	if ( @_ == 1 and ref( $_[0] ) ) {
-		# The user used ->feature like ->features by passing in the second
-		# argument as a reference.  Accomodate for that.
-		$mods = $_[0];
-	} else {
-		$mods = \@_;
-	}
-
-	my $count = 0;
-	push @$features, (
-		$name => [
-			map {
-				ref($_) ? ( ref($_) eq 'HASH' ) ? %$_ : @$_ : $_
-			} @$mods
-		]
-	);
-
-	return @$features;
-}
-
-sub features {
-	my $self = shift;
-	while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
-		$self->feature( $name, @$mods );
-	}
-	return $self->{values}{features}
-		? @{ $self->{values}{features} }
-		: ();
-}
-
-sub no_index {
-	my $self = shift;
-	my $type = shift;
-	push @{ $self->{values}{no_index}{$type} }, @_ if $type;
-	return $self->{values}{no_index};
-}
-
-sub read {
-	my $self = shift;
-	$self->include_deps( 'YAML::Tiny', 0 );
-
-	require YAML::Tiny;
-	my $data = YAML::Tiny::LoadFile('META.yml');
-
-	# Call methods explicitly in case user has already set some values.
-	while ( my ( $key, $value ) = each %$data ) {
-		next unless $self->can($key);
-		if ( ref $value eq 'HASH' ) {
-			while ( my ( $module, $version ) = each %$value ) {
-				$self->can($key)->($self, $module => $version );
-			}
-		} else {
-			$self->can($key)->($self, $value);
-		}
-	}
-	return $self;
-}
-
-sub write {
-	my $self = shift;
-	return $self unless $self->is_admin;
-	$self->admin->write_meta;
-	return $self;
-}
-
-sub version_from {
-	require ExtUtils::MM_Unix;
-	my ( $self, $file ) = @_;
-	$self->version( ExtUtils::MM_Unix->parse_version($file) );
-}
-
-sub abstract_from {
-	require ExtUtils::MM_Unix;
-	my ( $self, $file ) = @_;
-	$self->abstract(
-		bless(
-			{ DISTNAME => $self->name },
-			'ExtUtils::MM_Unix'
-		)->parse_abstract($file)
-	 );
-}
-
-# Add both distribution and module name
-sub name_from {
-	my ($self, $file) = @_;
-	if (
-		Module::Install::_read($file) =~ m/
-		^ \s*
-		package \s*
-		([\w:]+)
-		\s* ;
-		/ixms
-	) {
-		my ($name, $module_name) = ($1, $1);
-		$name =~ s{::}{-}g;
-		$self->name($name);
-		unless ( $self->module_name ) {
-			$self->module_name($module_name);
-		}
-	} else {
-		die("Cannot determine name from $file\n");
-	}
-}
-
-sub perl_version_from {
-	my $self = shift;
-	if (
-		Module::Install::_read($_[0]) =~ m/
-		^
-		(?:use|require) \s*
-		v?
-		([\d_\.]+)
-		\s* ;
-		/ixms
-	) {
-		my $perl_version = $1;
-		$perl_version =~ s{_}{}g;
-		$self->perl_version($perl_version);
-	} else {
-		warn "Cannot determine perl version info from $_[0]\n";
-		return;
-	}
-}
-
-sub author_from {
-	my $self    = shift;
-	my $content = Module::Install::_read($_[0]);
-	if ($content =~ m/
-		=head \d \s+ (?:authors?)\b \s*
-		([^\n]*)
-		|
-		=head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
-		.*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
-		([^\n]*)
-	/ixms) {
-		my $author = $1 || $2;
-		$author =~ s{E<lt>}{<}g;
-		$author =~ s{E<gt>}{>}g;
-		$self->author($author);
-	} else {
-		warn "Cannot determine author info from $_[0]\n";
-	}
-}
-
-sub license_from {
-	my $self = shift;
-	if (
-		Module::Install::_read($_[0]) =~ m/
-		(
-			=head \d \s+
-			(?:licen[cs]e|licensing|copyright|legal)\b
-			.*?
-		)
-		(=head\\d.*|=cut.*|)
-		\z
-	/ixms ) {
-		my $license_text = $1;
-		my @phrases      = (
-			'under the same (?:terms|license) as perl itself' => 'perl',        1,
-			'GNU general public license'                      => 'gpl',         1,
-			'GNU public license'                              => 'gpl',         1,
-			'GNU lesser general public license'               => 'lgpl',        1,
-			'GNU lesser public license'                       => 'lgpl',        1,
-			'GNU library general public license'              => 'lgpl',        1,
-			'GNU library public license'                      => 'lgpl',        1,
-			'BSD license'                                     => 'bsd',         1,
-			'Artistic license'                                => 'artistic',    1,
-			'GPL'                                             => 'gpl',         1,
-			'LGPL'                                            => 'lgpl',        1,
-			'BSD'                                             => 'bsd',         1,
-			'Artistic'                                        => 'artistic',    1,
-			'MIT'                                             => 'mit',         1,
-			'proprietary'                                     => 'proprietary', 0,
-		);
-		while ( my ($pattern, $license, $osi) = splice(@phrases, 0, 3) ) {
-			$pattern =~ s{\s+}{\\s+}g;
-			if ( $license_text =~ /\b$pattern\b/i ) {
-				$self->license($license);
-				return 1;
-			}
-		}
-	}
-
-	warn "Cannot determine license info from $_[0]\n";
-	return 'unknown';
-}
-
-sub bugtracker_from {
-	my $self    = shift;
-	my $content = Module::Install::_read($_[0]);
-	my @links   = $content =~ m/L\<(http\:\/\/rt\.cpan\.org\/[^>]+)\>/g;
-	unless ( @links ) {
-		warn "Cannot determine bugtracker info from $_[0]\n";
-		return 0;
-	}
-	if ( @links > 1 ) {
-		warn "Found more than on rt.cpan.org link in $_[0]\n";
-		return 0;
-	}
-
-	# Set the bugtracker
-	bugtracker( $links[0] );
-	return 1;
-}
-
-# Convert triple-part versions (eg, 5.6.1 or 5.8.9) to
-# numbers (eg, 5.006001 or 5.008009).
-# Also, convert double-part versions (eg, 5.8)
-sub _perl_version {
-	my $v = $_[-1];
-	$v =~ s/^([1-9])\.([1-9]\d?\d?)$/sprintf("%d.%03d",$1,$2)/e;	
-	$v =~ s/^([1-9])\.([1-9]\d?\d?)\.(0|[1-9]\d?\d?)$/sprintf("%d.%03d%03d",$1,$2,$3 || 0)/e;
-	$v =~ s/(\.\d\d\d)000$/$1/;
-	$v =~ s/_.+$//;
-	if ( ref($v) ) {
-		$v = $v + 0; # Numify
-	}
-	return $v;
-}
-
-
-
-
-
-######################################################################
-# MYMETA.yml Support
-
-sub WriteMyMeta {
-	$_[0]->write_mymeta;
-}
-
-sub write_mymeta {
-	my $self = shift;
-	
-	# If there's no existing META.yml there is nothing we can do
-	return unless -f 'META.yml';
-
-	# Merge the perl version into the dependencies
-	my $val  = $self->Meta->{values};
-	my $perl = delete $val->{perl_version};
-	if ( $perl ) {
-		$val->{requires} ||= [];
-		my $requires = $val->{requires};
-
-		# Canonize to three-dot version after Perl 5.6
-		if ( $perl >= 5.006 ) {
-			$perl =~ s{^(\d+)\.(\d\d\d)(\d*)}{join('.', $1, int($2||0), int($3||0))}e
-		}
-		unshift @$requires, [ perl => $perl ];
-	}
-
-	# Load the advisory META.yml file
-	require YAML::Tiny;
-	my @yaml = YAML::Tiny::LoadFile('META.yml');
-	my $meta = $yaml[0];
-
-	# Overwrite the non-configure dependency hashs
-	delete $meta->{requires};
-	delete $meta->{build_requires};
-	delete $meta->{recommends};
-	if ( exists $val->{requires} ) {
-		$meta->{requires} = { map { @$_ } @{ $val->{requires} } };
-	}
-	if ( exists $val->{build_requires} ) {
-		$meta->{build_requires} = { map { @$_ } @{ $val->{build_requires} } };
-	}
-
-	# Save as the MYMETA.yml file
-	YAML::Tiny::DumpFile('MYMETA.yml', $meta);
-}
-
-1;
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
deleted file mode 100644
index 2bd721a..0000000
--- a/inc/Module/Install/Win32.pm
+++ /dev/null
@@ -1,64 +0,0 @@
-#line 1
-package Module::Install::Win32;
-
-use strict;
-use Module::Install::Base;
-
-use vars qw{$VERSION @ISA $ISCORE};
-BEGIN {
-	$VERSION = '0.79';
-	@ISA     = qw{Module::Install::Base};
-	$ISCORE  = 1;
-}
-
-# determine if the user needs nmake, and download it if needed
-sub check_nmake {
-	my $self = shift;
-	$self->load('can_run');
-	$self->load('get_file');
-
-	require Config;
-	return unless (
-		$^O eq 'MSWin32'                     and
-		$Config::Config{make}                and
-		$Config::Config{make} =~ /^nmake\b/i and
-		! $self->can_run('nmake')
-	);
-
-	print "The required 'nmake' executable not found, fetching it...\n";
-
-	require File::Basename;
-	my $rv = $self->get_file(
-		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
-		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
-		local_dir => File::Basename::dirname($^X),
-		size      => 51928,
-		run       => 'Nmake15.exe /o > nul',
-		check_for => 'Nmake.exe',
-		remove    => 1,
-	);
-
-	die <<'END_MESSAGE' unless $rv;
-
--------------------------------------------------------------------------------
-
-Since you are using Microsoft Windows, you will need the 'nmake' utility
-before installation. It's available at:
-
-  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
-      or
-  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
-
-Please download the file manually, save it to a directory in %PATH% (e.g.
-C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
-that directory, and run "Nmake15.exe" from there; that will create the
-'nmake.exe' file needed by this module.
-
-You may then resume the installation process described in README.
-
--------------------------------------------------------------------------------
-END_MESSAGE
-
-}
-
-1;
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
deleted file mode 100644
index 3819d78..0000000
--- a/inc/Module/Install/WriteAll.pm
+++ /dev/null
@@ -1,40 +0,0 @@
-#line 1
-package Module::Install::WriteAll;
-
-use strict;
-use Module::Install::Base;
-
-use vars qw{$VERSION @ISA $ISCORE};
-BEGIN {
-	$VERSION = '0.79';
-	@ISA     = qw{Module::Install::Base};
-	$ISCORE  = 1;
-}
-
-sub WriteAll {
-	my $self = shift;
-	my %args = (
-		meta        => 1,
-		sign        => 0,
-		inline      => 0,
-		check_nmake => 1,
-		@_,
-	);
-
-	$self->sign(1)                if $args{sign};
-	$self->Meta->write            if $args{meta};
-	$self->admin->WriteAll(%args) if $self->is_admin;
-
-	$self->check_nmake if $args{check_nmake};
-	unless ( $self->makemaker_args->{PL_FILES} ) {
-		$self->makemaker_args( PL_FILES => {} );
-	}
-
-	if ( $args{inline} ) {
-		$self->Inline->write;
-	} else {
-		$self->Makefile->write;
-	}
-}
-
-1;

commit baecc51775cb8ba4eef5fb6657c6c0eb8f24f585
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Mar 3 00:45:28 2009 +0000

    Add install_share to modules that need it, and remove share/ from modules that don't
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@6531 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/Makefile.PL b/Makefile.PL
index 700c32c..9fbb346 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -12,4 +12,6 @@ recommends('Image::Info'); # for testing
 auto_install();
 tests(qw( t/*/t/*.t ));
 
+install_share;
+
 WriteAll;

commit a68c70158960726500216cb487dcefab735fede8
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Mar 26 03:00:04 2009 +0000

    Bump VERSION of previously-core'd modules, to higher than the core they were bundled with
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@6705 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 2107352..cd40237 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -4,7 +4,7 @@ use warnings;
 package Jifty::Plugin::Chart;
 use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 
-our $VERSION = '0.01';
+our $VERSION = '0.9';
 
 use Jifty::Plugin::Chart::Web;
 use Scalar::Util qw/ blessed /;

commit 14b52ef5d920c3ce61d2b0102c60bcfdd6ca29dc
Author: Yves Agostini <agostini at univ-metz.fr>
Date:   Wed Jun 10 08:29:25 2009 +0000

    ready to upload to cpan
     * fix Makefile all_from
     * add Changes and README
     * try to remove ex rights on excanvas.js
     * add NAMME section on GoogleViz.pm
    
    
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7215 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/Changes b/Changes
new file mode 100644
index 0000000..64f49d0
--- /dev/null
+++ b/Changes
@@ -0,0 +1,4 @@
+Revision history for Perl module Jifty::Plugin::Chart
+
+0.9  Wed, 10 Jun 2009 09:33:32 +0200
+    - original version for CPAN
diff --git a/Makefile.PL b/Makefile.PL
index 9fbb346..e74a9df 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -1,6 +1,6 @@
-use inc::Module::Install 0.46;
+use inc::Module::Install;
 name('Jifty-Plugin-Chart');
-version_from('lib/Jifty/Plugin/Chart.pm');
+all_from('lib/Jifty/Plugin/Chart.pm');
 
 requires('Jifty');
 recommends('Chart::Base');
diff --git a/README b/README
new file mode 100644
index 0000000..f05c9c0
--- /dev/null
+++ b/README
@@ -0,0 +1,96 @@
+NAME
+    Jifty::Plugin::Chart - A charting API for Jifty
+
+SYNOPSIS
+    In your config.yml:
+
+      Plugins:
+        - Chart: {}
+
+    In your Mason templates:
+
+      <% Jifty->web->chart(
+          type   => 'Bar',
+          width  => 400,
+          height => 300,
+          data   => [
+              [ '2004', '2005', '2006', '2007' ], # labels
+              [ 14,     15,     17,     22     ], # first data set
+              [ 22,     25,     20,     21     ], # second data set
+          ],
+      ) %>
+
+DESCRIPTION
+    CAUTION: This plugin is experimental. The API *will* change.
+
+    This plugin provides a charting API that can be used by Jifty
+    applications to build data visualizations without regard to the
+    underlying rendering mechanism.
+
+    As of this writing, the API is a barely veiled interface over Chart.
+    However, I intend to expand the interface to apply to something like
+    Maani's XML/SWF Charts or Imprise Javascript charts or even something
+    like OpenLaszlo (or something Open Source and Perl if I can find or
+    build such a thing in time).
+
+INTERFACE
+    By adding this method to the plugin configuration for your Jifty
+    application, you will cause Jifty::Web to inherit a new method, "chart",
+    which is the cornerstone of this API.
+
+    This method is described in Jifty::Plugin::Chart::Web and an example is
+    shown in the "SYNOPSIS" above.
+
+CONFIGURATION
+    Here is an example configuration for config.yml:
+
+      Plugins:
+        - Chart:
+            DefaultRenderer: PlotKit
+            PreloadRenderers:
+             - XMLSWF
+             - SimpleBars
+             - App::Renderer::Custom
+
+    The available options are:
+
+    DefaultRenderer
+        This is the name of the class to use as the default renderer.
+        Jifty::Plugin::Chart::Renderer::Chart is the current default, but
+        that could change in the future. It's recommended that you set this
+        to your preference.
+
+    PreloadRenderers
+        This is a list of other render classes to load during
+        initialization. If they are not loaded during initialization some
+        renderers may not work correctly the first time they are run because
+        they are not able to inform Jifty of the CSS or JS files they need
+        before that part of the page is already rendered. If you use the
+        "renderer" option of "chart" in Jifty::Plugin::Chart::Web, then you
+        should make sure any value you use is set here in the configuration
+        to make sure it works properly.
+
+METHODS
+  init
+    Adds the "chart" in Jifty::Plugin::Chart::Web method to Jifty::Web.
+
+  init_renderer
+      my $renderer = $chart_plugin->init_renderer($renderer_class)
+
+    This is a helper method that is used by the API to initialize the
+    renderer class. This is handled automatically so you probably shouldn't
+    use this.
+
+SEE ALSO
+    Jifty::Plugin, Jifty::Web, Jifty::Plugin::Chart::Renderer,
+    Jifty::Plugin::Chart::Renderer::Chart, Jifty::Plugin::Chart::View
+
+AUTHOR
+    Andrew Sterling Hanenkamp "<andrew.hanenkamp at boomer.com>"
+
+COPYRIGHT AND LICENSE
+    Copyright 2007 Boomer Consulting, Inc.
+
+    This is free software and may be modified and redistributed under the
+    same terms as Perl itself.
+
diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 662c93e..5e4700d 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -5,6 +5,10 @@ use base 'Jifty::Plugin::Chart::Renderer';
 
 use Jifty::JSON 'objToJson';
 
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::GoogleViz - chart renderer using Google Charts JS
+
 =head2 init
 
 We need to load Google's JS.

commit 9545b1b44baf00aa56668d47eaad06c3c0288719
Author: Yves Agostini <agostini at univ-metz.fr>
Date:   Fri Jun 26 12:04:39 2009 +0000

    * add dep on Jifty 0.90409 to use Jifty::Test::Dist
    * bump version number for cpan
    
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7279 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/Changes b/Changes
index 64f49d0..ab1cc28 100644
--- a/Changes
+++ b/Changes
@@ -1,4 +1,7 @@
 Revision history for Perl module Jifty::Plugin::Chart
 
+1.00 Fri, 26 Jun 2009 10:18:45 +0200
+    - add dep on Jifty 0.90409 to use Jifty::Test::Dist
+
 0.9  Wed, 10 Jun 2009 09:33:32 +0200
     - original version for CPAN
diff --git a/Makefile.PL b/Makefile.PL
index e74a9df..4c84139 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -2,7 +2,7 @@ use inc::Module::Install;
 name('Jifty-Plugin-Chart');
 all_from('lib/Jifty/Plugin/Chart.pm');
 
-requires('Jifty');
+requires('Jifty' => '0.90409');
 recommends('Chart::Base');
 recommends('GD');          # for a testing hack
 recommends('GD::Graph');
diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index cd40237..024ed14 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -4,7 +4,7 @@ use warnings;
 package Jifty::Plugin::Chart;
 use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 
-our $VERSION = '0.9';
+our $VERSION = '1.00';
 
 use Jifty::Plugin::Chart::Web;
 use Scalar::Util qw/ blessed /;

commit d75218cea18a46f7435bf42372d81c9a900d71c5
Author: Yves Agostini <agostini at univ-metz.fr>
Date:   Mon Jun 29 06:57:26 2009 +0000

    typo s/Chart::pie/Chart::Pie/ found by gregor Herrmann
    
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7281 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/t/TestApp-Plugin-Chart/t/chart.t b/t/TestApp-Plugin-Chart/t/chart.t
index 3a5640b..dad2ec0 100644
--- a/t/TestApp-Plugin-Chart/t/chart.t
+++ b/t/TestApp-Plugin-Chart/t/chart.t
@@ -4,8 +4,10 @@ use warnings;
 
 # XXX FIXME This is here to prevent a segfault on my machine during testing.
 #   -- sterling
+# typo: s/Chart::pie/Chart::Pie/ certainly fix this
+#   -- yves (found by gregor Herrmann)
 use Test::More;
-eval "use GD; use Chart::pie; 1";
+eval "use GD; use Chart::Pie; 1";
 if ($@) {
     plan skip_all => 'Chart is not installed.';
 }

commit 6082fca93a9d3dcd01c3f79b9909a805f847c91c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jul 10 16:23:13 2009 -0400

    Whether we have a label or not, include it as a <string> tag.  If there's no tag for the label, xmlswf will be off by one when it displays the data as it takes the first tag to be a label.
    
    This fixes the off by one issue r5850 introduced, but may break the issue r5850 was trying to fix.
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7305 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/View.pm b/lib/Jifty/Plugin/Chart/View.pm
index 27010ea..490a2ff 100644
--- a/lib/Jifty/Plugin/Chart/View.pm
+++ b/lib/Jifty/Plugin/Chart/View.pm
@@ -121,8 +121,8 @@ template 'chart/xmlswf' => sub {
         my $label = $args->{legend}[$i];
 
         push @{$chart{'chart_data'}{'row'}}, {
+            string => [ defined $label ? $label : {} ],
             number => $args->{data}[$i],
-            defined($label) ? (string => [ $label ]) : (),
         };
     }
 

commit 50e15cb88718a809c1c61833a054b534c0846185
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Jul 14 18:02:20 2009 -0400

    Charts are now able to accept options
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7316 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 5e4700d..9d0e625 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -32,9 +32,9 @@ sub render {
     my $chart_id = 'chart_' . Jifty->web->serial;
     my $chart_class = $self->chart_class;
     my $load_params = objToJson($self->load_params);
-    my $draw_params = objToJson($self->draw_params);
+    my $draw_params = objToJson($self->draw_params($args{options}));
     my $callback_name = 'callback_' . Jifty->web->serial;
-
+    
     Jifty->web->out(<< "JS_HEADER");
         <script type="text/javascript">
             google.load('visualization', 1, $load_params);
@@ -58,7 +58,7 @@ JS_FOOTER
             style="width: $args{width}; height: $args{height};"
             id="$chart_id"
         ></div>
-    });
+    }); #"
 
     return;
 }
diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm
index b52ad80..c01a180 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz/AnnotatedTimeline.pm
@@ -5,9 +5,12 @@ use base 'Jifty::Plugin::Chart::Renderer::GoogleViz';
 
 use constant packages_to_load => 'annotatedtimeline';
 use constant chart_class => 'google.visualization.AnnotatedTimeLine';
-use constant draw_params => {
-    displayAnnotations => "true",
-};
+
+sub draw_params {
+    my $self = shift;
+    my $opts = shift || {};
+    return { displayAnnotations => 'true', %$opts };
+}
 
 1;
 

commit 063c08d87a1bb095014d7b691a4abb68db6ff4aa
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Jul 14 18:25:17 2009 -0400

    Bump version for dependencies
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7317 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart.pm b/lib/Jifty/Plugin/Chart.pm
index 024ed14..02d53c6 100644
--- a/lib/Jifty/Plugin/Chart.pm
+++ b/lib/Jifty/Plugin/Chart.pm
@@ -4,7 +4,7 @@ use warnings;
 package Jifty::Plugin::Chart;
 use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 
-our $VERSION = '1.00';
+our $VERSION = '1.01';
 
 use Jifty::Plugin::Chart::Web;
 use Scalar::Util qw/ blessed /;

commit 27bfa4a8e895bf21e1f641102c09e60054d39a2a
Author: Yves Agostini <agostini at univ-metz.fr>
Date:   Thu Jul 16 12:54:15 2009 +0000

    add missing changelog for 1.01
    
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7329 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/Changes b/Changes
index ab1cc28..6adc688 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,9 @@
 Revision history for Perl module Jifty::Plugin::Chart
 
+1.01  Wed, 15 Jul 2009 10:48:45 +0200
+    - Charts are now able to accept options (trs)
+    - Labels for xmlswf (tom) 
+
 1.00 Fri, 26 Jun 2009 10:18:45 +0200
     - add dep on Jifty 0.90409 to use Jifty::Test::Dist
 

commit 0406403062b9d69c94c27ca171775c83aa4d1a4f
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 16 10:35:52 2009 -0400

    Same person :)
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7330 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/Changes b/Changes
index 6adc688..f04124d 100644
--- a/Changes
+++ b/Changes
@@ -2,7 +2,7 @@ Revision history for Perl module Jifty::Plugin::Chart
 
 1.01  Wed, 15 Jul 2009 10:48:45 +0200
     - Charts are now able to accept options (trs)
-    - Labels for xmlswf (tom) 
+    - Labels for xmlswf (trs) 
 
 1.00 Fri, 26 Jun 2009 10:18:45 +0200
     - add dep on Jifty 0.90409 to use Jifty::Test::Dist

commit 728cdf5b55abf4a7bf380cd43ae73d339ec59556
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 16 10:39:17 2009 -0400

    Better description too, while I'm at it
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7331 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/Changes b/Changes
index f04124d..90746e2 100644
--- a/Changes
+++ b/Changes
@@ -1,8 +1,8 @@
 Revision history for Perl module Jifty::Plugin::Chart
 
 1.01  Wed, 15 Jul 2009 10:48:45 +0200
-    - Charts are now able to accept options (trs)
-    - Labels for xmlswf (trs) 
+    - GoogleViz charts are now able to specify options (trs)
+    - Fix chart labels for xmlswf (trs) 
 
 1.00 Fri, 26 Jun 2009 10:18:45 +0200
     - add dep on Jifty 0.90409 to use Jifty::Test::Dist

commit 137a6b9f290f7e3db4ad79bfb97ebf2e6e896654
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 23 16:17:37 2009 -0400

    Fix a couple glaring issues with simple data encoding for Google charts
    
    * Multiple datasets were ignored and only the first was encoded (!)
    * Out of bounds indexes were compared and set to the length of the
      mappable array rather than the last index
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7344 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index d506e1c..09ca117 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -169,7 +169,7 @@ sub render {
         $url .= "&chld=" . join '', @{ $args{'codes'} };
         
         # We need to do simple encoding
-        $url .= "&chd=s:" . $self->_simple_encode_data( $args{'max_value'}, @{$args{'data'}} );
+        $url .= "&chd=s:" . $self->_simple_encode_data( $args{'max_value'}, $args{'data'} );
     }
     else {
         # Deal with out of range horizontal markers here by fixing our range
@@ -388,14 +388,17 @@ sub _simple_encode_data {
     my $i = 0;
     my $result = '';
     my @map = ('A'..'Z', 'a'..'z', 0..9);
-    for my $value ( @$data ) {
-        if ( looks_like_number($value) ) {
-            my $index = int($value / $maxes->[$i] * (@map - 1));
-            $index = 0 if $index < 0;
-            $index = @map if $index > @map;
-            $result .= $map[$index];
-        } else {
-            $result .= '_';
+    for my $set ( @$data ) {
+        $result .= ',' if $i; # if this isn't the first set
+        for my $value ( @$set ) {
+            if ( looks_like_number($value) ) {
+                my $index = int($value / $maxes->[$i] * $#map);
+                $index = 0 if $index < 0;
+                $index = $#map if $index > $#map;
+                $result .= $map[$index];
+            } else {
+                $result .= '_';
+            }
         }
         ++$i;
     }

commit 285cdcc147d90f212ddafd9228f999fe164c92fd
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 23 17:40:31 2009 -0400

    * Correctly handle label positions, esp. for multiple datasets
    * Allow the native Google chart types to be used
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7345 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 09ca117..9b13f74 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -61,6 +61,9 @@ sub render {
         geo                     => 't',
     );
 
+    # The Google chart types themselves are valid too
+    $types{$_} = $_ for values %types;
+
     # Make sure the type is ready to be used
     my $type = $types{ lc $args{type} } || undef;
 
@@ -281,8 +284,20 @@ sub render {
             $url .= "&chxs=" . join '|', @styles if @styles;
 
             # label positions
-            $url .= "&chxp=" . join ',', @{ $args{'positions'} }
-                if defined $args{'positions'};
+            if ( defined $args{'positions'} ) {
+                $url .= "&chxp=";
+                if ( ref $args{'positions'}->[0] eq 'ARRAY' ) {
+                    my @sets;
+                    my $idx = 0;
+                    for my $set ( @{ $args{'positions'} } ) {
+                        push @sets, join ',', $idx, @$set;
+                        $idx++;
+                    }
+                    $url .= join '|', @sets;
+                } else {
+                    $url .= join ',', 0, @{$args{'positions'}};
+                }
+            }
         }
     }
 

commit 069be51dae4a5d892ed111e27d9abaf579425e52
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 23 18:22:22 2009 -0400

    * Allow a choice between text encoding with data scaling or simple encoding
    * Fix a max_plus bug where it was subtracted from the maxes rather than added
    
    git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/plugins/Jifty-Plugin-Chart@7346 e84bef0a-9b06-0410-84ba-c4c9edb13aeb

diff --git a/lib/Jifty/Plugin/Chart/Renderer/Google.pm b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
index 9b13f74..5fcbcee 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/Google.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/Google.pm
@@ -28,6 +28,7 @@ Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
 sub render {
     my $self = shift;
     my %args = (
+        encoding  => 'text',
         width     => 200,
         height    => 100,
         labels    => [],
@@ -200,7 +201,7 @@ sub render {
         }
 
         my @min = map { $_ - $args{'min_minus'} } @{ $args{'min_value'} };
-        my @max = map { $_ - $args{'max_plus'}  } @{ $args{'max_value'} };
+        my @max = map { $_ + $args{'max_plus'}  } @{ $args{'max_value'} };
 
         # repeat if necessary
         push @min, ($min[-1]) x (@{ $args{'data'} } - @min);
@@ -228,10 +229,15 @@ sub render {
             ];
         }
 
-        # Let's do text encoding with data scaling
-        $url .= "&chd=t:" . join '|', map { join ',', @$_ } @data;
-
-        $url .= "&chds=" . join(',', mesh @min, @max);
+        if ( $args{'encoding'} eq 'simple' ) {
+            # We need to do simple encoding
+            $url .= "&chd=s:" . $self->_simple_encode_data( $args{'calculated_max'}, $args{'data'} );
+        }
+        else {
+            # Let's do text encoding with data scaling
+            $url .= "&chd=t:" . join '|', map { join ',', @$_ } @data;
+            $url .= "&chds=" . join(',', mesh @min, @max);
+        }
     }
 
     # Add a title

commit 7e08a04442a523c483d688c5dc2ff254b70891f8
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jan 7 18:10:05 2010 -0500

    Update JSON handling to use new Jifty::JSON

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index 9d0e625..ec3f010 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use base 'Jifty::Plugin::Chart::Renderer';
 
-use Jifty::JSON 'objToJson';
+use Jifty::JSON 'encode_json';
 
 =head1 NAME
 
@@ -31,8 +31,8 @@ sub render {
 
     my $chart_id = 'chart_' . Jifty->web->serial;
     my $chart_class = $self->chart_class;
-    my $load_params = objToJson($self->load_params);
-    my $draw_params = objToJson($self->draw_params($args{options}));
+    my $load_params = encode_json($self->load_params);
+    my $draw_params = encode_json($self->draw_params($args{options}));
     my $callback_name = 'callback_' . Jifty->web->serial;
     
     Jifty->web->out(<< "JS_HEADER");
@@ -207,7 +207,7 @@ sub encode_value {
         die "Can't handle the date '$value'";
     }
 
-    return objToJson($value);
+    return encode_json($value);
 }
 
 1;
diff --git a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
index 191c2b4..915c111 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
@@ -90,9 +90,9 @@ $div
 var plot = function() {
     var plotter = PlotKit.EasyPlot(
         "$args{type}",
-        @{[Jifty::JSON::objToJson( $args{options} )]},
+        @{[Jifty->web->escape(Jifty::JSON::encode_json( $args{options} ))]},
         \$("$chart_id"),
-        @{[Jifty::JSON::objToJson( $args{data} )]}
+        @{[Jifty->web->escape(Jifty::JSON::encode_json( $args{data} ))]}
     );
 };
 YAHOO.util.Event.onAvailable( "$chart_id", plot );

commit 0676cc664fa6d186c47e3e1e19e1a591d3c88520
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jan 13 17:25:42 2010 -0500

    Load JS from a schemeless URL so HTTP/HTTPS works

diff --git a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
index ec3f010..99347b5 100644
--- a/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
+++ b/lib/Jifty/Plugin/Chart/Renderer/GoogleViz.pm
@@ -18,7 +18,7 @@ We need to load Google's JS.
 sub init {
     my $self = shift;
 
-    Jifty->web->add_external_javascript("http://www.google.com/jsapi");
+    Jifty->web->add_external_javascript("//www.google.com/jsapi");
 }
 
 =head2 render

commit 8f654e620172dc2b67df803ea3bf644c5229c68a
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jan 15 14:22:00 2010 -0500

    Use jQuery, not Prototype to get dimensions

diff --git a/share/web/static/js/chart_img_behaviour.js b/share/web/static/js/chart_img_behaviour.js
index 46ec316..ba944cb 100644
--- a/share/web/static/js/chart_img_behaviour.js
+++ b/share/web/static/js/chart_img_behaviour.js
@@ -6,7 +6,6 @@
 
 Behaviour.register({
     'img.chart': function(e) {
-        var dim = Element.getDimensions(e);
         var url = e.src;
 
         var path  = url;
@@ -23,8 +22,8 @@ Behaviour.register({
             }
         }
 
-        query.set('width', dim.width + 'px');
-        query.set('height', dim.height + 'px');
+        query.set('width', jQuery(e).width() + 'px');
+        query.set('height', jQuery(e).height() + 'px');
 
         url = path + '?' + query.toQueryString();
 

-----------------------------------------------------------------------


More information about the Jifty-commit mailing list