The If Works This dirt was a building before

Announcing Bluff, plus a few other project updates

I put out a few new software releases over the last few days, and thought I’d gather them into one post rather than lots of little ones. Let’s start with the biggest.

Update: for those wishing to contribute bug reports (as failing test cases) and patches, Bluff is hosted on Github.

In what can only be described as a colossal case of wheel reinvention, I’ve ported Geoffrey Grosenbach’s Gruff graphing library to JavaScript and called it Bluff. My major gripe with current JavaScript graphing libraries is that they either have too large a dependency requirement (i.e. they depend on one of the large JS frameworks or on Flash), or they are large themselves and kinda unwieldy to use. Bluff only requires a copy of JS.Class to work, and that’s only 2kb on the wire. Bluff itself is 8kb, plus 4kb for ExCanvas if you want your graphs to work in IE. I actually went to the trouble of writing a compiler that would take JS.Class-based code and generate ‘raw’ JavaScript from it, but the output from that ended up being twice the size of the JS.Class version and didn’t run fast enough to make up the difference so I’m keeping the JS.Class requirement for now.

Why did I do this? About a year ago I wrote an app called DesalData for my then employer, and it has a section that lets you search for water plants from a database of ~15,000 entries using a stack of filters and graph data from them in real time, and it lets you save those graphs to your machine (you can’t see it without a login, sorry). I was doing the site in Rails so I figured I’d use Gruff, but we started it off on shared hosting, so I didn’t want the server to deal with generating all those images on top of the database load. So, I had to generate graphs on the client but keep the ability to generate identical graphs on the server for download if required. So, I stuck with Gruff and reimplemented it on the client — the client’s machine handles most of the rendering so the server’s workload is a little lighter and the app is more responsive.

(Yes, 15,000 entries doesn’t sound like much but the data model for this thing involves some very wide tables and lots of joins. Getting this to perform decently involved patching Rails to support multiple SQL calculations on one query, but that’s another story.)

Anyway, this release is a complete rewrite (my original version for DesalData is closed-source and incomplete), and is pretty much equivalent to Gruff 0.3.4, the current release. A few graph types (the ones that use images) are missing, but they’re kinda niche and I’d rather keep this lightweight. Give it a whirl and let me know what you think.

The second announcement concerns JS.Class, in that version 2.0.1 is out. It’s a very minor bug fix release and should be a drop-in replacement for 2.0.0, so go ahead and upgrade straight away. The fixes include use of strict equality (=== rather than ==) throughout, and a fix in Module#include that fixes this very rare edge case:

M = new JS.Module({
  extend: {
    included: function(base) {
      // Redefine include() on the including class
      base.extend({ include: function() { /* ... */ } });
    }
  }
});

C = new JS.Class({
  include: M,
  foo: function() { /* ... */ }
});

2.0.1 makes sure that the M.included hook’s redefinition of base.include takes effect before the class’ own methods are added to it — like I said, edge case but I ran into it recently to now it’s fixed.

My final announcements are a couple of new Ruby gems that I’m using to build my JavaScript projects these days. oyster is a really simple commandline option parser, inspired by Trollop but with some subtle differences, and jake is an automated build tool that merges and compresses JavaScript files using PackR and lets you use ERB snippets and helper functions in source files. I won’t say much about either, as I’m only really releasing them so my colleagues can build my projects on their own machines, but do feel free to consult their READMEs and gem install either to have a play around. Any bug reports are, as always, gratefully received.


44 Comments

I’m flattered!

Great work. It’s a great time for client-side graphs now that more browsers are beginning to support the canvas element.

Posted by Geoffrey Grosenbach on 16 September 2008 @ 5pm

Bluff is AWESOME.

I did notice one bug (which I worked around by clever use of minimum_value/maximum_value) that appears to be a rounding issue.

See how hippos datapoints hover around 1.0, and giraffes around 2.0? The graphs show this correctly, but the y-axis labels seem to have been rounded, making it appear that everything is shifted 0.5 (hippos to 1.5 and giraffes to 2.5):

