Skip to content
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 label style option #5622

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 35 additions & 19 deletions docs/configuration/legend.md
Expand Up @@ -7,7 +7,7 @@ The legend configuration is passed into the `options.legend` namespace. The glob

| Name | Type | Default | Description
| -----| ---- | --------| -----------
| `display` | `Boolean` | `true` | is the legend shown
| `display` | `Boolean` | `true` | Whether the legend is shown
| `position` | `String` | `'top'` | Position of the legend. [more...](#position)
| `fullWidth` | `Boolean` | `true` | Marks that this box should take the full width of the canvas (pushing down other boxes). This is unlikely to need to be changed in day-to-day use.
| `onClick` | `Function` | | A callback that is called when a click event is registered on a label item
Expand All @@ -28,15 +28,28 @@ The legend label configuration is nested below the legend configuration using th

| Name | Type | Default | Description
| -----| ---- | --------| -----------
| `boxWidth` | `Number` | `40` | width of coloured box
| `fontSize` | `Number` | `12` | font size of text
| `fontStyle` | `String` | `'normal'` | font style of text
| `boxWidth` | `Number` | `40` | Width of coloured box
| `fontSize` | `Number` | `12` | Font size of text
| `fontStyle` | `String` | `'normal'` | Font style of text
| `fontColor` | `Color` | `'#666'` | Color of text
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family of legend text.
| `padding` | `Number` | `10` | Padding between rows of colored boxes.
| `generateLabels` | `Function` | | Generates legend items for each thing in the legend. Default implementation returns the text + styling for the color box. See [Legend Item](#legend-item-interface) for details.
| `filter` | `Function` | `null` | Filters legend items out of the legend. Receives 2 parameters, a [Legend Item](#legend-item-interface) and the chart data.
| `usePointStyle` | `Boolean` | `false` | Label style will match corresponding point style (size is based on fontSize, boxWidth is not used in this case).
| `style` | `String` | | Style of the label item. [more...](#label-style)

### Label Style

Possible label style values are:
* `'box'`
* `'line'`
* `'point'`

`'box'` will draw a box using the background color, border width and color of the corresponding element.
`'line'` will draw a line using the corresponding line style.
`'point'` will make the label style match the corresponding point style (size is based on `fontSize`, `boxWidth` is not used in this case).

If not set, the `'line'` style is used for line elements, and the `'box'` style for other elements.

## Legend Item Interface

Expand All @@ -53,26 +66,29 @@ Items passed to the legend `onClick` function are the ones returned from `labels
// If true, this item represents a hidden dataset. Label will be rendered with a strike-through effect
hidden: Boolean,

// For box border. See https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap
// For line border. See https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap
lineCap: String,

// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
// For line border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
lineDash: Array[Number],

// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset
// For line border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset
lineDashOffset: Number,

// For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
// For line border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
lineJoin: String,

// Width of box border
// Width of box border or line
lineWidth: Number,

// Stroke style of the legend box
strokeStyle: Color
// Stroke style of the legend box or line
strokeStyle: Color,

// Point style of the legend box (only used if usePointStyle is true)
pointStyle: String
// Point style of the legend box (only used if style is 'point')
pointStyle: String,

// Style of the legend box
style: String
}
```

Expand All @@ -91,7 +107,7 @@ var chart = new Chart(ctx, {
fontColor: 'rgb(255, 99, 132)'
}
}
}
}
});
```

Expand All @@ -107,7 +123,7 @@ function(e, legendItem) {
var meta = ci.getDatasetMeta(index);

// See controller.isDatasetVisible comment
meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;

// We hid a dataset ... rerender the chart
ci.update();
Expand All @@ -118,7 +134,7 @@ Lets say we wanted instead to link the display of the first two datasets. We cou

```javascript
var defaultLegendClickHandler = Chart.defaults.global.legend.onClick;
var newLegendClickHandler = function (e, legendItem) {
var newLegendClickHandler = function(e, legendItem) {
var index = legendItem.datasetIndex;

if (index > 1) {
Expand All @@ -128,11 +144,11 @@ var newLegendClickHandler = function (e, legendItem) {
let ci = this.chart;
[ci.getDatasetMeta(0),
ci.getDatasetMeta(1)].forEach(function(meta) {
meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
});
ci.update();
}
};
});

var chart = new Chart(ctx, {
type: 'line',
Expand Down
4 changes: 4 additions & 0 deletions src/controllers/controller.doughnut.js
Expand Up @@ -50,13 +50,17 @@ defaults._set('doughnut', {
var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
var labelOpts = chart.options.legend.labels;
var style = labelOpts.style;

return {
text: label,
fillStyle: fill,
strokeStyle: stroke,
lineWidth: bw,
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
// `usePointStyle` is deprecated. To be removed at version 3
style: !style && labelOpts.usePointStyle ? 'point' : style,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding support for the older option


// Extra data used for toggling the correct item
index: i
Expand Down
4 changes: 4 additions & 0 deletions src/controllers/controller.polarArea.js
Expand Up @@ -64,13 +64,17 @@ defaults._set('polarArea', {
var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
var labelOpts = chart.options.legend.labels;
var style = labelOpts.style;

return {
text: label,
fillStyle: fill,
strokeStyle: stroke,
lineWidth: bw,
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
// `usePointStyle` is deprecated. To be removed at version 3
style: !style && labelOpts.usePointStyle ? 'point' : style,

// Extra data used for toggling the correct item
index: i
Expand Down
47 changes: 35 additions & 12 deletions src/plugins/plugin.legend.js
Expand Up @@ -47,6 +47,19 @@ defaults._set('global', {
generateLabels: function(chart) {
var data = chart.data;
return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
var labelOpts = chart.options.legend.labels;
var style = labelOpts.style;
var type = chart.getDatasetMeta(i).type;

if (!style) {
// `usePointStyle` is deprecated. To be removed at version 3
if (labelOpts.usePointStyle) {
style = 'point';
} else if (type === 'line' || type === 'radar') {
style = 'line';
}
}

return {
text: dataset.label,
fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
Expand All @@ -58,6 +71,7 @@ defaults._set('global', {
lineWidth: dataset.borderWidth,
strokeStyle: dataset.borderColor,
pointStyle: dataset.pointStyle,
style: style,

// Below is extra data used for toggling the datasets
datasetIndex: i
Expand All @@ -83,13 +97,14 @@ defaults._set('global', {
});

/**
* Helper function to get the box width based on the usePointStyle option
* @param labelopts {Object} the label options on the legend
* Helper function to get the box width based on the style option
* @param legendItem {Object} the legend item
* @param labelOpts {Object} the label options on the legend
* @param fontSize {Number} the label font size
* @return {Number} width of the color box area
*/
function getBoxWidth(labelOpts, fontSize) {
return labelOpts.usePointStyle ?
function getBoxWidth(legendItem, labelOpts, fontSize) {
return legendItem.style === 'point' ?
fontSize * Math.SQRT2 :
labelOpts.boxWidth;
}
Expand Down Expand Up @@ -247,7 +262,7 @@ var Legend = Element.extend({
ctx.textBaseline = 'top';

helpers.each(me.legendItems, function(legendItem, i) {
var boxWidth = getBoxWidth(labelOpts, fontSize);
var boxWidth = getBoxWidth(legendItem, labelOpts, fontSize);
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;

if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
Expand Down Expand Up @@ -277,7 +292,7 @@ var Legend = Element.extend({
var itemHeight = fontSize + vPadding;

helpers.each(me.legendItems, function(legendItem, i) {
var boxWidth = getBoxWidth(labelOpts, fontSize);
var boxWidth = getBoxWidth(legendItem, labelOpts, fontSize);
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;

// If too tall, go to new column
Expand Down Expand Up @@ -346,11 +361,10 @@ var Legend = Element.extend({
ctx.fillStyle = fontColor; // render in correct colour
ctx.font = labelFont;

var boxWidth = getBoxWidth(labelOpts, fontSize);
var hitboxes = me.legendHitBoxes;

// current position
var drawLegendBox = function(x, y, legendItem) {
var drawLegendBox = function(x, y, legendItem, boxWidth) {
if (isNaN(boxWidth) || boxWidth <= 0) {
return;
}
Expand All @@ -371,7 +385,7 @@ var Legend = Element.extend({
ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
}

if (opts.labels && opts.labels.usePointStyle) {
if (legendItem.style === 'point') {
// Recalculate x and y for drawPoint() because its expecting
// x and y to be center of figure (instead of top left)
var radius = fontSize * Math.SQRT2 / 2;
Expand All @@ -381,6 +395,14 @@ var Legend = Element.extend({

// Draw pointStyle as legend symbol
helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
} else if (legendItem.style === 'line') {
// Draw line as legend symbol
if (!isLineWidthZero) {
ctx.beginPath();
ctx.moveTo(x, y + fontSize / 2);
ctx.lineTo(x + boxWidth, y + fontSize / 2);
ctx.stroke();
}
} else {
// Draw box as legend symbol
if (!isLineWidthZero) {
Expand All @@ -391,7 +413,7 @@ var Legend = Element.extend({

ctx.restore();
};
var fillText = function(x, y, legendItem, textWidth) {
var fillText = function(x, y, legendItem, boxWidth, textWidth) {
var halfFontSize = fontSize / 2;
var xLeft = boxWidth + halfFontSize + x;
var yMiddle = y + halfFontSize;
Expand Down Expand Up @@ -426,6 +448,7 @@ var Legend = Element.extend({

var itemHeight = fontSize + labelOpts.padding;
helpers.each(me.legendItems, function(legendItem, i) {
var boxWidth = getBoxWidth(legendItem, labelOpts, fontSize);
var textWidth = ctx.measureText(legendItem.text).width;
var width = boxWidth + (fontSize / 2) + textWidth;
var x = cursor.x;
Expand All @@ -443,13 +466,13 @@ var Legend = Element.extend({
cursor.line++;
}

drawLegendBox(x, y, legendItem);
drawLegendBox(x, y, legendItem, boxWidth);

hitboxes[i].left = x;
hitboxes[i].top = y;

// Fill the actual label
fillText(x, y, legendItem, textWidth);
fillText(x, y, legendItem, boxWidth, textWidth);

if (isHorizontal) {
cursor.x += width + (labelOpts.padding);
Expand Down
18 changes: 12 additions & 6 deletions test/specs/global.defaults.tests.js
Expand Up @@ -128,21 +128,24 @@ describe('Default Configs', function() {
hidden: false,
index: 0,
strokeStyle: '#000',
lineWidth: 2
lineWidth: 2,
style: undefined
}, {
text: 'label2',
fillStyle: 'green',
hidden: false,
index: 1,
strokeStyle: '#000',
lineWidth: 2
lineWidth: 2,
style: undefined
}, {
text: 'label3',
fillStyle: 'blue',
hidden: true,
index: 2,
strokeStyle: '#000',
lineWidth: 2
lineWidth: 2,
style: undefined
}];
expect(chart.legend.legendItems).toEqual(expected);
});
Expand Down Expand Up @@ -244,21 +247,24 @@ describe('Default Configs', function() {
hidden: false,
index: 0,
strokeStyle: '#000',
lineWidth: 2
lineWidth: 2,
style: undefined
}, {
text: 'label2',
fillStyle: 'green',
hidden: false,
index: 1,
strokeStyle: '#000',
lineWidth: 2
lineWidth: 2,
style: undefined
}, {
text: 'label3',
fillStyle: 'blue',
hidden: true,
index: 2,
strokeStyle: '#000',
lineWidth: 2
lineWidth: 2,
style: undefined
}];
expect(chart.legend.legendItems).toEqual(expected);
});
Expand Down
26 changes: 26 additions & 0 deletions test/specs/global.deprecations.tests.js
Expand Up @@ -6,6 +6,32 @@ describe('Deprecations', function() {
expect(Chart.layoutService).toBe(Chart.layouts);
});
});

describe('Legend Labels: usePointStyle option', function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simonbrunel should we put this inside a describe('Version 2.8.0', ...) block?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already inside 2.8.0 deprecations

it('should use the style property', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
label: '',
data: []
}],
labels: []
},
options: {
legend: {
labels: {
usePointStyle: true
}
}
}
});

expect(chart.legend.legendItems[0].style).toEqual('point');
expect(chart.legend.legendHitBoxes[0].height).toBeCloseToPixel(12);
expect(chart.legend.legendHitBoxes[0].width).toBeCloseToPixel(23);
});
});
});

describe('Version 2.7.0', function() {
Expand Down