Custom Legend Rendering

1,691 views
Skip to first unread message

John L. Cheng

unread,
Sep 23, 2009, 8:55:35 PM9/23/09
to jqplot-users
A quick search on the mailing list showed some common questions about
legends: Hiding legends for some series, making legends react to
clicks, etc., One way to handle these requests is with a jqPlot plugin
for rendering legends. I needed to do this my own project, so I figure
I'd share my experience with everyone.

First, make a copy jqplot.tableLegendRenderer.js. just call it
jqplot.customLegendRenderer.js for now. Make sure you change any
references of TableLegendRenderer to CustomLegendRenderer.

Then, include your new plugin in your html:

<script type="text/javascript" src="jqplot.customLegendRenderer.js"></
script>

Then, in your javascript, register your new plugin as the renderer for
legends:

$j.jqplot.TableLegendRenderer = $j.jqplot.CustomLegendRenderer;

Now jqPlot will use your plugin to render legends. I needed some post-
processing to each of the "row" elements in the legends table. So I
added this code to the end of the draw method:

if ( this.rowHook ) {
// if a function called "rowHook" is specified as a legend option,
use it to process the legend row
this.rowHook.call(this.rowHook, elem, label);
}

Then for my jqPlot options object, I have:

var optionObj = {
legend:{
show:true,
rowHook: function(elem, label) {
// adds a right aligned checkbox that calls a javascript
when clicked
elem.append('<input type="checkbox" onclick="alert
(\''+label+'\')" style="float:right"/>');
}
}
}

Voila, now I have clickable legends.

Chris, I think something like this in the core code might be useful, I
would love to submit a patch for TableLegendRenderer if it makes
sense. However, I'm not sure if my hack fits with the design. For
example, it might be more consistent to have a
$.jqplot.transformLegendRowHooks that applies a transformation to each
row (so that trend lines are also handled). What do you think?

Chris Leonello

unread,
Sep 23, 2009, 10:02:32 PM9/23/09
to jqplot...@googlegroups.com
Great example John!

I llke your idea of adding a hook that is called after each legend row
is drawn. Something like $.jqplot.postLegendAddRowHooks (called at
the end of the legend's addrow() method).

It might be more useful if I just trigger events when there is a click
on a legend row. Like fire a 'jqplotLegenRowClick' event and pass the
row index, element and label to any event handler registered to the
trigger.

Or maybe I should do both. Writing event handling code can turn off
many js programmers.

--
Chris Leonello

Jeppe Nejsum Madsen

unread,
Sep 24, 2009, 4:19:12 AM9/24/09
to jqplot...@googlegroups.com
Chris Leonello <chris.l...@gmail.com> writes:

> Great example John!
>
> I llke your idea of adding a hook that is called after each legend row
> is drawn. Something like $.jqplot.postLegendAddRowHooks (called at
> the end of the legend's addrow() method).
>
> It might be more useful if I just trigger events when there is a click
> on a legend row. Like fire a 'jqplotLegenRowClick' event and pass the
> row index, element and label to any event handler registered to the
> trigger.

Only for this specific use case. In general, having the addRowHook will
allow for people to do all kind of weird stuff like adding other dom
elements etc.

> Or maybe I should do both. Writing event handling code can turn off
> many js programmers.

I think that would be nice. As written earlier, I would like to see
general event handling when chart stuff is clicked since this seems like
a common scenario. Otoh, having hooks called at render time gives a lot
of flexibility for customization.

/Jeppe

John L. Cheng

unread,
Sep 24, 2009, 11:59:51 AM9/24/09
to jqplot-users
> > Or maybe I should do both. Writing event handling code can turn off
> > many js programmers.
>
> I think that would be nice. As written earlier, I would like to see
> general event handling when chart stuff is clicked since this seems like
> a common scenario. Otoh, having hooks called at render time gives a lot
> of flexibility for customization.
>
> /Jeppe

From my point of view (not being greedy), a
$.jqplot.postLegendAddRowHooks
will be sufficient due to its flexibility. A event handling system
will be
great, but I would need access to mouseover, mouseout, and probably a
lot
of other events I can't think of at the moment. Even then, I might
just end
up using a postLegendAddRowhook to bind my own events, i.e.,

$.jqplot.postLegendAddRowHooks.push(function(idx, row, label) {
$(row).bind('mouseover mouseleave', function(event) {
toggleFocusOnSeries(this);
});
$(row).bind('click', function(event) {
applyGroovyEffects(this);
});
})

John L. Cheng

unread,
Sep 24, 2009, 3:11:17 PM9/24/09
to jqplot-users
Here's a version of jqplot.tableLegendRenderer.js that supports
postLegendAddRowHooks

http://groups.google.com/group/jqplot-users/web/customLegendRenderer.zip

Just unzip in the jqplot/src to see it (or place in the same directory
as jquery.jqplot.js and jquery-1.3.2.js).

The only real change is adding:

for ( var j = 0; j < $.jqplot.postLegendAddRowHooks.length; j++ ) {
$.jqplot.postLegendAddRowHooks[j].call(this, tr, label);
}

Which calls the hooks on the "tr" element of the legends, which
in theory gives the user the option to manipulate the entire legends
row.

The html just shows how the hook can be used:

$.jqplot.postLegendAddRowHooks.push( function(row, label) {
var elem = $(row.find('td')[1]);
elem.wrapInner('<div style="float:left"></div>');
cb = $('<input type="checkbox" style="float:right;"/>');
var series = $.grep(jqPlotData,function(elem){return
elem.jqOptions.label==label;})[0]
if ( series.jqOptions.showLine ) {
cb.attr('checked','checked');
} else {
cb.removeAttr('checked');
}
cb.bind('click',function(event){toggleSeries(series)});
elem.append(cb);
} );

This is kind of painful since it is only exposing the "row" element,
which puts the burden on the postLegendAddRowHooks to know how to
manipulate the rows. Perhaps it might be better to change the
TableLegendRenderer.addrow() method to give the users more control
over how each row is rendered.

Chris Leonello

unread,
Sep 24, 2009, 5:02:28 PM9/24/09
to jqplot...@googlegroups.com
> ... Perhaps it might be better to change the
> TableLegendRenderer.addrow() method to give the users more control
> over how each row is rendered

Yeah, or maybe a just a hook at the end of legend draw to manipulate
the whole legend before the element is put into the dom?

--
Chris Leonello

John L. Cheng

unread,
Sep 24, 2009, 8:35:47 PM9/24/09
to jqplot-users
On Sep 24, 2:02 pm, Chris Leonello <chris.leone...@gmail.com> wrote:
> > ... Perhaps it might be better to change the
> > TableLegendRenderer.addrow() method to give the users more control
> > over how each row is rendered
>
> Yeah, or maybe a just a hook at the end of legend draw to manipulate  
> the whole legend before the element is put into the dom?
>

I re-read my message and I realized it was a little confusing. When I
said that my implementation was "painful", I meant that I assumed each
row (tr element) contained 2 "td" elements, and that the label is in
the second "td" element. By passing a "tr" element into the
postLegendAddRowHook, I required the users to know how jqPlot draws
the legends table. It this bad? It's probably not too big of a
problem....

I think a good feature to add is something similar to flot's
labelFormatter:

labelFormatter: function(label, series) {
// series is the series object for the label
return '<a href="#' + label + '">' + label + '</a>';
}

It give the user the ability to manipulate just the element where the
label is rendered (the elem.text()/elem.html() call in addrow()). I
think it will take care of a good number of cases without adding
a lot to the jqPlot code.

Or there could be hooks for how the label is rendered, how each row is
rendered, and how the entire table is rendered... but that seems like
overkill.

Chris Leonello

unread,
Sep 24, 2009, 9:26:01 PM9/24/09
to jqplot...@googlegroups.com
I like the labelFormatter idea. Abstract the actual formatting of the
label out of the tableLegendRenderer. Handling of the color swatch
needs to be addressed in all this too.

I wouldn't want to add a lot of hooks. For that extreme it seems more
efficient to just create a new renderer. There's only 55 lines of
code that do anything in the table renderer (with the exception of the
pack method to position the legend). Just subclass it and override
the draw and addrow methods if you want more control.


--
Chris Leonello
Reply all
Reply to author
Forward
0 new messages