var g = new Bluff.Line(‘animals’, 600);
g.theme_keynote();
g.minimum_value = 0.5;
g.maximum_value = 4.5;
g.markers = 4;
g.title = ‘Animals’;

g.data(‘giraffes’, [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0,
2.0, 2.0, 2.08, 2.04, 1.77, 2.08, 2.12]);
g.data(‘monkeys’, [3.97, 2.24, 2.81, 3.2, 1.11, 2.98,
2.33, 1.89, 3.82, 1.34, 1.87, 1.49, 1.54, 2.45, 1.97]);
g.data(‘hippos’, [0.88, 1.00, 1.00, 1.0, 1.0, 1.0, 1.0, 1.06,
1.17, 1.02, 1, 1.01, 1.14, 1.09]);

g.draw();

Posted by Jack on 16 September 2008 @ 10pm

Very cool, I’ve been looking for something like this for ages. I too had the problem that rendering graphs serverside was taking up way too much resources.

Props for a job well done, this beats all the other JS graphing scripts due to its relatively small size.

One suggestion: mouseover values at each “plot” of the graph?

Posted by marcel on 17 September 2008 @ 10pm

Actually I just found out that you can’t set the height of the graph area? This, unfortunately makes bluff useless to me because it doesn’t integrate into my layouts at all (i need wide, thin graphs, i.e. 800×300).

PLEASE add a height attribute!

Posted by marcel on 18 September 2008 @ 12am

Actually, you can set the height, I just seem to have forgotten to put it in the docs… I’ll update ASAP. You can set the height like so:

g = new Bluff.Line(‘example’, ’800×300′);

Posted by James Coglan on 18 September 2008 @ 6am

Oh, how excellent! Thanks!

Posted by marcel on 18 September 2008 @ 11pm

Found another bug:

var g = new Bluff.Pie(‘example’, 400);
g.theme_37signals();
g.title = ‘My Graph’;

g.data(“Apples”, 0);
g.data(“Oranges”, 2);
g.data(“Watermelon”, 8);
g.data(“Peaches”, 10);
g.draw();

Results in a pie chart with peaches filling up the whole circle. The zero for apples is being interpreted as an empty value at line 436:

data_points = data_points || [];

This empty data set is causing _sums_for_pie to calculate a NaN total sum at line 1487.

Workaround using brackets: g.data(“Apples”, [0]);

This Ruby idiom doesn’t travel so well in the javascript world when you are using dynamic parameters, unfortunately.

Posted by Dylan B on 18 September 2008 @ 11pm

Other than that, great little library :) I was just looking for a good graphing library and stumbled on this; thanks for saving me the work of having to write it myself.

Posted by Dylan B on 18 September 2008 @ 11pm

Here are some suggestions for new features, I hope you’ll implement at least a few of them ;)

-option to show dots on “area” graph!
-option for a top-line on “area” graph (basically overlay line graph on area graph)
-option for smaller dots
-option for thinner lines on “line” graph
-option for vertical grid (y axis grid)
-option for “dashed” grid lines
-equal grid thickness (some are thicker and a bit blurry)
-mouseover y values

Posted by marcel on 19 September 2008 @ 1am

Two bugs that are quite serious:

1) Y-axis and x-axis labels are for some reason offset by ~5 pixels to in my graph, not on par with the grid-lines.

2) When drawing another graph in the same canvas, the old y-axis and x-axis labels are NOT wiped and the new ones are just put right on top, creating an ugly text mess (it becomes unreadable).

Tested in Firefox 3 and Safari, it doesn’t not seem to be browser related.

Fix please!

Posted by marcel on 21 September 2008 @ 1am

Marcel,

1) Could you send me a link to the page you’re running the graph on so I can take a look? I can’t fix a bug if I can’t reproduce it.

2) You need to call clear() on the original graph to remove all the text nodes. There’s no way for the second graph to know that there’s already a graph on that canvas, so you need to clear the first one yourself.

Posted by James Coglan on 21 September 2008 @ 9am

Hi,

1) My apologies, turns out this was layout related. I had a padding-top on the canvas and the entire graph except the y-axis text was moved down.

2) I did just that, I called it right after the object was declared, but it had no effect, so maybe g.clear() is broken? Also, the “graph” part of the canvas IS cleared by default, why wouldn’t the text be cleared as well before it is rendered?

Posted by marcel on 21 September 2008 @ 5pm

Could you post the code you’re using? My suspicion is that you’re clearing the wrong object. Basically, each graph instance stores a list of references to the text nodes it creates, so that they can be removed. Any other graph objects, and the canvases they use, have no references to the text nodes and so cannot do anything about them (though I may make an attempt to work around this in a future release). You need to do something like this:

// First graph
var g = new Bluff.Line('example', 400);
// data, settings...
g.draw();

// Clear, then create a new graph
g.clear();
g = new Bluff.StackedBar('example', 400);
// continue as usual

Posted by James Coglan on 21 September 2008 @ 5pm

The problem is, I can’t reference the object again because I am dynamically drawing graphs (draw one, on click of a button draw a new one).

Yes I could probably work around this by using the window[] object to store a reference to it, but thats way too complicated considering that I just want to wipe the canvas!

Here’s what I’m using (simplified):

//draw graph
function drawGraph(interval) {
var g = new Bluff.Area(‘graph’, ’800×180′);

g.title = ‘Stock Performance’;
g.hide_title = ‘True’;
g.hide_legend = ‘True’;
g.marker_font_size = ’11′;
g.minimum_value = ’0′;
g.no_data_message = ‘No data’;

g.data(‘Stock Value’, hourly.reverse());

g.labels = x_labels;
g.draw();
}

Buttons => drawGraph(interval);

It would be very helpful if Bluff just cleared the entire canvas before drawing (or if there was a function/option to do that).

Posted by marcel on 21 September 2008 @ 5pm

As I explained, this is currently not possible. The text is created using a stack of divs, they are not part of the canvas and need to be managed separately. The only object that can remove them is the one that created them, since that’s where the references are stored. Therefore, you’re going to need to keep a reference to the original graph. Your situation is easily fixed without creating extra global vars:

var Graph = {
    draw: function(interval) {
        if (this._currentGraph) this._currentGraph.clear();

        var g = new Bluff.Area('graph', '800x180');
        this._currentGraph = g;

        g.title = 'Stock Performance';
        g.hide_title = 'True';
        g.hide_legend = 'True';
        g.marker_font_size = '11';
        g.minimum_value = '0';
        g.no_data_message = 'No data';

        g.data('Stock Value', hourly.reverse());

        g.labels = x_labels;
        g.draw();
    }
}

Just call Graph.draw() and if a graph already exists, it will be cleared.

Posted by James Coglan on 21 September 2008 @ 6pm

Unfortunately that does not seem to work:

http://www.n8t.net/test.html

clear() is never called. I’ve never used self-made javascript classes before, so maybe I implemented it wrong?

Posted by marcel on 21 September 2008 @ 8pm

Thank you for pointing that out, that worked great!

I didn’t know you could assign a function/class to a variable and still call it normally, you javascript wiz you :)

Posted by marcel on 21 September 2008 @ 10pm

Hi James,

I’m currently working on a Drupal module for Bluff integration and while working on it I noticed a bit of an issue with the way Bluff injects the labels onto the page.

The labels are being injected outside of the canvas tags they apply to and position themselves via position: absolute. The problem this causes is that if you have any dynamic elements (such as collapsible fieldsets) or the users uses one of the many browser zoom functions the labels will no longer be in the correct place.

Is it at all possible that you inject the labels inside the canvases and just a combination of position: absolute and position: relative so that if the canvas moves so does the labels?

I’d be more than happy to help with this if you do so require it.

Thanks for the good work so far.

Cheers,
Decipher.

Posted by Decipher on 24 September 2008 @ 3am

