Split legend and title block functionality into 2 separate blocks. This allows the title to be on the top of the chart while the legend is on the bottom

This commit is contained in:
Evert Timberg
2015-12-06 10:20:38 -05:00
parent eaf183352c
commit 792ee587ae
8 changed files with 237 additions and 108 deletions

View File

@@ -72,11 +72,11 @@
responsive: true,
legend: {
position: 'top',
title: {
display: true,
text: 'Our 3 Favorite Datasets'
}
},
title: {
display: true,
text: 'Our 3 Favorite Datasets'
}
}
});

View File

@@ -107,11 +107,11 @@
responsive: true,
legend: {
position: 'top',
title: {
display: true,
text: 'Our Favorite Datasets'
}
},
title: {
display: true,
text: 'Our Favorite Datasets'
}
}
};

View File

@@ -61,11 +61,7 @@
options: {
responsive: true,
legend: {
position: 'top',
title: {
display: true,
text: 'Our 4 Favorite Datasets'
}
position: 'bottom',
},
hover: {
mode: 'label'
@@ -85,6 +81,10 @@
labelString: 'Value'
}
}]
},
title: {
display: true,
text: 'Our 4 Favorite Datasets'
}
}
};

View File

@@ -62,10 +62,10 @@
responsive: true,
legend: {
position: 'top',
title: {
display: true,
text: 'Our Favorite Dataset'
}
},
title: {
display: true,
text: 'Our Favorite Dataset'
},
scale: {
ticks: {

View File

@@ -58,13 +58,13 @@
options: {
legend: {
position: 'top',
title: {
display: true,
text: 'Our 3 Favorite Datasets'
}
},
title: {
display: true,
text: 'Our 3 Favorite Datasets'
},
scale: {
reverse: true,
reverse: false,
ticks: {
beginAtZero: true
}

View File

@@ -59,7 +59,7 @@
this.ensureScalesHaveIDs();
this.buildOrUpdateControllers();
this.buildScales();
this.buildLegends();
this.buildSurroundingItems();
this.updateLayout();
this.resetElements();
this.initToolTip();
@@ -170,18 +170,26 @@
Chart.scaleService.addScalesToLayout(this);
},
buildLegends: function() {
if (!this.options.legend) {
return;
buildSurroundingItems: function() {
if (this.options.title) {
this.titleBlock = new Chart.Title({
ctx: this.chart.ctx,
options: this.options.title,
chart: this
});
Chart.layoutService.addBox(this, this.titleBlock);
}
this.legend = new Chart.Legend({
ctx: this.chart.ctx,
options: this.options.legend,
chart: this,
});
if (this.options.legend) {
this.legend = new Chart.Legend({
ctx: this.chart.ctx,
options: this.options.legend,
chart: this,
});
Chart.layoutService.addBox(this, this.legend);
Chart.layoutService.addBox(this, this.legend);
}
},
updateLayout: function() {

View File

@@ -12,21 +12,6 @@
fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
onClick: false, // a callback will override the default behavior of toggling the datasets
title: {
position: 'top',
fontColor: '#666',
fontFamily: 'Helvetica Neue',
fontSize: 12,
fontStyle: 'bold',
padding: 10,
// actual title
text: '',
// display property
display: false,
},
labels: {
boxWidth: 40,
fontSize: 12,
@@ -136,7 +121,6 @@
fit: function() {
var ctx = this.ctx;
var titleFont = helpers.fontString(this.options.title.fontSize, this.options.title.fontStyle, this.options.title.fontFamily);
var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
// Reset hit boxes
@@ -158,12 +142,6 @@
// Increase sizes here
if (this.isHorizontal()) {
// Title
if (this.options.title.display) {
this.minSize.height += this.options.title.fontSize + (this.options.title.padding * 2);
}
// Labels
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
@@ -215,7 +193,7 @@
var ctx = this.ctx;
var cursor = {
x: this.left + ((this.width - this.lineWidths[0]) / 2),
y: this.top,
y: this.top + this.options.labels.padding,
line: 0,
};
@@ -223,17 +201,6 @@
// Horizontal
if (this.isHorizontal()) {
// Title Spacing if on top
if (this.options.title.display) {
if (this.options.title.position === 'top') {
cursor.y += this.options.title.fontSize + (this.options.title.padding * 2);
} else {
// bottom
cursor.y += this.options.labels.padding;
}
}
// Labels
ctx.textAlign = "left";
ctx.textBaseline = 'top';
@@ -299,49 +266,8 @@
cursor.x += width + (this.options.labels.padding);
}, this);
// Title Spacing if on bottom
if (this.options.title.display && this.options.title.position === 'bottom') {
cursor.y += this.options.title.fontSize + (this.options.title.padding * 2);
}
// Title
if (this.options.title.display) {
ctx.textAlign = "center";
ctx.textBaseline = 'middle';
ctx.fillStyle = this.options.title.fontColor; // render in correct colour
ctx.font = helpers.fontString(this.options.title.fontSize, this.options.title.fontStyle, this.options.title.fontFamily);
var titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width
var titleY = this.options.title.position == 'bottom' ? this.bottom - (this.options.title.fontSize / 2) - this.options.title.padding : this.top + (this.options.title.fontSize / 2) + this.options.title.padding;
ctx.fillText(this.options.title.text, titleX, titleY);
}
} else {
// Title
if (this.options.title.display) {
// Draw the legend label
titleX = this.options.position == 'left' ? this.left + (this.options.title.fontSize / 2) : this.right - (this.options.title.fontSize / 2);
titleY = this.top + ((this.bottom - this.top) / 2);
var rotation = this.options.position == 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
ctx.save();
ctx.translate(titleX, titleY);
ctx.rotate(rotation);
ctx.textAlign = "center";
ctx.fillStyle = this.options.title.fontColor; // render in correct colour
ctx.font = helpers.fontString(this.options.title.fontSize, this.options.title.fontStyle, this.options.title.fontFamily);
ctx.textBaseline = 'middle';
ctx.fillText(this.options.title.text, 0, 0);
ctx.restore();
}
}
}
},
@@ -363,7 +289,6 @@
this.chart.data.datasets[i].hidden = !this.chart.data.datasets[i].hidden;
// We hid a dataset ... rerender the chart
//this.chart.render();
this.chart.update();
break;
}

196
src/core/core.title.js Normal file
View File

@@ -0,0 +1,196 @@
(function() {
"use strict";
var root = this,
Chart = root.Chart,
helpers = Chart.helpers;
Chart.defaults.title = {
display: true,
position: 'top',
fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
fontColor: '#666',
fontFamily: 'Helvetica Neue',
fontSize: 12,
fontStyle: 'bold',
padding: 10,
// actual title
text: '',
};
Chart.Title = Chart.Element.extend({
initialize: function(config) {
helpers.extend(this, config);
this.options = helpers.configMerge(Chart.defaults.title, config.options);
// Contains hit boxes for each dataset (in dataset order)
this.legendHitBoxes = [];
},
// These methods are ordered by lifecyle. Utilities then follow.
beforeUpdate: helpers.noop,
update: function(maxWidth, maxHeight, margins) {
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
this.beforeUpdate();
// Absorb the master measurements
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this.margins = margins;
// Dimensions
this.beforeSetDimensions();
this.setDimensions();
this.afterSetDimensions();
// Labels
this.beforeBuildLabels();
this.buildLabels();
this.afterBuildLabels();
// Fit
this.beforeFit();
this.fit();
this.afterFit();
//
this.afterUpdate();
return this.minSize;
},
afterUpdate: helpers.noop,
//
beforeSetDimensions: helpers.noop,
setDimensions: function() {
// Set the unconstrained dimension before label rotation
if (this.isHorizontal()) {
// Reset position before calculating rotation
this.width = this.maxWidth;
this.left = 0;
this.right = this.width;
} else {
this.height = this.maxHeight;
// Reset position before calculating rotation
this.top = 0;
this.bottom = this.height;
}
// Reset padding
this.paddingLeft = 0;
this.paddingTop = 0;
this.paddingRight = 0;
this.paddingBottom = 0;
// Reset minSize
this.minSize = {
width: 0,
height: 0,
};
},
afterSetDimensions: helpers.noop,
//
beforeBuildLabels: helpers.noop,
buildLabels: helpers.noop,
afterBuildLabels: helpers.noop,
//
beforeFit: helpers.noop,
fit: function() {
var ctx = this.ctx;
var titleFont = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
// Width
if (this.isHorizontal()) {
this.minSize.width = this.maxWidth; // fill all the width
} else {
this.minSize.width = 0;
}
// height
if (this.isHorizontal()) {
this.minSize.height = 0;
} else {
this.minSize.height = this.maxHeight; // fill all the height
}
// Increase sizes here
if (this.isHorizontal()) {
// Title
if (this.options.display) {
this.minSize.height += this.options.fontSize + (this.options.padding * 2);
}
} else {
// TODO vertical
}
this.width = this.minSize.width;
this.height = this.minSize.height;
},
afterFit: helpers.noop,
// Shared Methods
isHorizontal: function() {
return this.options.position == "top" || this.options.position == "bottom";
},
// Actualy draw the title block on the canvas
draw: function() {
if (this.options.display) {
var ctx = this.ctx;
var titleX, titleY;
// Horizontal
if (this.isHorizontal()) {
// Title
if (this.options.display) {
ctx.textAlign = "center";
ctx.textBaseline = 'middle';
ctx.fillStyle = this.options.fontColor; // render in correct colour
ctx.font = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width
titleY = this.top + ((this.bottom - this.top) / 2); // midpoint of the height
ctx.fillText(this.options.text, titleX, titleY);
}
} else {
// Title
if (this.options.display) {
titleX = this.options.position == 'left' ? this.left + (this.options.fontSize / 2) : this.right - (this.options.fontSize / 2);
titleY = this.top + ((this.bottom - this.top) / 2);
var rotation = this.options.position == 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
ctx.save();
ctx.translate(titleX, titleY);
ctx.rotate(rotation);
ctx.textAlign = "center";
ctx.fillStyle = this.options.fontColor; // render in correct colour
ctx.font = helpers.fontString(this.options.fontSize, this.options.fontStyle, this.options.fontFamily);
ctx.textBaseline = 'middle';
ctx.fillText(this.options.text, 0, 0);
ctx.restore();
}
}
}
}
});
}).call(this);