New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add legend for line charts #177

Closed
wants to merge 13 commits into
base: master
from

Conversation

Projects
None yet
@ngallagher87

ngallagher87 commented Jul 11, 2013

Line chart legends

This pull request adds an option to show a legend for a line chart. The legend is optional, but if enabled requires the title field to be defined in the dataset:

Example dataset with showLegend

var data = {
    labels : ["January","February","March","April","May","June","July"],
    datasets : [
        {
            fillColor : "rgba(220,220,220,0.5)",
            strokeColor : "rgba(220,220,220,1)",
            pointColor : "rgba(220,220,220,1)",
            pointStrokeColor : "#fff",
            data : [65,59,90,81,56,55,40],
            //Optional: Add this if you want to show a legend
            title : "Foo"
        },
        {
            fillColor : "rgba(151,187,205,0.5)",
            strokeColor : "rgba(151,187,205,1)",
            pointColor : "rgba(151,187,205,1)",
            pointStrokeColor : "#fff",
            data : [28,48,40,19,96,27,100],
            //Optional: Add this if you want to show a legend
            title : "Bar"
        }
    ]
}

This produces something like this:

The pull request also includes updates to documentation and samples.

@Wikunia

This comment has been minimized.

Show comment
Hide comment
@Wikunia

Wikunia Jul 11, 2013

Nice idea but it is not working for long titles because the gap isn't considering the title.length.

Wikunia commented Jul 11, 2013

Nice idea but it is not working for long titles because the gap isn't considering the title.length.

@ngallagher87

This comment has been minimized.

Show comment
Hide comment
@ngallagher87

ngallagher87 Jul 12, 2013

@Wikunia good call - I've added text width to the calculations so the legend adjusts to long text:

Example 1

Example 2

ngallagher87 commented Jul 12, 2013

@Wikunia good call - I've added text width to the calculations so the legend adjusts to long text:

Example 1

Example 2

@Wikunia

This comment has been minimized.

Show comment
Hide comment
@Wikunia

Wikunia Jul 12, 2013

It's me again :D
You can test your code with three labels and the second one is a short one.
Then the labels are overlapping because you save in the var textwidth only the last textwidth.
I think this work:

var gap = 1; // instead of textWidth = 1;

if (config.showLegend) {
    ctx.textAlign = 'left';
    var space = 25 * i,
        x = yAxisPosX, //instead of 60
        y = 5,
            sqrSize = 10;
        ctx.rect(x + gap + space + 2, y-sqrSize/2, sqrSize, sqrSize);
    ctx.fill();
    ctx.fillText(data.datasets[i].title, x + gap + space + sqrSize+5, y);
        // save the current gap between new label and yAxisPosX
    gap = gap+ctx.measureText(data.datasets[i].title).width;  
}

Wikunia commented Jul 12, 2013

It's me again :D
You can test your code with three labels and the second one is a short one.
Then the labels are overlapping because you save in the var textwidth only the last textwidth.
I think this work:

var gap = 1; // instead of textWidth = 1;

if (config.showLegend) {
    ctx.textAlign = 'left';
    var space = 25 * i,
        x = yAxisPosX, //instead of 60
        y = 5,
            sqrSize = 10;
        ctx.rect(x + gap + space + 2, y-sqrSize/2, sqrSize, sqrSize);
    ctx.fill();
    ctx.fillText(data.datasets[i].title, x + gap + space + sqrSize+5, y);
        // save the current gap between new label and yAxisPosX
    gap = gap+ctx.measureText(data.datasets[i].title).width;  
}
@ngallagher87

This comment has been minimized.

Show comment
Hide comment
@ngallagher87

ngallagher87 Jul 12, 2013

That worked great!

I also added some logic to wrap legends if they're going to render off canvas. If they are, it resets a bunch of x pos modifiers, and increments y by a sane amount.

I also increased my test cases to try and be more thorough:

Long and short names with wrap

More normal usecase

Longer names names with wrap

Very long names with 2 lines of wrap

When it gets down to multiple lines of legends, things start to overlap with the chart, and that's less than ideal. But the user could also adjust this by using smaller titles, or making the canvas width larger.

The line wrapping of legends was to cover the edge case - in most uses it's pretty easy to fit 2 - 7 succinct legend titles on the top line without intruding on the graph.

Padding around the legends might be a good idea to prevent the legend from overlapping max graph values. I might play with that this weekend.

ngallagher87 commented Jul 12, 2013

That worked great!

I also added some logic to wrap legends if they're going to render off canvas. If they are, it resets a bunch of x pos modifiers, and increments y by a sane amount.

I also increased my test cases to try and be more thorough:

Long and short names with wrap

More normal usecase

Longer names names with wrap

Very long names with 2 lines of wrap

When it gets down to multiple lines of legends, things start to overlap with the chart, and that's less than ideal. But the user could also adjust this by using smaller titles, or making the canvas width larger.

The line wrapping of legends was to cover the edge case - in most uses it's pretty easy to fit 2 - 7 succinct legend titles on the top line without intruding on the graph.

