Merge branch 'v2.0-dev' into fix/1725

Conflicts:
	src/core/core.controller.js
This commit is contained in:
Evert Timberg 2015-12-12 09:01:59 -05:00
commit 95332e39f5
25 changed files with 1474 additions and 456 deletions

View File

@ -31,7 +31,7 @@ You can also grab Chart.js using bower, npm, or CDN:
bower install Chart.js --save
```
```bash
npm install Chart.js --save
npm install chart.js --save
```
https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.0-beta/Chart.js
@ -105,6 +105,37 @@ onClick | Function | null | Called if the event is of type 'mouseup' or 'click'.
defaultColor | Color | 'rgba(0,0,0,0.1)' |
legendCallback | Function | ` function (chart) { // the chart object to generate a legend from. }` | Function to generate a legend. Default implementation returns an HTML string.
The global options for the chart title is defined in `Chart.defaults.global.title`
Name | Type | Default | Description
--- | --- | --- | ---
display | Boolean | true | Show the title block
position | String | 'top' | Position of the title. 'top' or 'bottom' are allowed
fullWidth | Boolean | true | Marks that this box should take the full width of the canvas (pushing down other boxes)
fontColor | Color | '#666' | Text color
fontFamily | String | 'Helvetica Neue' |
fontSize | Number | 12 |
fontStyle | String | 'bold' |
padding | Number | 10 | Number of pixels to add above and below the title text
text | String | '' | Title text
The global options for the chart legend is defined in `Chart.defaults.global.legend`
Name | Type | Default | Description
--- | --- | --- | ---
display | Boolean | true | Is the legend displayed
position | String | 'top' | Position of the legend. Options are 'top' or 'bottom'
fullWidth | Boolean | true | Marks that this box should take the full width of the canvas (pushing down other boxes)
onClick | Function | `function(event, legendItem) {}` | A callback that is called when a click is registered on top of a label item
labels |-|-|-
*labels*boxWidth | Number | 40 | Width of coloured box
*labels*fontSize | Number | 12 | Font size
*labels*fontStyle | String | "normal" |
*labels*fontColor | Color | "#666" |
*labels*fontFamily | String | "Helvetica Neue" |
*labels*padding | Number | 10 | Padding between rows of colored boxes
*labels*generateLabels: | Function | `function(data) { } | Generates legend items for each thing in the legend. Default implementation returns the text + styling for the color box. Styles that can be returned are `fillStyle`, `strokeStyle`, `lineCap`, `lineDash`, `lineDashOffset`, `lineWidth`, `lineJoin`. Return a `hidden` attribute to indicate that the label refers to something that is not visible. A strikethrough style will be given to the text in this case.
The global options for tooltips are defined in `Chart.defaults.global.tooltips`.
Name | Type | Default | Description

View File

@ -67,6 +67,8 @@ scale | Array | [See Scales](#scales) and [Defaults for Radial Linear Scale](#ge
*scale*.lineArc | Boolean | true | When true, lines are circular.
*animation*.animateRotate | Boolean |true | If true, will animate the rotation of the chart.
*animation*.animateScale | Boolean | true | If true, will animate scaling the chart.
*legend*.*labels*.generateLabels | Function | `function(data) {} ` | Returns labels for each the legend
*legend*.onClick | Function | function(event, legendItem) {} ` | Handles clicking an individual legend item
You can override these for your `Chart` instance by passing a second argument into the `PolarArea` method as an object with the keys you want to override.

View File

@ -78,6 +78,8 @@ scale | Array | [See Scales](#scales) and [Defaults for Radial Linear Scale](#ge
*scale*.lineArc | Boolean | true | When true, lines are arced compared to straight when false.
*animation*.animateRotate | Boolean |true | If true, will animate the rotation of the chart.
*animation*.animateScale | Boolean | false | If true, will animate scaling the Doughnut from the centre.
*legend*.*labels*.generateLabels | Function | `function(data) {} ` | Returns labels for each the legend
*legend*.onClick | Function | function(event, legendItem) {} ` | Handles clicking an individual legend item
You can override these for your `Chart` instance by passing a second argument into the `Doughnut` method as an object with the keys you want to override.

View File

@ -70,6 +70,13 @@
data: barChartData,
options: {
responsive: true,
legend: {
position: 'top',
},
title: {
display: true,
text: 'Our 3 Favorite Datasets'
}
}
});

View File

@ -44,6 +44,7 @@
};
var config = {
type: 'doughnut',
data: {
datasets: [{
data: [
@ -60,6 +61,7 @@
"#949FB1",
"#4D5360",
],
label: 'Dataset 1'
}, {
hidden: true,
data: [
@ -76,6 +78,7 @@
"#949FB1",
"#4D5360",
],
label: 'Dataset 2'
}, {
data: [
randomScalingFactor(),
@ -91,6 +94,7 @@
"#949FB1",
"#4D5360",
],
label: 'Dataset 3'
}],
labels: [
"Red",
@ -101,7 +105,14 @@
]
},
options: {
responsive: true
responsive: true,
legend: {
position: 'top',
},
title: {
display: true,
text: 'Our Favorite Datasets'
}
}
};
@ -113,7 +124,7 @@
window.onload = function() {
var ctx = document.getElementById("chart-area").getContext("2d");
window.myDoughnut = Chart.Doughnut(ctx, config);
window.myDoughnut = new Chart(ctx, config);
console.log(window.myDoughnut);
updateLegend();
@ -138,6 +149,7 @@
var newDataset = {
backgroundColor: [],
data: [],
label: 'New dataset ' + config.data.datasets.length,
};
for (var index = 0; index < config.data.labels.length; ++index) {

182
samples/line-legend.html Normal file
View File

@ -0,0 +1,182 @@
<!doctype html>
<html>
<head>
<title>Line Chart</title>
<script src="../Chart.js"></script>
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<style>
canvas {
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, .5);
}
</style>
</head>
<body>
<div style="width:100%;">
<canvas id="canvas" style="width:100%;height:100%"></canvas>
</div>
<br>
<br>
<button id="randomizeData">Randomize Data</button>
<button id="addDataset">Add Dataset</button>
<button id="removeDataset">Remove Dataset</button>
<button id="addData">Add Data</button>
<button id="removeData">Remove Data</button>
<script>
var randomScalingFactor = function() {
return Math.round(Math.random() * 100 * (Math.random() > 0.5 ? -1 : 1));
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var randomColor = function(opacity) {
return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
};
var config = {
type: 'line',
data: {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
label: "My First dataset",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
fill: false,
borderDash: [5, 5],
}, {
label: "My Second dataset",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
fill: false,
borderDash: [5, 5],
}, {
label: "My Third dataset",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
fill: false,
}, {
label: "My Fourth dataset",
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
fill: false,
}]
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
hover: {
mode: 'label'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
show: true,
labelString: 'Month'
}
}],
yAxes: [{
display: true,
scaleLabel: {
show: true,
labelString: 'Value'
}
}]
},
title: {
display: true,
text: 'Our 4 Favorite Datasets'
}
}
};
$.each(config.data.datasets, function(i, dataset) {
var background = randomColor(0.5);
dataset.borderColor = background;
dataset.backgroundColor = background;
dataset.pointBorderColor = background;
dataset.pointBackgroundColor = background;
dataset.pointBorderWidth = 1;
});
console.log(config.data);
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myLine = new Chart(ctx, config);
updateLegend();
};
function updateLegend() {
$legendContainer = $('#legendContainer');
$legendContainer.empty();
$legendContainer.append(window.myLine.generateLegend());
}
$('#randomizeData').click(function() {
$.each(config.data.datasets, function(i, dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myLine.update();
updateLegend();
});
$('#addDataset').click(function() {
var background = randomColor(0.5);
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
borderColor: background,
backgroundColor: background,
pointBorderColor: background,
pointBackgroundColor: background,
pointBorderWidth: 1,
fill: false,
data: [],
};
for (var index = 0; index < config.data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}
config.data.datasets.push(newDataset);
window.myLine.update();
updateLegend();
});
$('#addData').click(function() {
if (config.data.datasets.length > 0) {
config.data.labels.push('dataset #' + config.data.labels.length);
$.each(config.data.datasets, function(i, dataset) {
dataset.data.push(randomScalingFactor());
});
window.myLine.update();
updateLegend();
}
});
$('#removeDataset').click(function() {
config.data.datasets.splice(0, 1);
window.myLine.update();
updateLegend();
});
$('#removeData').click(function() {
config.data.labels.splice(-1, 1); // remove the label first
config.data.datasets.forEach(function(dataset, datasetIndex) {
dataset.data.pop();
});
window.myLine.update();
updateLegend();
});
</script>
</body>
</html>

View File

@ -48,6 +48,7 @@
"#949FB1",
"#4D5360",
],
label: 'My dataset' // for legend
}],
labels: [
"Red",
@ -59,6 +60,13 @@
},
options: {
responsive: true,
legend: {
position: 'top',
},
title: {
display: true,
text: 'Our Favorite Dataset'
},
scale: {
ticks: {
beginAtZero: true

View File

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

View File

@ -5,29 +5,7 @@
var Chart = root.Chart;
var helpers = Chart.helpers;
var defaultConfig = {
aspectRatio: 1,
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend">');
if (chart.data.datasets.length) {
for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '">');
if (chart.data.labels[i]) {
text.push(chart.data.labels[i]);
}
text.push('</span></li>');
}
}
text.push('</ul>');
return text.join("");
}
};
Chart.Doughnut = function(context, config) {
config.options = helpers.configMerge(defaultConfig, config.options);
config.type = 'doughnut';
return new Chart(context, config);

View File

@ -5,29 +5,7 @@
var Chart = root.Chart;
var helpers = Chart.helpers;
var defaultConfig = {
aspectRatio: 1,
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend">');
if (chart.data.datasets.length) {
for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '">');
if (chart.data.labels[i]) {
text.push(chart.data.labels[i]);
}
text.push('</span></li>');
}
}
text.push('</ul>');
return text.join("");
}
};
Chart.PolarArea = function(context, config) {
config.options = helpers.configMerge(defaultConfig, config.options);
config.type = 'polarArea';
return new Chart(context, config);

View File

@ -13,9 +13,59 @@
//Boolean - Whether we animate scaling the Doughnut from the centre
animateScale: false,
},
aspectRatio: 1,
hover: {
mode: 'single'
},
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend">');
if (chart.data.datasets.length) {
for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '">');
if (chart.data.labels[i]) {
text.push(chart.data.labels[i]);
}
text.push('</span></li>');
}
}
text.push('</ul>');
return text.join("");
},
legend: {
labels: {
generateLabels: function(data) {
return data.labels.map(function(label, i) {
return {
text: label,
fillStyle: data.datasets[0].backgroundColor[i],
hidden: isNaN(data.datasets[0].data[i]),
// Extra data used for toggling the correct item
index: i
};
});
}
},
onClick: function(e, legendItem) {
helpers.each(this.chart.data.datasets, function(dataset) {
dataset.metaHiddenData = dataset.metaHiddenData || [];
var idx = legendItem.index;
if (!isNaN(dataset.data[idx])) {
dataset.metaHiddenData[idx] = dataset.data[idx];
dataset.data[idx] = NaN;
} else if (!isNaN(dataset.metaHiddenData[idx])) {
dataset.data[idx] = dataset.metaHiddenData[idx];
}
});
this.chart.update();
}
},
//The percentage of the chart that we cut out of the middle.
cutoutPercentage: 50,
@ -129,14 +179,17 @@
},
update: function update(reset) {
var minSize = Math.min(this.chart.chartArea.right - this.chart.chartArea.left, this.chart.chartArea.bottom - this.chart.chartArea.top);
this.chart.outerRadius = Math.max((helpers.min([this.chart.chart.width, this.chart.chart.height]) / 2) - this.chart.options.elements.arc.borderWidth / 2, 0);
this.chart.outerRadius = Math.max((minSize / 2) - this.chart.options.elements.arc.borderWidth / 2, 0);
this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0);
this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.getVisibleDatasetCount();
this.getDataset().total = 0;
helpers.each(this.getDataset().data, function(value) {
this.getDataset().total += Math.abs(value);
if (!isNaN(value)) {
this.getDataset().total += Math.abs(value);
}
}, this);
this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.getRingIndex(this.index));
@ -147,10 +200,13 @@
}, this);
},
updateElement: function(arc, index, reset) {
var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2;
var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2;
var resetModel = {
x: this.chart.chart.width / 2,
y: this.chart.chart.height / 2,
startAngle: Math.PI * -0.5, // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function,
x: centerX,
y: centerY,
startAngle: Math.PI * -0.5, // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function
endAngle: Math.PI * -0.5,
circumference: (this.chart.options.animation.animateRotate) ? 0 : this.calculateCircumference(this.getDataset().data[index]),
outerRadius: (this.chart.options.animation.animateScale) ? 0 : this.outerRadius,
@ -165,8 +221,8 @@
// Desired view properties
_model: reset ? resetModel : {
x: this.chart.chart.width / 2,
y: this.chart.chart.height / 2,
x: centerX,
y: centerY,
circumference: this.calculateCircumference(this.getDataset().data[index]),
outerRadius: this.outerRadius,
innerRadius: this.innerRadius,
@ -226,7 +282,7 @@
},
calculateCircumference: function(value) {
if (this.getDataset().total > 0) {
if (this.getDataset().total > 0 && !isNaN(value)) {
return (Math.PI * 1.999999) * (value / this.getDataset().total);
} else {
return 0;

View File

@ -144,7 +144,7 @@
// Model
_model: {
// Appearance
tension: line.custom && line.custom.tension ? line.custom.tension : (this.getDataset().tension || this.chart.options.elements.line.tension),
tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor),
borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth),
borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor),
@ -238,7 +238,7 @@
x: xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo),
y: reset ? scaleBase : this.calculatePointY(this.getDataset().data[index], index, this.index, this.chart.isCombo),
// Appearance
tension: point.custom && point.custom.tension ? point.custom.tension : (this.getDataset().tension || this.chart.options.elements.line.tension),
tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius),
backgroundColor: this.getPointBackgroundColor(point, index),
borderColor: this.getPointBorderColor(point, index),

View File

@ -18,6 +18,56 @@
animateRotate: true,
animateScale: true,
aspectRatio: 1,
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend">');
if (chart.data.datasets.length) {
for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '">');
if (chart.data.labels[i]) {
text.push(chart.data.labels[i]);
}
text.push('</span></li>');
}
}
text.push('</ul>');
return text.join("");
},
legend: {
labels: {
generateLabels: function(data) {
return data.labels.map(function(label, i) {
return {
text: label,
fillStyle: data.datasets[0].backgroundColor[i],
hidden: isNaN(data.datasets[0].data[i]),
// Extra data used for toggling the correct item
index: i
};
});
}
},
onClick: function(e, legendItem) {
helpers.each(this.chart.data.datasets, function(dataset) {
dataset.metaHiddenData = dataset.metaHiddenData || [];
var idx = legendItem.index;
if (!isNaN(dataset.data[idx])) {
dataset.metaHiddenData[idx] = dataset.data[idx];
dataset.data[idx] = NaN;
} else if (!isNaN(dataset.metaHiddenData[idx])) {
dataset.data[idx] = dataset.metaHiddenData[idx];
}
});
this.chart.update();
}
},
// Need to override these to give a nice default
tooltips: {
callbacks: {
@ -111,7 +161,8 @@
},
update: function update(reset) {
this.chart.outerRadius = Math.max((helpers.min([this.chart.chart.width, this.chart.chart.height]) - this.chart.options.elements.arc.borderWidth / 2) / 2, 0);
var minSize = Math.min(this.chart.chartArea.right - this.chart.chartArea.left, this.chart.chartArea.bottom - this.chart.chartArea.top);
this.chart.outerRadius = Math.max((minSize - this.chart.options.elements.arc.borderWidth / 2) / 2, 0);
this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0);
this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.getVisibleDatasetCount();
@ -127,14 +178,27 @@
this.updateElement(arc, index, reset);
}, this);
},
updateElement: function(arc, index, reset) {
var circumference = 1 / this.getDataset().data.length * 2;
var startAngle = (-0.5 * Math.PI) + (Math.PI * circumference) * index;
var endAngle = startAngle + (circumference * Math.PI);
var circumference = this.calculateCircumference(this.getDataset().data[index]);
var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2;
var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2;
// If there is NaN data before us, we need to calculate the starting angle correctly.
// We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
var notNullIndex = 0;
for (var i = 0; i < index; ++i) {
if (!isNaN(this.getDataset().data[i])) {
++notNullIndex;
}
}
var startAngle = (-0.5 * Math.PI) + (circumference * notNullIndex);
var endAngle = startAngle + circumference;
var resetModel = {
x: this.chart.chart.width / 2,
y: this.chart.chart.height / 2,
x: centerX,
y: centerY,
innerRadius: 0,
outerRadius: this.chart.options.animateScale ? 0 : this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[index]),
startAngle: this.chart.options.animateRotate ? Math.PI * -0.5 : startAngle,
@ -157,8 +221,8 @@
// Desired view properties
_model: reset ? resetModel : {
x: this.chart.chart.width / 2,
y: this.chart.chart.height / 2,
x: centerX,
y: centerY,
innerRadius: 0,
outerRadius: this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[index]),
startAngle: startAngle,
@ -202,10 +266,15 @@
},
calculateCircumference: function(value) {
if (this.getDataset().total > 0) {
return (Math.PI * 2) * (value / this.getDataset().total);
} else {
if (isNaN(value)) {
return 0;
} else {
// Count the number of NaN values
var numNaN = helpers.where(this.getDataset().data, function(data) {
return isNaN(data);
}).length;
return (2 * Math.PI) / (this.getDataset().data.length - numNaN);
}
},
updateScaleRange: function() {

View File

@ -136,7 +136,7 @@
// Model
_model: {
// Appearance
tension: this.getDataset().tension || this.chart.options.elements.line.tension,
tension: helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
backgroundColor: this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor,
borderWidth: this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth,
borderColor: this.getDataset().borderColor || this.chart.options.elements.line.borderColor,
@ -175,7 +175,7 @@
y: reset ? this.chart.scale.yCenter : pointPosition.y,
// Appearance
tension: point.custom && point.custom.tension ? point.custom.tension : this.chart.options.elements.line.tension,
tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.getDataset().pointRadius, index, this.chart.options.elements.point.radius),
backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor),
borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor),

View File

@ -59,6 +59,8 @@
this.ensureScalesHaveIDs();
this.buildOrUpdateControllers();
this.buildScales();
this.buildSurroundingItems();
this.updateLayout();
this.resetElements();
this.initToolTip();
this.update();
@ -165,7 +167,33 @@
this.scales.radialScale = scale;
}
Chart.scaleService.update(this, this.chart.width, this.chart.height);
Chart.scaleService.addScalesToLayout(this);
},
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);
}
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);
}
},
updateLayout: function() {
Chart.layoutService.update(this, this.chart.width, this.chart.height);
},
buildOrUpdateControllers: function buildOrUpdateControllers(resetNewControllers) {
@ -212,7 +240,7 @@
// Make sure dataset controllers are updated and new controllers are reset
this.buildOrUpdateControllers(true);
Chart.scaleService.update(this, this.chart.width, this.chart.height);
Chart.layoutService.update(this, this.chart.width, this.chart.height);
// Make sure all dataset controllers have correct meta data counts
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
@ -261,8 +289,8 @@
this.clear();
// Draw all the scales
helpers.each(this.scales, function(scale) {
scale.draw(this.chartArea);
helpers.each(this.boxes, function(box) {
box.draw(this.chartArea);
}, this);
if (this.scale) {
this.scale.draw();
@ -391,30 +419,23 @@
this.active = [];
this.tooltipActive = [];
} else {
this.active = function() {
switch (this.options.hover.mode) {
var _this = this;
var getItemsForMode = function(mode) {
switch (mode) {
case 'single':
return this.getElementAtEvent(e);
return _this.getElementAtEvent(e);
case 'label':
return this.getElementsAtEvent(e);
return _this.getElementsAtEvent(e);
case 'dataset':
return this.getDatasetAtEvent(e);
return _this.getDatasetAtEvent(e);
default:
return e;
}
}.call(this);
this.tooltipActive = function() {
switch (this.options.tooltips.mode) {
case 'single':
return this.getElementAtEvent(e);
case 'label':
return this.getElementsAtEvent(e);
case 'dataset':
return this.getDatasetAtEvent(e);
default:
return e;
}
}.call(this);
};
this.active = getItemsForMode(this.options.hover.mode);
this.tooltipActive = getItemsForMode(this.options.tooltips.mode);
}
// On Hover hook
@ -426,6 +447,10 @@
if (this.options.onClick) {
this.options.onClick.call(this, e, this.active);
}
if (this.legend && this.legend.handleEvent) {
this.legend.handleEvent(e);
}
}
var dataset;

View File

@ -171,6 +171,9 @@
return value;
},
getValueOrDefault = helpers.getValueOrDefault = function(value, defaultValue) {
return value === undefined ? defaultValue : value;
},
indexOf = helpers.indexOf = function(arrayToSearch, item) {
if (Array.prototype.indexOf) {
return arrayToSearch.indexOf(item);

View File

@ -0,0 +1,324 @@
(function() {
"use strict";
var root = this,
Chart = root.Chart,
helpers = Chart.helpers;
// The layout service is ver self explanatory. It's responsible for the layout within a chart.
// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
// It is this service's responsibility of carrying out that layout.
Chart.layoutService = {
defaults: {},
// Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
addBox: function(chartInstance, box) {
if (!chartInstance.boxes) {
chartInstance.boxes = [];
}
chartInstance.boxes.push(box);
},
removeBox: function(chartInstance, box) {
if (!chartInstance.boxes) {
return;
}
chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
},
// The most important function
update: function(chartInstance, width, height) {
if (!chartInstance) {
return;
}
var xPadding = width > 30 ? 5 : 2;
var yPadding = height > 30 ? 5 : 2;
var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "left";
});
var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "right";
});
var topBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "top";
});
var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "bottom";
});
// Boxes that overlay the chartarea such as the radialLinear scale
var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position == "chartArea";
});
function fullWidthSorter(a, b) {
}
// Ensure that full width boxes are at the very top / bottom
topBoxes.sort(function(a, b) {
return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
});
bottomBoxes.sort(function(a, b) {
return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
});
// Essentially we now have any number of boxes on each of the 4 sides.
// Our canvas looks like the following.
// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
// B1 is the bottom axis
// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
// These locations are single-box locations only, when trying to register a chartArea location that is already taken,
// an error will be thrown.
//
// |----------------------------------------------------|
// | T1 (Full Width) |
// |----------------------------------------------------|
// | | | T2 | |
// | |----|-------------------------------------|----|
// | | | C1 | | C2 | |
// | | |----| |----| |
// | | | | |
// | L1 | L2 | ChartArea (C0) | R1 |
// | | | | |
// | | |----| |----| |
// | | | C3 | | C4 | |
// | |----|-------------------------------------|----|
// | | | B1 | |
// |----------------------------------------------------|
// | B2 (Full Width) |
// |----------------------------------------------------|
//
// What we do to find the best sizing, we do the following
// 1. Determine the minimum size of the chart area.
// 2. Split the remaining width equally between each vertical axis
// 3. Split the remaining height equally between each horizontal axis
// 4. Give each layout the maximum size it can be. The layout will return it's minimum size
// 5. Adjust the sizes of each axis based on it's minimum reported size.
// 6. Refit each axis
// 7. Position each axis in the final location
// 8. Tell the chart the final location of the chart area
// 9. Tell any axes that overlay the chart area the positions of the chart area
// Step 1
var chartWidth = width - (2 * xPadding);
var chartHeight = height - (2 * yPadding);
var chartAreaWidth = chartWidth / 2; // min 50%
var chartAreaHeight = chartHeight / 2; // min 50%
// Step 2
var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
// Step 3
var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
// Step 4
var maxChartAreaWidth = chartWidth;
var maxChartAreaHeight = chartHeight;
var minBoxSizes = [];
helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
function getMinimumBoxSize(box) {
var minSize;
var isHorizontal = box.isHorizontal();
if (isHorizontal) {
minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
maxChartAreaHeight -= minSize.height;
} else {
minSize = box.update(verticalBoxWidth, chartAreaHeight);
maxChartAreaWidth -= minSize.width;
}
minBoxSizes.push({
horizontal: isHorizontal,
minSize: minSize,
box: box,
});
}
// At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
// be if the axes are drawn at their minimum sizes.
// Steps 5 & 6
var totalLeftBoxesWidth = xPadding;
var totalRightBoxesWidth = xPadding;
var totalTopBoxesHeight = yPadding;
var totalBottomBoxesHeight = yPadding;
// Update, and calculate the left and right margins for the horizontal boxes
helpers.each(leftBoxes.concat(rightBoxes), fitBox);
helpers.each(leftBoxes, function(box) {
totalLeftBoxesWidth += box.width;
});
helpers.each(rightBoxes, function(box) {
totalRightBoxesWidth += box.width;
});
// Set the Left and Right margins for the horizontal boxes
helpers.each(topBoxes.concat(bottomBoxes), fitBox);
// Function to fit a box
function fitBox(box) {
var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
return minBoxSize.box === box;
});
if (minBoxSize) {
if (box.isHorizontal()) {
var scaleMargin = {
left: totalLeftBoxesWidth,
right: totalRightBoxesWidth,
top: 0,
bottom: 0,
};
box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, minBoxSize.minSize.height, scaleMargin);
} else {
box.update(minBoxSize.minSize.width, maxChartAreaHeight);
}
}
}
// Figure out how much margin is on the top and bottom of the vertical boxes
helpers.each(topBoxes, function(box) {
totalTopBoxesHeight += box.height;
});
helpers.each(bottomBoxes, function(box) {
totalBottomBoxesHeight += box.height;
});
// Let the left layout know the final margin
helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
function finalFitVerticalBox(box) {
var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
return minBoxSize.box === box;
});
var scaleMargin = {
left: 0,
right: 0,
top: totalTopBoxesHeight,
bottom: totalBottomBoxesHeight
};
if (minBoxSize) {
box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
}
}
// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
totalLeftBoxesWidth = xPadding;
totalRightBoxesWidth = xPadding;
totalTopBoxesHeight = yPadding;
totalBottomBoxesHeight = yPadding;
helpers.each(leftBoxes, function(box) {
totalLeftBoxesWidth += box.width;
});
helpers.each(rightBoxes, function(box) {
totalRightBoxesWidth += box.width;
});
helpers.each(topBoxes, function(box) {
totalTopBoxesHeight += box.height;
});
helpers.each(bottomBoxes, function(box) {
totalBottomBoxesHeight += box.height;
});
// Figure out if our chart area changed. This would occur if the dataset layout label rotation
// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
// without calling `fit` again
var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
helpers.each(leftBoxes, function(box) {
box.height = newMaxChartAreaHeight;
});
helpers.each(rightBoxes, function(box) {
box.height = newMaxChartAreaHeight;
});
helpers.each(topBoxes, function(box) {
box.width = newMaxChartAreaWidth;
});
helpers.each(bottomBoxes, function(box) {
box.width = newMaxChartAreaWidth;
});
maxChartAreaHeight = newMaxChartAreaHeight;
maxChartAreaWidth = newMaxChartAreaWidth;
}
// Step 7 - Position the boxes
var left = xPadding;
var top = yPadding;
var right = 0;
var bottom = 0;
helpers.each(leftBoxes.concat(topBoxes), placeBox);
// Account for chart width and height
left += maxChartAreaWidth;
top += maxChartAreaHeight;
helpers.each(rightBoxes, placeBox);
helpers.each(bottomBoxes, placeBox);
function placeBox(box) {
if (box.isHorizontal()) {
box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
box.top = top;
box.bottom = top + box.height;
// Move to next point
top = box.bottom;
} else {
box.left = left;
box.right = left + box.width;
box.top = totalTopBoxesHeight;
box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
// Move to next point
left = box.right;
}
}
// Step 8
chartInstance.chartArea = {
left: totalLeftBoxesWidth,
top: totalTopBoxesHeight,
right: totalLeftBoxesWidth + maxChartAreaWidth,
bottom: totalTopBoxesHeight + maxChartAreaHeight,
};
// Step 9
helpers.each(chartAreaBoxes, function(box) {
box.left = chartInstance.chartArea.left;
box.top = chartInstance.chartArea.top;
box.right = chartInstance.chartArea.right;
box.bottom = chartInstance.chartArea.bottom;
box.update(maxChartAreaWidth, maxChartAreaHeight);
});
}
};
}).call(this);

325
src/core/core.legend.js Normal file
View File

@ -0,0 +1,325 @@
(function() {
"use strict";
var root = this,
Chart = root.Chart,
helpers = Chart.helpers;
Chart.defaults.global.legend = {
display: true,
position: 'top',
fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
// a callback that will handle
onClick: function(e, legendItem) {
var dataset = this.chart.data.datasets[legendItem.datasetIndex];
dataset.hidden = !dataset.hidden;
// We hid a dataset ... rerender the chart
this.chart.update();
},
labels: {
boxWidth: 40,
fontSize: 12,
fontStyle: "normal",
fontColor: "#666",
fontFamily: "Helvetica Neue",
padding: 10,
// Generates labels shown in the legend
// Valid properties to return:
// text : text to display
// fillStyle : fill of coloured box
// strokeStyle: stroke of coloured box
// hidden : if this legend item refers to a hidden item
// lineCap : cap style for line
// lineDash
// lineDashOffset :
// lineJoin :
// lineWidth :
generateLabels: function(data) {
return data.datasets.map(function(dataset, i) {
return {
text: dataset.label,
fillStyle: dataset.backgroundColor,
hidden: dataset.hidden,
lineCap: dataset.borderCapStyle,
lineDash: dataset.borderDash,
lineDashOffset: dataset.borderDashOffset,
lineJoin: dataset.borderJoinStyle,
lineWidth: dataset.borderWidth,
strokeStyle: dataset.borderColor,
// Below is extra data used for toggling the datasets
datasetIndex: i
};
}, this);
}
},
};
Chart.Legend = Chart.Element.extend({
initialize: function(config) {
helpers.extend(this, config);
// Contains hit boxes for each dataset (in dataset order)
this.legendHitBoxes = [];
// Are we in doughnut mode which has a different data type
this.doughnutMode = false;
},
// These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all legend types.
// Any function can be extended by the legend type
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: function() {
this.legendItems = this.options.labels.generateLabels.call(this, this.chart.data);
},
afterBuildLabels: helpers.noop,
//
beforeFit: helpers.noop,
fit: function() {
var ctx = this.ctx;
var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
// Reset hit boxes
this.legendHitBoxes = [];
// Width
if (this.isHorizontal()) {
this.minSize.width = this.maxWidth; // fill all the width
} else {
this.minSize.width = this.options.display ? 10 : 0;
}
// height
if (this.isHorizontal()) {
this.minSize.height = this.options.display ? 10 : 0;
} else {
this.minSize.height = this.maxHeight; // fill all the height
}
// Increase sizes here
if (this.options.display) {
if (this.isHorizontal()) {
// Labels
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
this.lineWidths = [0];
var totalHeight = this.legendItems.length ? this.options.labels.fontSize + (this.options.labels.padding) : 0;
ctx.textAlign = "left";
ctx.textBaseline = 'top';
ctx.font = labelFont;
helpers.each(this.legendItems, function(legendItem, i) {
var width = this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + ctx.measureText(legendItem.text).width;
if (this.lineWidths[this.lineWidths.length - 1] + width >= this.width) {
totalHeight += this.options.labels.fontSize + (this.options.labels.padding);
this.lineWidths[this.lineWidths.length] = this.left;
}
// Store the hitbox width and height here. Final position will be updated in `draw`
this.legendHitBoxes[i] = {
left: 0,
top: 0,
width: width,
height: this.options.labels.fontSize,
};
this.lineWidths[this.lineWidths.length - 1] += width + this.options.labels.padding;
}, this);
this.minSize.height += totalHeight;
} 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 legend on the canvas
draw: function() {
if (this.options.display) {
var ctx = this.ctx;
var cursor = {
x: this.left + ((this.width - this.lineWidths[0]) / 2),
y: this.top + this.options.labels.padding,
line: 0,
};
var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
// Horizontal
if (this.isHorizontal()) {
// Labels
ctx.textAlign = "left";
ctx.textBaseline = 'top';
ctx.lineWidth = 0.5;
ctx.strokeStyle = this.options.labels.fontColor; // for strikethrough effect
ctx.fillStyle = this.options.labels.fontColor; // render in correct colour
ctx.font = labelFont;
helpers.each(this.legendItems, function(legendItem, i) {
var textWidth = ctx.measureText(legendItem.text).width;
var width = this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + textWidth;
if (cursor.x + width >= this.width) {
cursor.y += this.options.labels.fontSize + (this.options.labels.padding);
cursor.line++;
cursor.x = this.left + ((this.width - this.lineWidths[cursor.line]) / 2);
}
// Set the ctx for the box
ctx.save();
var itemOrDefault = function(item, defaulVal) {
return item !== undefined ? item : defaulVal;
};
ctx.fillStyle = itemOrDefault(legendItem.fillStyle, Chart.defaults.global.defaultColor);
ctx.lineCap = itemOrDefault(legendItem.lineCap, Chart.defaults.global.elements.line.borderCapStyle);
ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, Chart.defaults.global.elements.line.borderDashOffset);
ctx.lineJoin = itemOrDefault(legendItem.lineJoin, Chart.defaults.global.elements.line.borderJoinStyle);
ctx.lineWidth = itemOrDefault(legendItem.lineWidth, Chart.defaults.global.elements.line.borderWidth);
ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, Chart.defaults.global.defaultColor);
if (ctx.setLineDash) {
// IE 9 and 10 do not support line dash
ctx.setLineDash(itemOrDefault(legendItem.lineDash, Chart.defaults.global.elements.line.borderDash));
}
// Draw the box
ctx.strokeRect(cursor.x, cursor.y, this.options.labels.boxWidth, this.options.labels.fontSize);
ctx.fillRect(cursor.x, cursor.y, this.options.labels.boxWidth, this.options.labels.fontSize);
ctx.restore();
this.legendHitBoxes[i].left = cursor.x;
this.legendHitBoxes[i].top = cursor.y;
// Fill the actual label
ctx.fillText(legendItem.text, this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x, cursor.y);
if (legendItem.hidden) {
// Strikethrough the text if hidden
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x, cursor.y + (this.options.labels.fontSize / 2));
ctx.lineTo(this.options.labels.boxWidth + (this.options.labels.fontSize / 2) + cursor.x + textWidth, cursor.y + (this.options.labels.fontSize / 2));
ctx.stroke();
}
cursor.x += width + (this.options.labels.padding);
}, this);
} else {
}
}
},
// Handle an event
handleEvent: function(e) {
var position = helpers.getRelativePosition(e, this.chart.chart);
if (position.x >= this.left && position.x <= this.right && position.y >= this.top && position.y <= this.bottom) {
// See if we are touching one of the dataset boxes
for (var i = 0; i < this.legendHitBoxes.length; ++i) {
var hitBox = this.legendHitBoxes[i];
if (position.x >= hitBox.left && position.x <= hitBox.left + hitBox.width && position.y >= hitBox.top && position.y <= hitBox.top + hitBox.height) {
// Touching an element
if (this.options.onClick) {
this.options.onClick.call(this, e, this.legendItems[i]);
}
break;
}
}
}
}
});
}).call(this);

View File

@ -67,7 +67,12 @@
// Absorb the master measurements
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this.margins = margins;
this.margins = helpers.extend({
left: 0,
right: 0,
top: 0,
bottom: 0
}, margins);
// Dimensions
this.beforeSetDimensions();
@ -166,7 +171,6 @@
var originalLabelWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
var cosRotation;
var sinRotation;
var firstRotatedWidth;
this.labelWidth = originalLabelWidth;
@ -228,7 +232,8 @@
// Width
if (this.isHorizontal()) {
this.minSize.width = this.maxWidth; // fill all the width
// subtract the margins to line up with the chartArea if we are a full width scale
this.minSize.width = this.isFullWidth() ? this.maxWidth - this.margins.left - this.margins.right : this.maxWidth;
} else {
this.minSize.width = this.options.gridLines.display && this.options.display ? 10 : 0;
}
@ -256,7 +261,6 @@
if (this.isHorizontal()) {
// A horizontal axis is more constrained by the height.
var maxLabelHeight = this.maxHeight - this.minSize.height;
var longestLabelWidth = helpers.longestText(this.ctx, labelFont, this.ticks);
// TODO - improve this calculation
@ -319,7 +323,10 @@
// Shared Methods
isHorizontal: function() {
return this.options.position == "top" || this.options.position == "bottom";
return this.options.position === "top" || this.options.position === "bottom";
},
isFullWidth: function() {
return (this.options.fullWidth);
},
// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
@ -362,7 +369,10 @@
if (includeOffset) {
pixel += tickWidth / 2;
}
return this.left + Math.round(pixel);
var finalVal = this.left + Math.round(pixel);
finalVal += this.isFullWidth() ? this.margins.left : 0;
return finalVal;
} else {
var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
return this.top + (index * (innerHeight / (this.ticks.length - 1)));
@ -370,12 +380,14 @@
},
// Utility for getting the pixel location of a percentage of scale
getPixelForDecimal: function(decimal, includeOffset) {
getPixelForDecimal: function(decimal/*, includeOffset*/) {
if (this.isHorizontal()) {
var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
var valueOffset = (innerWidth * decimal) + this.paddingLeft;
return this.left + Math.round(valueOffset);
var finalVal = this.left + Math.round(valueOffset);
finalVal += this.isFullWidth() ? this.margins.left : 0;
return finalVal;
} else {
return this.top + (decimal * this.height);
}
@ -398,8 +410,8 @@
if (this.isHorizontal()) {
setContextLineSettings = true;
var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 10;
var yTickEnd = this.options.position == "bottom" ? this.top + 10 : this.bottom;
var yTickStart = this.options.position === "bottom" ? this.top : this.bottom - 10;
var yTickEnd = this.options.position === "bottom" ? this.top + 10 : this.bottom;
skipRatio = false;
if ((this.options.ticks.fontSize + 4) * this.ticks.length > (this.width - (this.paddingLeft + this.paddingRight))) {
@ -466,15 +478,15 @@
this.ctx.font = helpers.fontString(this.options.scaleLabel.fontSize, this.options.scaleLabel.fontStyle, this.options.scaleLabel.fontFamily);
scaleLabelX = this.left + ((this.right - this.left) / 2); // midpoint of the width
scaleLabelY = this.options.position == 'bottom' ? this.bottom - (this.options.scaleLabel.fontSize / 2) : this.top + (this.options.scaleLabel.fontSize / 2);
scaleLabelY = this.options.position === 'bottom' ? this.bottom - (this.options.scaleLabel.fontSize / 2) : this.top + (this.options.scaleLabel.fontSize / 2);
this.ctx.fillText(this.options.scaleLabel.labelString, scaleLabelX, scaleLabelY);
}
} else {
setContextLineSettings = true;
var xTickStart = this.options.position == "right" ? this.left : this.right - 5;
var xTickEnd = this.options.position == "right" ? this.left + 5 : this.right;
var xTickStart = this.options.position === "right" ? this.left : this.right - 5;
var xTickEnd = this.options.position === "right" ? this.left + 5 : this.right;
helpers.each(this.ticks, function(label, index) {
// If the callback returned a null or undefined value, do not draw this line
@ -522,7 +534,7 @@
this.ctx.save();
if (this.options.position == "left") {
if (this.options.position === "left") {
if (this.options.ticks.mirror) {
xLabelValue = this.right + this.options.ticks.padding;
this.ctx.textAlign = "left";
@ -553,9 +565,9 @@
if (this.options.scaleLabel.display) {
// Draw the scale label
scaleLabelX = this.options.position == 'left' ? this.left + (this.options.scaleLabel.fontSize / 2) : this.right - (this.options.scaleLabel.fontSize / 2);
scaleLabelX = this.options.position === 'left' ? this.left + (this.options.scaleLabel.fontSize / 2) : this.right - (this.options.scaleLabel.fontSize / 2);
scaleLabelY = this.top + ((this.bottom - this.top) / 2);
var rotation = this.options.position == 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
var rotation = this.options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
this.ctx.save();
this.ctx.translate(scaleLabelX, scaleLabelY);

View File

@ -5,9 +5,6 @@
Chart = root.Chart,
helpers = Chart.helpers;
// The scale service is used to resize charts along with all of their axes. We make this as
// a service where scales are registered with their respective charts so that changing the
// scales does not require
Chart.scaleService = {
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
// use the new chart options to grab the correct scale
@ -28,302 +25,12 @@
// Return the scale defaults merged with the global settings so that we always use the latest ones
return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
},
// The interesting function
update: function(chartInstance, width, height) {
var xPadding = width > 30 ? 5 : 2;
var yPadding = height > 30 ? 5 : 2;
if (chartInstance) {
var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
return scaleInstance.options.position == "left";
});
var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
return scaleInstance.options.position == "right";
});
var topScales = helpers.where(chartInstance.scales, function(scaleInstance) {
return scaleInstance.options.position == "top";
});
var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
return scaleInstance.options.position == "bottom";
});
// Scales that overlay the chartarea such as the radialLinear scale
var chartAreaScales = helpers.where(chartInstance.scales, function(scaleInstance) {
return scaleInstance.options.position == "chartArea";
});
// Essentially we now have any number of scales on each of the 4 sides.
// Our canvas looks like the following.
// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
// B1 is the bottom axis
// |------------------------------------------------------|
// | | T1 | |
// |----|-----|-------------------------------------|-----|
// | | | | |
// | L1 | L2 | Chart area | R1 |
// | | | | |
// | | | | |
// |----|-----|-------------------------------------|-----|
// | | B1 | |
// | | | |
// |------------------------------------------------------|
// What we do to find the best sizing, we do the following
// 1. Determine the minimum size of the chart area.
// 2. Split the remaining width equally between each vertical axis
// 3. Split the remaining height equally between each horizontal axis
// 4. Give each scale the maximum size it can be. The scale will return it's minimum size
// 5. Adjust the sizes of each axis based on it's minimum reported size.
// 6. Refit each axis
// 7. Position each axis in the final location
// 8. Tell the chart the final location of the chart area
// 9. Tell any axes that overlay the chart area the positions of the chart area
// Step 1
var chartWidth = width / 2; // min 50%
var chartHeight = height / 2; // min 50%
chartWidth -= (2 * xPadding);
chartHeight -= (2 * yPadding);
// Step 2
var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
// Step 3
var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length);
// Step 4;
var maxChartHeight = height - (2 * yPadding);
var maxChartWidth = width - (2 * xPadding);
var minimumScaleSizes = [];
var verticalScaleMinSizeFunction = function(scaleInstance) {
var minSize = scaleInstance.update(verticalScaleWidth, chartHeight);
minimumScaleSizes.push({
horizontal: false,
minSize: minSize,
scale: scaleInstance,
});
maxChartWidth -= minSize.width;
};
var horizontalScaleMinSizeFunction = function(scaleInstance) {
var minSize = scaleInstance.update(maxChartWidth, horizontalScaleHeight);
minimumScaleSizes.push({
horizontal: true,
minSize: minSize,
scale: scaleInstance,
});
maxChartHeight -= minSize.height;
};
// vertical scales
helpers.each(leftScales, verticalScaleMinSizeFunction);
helpers.each(rightScales, verticalScaleMinSizeFunction);
// horizontal scales
helpers.each(topScales, horizontalScaleMinSizeFunction);
helpers.each(bottomScales, horizontalScaleMinSizeFunction);
// At this point, maxChartHeight and maxChartWidth are the size the chart area could
// be if the axes are drawn at their minimum sizes.
// Steps 5 & 6
var verticalScaleFitFunction = function(scaleInstance) {
var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
return wrapper.scale === scaleInstance;
});
if (wrapper) {
scaleInstance.update(wrapper.minSize.width, maxChartHeight);
}
};
var horizontalScaleFitFunction = function(scaleInstance) {
var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
return wrapper.scale === scaleInstance;
});
var scaleMargin = {
left: totalLeftWidth,
right: totalRightWidth,
top: 0,
bottom: 0,
};
if (wrapper) {
scaleInstance.update(maxChartWidth, wrapper.minSize.height, scaleMargin);
}
};
var totalLeftWidth = xPadding;
var totalRightWidth = xPadding;
var totalTopHeight = yPadding;
var totalBottomHeight = yPadding;
helpers.each(leftScales, verticalScaleFitFunction);
helpers.each(rightScales, verticalScaleFitFunction);
// Figure out how much margin is on the left and right of the horizontal axes
helpers.each(leftScales, function(scaleInstance) {
totalLeftWidth += scaleInstance.width;
});
helpers.each(rightScales, function(scaleInstance) {
totalRightWidth += scaleInstance.width;
});
helpers.each(topScales, horizontalScaleFitFunction);
helpers.each(bottomScales, horizontalScaleFitFunction);
helpers.each(topScales, function(scaleInstance) {
totalTopHeight += scaleInstance.height;
});
helpers.each(bottomScales, function(scaleInstance) {
totalBottomHeight += scaleInstance.height;
});
// Let the left scale know the final margin
helpers.each(leftScales, function(scaleInstance) {
var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
return wrapper.scale === scaleInstance;
});
var scaleMargin = {
left: 0,
right: 0,
top: totalTopHeight,
bottom: totalBottomHeight
};
if (wrapper) {
scaleInstance.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
}
});
helpers.each(rightScales, function(scaleInstance) {
var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
return wrapper.scale === scaleInstance;
});
var scaleMargin = {
left: 0,
right: 0,
top: totalTopHeight,
bottom: totalBottomHeight
};
if (wrapper) {
scaleInstance.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
}
});
// Recalculate because the size of each scale might have changed slightly due to the margins (label rotation for instance)
totalLeftWidth = xPadding;
totalRightWidth = xPadding;
totalTopHeight = yPadding;
totalBottomHeight = yPadding;
helpers.each(leftScales, function(scaleInstance) {
totalLeftWidth += scaleInstance.width;
});
helpers.each(rightScales, function(scaleInstance) {
totalRightWidth += scaleInstance.width;
});
helpers.each(topScales, function(scaleInstance) {
totalTopHeight += scaleInstance.height;
});
helpers.each(bottomScales, function(scaleInstance) {
totalBottomHeight += scaleInstance.height;
});
// Figure out if our chart area changed. This would occur if the dataset scale label rotation
// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
// without calling `fit` again
var newMaxChartHeight = height - totalTopHeight - totalBottomHeight;
var newMaxChartWidth = width - totalLeftWidth - totalRightWidth;
if (newMaxChartWidth !== maxChartWidth || newMaxChartHeight !== maxChartHeight) {
helpers.each(leftScales, function(scale) {
scale.height = newMaxChartHeight;
});
helpers.each(rightScales, function(scale) {
scale.height = newMaxChartHeight;
});
helpers.each(topScales, function(scale) {
scale.width = newMaxChartWidth;
});
helpers.each(bottomScales, function(scale) {
scale.width = newMaxChartWidth;
});
maxChartHeight = newMaxChartHeight;
maxChartWidth = newMaxChartWidth;
}
// Step 7
// Position the scales
var left = xPadding;
var top = yPadding;
var right = 0;
var bottom = 0;
var verticalScalePlacer = function(scaleInstance) {
scaleInstance.left = left;
scaleInstance.right = left + scaleInstance.width;
scaleInstance.top = totalTopHeight;
scaleInstance.bottom = totalTopHeight + maxChartHeight;
// Move to next point
left = scaleInstance.right;
};
var horizontalScalePlacer = function(scaleInstance) {
scaleInstance.left = totalLeftWidth;
scaleInstance.right = totalLeftWidth + maxChartWidth;
scaleInstance.top = top;
scaleInstance.bottom = top + scaleInstance.height;
// Move to next point
top = scaleInstance.bottom;
};
helpers.each(leftScales, verticalScalePlacer);
helpers.each(topScales, horizontalScalePlacer);
// Account for chart width and height
left += maxChartWidth;
top += maxChartHeight;
helpers.each(rightScales, verticalScalePlacer);
helpers.each(bottomScales, horizontalScalePlacer);
// Step 8
chartInstance.chartArea = {
left: totalLeftWidth,
top: totalTopHeight,
right: totalLeftWidth + maxChartWidth,
bottom: totalTopHeight + maxChartHeight,
};
// Step 9
helpers.each(chartAreaScales, function(scaleInstance) {
scaleInstance.left = chartInstance.chartArea.left;
scaleInstance.top = chartInstance.chartArea.top;
scaleInstance.right = chartInstance.chartArea.right;
scaleInstance.bottom = chartInstance.chartArea.bottom;
scaleInstance.update(maxChartWidth, maxChartHeight);
});
}
}
addScalesToLayout: function(chartInstance) {
// Adds each scale to the chart.boxes array to be sized accordingly
helpers.each(chartInstance.scales, function(scale) {
Chart.layoutService.addBox(chartInstance, scale);
});
},
};

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

@ -0,0 +1,195 @@
(function() {
"use strict";
var root = this,
Chart = root.Chart,
helpers = Chart.helpers;
Chart.defaults.global.title = {
display: false,
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.global.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);

View File

@ -161,7 +161,7 @@
}
}, this);
if (this._loop) {
if (this._loop && this._children.length > 0) {
loopBackToStart();
}

View File

@ -62,9 +62,11 @@ describe('Doughnut controller tests', function() {
it ('Should reset and update elements', function() {
var chart = {
chart: {
width: 100,
height: 200,
chartArea: {
left: 0,
top: 0,
right: 100,
bottom: 200,
},
data: {
datasets: [{
@ -232,9 +234,11 @@ describe('Doughnut controller tests', function() {
it ('should draw all arcs', function() {
var chart = {
chart: {
width: 100,
height: 200,
chartArea: {
left: 0,
top: 0,
right: 100,
bottom: 200,
},
data: {
datasets: [{
@ -279,9 +283,11 @@ describe('Doughnut controller tests', function() {
it ('should set the hover style of an arc', function() {
var chart = {
chart: {
width: 100,
height: 200,
chartArea: {
left: 0,
top: 0,
right: 100,
bottom: 200,
},
data: {
datasets: [{
@ -358,9 +364,11 @@ describe('Doughnut controller tests', function() {
it ('should unset the hover style of an arc', function() {
var chart = {
chart: {
width: 100,
height: 200,
chartArea: {
left: 0,
top: 0,
right: 100,
bottom: 200,
},
data: {
datasets: [{

View File

@ -342,7 +342,7 @@ describe('Line controller tests', function() {
});
// Use dataset level styles for lines & points
chart.data.datasets[0].tension = 0.2;
chart.data.datasets[0].tension = 0;
chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)';
chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)';
chart.data.datasets[0].borderWidth = 0.55;
@ -370,7 +370,7 @@ describe('Line controller tests', function() {
borderJoinStyle: 'miter',
borderWidth: 0.55,
fill: false,
tension: 0.2,
tension: 0,
scaleTop: 0,
scaleBottom: 200,
@ -384,7 +384,7 @@ describe('Line controller tests', function() {
hitRadius: 3.3,
radius: 22,
skip: false,
tension: 0.2,
tension: 0,
// Point
x: 81,
@ -393,8 +393,8 @@ describe('Line controller tests', function() {
// Control points
controlPointPreviousX: 81,
controlPointPreviousY: 62,
controlPointNextX: 91,
controlPointNextY: 52.6,
controlPointNextX: 81,
controlPointNextY: 62,
});
expect(chart.data.datasets[0].metaData[1]._model).toEqual({
@ -404,17 +404,17 @@ describe('Line controller tests', function() {
hitRadius: 3.3,
radius: 22,
skip: false,
tension: 0.2,
tension: 0,
// Point
x: 131,
y: 15,
// Control points
controlPointPreviousX: 124.65778768378175,
controlPointPreviousY: 9.097346953222619,
controlPointNextX: 144.85778768378177,
controlPointNextY: 27.897346953222623,
controlPointPreviousX: 131,
controlPointPreviousY: 15,
controlPointNextX: 131,
controlPointNextY: 15,
});
expect(chart.data.datasets[0].metaData[2]._model).toEqual({
@ -424,17 +424,17 @@ describe('Line controller tests', function() {
hitRadius: 3.3,
radius: 22,
skip: false,
tension: 0.2,
tension: 0,
// Point
x: 182,
y: 156,
// Control points
controlPointPreviousX: 167.76304506745115,
controlPointPreviousY: 130.76816898092827,
controlPointNextX: 187.96304506745116,
controlPointNextY: 166.56816898092828,
controlPointPreviousX: 182,
controlPointPreviousY: 156,
controlPointNextX: 182,
controlPointNextY: 156,
});
expect(chart.data.datasets[0].metaData[3]._model).toEqual({
@ -444,15 +444,15 @@ describe('Line controller tests', function() {
hitRadius: 3.3,
radius: 22,
skip: false,
tension: 0.2,
tension: 0,
// Point
x: 232,
y: 194,
// Control points
controlPointPreviousX: 222,
controlPointPreviousY: 186.4,
controlPointPreviousX: 232,
controlPointPreviousY: 194,
controlPointNextX: 232,
controlPointNextY: 194,
});

View File

@ -1,8 +1,8 @@
// Tests of the scale service
describe('Test the scale service', function() {
describe('Test the layout service', function() {
it('should fit a simple chart with 2 scales', function() {
var chartInstance = {
scales: [],
boxes: [],
};
var xScaleID = 'xScale';
@ -38,12 +38,12 @@ describe('Test the scale service', function() {
id: yScaleID
});
chartInstance.scales.push(xScale);
chartInstance.scales.push(yScale);
chartInstance.boxes.push(xScale);
chartInstance.boxes.push(yScale);
var canvasWidth = 250;
var canvasHeight = 150;
Chart.scaleService.update(chartInstance, canvasWidth, canvasHeight);
Chart.layoutService.update(chartInstance, canvasWidth, canvasHeight);
expect(chartInstance.chartArea).toEqual({
left: 55,
@ -68,7 +68,7 @@ describe('Test the scale service', function() {
it('should fit scales that are in the top and right positions', function() {
var chartInstance = {
scales: [],
boxes: [],
};
var xScaleID = 'xScale';
@ -106,12 +106,12 @@ describe('Test the scale service', function() {
id: yScaleID
});
chartInstance.scales.push(xScale);
chartInstance.scales.push(yScale);
chartInstance.boxes.push(xScale);
chartInstance.boxes.push(yScale);
var canvasWidth = 250;
var canvasHeight = 150;
Chart.scaleService.update(chartInstance, canvasWidth, canvasHeight);
Chart.layoutService.update(chartInstance, canvasWidth, canvasHeight);
expect(chartInstance.chartArea).toEqual({
left: 5,
@ -136,7 +136,7 @@ describe('Test the scale service', function() {
it('should fit multiple axes in the same position', function() {
var chartInstance = {
scales: [],
boxes: [],
};
var xScaleID = 'xScale';
@ -184,13 +184,13 @@ describe('Test the scale service', function() {
id: yScaleID2
});
chartInstance.scales.push(xScale);
chartInstance.scales.push(yScale1);
chartInstance.scales.push(yScale2);
chartInstance.boxes.push(xScale);
chartInstance.boxes.push(yScale1);
chartInstance.boxes.push(yScale2);
var canvasWidth = 250;
var canvasHeight = 150;
Chart.scaleService.update(chartInstance, canvasWidth, canvasHeight);
Chart.layoutService.update(chartInstance, canvasWidth, canvasHeight);
expect(chartInstance.chartArea).toEqual({
left: 115,
@ -223,7 +223,7 @@ describe('Test the scale service', function() {
// due to the lack of label rotation
it('should fit scales that overlap the chart area', function() {
var chartInstance = {
scales: [],
boxes: [],
};
var scaleID = 'scaleID';
@ -250,11 +250,11 @@ describe('Test the scale service', function() {
id: scaleID
});
chartInstance.scales.push(scale);
chartInstance.boxes.push(scale);
var canvasWidth = 300;
var canvasHeight = 350;
Chart.scaleService.update(chartInstance, canvasWidth, canvasHeight);
Chart.layoutService.update(chartInstance, canvasWidth, canvasHeight);
expect(chartInstance.chartArea).toEqual({
left: 5,
@ -270,4 +270,91 @@ describe('Test the scale service', function() {
expect(scale.width).toBe(290);
expect(scale.height).toBe(340)
});
it ('should fix a full width box correctly', function() {
var chartInstance = {
boxes: [],
};
var xScaleID1= 'xScale1';
var xScaleID2 = 'xScale2';
var yScaleID = 'yScale2';
var mockData = {
datasets: [{
xAxisID: xScaleID1,
data: [10, 5, 0, 25, 78, -10]
}, {
xAxisID: xScaleID2,
data: [-19, -20, 0, -99, -50, 0]
}],
labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']
};
var mockContext = window.createMockContext();
var xScaleConfig = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category'));
var XConstructor = Chart.scaleService.getScaleConstructor('category');
var xScale1 = new XConstructor({
ctx: mockContext,
options: xScaleConfig,
chart: {
data: mockData
},
id: xScaleID1
});
var xScale2 = new XConstructor({
ctx: mockContext,
options: Chart.helpers.extend(Chart.helpers.clone(xScaleConfig), {
position: 'top',
fullWidth: true
}),
chart: {
data: mockData,
},
id: xScaleID2
});
var yScaleConfig = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('linear'));
var YConstructor = Chart.scaleService.getScaleConstructor('linear');
var yScale = new YConstructor({
ctx: mockContext,
options: yScaleConfig,
chart: {
data: mockData
},
id: yScaleID
});
chartInstance.boxes.push(xScale1);
chartInstance.boxes.push(xScale2);
chartInstance.boxes.push(yScale);
var canvasWidth = 250;
var canvasHeight = 150;
Chart.layoutService.update(chartInstance, canvasWidth, canvasHeight);
expect(chartInstance.chartArea).toEqual({
left: 45,
right: 245,
top: 45,
bottom: 105,
});
// Are xScales at the right spot
expect(xScale1.left).toBe(45);
expect(xScale1.right).toBe(245);
expect(xScale1.top).toBe(105);
expect(xScale1.bottom).toBe(145);
expect(xScale2.left).toBe(5);
expect(xScale2.right).toBe(245);
expect(xScale2.top).toBe(5);
expect(xScale2.bottom).toBe(45);
// Is yScale at the right spot
expect(yScale.left).toBe(5);
expect(yScale.right).toBe(45);
expect(yScale.top).toBe(45);
expect(yScale.bottom).toBe(105);
});
});