I just tried that approach and the text does not display if added inside the canvas tag. I think I might be able to solve this by wrapping the canvas in an extra div to hold the text nodes, so they’re positioned relative to the wrapper rather than relative to the whole page.

I’ll post something here if and when I fix this.

Posted by James Coglan on 24 September 2008 @ 8am

Are you planning on releasing future versions that incorporate some of the features that were suggested?

And maybe a bug tracker and possibly releasing the source code (i.e. the file in readable format).

This project has SO much potential!

Posted by Jordan on 25 September 2008 @ 4am

I’ve already fixed some of the reported bugs from the first release, and there will probably be a bug fix release in the next week or so. I’ll post here when that happens. Some feature requests depend on whether I can add them to Gruff as well, since Bluff is supposed to be a faithful mirror of Gruff.

There’s no official bug tracker, but the project is on Github. Feel free to fork it and add failing test cases and fixes.

http://github.com/jcoglan/bluff

There’s a readable version in the download (bluff-src.js), and you can also browse the source through the git repo.

Posted by James Coglan on 25 September 2008 @ 8am

Hi,
i got a hidden Div on my page the canvas is in that. so when i building graph than the labels created calculate wrong offset.
How to create them after loading the page

Posted by AeUser on 8 October 2008 @ 11am

Hi,

Here is the link,

http://www.kailashyadav.com/bluff.php

My problem is that i am using two div’s and i am hiding them on clicking. The problem is that the Labels are not cleared when i am hiding the div in which the canvas is shown.

Posted by AeUser on 8 October 2008 @ 12pm

Hey James,

Did you able to review my problem? Actually i have to use these maps?if this problem can be resolved then tell me?
we can do it one way if you can tell how to add a classname to all the Div labels

Posted by AeUser on 10 October 2008 @ 5am

Hey,
Bluff is really nice. I like it.
Here’s a little suggestion:
add this line to Bluff.Renderer._text_node:
div.style.zIndex = this._canvas.style.zIndex;

this would be really usefull in some designs ;)

Posted by Ralf on 14 October 2008 @ 7am

Hey James,
i also figured out a little bug.
If a label for a data set is a longer string, the legend items will overlap.
The error occurred at a canvas size of 400 px and a label length of about 33 chars.

Regards

Posted by Ralf on 15 October 2008 @ 8am

Wonderful to see a JS implementation for charting! This has been a long time in the making and I was somewhat baulking at using Flex or Google Chart to undertake simple charts…

A quick question, I noticed a reference to “background_image” in the bluff-min.js. Is is possible to use an image as the background for a chart?

Regards!

Posted by aidee on 24 October 2008 @ 12am

@aidee Background images are not yet implemented. I’m not sure if they’re supported by ExCanvas, but it might make sense to implement them for more capable browsers. I’ll put it on my list for the next release. In the meantime, if you find any bugs, don’t hesitate to get in touch.

Posted by James Coglan on 27 October 2008 @ 9pm

@Ralf As of the latest release, text rendered by Bluff is contained in divs with a class of “bluff-text”. Use that in your stylesheet if you need to modify presentation of the text labels.

Posted by James Coglan on 27 October 2008 @ 9pm

Hi there!

I just discovered JS.Class last night and I am totally impressed by your work. This is one of the best if not the best Javascript libraries I have seen. I have started using it right away.

The best thing is that is totally encapsulated and doesn’t interfere with other libraries, so it can be used with Prototype, jQuery or even Mootools. I just love it.

Also, from what I have seen, Bluff is very promising.

Posted by Eneko Alonso on 29 November 2008 @ 4am

Great work! Amazing.

Did you think about using http://typeface.neocracy.org/ for the text? It would make the images copy-able, it would also allow people to use custom typography.

Do you see a future in Javascript for something like:
http://flare.prefuse.org/

I checked out some physics and it looks totally possible, see:
http://blobsallad.se/

Posted by Patrick Keenan on 9 December 2008 @ 6pm