Padding around the legends might be a good idea to prevent the legend from overlapping max graph values. I might play with that this weekend.

@Wikunia

This comment has been minimized.

Show comment
Hide comment
@Wikunia

Wikunia Jul 12, 2013

Good work!
Sometimes I like interactivity in the legend so click on a label and the Chart.js only show that relevant data.
For this I use:
https://github.com/bebraw/Chart.js.legend and modify it a bit.

Wikunia commented Jul 12, 2013

Good work!
Sometimes I like interactivity in the legend so click on a label and the Chart.js only show that relevant data.
For this I use:
https://github.com/bebraw/Chart.js.legend and modify it a bit.

@vonloxley

This comment has been minimized.

Show comment
Hide comment
@vonloxley

vonloxley Jul 16, 2013

For my firefox (14.0.1) I needed to move this function up to the beginning of the block.

vonloxley commented on Chart.js in 8c18b2c Jul 16, 2013

For my firefox (14.0.1) I needed to move this function up to the beginning of the block.

This comment has been minimized.

Show comment
Hide comment
@ngallagher87

ngallagher87 Jul 16, 2013

Owner

Thanks for the info - I've added a commit (9c91cf9) to master that moves this function declaration up before it gets called.

I tested in Firefox 22 and it works - I don't have access to a version of Firefox 14 but it should be fixed there as well.

Owner

ngallagher87 replied Jul 16, 2013

Thanks for the info - I've added a commit (9c91cf9) to master that moves this function declaration up before it gets called.

I tested in Firefox 22 and it works - I don't have access to a version of Firefox 14 but it should be fixed there as well.

@joetime

This comment has been minimized.

Show comment
Hide comment
@joetime

joetime Jul 19, 2013

Are there still open issues here? I'd be happy to help

joetime commented Jul 19, 2013

Are there still open issues here? I'd be happy to help

@mintuz

This comment has been minimized.

Show comment
Hide comment
@mintuz

mintuz Aug 5, 2013

Why just add a legend for a line chart and not other types? Also getting an error of "Uncaught TypeError: Cannot read property 'width' of undefined.

mintuz commented Aug 5, 2013

Why just add a legend for a line chart and not other types? Also getting an error of "Uncaught TypeError: Cannot read property 'width' of undefined.

@ngallagher87

This comment has been minimized.

Show comment
Hide comment
@ngallagher87

ngallagher87 Aug 6, 2013

Initially I was just proving the idea out - so I focused it on line graphs. I can look into adding it for other chart types as well.

Also getting an error of "Uncaught TypeError: Cannot read property 'width' of undefined.

Can you give me some more information on this? I'd love to fix it but it's not occurring for me using the line.html sample. I tested in Chrome and Firefox (latest) and couldn't reproduce.

I've enabled issue tracking for my fork, so you can create bugs/feature requests here and I'll address them when I can.

ngallagher87 commented Aug 6, 2013

Initially I was just proving the idea out - so I focused it on line graphs. I can look into adding it for other chart types as well.

Also getting an error of "Uncaught TypeError: Cannot read property 'width' of undefined.

Can you give me some more information on this? I'd love to fix it but it's not occurring for me using the line.html sample. I tested in Chrome and Firefox (latest) and couldn't reproduce.

I've enabled issue tracking for my fork, so you can create bugs/feature requests here and I'll address them when I can.

@devianpctek

This comment has been minimized.

Show comment
Hide comment
@devianpctek

devianpctek Oct 19, 2013

This is great, I just added the exactly same code you use here to the bar chart and works perfect, perhaps you should update yours
top500

devianpctek commented Oct 19, 2013

This is great, I just added the exactly same code you use here to the bar chart and works perfect, perhaps you should update yours
top500

ngallagher87 added some commits Oct 19, 2013

Adds legends to bar charts
This centralizes the show legend code into a function that can be called easily for multiple charts. This adds legends for barcharts if showLegend: true
@ngallagher87

This comment has been minimized.

Show comment
Hide comment
@ngallagher87

ngallagher87 Oct 19, 2013

@devianpctek - I took your suggestion and added legends to bar charts as well. I centralized the drawLegend() code into a function that is now called from both line and bar charts. This way it reduces a bit of code duplication.

Thanks for the idea!

ngallagher87 commented Oct 19, 2013

@devianpctek - I took your suggestion and added legends to bar charts as well. I centralized the drawLegend() code into a function that is now called from both line and bar charts. This way it reduces a bit of code duplication.

Thanks for the idea!

Update Chart.min.js
Updated minified file
@cvan

This comment has been minimized.

Show comment
Hide comment
@cvan

cvan Jan 22, 2014

this looks awesome - would love to see this landed! is this waiting on an additional review?

cvan commented Jan 22, 2014

this looks awesome - would love to see this landed! is this waiting on an additional review?

@ngallagher87

This comment has been minimized.

Show comment
Hide comment
@ngallagher87

ngallagher87 Jan 22, 2014

