Added usePointStyle option to label boxes

- Closes #2252
- Allows label boxes to match the shape(pointStyle) of the corresponding data.
* Removed unused varaible from legend's draw()
This commit is contained in:
Shayne Linhart 2016-07-05 20:08:29 -06:00 committed by Tanner Linsley
parent 3cdd66ca58
commit 0dccc85e3a
7 changed files with 157 additions and 105 deletions

View File

@ -132,6 +132,7 @@ fontColor | Color | "#666" | Font color inherited from global configuration
fontFamily | String | "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif" | Font family inherited from global configuration
padding | Number | 10 | Padding between rows of colored boxes
generateLabels: | Function | `function(chart) { }` | Generates legend items for each thing in the legend. Default implementation returns the text + styling for the color box. See [Legend Item](#chart-configuration-legend-item-interface) for details.
usePointStyle | Boolean | false | Label style will match corresponding point style (size is based on fontSize, boxWidth is not used in this case).
#### Legend Item Interface
@ -165,6 +166,9 @@ Items passed to the legend `onClick` function are the ones returned from `labels
// Stroke style of the legend box
strokeStyle: Color
// Point style of the legend box (only used if usePointStyle is true)
pointStyle: String
}
```

View File

@ -4,6 +4,7 @@
var Chart = require('./core/core.js')();
require('./core/core.helpers')(Chart);
require('./core/core.canvasHelpers')(Chart);
require('./core/core.element')(Chart);
require('./core/core.animation')(Chart);
require('./core/core.controller')(Chart);

View File

@ -0,0 +1,104 @@
"use strict";
module.exports = function(Chart) {
// Global Chart canvas helpers object for drawing items to canvas
var helpers = Chart.canvasHelpers = {};
helpers.drawPoint = function(ctx, pointStyle, radius, x, y) {
var type, edgeLength, xOffset, yOffset, height, size;
if (typeof pointStyle === 'object') {
type = pointStyle.toString();
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2);
return;
}
}
if (isNaN(radius) || radius <= 0) {
return;
}
switch (pointStyle) {
// Default includes circle
default:
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
break;
case 'triangle':
ctx.beginPath();
edgeLength = 3 * radius / Math.sqrt(3);
height = edgeLength * Math.sqrt(3) / 2;
ctx.moveTo(x - edgeLength / 2, y + height / 3);
ctx.lineTo(x + edgeLength / 2, y + height / 3);
ctx.lineTo(x, y - 2 * height / 3);
ctx.closePath();
ctx.fill();
break;
case 'rect':
size = 1 / Math.SQRT2 * radius;
ctx.beginPath();
ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
break;
case 'rectRot':
size = 1 / Math.SQRT2 * radius;
ctx.beginPath();
ctx.moveTo(x - size, y);
ctx.lineTo(x, y + size);
ctx.lineTo(x + size, y);
ctx.lineTo(x, y - size);
ctx.closePath();
ctx.fill();
break;
case 'cross':
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y - radius);
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
case 'crossRot':
ctx.beginPath();
xOffset = Math.cos(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x - xOffset, y + yOffset);
ctx.lineTo(x + xOffset, y - yOffset);
ctx.closePath();
break;
case 'star':
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y - radius);
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
xOffset = Math.cos(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x - xOffset, y + yOffset);
ctx.lineTo(x + xOffset, y - yOffset);
ctx.closePath();
break;
case 'line':
ctx.beginPath();
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
case 'dash':
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
}
ctx.stroke();
};
};

View File

@ -52,6 +52,7 @@ module.exports = function(Chart) {
lineJoin: dataset.borderJoinStyle,
lineWidth: dataset.borderWidth,
strokeStyle: dataset.borderColor,
pointStyle: dataset.pointStyle,
// Below is extra data used for toggling the datasets
datasetIndex: i
@ -201,7 +202,11 @@ module.exports = function(Chart) {
ctx.textBaseline = 'top';
helpers.each(me.legendItems, function(legendItem, i) {
var width = labelOpts.boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
var boxWidth = labelOpts.usePointStyle ?
fontSize * Math.sqrt(2) :
labelOpts.boxWidth;
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
totalHeight += fontSize + (labelOpts.padding);
lineWidths[lineWidths.length] = me.left;
@ -229,7 +234,11 @@ module.exports = function(Chart) {
var itemHeight = fontSize + vPadding;
helpers.each(me.legendItems, function(legendItem, i) {
var itemWidth = labelOpts.boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
// If usePointStyle is set, multiple boxWidth by 2 since it represents
// the radius and not truly the width
var boxWidth = labelOpts.usePointStyle ? 2 * labelOpts.boxWidth : labelOpts.boxWidth;
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
// If too tall, go to new column
if (currentColHeight + itemHeight > minSize.height) {
@ -274,10 +283,10 @@ module.exports = function(Chart) {
var me = this;
var opts = me.options;
var labelOpts = opts.labels;
var globalDefault = Chart.defaults.global;
var lineDefault = globalDefault.elements.line;
var legendWidth = me.width;
var lineWidths = me.lineWidths;
var globalDefault = Chart.defaults.global,
lineDefault = globalDefault.elements.line,
legendWidth = me.width,
lineWidths = me.lineWidths;
if (opts.display) {
var ctx = me.ctx,
@ -302,6 +311,10 @@ module.exports = function(Chart) {
// current position
var drawLegendBox = function(x, y, legendItem) {
if (isNaN(boxWidth) || boxWidth <= 0) {
return;
}
// Set the ctx for the box
ctx.save();
@ -317,9 +330,22 @@ module.exports = function(Chart) {
ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash));
}
// Draw the box
ctx.strokeRect(x, y, boxWidth, fontSize);
ctx.fillRect(x, y, boxWidth, fontSize);
if (opts.labels && opts.labels.usePointStyle) {
// Recalulate 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;
var offSet = radius / Math.SQRT2;
var centerX = x + offSet;
var centerY = y + offSet;
// Draw pointStyle as legend symbol
Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
}
else {
// Draw box as legend symbol
ctx.strokeRect(x, y, boxWidth, fontSize);
ctx.fillRect(x, y, boxWidth, fontSize);
}
ctx.restore();
};
@ -347,7 +373,7 @@ module.exports = function(Chart) {
} else {
cursor = {
x: me.left + labelOpts.padding,
y: me.top,
y: me.top + labelOpts.padding,
line: 0
};
}
@ -355,13 +381,15 @@ module.exports = function(Chart) {
var itemHeight = fontSize + labelOpts.padding;
helpers.each(me.legendItems, function(legendItem, i) {
var textWidth = ctx.measureText(legendItem.text).width,
width = boxWidth + (fontSize / 2) + textWidth,
width = labelOpts.usePointStyle ?
fontSize + (fontSize / 2) + textWidth :
boxWidth + (fontSize / 2) + textWidth,
x = cursor.x,
y = cursor.y;
if (isHorizontal) {
if (x + width >= legendWidth) {
y = cursor.y += fontSize + (labelOpts.padding);
y = cursor.y += itemHeight;
cursor.line++;
x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
}

View File

@ -42,108 +42,16 @@ module.exports = function(Chart) {
var radius = vm.radius;
var x = vm.x;
var y = vm.y;
var type, edgeLength, xOffset, yOffset, height, size;
if (vm.skip) {
return;
}
if (typeof pointStyle === 'object') {
type = pointStyle.toString();
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2);
return;
}
}
if (isNaN(radius) || radius <= 0) {
return;
}
ctx.strokeStyle = vm.borderColor || defaultColor;
ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth);
ctx.fillStyle = vm.backgroundColor || defaultColor;
switch (pointStyle) {
// Default includes circle
default:
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
break;
case 'triangle':
ctx.beginPath();
edgeLength = 3 * radius / Math.sqrt(3);
height = edgeLength * Math.sqrt(3) / 2;
ctx.moveTo(x - edgeLength / 2, y + height / 3);
ctx.lineTo(x + edgeLength / 2, y + height / 3);
ctx.lineTo(x, y - 2 * height / 3);
ctx.closePath();
ctx.fill();
break;
case 'rect':
size = 1 / Math.SQRT2 * radius;
ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
break;
case 'rectRot':
size = 1 / Math.SQRT2 * radius;
ctx.beginPath();
ctx.moveTo(x - size, y);
ctx.lineTo(x, y + size);
ctx.lineTo(x + size, y);
ctx.lineTo(x, y - size);
ctx.closePath();
ctx.fill();
break;
case 'cross':
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y - radius);
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
case 'crossRot':
ctx.beginPath();
xOffset = Math.cos(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x - xOffset, y + yOffset);
ctx.lineTo(x + xOffset, y - yOffset);
ctx.closePath();
break;
case 'star':
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y - radius);
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
xOffset = Math.cos(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x - xOffset, y + yOffset);
ctx.lineTo(x + xOffset, y - yOffset);
ctx.closePath();
break;
case 'line':
ctx.beginPath();
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
case 'dash':
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
}
ctx.stroke();
Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y);
}
});
};

View File

@ -52,6 +52,7 @@ describe('Legend block tests', function() {
label: 'dataset3',
borderWidth: 10,
borderColor: 'green',
pointStyle: 'crossRot',
data: []
}],
labels: []
@ -68,6 +69,7 @@ describe('Legend block tests', function() {
lineJoin: undefined,
lineWidth: undefined,
strokeStyle: undefined,
pointStyle: undefined,
datasetIndex: 0
}, {
text: 'dataset2',
@ -79,6 +81,7 @@ describe('Legend block tests', function() {
lineJoin: 'miter',
lineWidth: undefined,
strokeStyle: undefined,
pointStyle: undefined,
datasetIndex: 1
}, {
text: 'dataset3',
@ -90,6 +93,7 @@ describe('Legend block tests', function() {
lineJoin: undefined,
lineWidth: 10,
strokeStyle: 'green',
pointStyle: 'crossRot',
datasetIndex: 2
}]);
});

View File

@ -164,6 +164,9 @@ describe('Point element tests', function() {
}, {
name: 'setFillStyle',
args: ['rgba(0, 255, 0)']
}, {
name: 'beginPath',
args: []
}, {
name: 'fillRect',
args: [10 - 1 / Math.SQRT2 * 2, 15 - 1 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2]