Slick program, but found a bug… I wrote a function to pull data out of a HTML table, and when you click to highlight a row it will re-draw a graph. The problem is that I cannot get the graph to draw at all in IE 7 or 8 (FIREFOX works like a charm). I have created a simplified HTML page to narrow down the problem, it is recreated it here:
http://dawgged.com/bluff/index.html
It looks like IE just does not like running Bluff.Line out of a function? Any suggestions? -Thanks

Posted by Neil on 23 January 2009 @ 8pm

The problem (I think — if this doesn’t work let me know) is the following:

window.onload = foo();

This does not register the function foo to run on load, instead it calls foo immediately and assigns the result on window.onload. Instead, you want this:

window.onload = foo;

This means that foo will not be called immediately but will be called on page load. That’s effectively what you’re doing with the last example:

window.onload = function() { /* ... */ };

You need to *assign* a function to window.onload rather than calling said function.

Posted by James Coglan on 24 January 2009 @ 1am

Hi,

There is a problem with the labels staying in an absolute position when the graph is placed inside another div block. I think this problem was mentioned before by Decipher above. I tried to wrap the canvas in an extra div and the problem still persists.

Please do let me know if there is a fix existing for this problem.

Thanks !

Ram

Posted by Ram on 27 January 2009 @ 5am

James,

I have a question for you regarding Bluff licensing. Please email me.

Regards,
RWey

Posted by RWey on 28 January 2009 @ 4pm

One suggestion…on occasion, I was running into the problem where the marker_label on the y axis would show a number like 1.239999999999999999 which is ugly…a simple change you could put in would be this:

marker_label = marker_label.toFixed(2);

after the line:
marker_label = index * this._increment + this.minimum_value;

obviously the 2 could be a configurable variable but this way it ensures that there are no strange elongated numbers when dealing with points that are close to each other.

Love what you did btw.

Posted by Adam on 6 February 2009 @ 11pm

To all who’ve reported badly formatted y-axis labels: a patch has now landed for this issue and will be rolled into the next release. Keep your eyes on this blog for further announcements.

Posted by James Coglan on 22 February 2009 @ 10am

hi There,

I am having a problem getting the graph to display in IE. It works fine in google chrome but for some reason is not working in IE 6 or 7. Any advice?

Posted by Andy on 1 September 2009 @ 12pm

@Andy, Have you included the excanvas.js library in your page? That script is required to make canvas work in IE. Also, make sure you wail until after window.onload fires to begin constructing graphs (see Neil’s problem and my follow-up).

Posted by James Coglan on 1 September 2009 @ 12pm

James,

I have the same problem that Neil had(cannot load graphs within ie) but i got confused when i saw your answer to this problem. Where should i place that function? In the controller, inside de view? Could you be more specific?
Best regards,
John

Posted by John on 22 October 2009 @ 10am

Can I put two canvases on the same page with a different line graph in each? I have tried this and the first canvas is blank but th second canvas appears just fine. Thanks.

Posted by Stephen on 18 November 2009 @ 11am

Hi
I like the graph. It is very nice. I am using v0.3.6. I couldn’t see a “y_axis_label “I set the property g.y_axis_label=”some text”, It is not displaying. But It allocates some left margin space for the y axis label. How can I display the Y axis label ?. x_axis_label is working fine.

Thank you

Posted by Surendran on 26 November 2009 @ 9pm

This should be fine, just make sure you use unique IDs and variable names for each one. e.g.

<canvas id="first" width="400" height="300"></canvas>
<canvas id="second" width="400" height="300"></canvas>

<script type="text/javascript">
(function() {
    var g = new Bluff.Line('first', '400x300');
    // ...

    var h = new Bluff.Line('second', '400x300');
    // ...
})();
</script>

Posted by James Coglan on 27 November 2009 @ 2am

Hi
I added .toFixed(2) to prevent the totilp displaying like “.9999999″

‘+f.toFixed(2)+’

hope this working fine.

Posted by Surendran on 27 November 2009 @ 4pm

Leave a Comment