@cvan unsure, the more reviews the better :) I'm unsure of the status of a merge - this repository has a lot of open pull requests (56 at the moment) so this is probably buried under some of those.

ngallagher87 commented Jan 22, 2014

@cvan unsure, the more reviews the better :) I'm unsure of the status of a merge - this repository has a lot of open pull requests (56 at the moment) so this is probably buried under some of those.

@rept

This comment has been minimized.

Show comment
Hide comment
@rept

rept Mar 27, 2014

Seems really nice!

rept commented Mar 27, 2014

Seems really nice!

@FVANCOP

This comment has been minimized.

Show comment
Hide comment
@FVANCOP

FVANCOP Mar 28, 2014

Title, Label, Legend, Annotations, Axis title, unit etc... All those things are integrated (for all graph types) in the alternative version (which is downcompatible with Chart.js) available at https://github.com/FVANCOP/ChartNew.js

See demo : http://www.favomo.be/graphjs

FVANCOP commented Mar 28, 2014

Title, Label, Legend, Annotations, Axis title, unit etc... All those things are integrated (for all graph types) in the alternative version (which is downcompatible with Chart.js) available at https://github.com/FVANCOP/ChartNew.js

See demo : http://www.favomo.be/graphjs

@mneil

This comment has been minimized.

Show comment
Hide comment
@mneil

mneil Jul 23, 2014

It would be nice to complete this and merge it. The documentation hints at a legend with "legendTemplate" being an available setting for line charts but a pull from master does not seem to generate anything.

new Chart(ctx).Line(data,{
      legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].lineColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
    });

Using just the examples on the docs this does not work. And no legend appears on any of the charts on the docs.

Ok, I dug through the source and for anyone else that wants to generate a legend you need to do something like:

var chart = new Chart(ctx).Line(data, options);
document.getElementById('legend').innerHTML = chart.generateLegend();

And since no css is included something like below to get you started.

.line-legend{
  min-height: 1em;
  list-style: none;
  margin: 1em 0;
  padding: 2em 2em 2em 1em;
  border: 1px solid #efefef;
  display: inline-block;
  background: rgba(85, 169, 203, 0.1);
}
.line-legend li span{
  display: inline-block;
  width: 2em;
  height: 1em;
  margin: 0 .5em 0 0
}

mneil commented Jul 23, 2014

It would be nice to complete this and merge it. The documentation hints at a legend with "legendTemplate" being an available setting for line charts but a pull from master does not seem to generate anything.

new Chart(ctx).Line(data,{
      legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].lineColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
    });

Using just the examples on the docs this does not work. And no legend appears on any of the charts on the docs.

Ok, I dug through the source and for anyone else that wants to generate a legend you need to do something like:

var chart = new Chart(ctx).Line(data, options);
document.getElementById('legend').innerHTML = chart.generateLegend();

And since no css is included something like below to get you started.

.line-legend{
  min-height: 1em;
  list-style: none;
  margin: 1em 0;
  padding: 2em 2em 2em 1em;
  border: 1px solid #efefef;
  display: inline-block;
  background: rgba(85, 169, 203, 0.1);
}
.line-legend li span{
  display: inline-block;
  width: 2em;
  height: 1em;
  margin: 0 .5em 0 0
}
@nnnick

This comment has been minimized.

Show comment
Hide comment
@nnnick

nnnick Jul 24, 2014

Member

@mneil yep you're solution is good - legends are most accessible as HTML and not just drawn onto a canvas as an image, as it lets users select & copy text etc.

I'm sure you can imagine trying to build configuration options to style all of those is sort of building a rendering and layout engine for text content within canvas, which would add considerable bloat and complexity. HTML & CSS is a much more straightforward option here.

I haven't included CSS by design, the html structure is quite trivial, and changeable as a template string to cater for using your existing css classes etc.

If you think the documentation around the generateLegend method could do woith some extra clarity (http://www.chartjs.org/docs/#advanced-usage-prototype-methods), feel free to submit a PR for that (https://github.com/nnnick/Chart.js/blob/master/docs/06-Advanced.md#generatelegend).

Going to close this now as it's pull requesting version 0.x, and the functionality is available in the new beta versions.

Member

nnnick commented Jul 24, 2014

@mneil yep you're solution is good - legends are most accessible as HTML and not just drawn onto a canvas as an image, as it lets users select & copy text etc.

I'm sure you can imagine trying to build configuration options to style all of those is sort of building a rendering and layout engine for text content within canvas, which would add considerable bloat and complexity. HTML & CSS is a much more straightforward option here.

I haven't included CSS by design, the html structure is quite trivial, and changeable as a template string to cater for using your existing css classes etc.

If you think the documentation around the generateLegend method could do woith some extra clarity (http://www.chartjs.org/docs/#advanced-usage-prototype-methods), feel free to submit a PR for that (https://github.com/nnnick/Chart.js/blob/master/docs/06-Advanced.md#generatelegend).

Going to close this now as it's pull requesting version 0.x, and the functionality is available in the new beta versions.

@nnnick nnnick closed this Jul 24, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment