mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Merge pull request #1132 from etimberg/feature/v2.0dev-xy
Scale rewrite for v2.0
This commit is contained in:
commit
48dd1cf024
132
samples/bar-multi-axis.html
Normal file
132
samples/bar-multi-axis.html
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Bar Chart Multi Axis</title>
|
||||||
|
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||||
|
<script src="../Chart.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="width: 50%">
|
||||||
|
<canvas id="canvas" height="450" width="600"></canvas>
|
||||||
|
</div>
|
||||||
|
<button id="randomizeData">Randomize Data</button>
|
||||||
|
<script>
|
||||||
|
var randomScalingFactor = function() {
|
||||||
|
return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
|
||||||
|
};
|
||||||
|
var randomColorFactor = function() {
|
||||||
|
return Math.round(Math.random() * 255);
|
||||||
|
};
|
||||||
|
|
||||||
|
var barChartData = {
|
||||||
|
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Dataset 1',
|
||||||
|
backgroundColor: "rgba(220,220,220,0.5)",
|
||||||
|
yAxisID: "y-axis-1",
|
||||||
|
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()]
|
||||||
|
}, {
|
||||||
|
label: 'Dataset 2',
|
||||||
|
backgroundColor: "rgba(151,187,205,0.5)",
|
||||||
|
yAxisID: "y-axis-2",
|
||||||
|
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()]
|
||||||
|
}, {
|
||||||
|
label: 'Dataset 3',
|
||||||
|
backgroundColor: "rgba(151,187,205,0.5)",
|
||||||
|
yAxisID: "y-axis-1",
|
||||||
|
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()]
|
||||||
|
}]
|
||||||
|
|
||||||
|
};
|
||||||
|
window.onload = function() {
|
||||||
|
var ctx = document.getElementById("canvas").getContext("2d");
|
||||||
|
window.myBar = new Chart(ctx).Bar({
|
||||||
|
data: barChartData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
hoverMode: 'label',
|
||||||
|
hoverAnimationDuration: 400,
|
||||||
|
stacked: false,
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "left",
|
||||||
|
id: "y-axis-1",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "right",
|
||||||
|
id: "y-axis-2",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
||||||
|
drawTicks: false,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#randomizeData').click(function() {
|
||||||
|
$.each(barChartData.datasets, function(i, dataset) {
|
||||||
|
dataset.backgroundColor = 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',.7)';
|
||||||
|
dataset.data = [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()];
|
||||||
|
|
||||||
|
});
|
||||||
|
window.myBar.update();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -8,7 +8,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div style="width: 100%">
|
<div style="width: 50%">
|
||||||
<canvas id="canvas" height="450" width="600"></canvas>
|
<canvas id="canvas" height="450" width="600"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<button id="randomizeData">Randomize Data</button>
|
<button id="randomizeData">Randomize Data</button>
|
||||||
@ -47,12 +47,15 @@
|
|||||||
};
|
};
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
var ctx = document.getElementById("canvas").getContext("2d");
|
var ctx = document.getElementById("canvas").getContext("2d");
|
||||||
window.myBar = new Chart(ctx).Bar(barChartData, {
|
window.myBar = new Chart(ctx).Bar({
|
||||||
responsive: true,
|
data: barChartData,
|
||||||
hoverMode: 'label',
|
options: {
|
||||||
scaleBeginAtZero: false,
|
responsive: true,
|
||||||
hoverAnimationDuration: 400,
|
hoverMode: 'label',
|
||||||
stacked: true,
|
scaleBeginAtZero: false,
|
||||||
|
hoverAnimationDuration: 400,
|
||||||
|
stacked: true,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,12 @@
|
|||||||
|
|
||||||
window.onload = function(){
|
window.onload = function(){
|
||||||
var ctx = document.getElementById("chart-area").getContext("2d");
|
var ctx = document.getElementById("chart-area").getContext("2d");
|
||||||
window.myDoughnut = new Chart(ctx).Doughnut(doughnutData, {responsive : true});
|
window.myDoughnut = new Chart(ctx).Doughnut({
|
||||||
|
data: doughnutData,
|
||||||
|
options: {
|
||||||
|
responsive : true
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -58,8 +58,11 @@
|
|||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
var ctx = document.getElementById("chart-area").getContext("2d");
|
var ctx = document.getElementById("chart-area").getContext("2d");
|
||||||
window.myDoughnut = new Chart(ctx).Doughnut(doughnutData, {
|
window.myDoughnut = new Chart(ctx).Doughnut({
|
||||||
responsive: true
|
data: doughnutData,
|
||||||
|
options: {
|
||||||
|
responsive: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -112,10 +112,13 @@
|
|||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
var ctx1 = document.getElementById("chart1").getContext("2d");
|
var ctx1 = document.getElementById("chart1").getContext("2d");
|
||||||
window.myLine = new Chart(ctx1).Line(lineChartData, {
|
window.myLine = new Chart(ctx1).Line({
|
||||||
showScale: false,
|
data: lineChartData,
|
||||||
pointDot : true,
|
options: {
|
||||||
responsive: true
|
showScale: false,
|
||||||
|
pointDot : true,
|
||||||
|
responsive: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var ctx2 = document.getElementById("chart2").getContext("2d");
|
var ctx2 = document.getElementById("chart2").getContext("2d");
|
||||||
|
|||||||
140
samples/line-multi-axis.html
Normal file
140
samples/line-multi-axis.html
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Line Chart Multiple Axes</title>
|
||||||
|
<script src="../Chart.js"></script>
|
||||||
|
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="width:50%">
|
||||||
|
<div>
|
||||||
|
<canvas id="canvas" height="450" width="600"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="randomizeData">Randomize Data</button>
|
||||||
|
<script>
|
||||||
|
var randomScalingFactor = function() {
|
||||||
|
return Math.round(Math.random() * 100 * (Math.random() > 0.5 ? -1 : 1));
|
||||||
|
};
|
||||||
|
var randomColor = function(opacity) {
|
||||||
|
return 'rgba(' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + (opacity || '.3') + ')';
|
||||||
|
};
|
||||||
|
|
||||||
|
var lineChartData = {
|
||||||
|
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||||
|
datasets: [{
|
||||||
|
label: "My First dataset",
|
||||||
|
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
|
||||||
|
yAxisID: "y-axis-1",
|
||||||
|
}, {
|
||||||
|
label: "My Second dataset",
|
||||||
|
data: [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()],
|
||||||
|
yAxisID: "y-axis-2"
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
$.each(lineChartData.datasets, function(i, dataset) {
|
||||||
|
dataset.borderColor = randomColor(0.4);
|
||||||
|
dataset.backgroundColor = randomColor(1);
|
||||||
|
dataset.pointBorderColor = randomColor(0.7);
|
||||||
|
dataset.pointBackgroundColor = randomColor(0.5);
|
||||||
|
dataset.pointBorderWidth = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(lineChartData);
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
var ctx = document.getElementById("canvas").getContext("2d");
|
||||||
|
window.myLine = new Chart(ctx).Line({
|
||||||
|
data: lineChartData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
hoverMode: 'label',
|
||||||
|
stacked: false,
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
gridLines: {
|
||||||
|
offsetGridLines: false
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "left",
|
||||||
|
id: "y-axis-1",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "right",
|
||||||
|
id: "y-axis-2",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
||||||
|
drawTicks: false,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#randomizeData').click(function() {
|
||||||
|
lineChartData.datasets[0].data = [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()];
|
||||||
|
|
||||||
|
lineChartData.datasets[1].data = [randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor(), randomScalingFactor()];
|
||||||
|
|
||||||
|
window.myLine.update();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -8,7 +8,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div style="width:100%">
|
<div style="width:50%">
|
||||||
<div>
|
<div>
|
||||||
<canvas id="canvas" height="450" width="600"></canvas>
|
<canvas id="canvas" height="450" width="600"></canvas>
|
||||||
</div>
|
</div>
|
||||||
@ -45,10 +45,20 @@
|
|||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
var ctx = document.getElementById("canvas").getContext("2d");
|
var ctx = document.getElementById("canvas").getContext("2d");
|
||||||
window.myLine = new Chart(ctx).Line(lineChartData, {
|
window.myLine = new Chart(ctx).Line({
|
||||||
responsive: true,
|
data: lineChartData,
|
||||||
hoverMode: 'label',
|
options: {
|
||||||
stacked: true
|
responsive: true,
|
||||||
|
hoverMode: 'label',
|
||||||
|
stacked: false,
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
gridLines: {
|
||||||
|
offsetGridLines: false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -145,10 +145,14 @@
|
|||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
var ctx1 = document.getElementById("chart-area1").getContext("2d");
|
var ctx1 = document.getElementById("chart-area1").getContext("2d");
|
||||||
window.myPie = new Chart(ctx1).Pie(pieData);
|
window.myPie = new Chart(ctx1).Pie({
|
||||||
|
data: pieData
|
||||||
|
});
|
||||||
|
|
||||||
var ctx2 = document.getElementById("chart-area2").getContext("2d");
|
var ctx2 = document.getElementById("chart-area2").getContext("2d");
|
||||||
window.myPie = new Chart(ctx2).Pie(pieData);
|
window.myPie = new Chart(ctx2).Pie({
|
||||||
|
data: pieData
|
||||||
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -52,7 +52,9 @@
|
|||||||
|
|
||||||
window.onload = function(){
|
window.onload = function(){
|
||||||
var ctx = document.getElementById("chart-area").getContext("2d");
|
var ctx = document.getElementById("chart-area").getContext("2d");
|
||||||
window.myPie = new Chart(ctx).Pie(pieData);
|
window.myPie = new Chart(ctx).Pie({
|
||||||
|
data: pieData
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$('#randomizeData').click(function(){
|
$('#randomizeData').click(function(){
|
||||||
|
|||||||
@ -53,8 +53,11 @@
|
|||||||
|
|
||||||
window.onload = function(){
|
window.onload = function(){
|
||||||
var ctx = document.getElementById("chart-area").getContext("2d");
|
var ctx = document.getElementById("chart-area").getContext("2d");
|
||||||
window.myPolarArea = new Chart(ctx).PolarArea(polarData, {
|
window.myPolarArea = new Chart(ctx).PolarArea({
|
||||||
responsive:true
|
data: polarData,
|
||||||
|
options: {
|
||||||
|
responsive:true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -44,8 +44,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.onload = function(){
|
window.onload = function(){
|
||||||
window.myRadar = new Chart(document.getElementById("canvas").getContext("2d")).Radar(radarChartData, {
|
window.myRadar = new Chart(document.getElementById("canvas").getContext("2d")).Radar({
|
||||||
responsive: true
|
data: radarChartData,
|
||||||
|
options: {
|
||||||
|
responsive: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
223
samples/scatter-multi-axis.html
Normal file
223
samples/scatter-multi-axis.html
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Scatter Chart Multi Axis</title>
|
||||||
|
<script src="../Chart.js"></script>
|
||||||
|
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="width:50%">
|
||||||
|
<div>
|
||||||
|
<canvas id="canvas" height="450" width="600"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="randomizeData">Randomize Data</button>
|
||||||
|
<script>
|
||||||
|
var randomScalingFactor = function() {
|
||||||
|
return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
|
||||||
|
};
|
||||||
|
var randomColor = function(opacity) {
|
||||||
|
return 'rgba(' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + (opacity || '.3') + ')';
|
||||||
|
};
|
||||||
|
|
||||||
|
var scatterChartData = {
|
||||||
|
datasets: [{
|
||||||
|
label: "My First dataset",
|
||||||
|
xAxisID: "x-axis-1",
|
||||||
|
yAxisID: "y-axis-1",
|
||||||
|
data: [{
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: "My Second dataset",
|
||||||
|
xAxisID: "x-axis-1",
|
||||||
|
yAxisID: "y-axis-2",
|
||||||
|
data: [{
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
$.each(scatterChartData.datasets, function(i, dataset) {
|
||||||
|
dataset.borderColor = randomColor(0.4);
|
||||||
|
dataset.backgroundColor = randomColor(0.1);
|
||||||
|
dataset.pointBorderColor = randomColor(0.7);
|
||||||
|
dataset.pointBackgroundColor = randomColor(0.5);
|
||||||
|
dataset.pointBorderWidth = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(scatterChartData);
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
var ctx = document.getElementById("canvas").getContext("2d");
|
||||||
|
window.myScatter = new Chart(ctx).Scatter({
|
||||||
|
data: scatterChartData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
hoverMode: 'single',
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
position: "bottom",
|
||||||
|
gridLines: {
|
||||||
|
zeroLineColor: "rgba(0,0,0,1)"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "left",
|
||||||
|
id: "y-axis-1",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "right",
|
||||||
|
id: "y-axis-2",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: false, // only want the grid lines for one axis to show up
|
||||||
|
drawTicks: false,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#randomizeData').click(function() {
|
||||||
|
scatterChartData.datasets[0].data = [{
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}];
|
||||||
|
scatterChartData.datasets[1].data = [{
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}]
|
||||||
|
window.myScatter.update();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
155
samples/scatter.html
Normal file
155
samples/scatter.html
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Scatter Chart</title>
|
||||||
|
<script src="../Chart.js"></script>
|
||||||
|
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="width:50%">
|
||||||
|
<div>
|
||||||
|
<canvas id="canvas" height="450" width="600"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="randomizeData">Randomize Data</button>
|
||||||
|
<script>
|
||||||
|
var randomScalingFactor = function() {
|
||||||
|
return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
|
||||||
|
};
|
||||||
|
var randomColor = function(opacity) {
|
||||||
|
return 'rgba(' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + (opacity || '.3') + ')';
|
||||||
|
};
|
||||||
|
|
||||||
|
var scatterChartData = {
|
||||||
|
datasets: [{
|
||||||
|
label: "My First dataset",
|
||||||
|
data: [{
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
label: "My Second dataset",
|
||||||
|
data: [{
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
$.each(scatterChartData.datasets, function(i, dataset) {
|
||||||
|
dataset.borderColor = randomColor(0.4);
|
||||||
|
dataset.backgroundColor = randomColor(0.1);
|
||||||
|
dataset.pointBorderColor = randomColor(0.7);
|
||||||
|
dataset.pointBackgroundColor = randomColor(0.5);
|
||||||
|
dataset.pointBorderWidth = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(scatterChartData);
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
var ctx = document.getElementById("canvas").getContext("2d");
|
||||||
|
window.myScatter = new Chart(ctx).Scatter({
|
||||||
|
data: scatterChartData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
hoverMode: 'single', // should always use single for a scatter chart
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
gridLines: {
|
||||||
|
zeroLineColor: "rgba(0,0,0,1)"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#randomizeData').click(function() {
|
||||||
|
scatterChartData.datasets[0].data = [{
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}];
|
||||||
|
scatterChartData.datasets[1].data = [{
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}, {
|
||||||
|
x: randomScalingFactor(),
|
||||||
|
y: randomScalingFactor(),
|
||||||
|
}]
|
||||||
|
window.myScatter.update();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
493
src/Chart.Bar.js
493
src/Chart.Bar.js
@ -7,23 +7,73 @@
|
|||||||
|
|
||||||
|
|
||||||
var defaultConfig = {
|
var defaultConfig = {
|
||||||
//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
|
scales: {
|
||||||
scaleBeginAtZero: true,
|
xAxes: [{
|
||||||
|
scaleType: "dataset", // scatter should not use a dataset axis
|
||||||
|
display: true,
|
||||||
|
position: "bottom",
|
||||||
|
id: "x-axis-1", // need an ID so datasets can reference the scale
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
offsetGridLines: true,
|
||||||
|
},
|
||||||
|
|
||||||
//Boolean - Whether grid lines are shown across the chart
|
// scale numbers
|
||||||
scaleShowGridLines: true,
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
//String - Colour of the grid lines
|
// label settings
|
||||||
scaleGridLineColor: "rgba(0,0,0,.05)",
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "left",
|
||||||
|
id: "y-axis-1",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true, // draw ticks extending towards the label
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
//Number - Width of the grid lines
|
// scale numbers
|
||||||
scaleGridLineWidth: 1,
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
//Boolean - Whether to show horizontal lines (except X axis)
|
// label settings
|
||||||
scaleShowHorizontalLines: true,
|
labels: {
|
||||||
|
show: true,
|
||||||
//Boolean - Whether to show vertical lines (except Y axis)
|
template: "<%=value%>",
|
||||||
scaleShowVerticalLines: true,
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
|
||||||
//Number - Pixel width of the bar border
|
//Number - Pixel width of the bar border
|
||||||
barBorderWidth: 2,
|
barBorderWidth: 2,
|
||||||
@ -46,144 +96,44 @@
|
|||||||
Chart.Type.extend({
|
Chart.Type.extend({
|
||||||
name: "Bar",
|
name: "Bar",
|
||||||
defaults: defaultConfig,
|
defaults: defaultConfig,
|
||||||
initialize: function(data) {
|
initialize: function() {
|
||||||
|
|
||||||
// Save data as a source for updating of values & methods
|
|
||||||
this.data = data;
|
|
||||||
|
|
||||||
var options = this.options;
|
|
||||||
|
|
||||||
var _this = this;
|
|
||||||
|
|
||||||
// Custom Scale Methods and Options
|
|
||||||
this.ScaleClass = Chart.Scale.extend({
|
|
||||||
offsetGridLines: true,
|
|
||||||
calculateBarBase: function(datasetIndex, index) {
|
|
||||||
|
|
||||||
var base = 0;
|
|
||||||
|
|
||||||
if (_this.options.stacked) {
|
|
||||||
var bar = _this.data.datasets[datasetIndex].metaData[index];
|
|
||||||
if (bar.value < 0) {
|
|
||||||
for (var i = 0; i < datasetIndex; i++) {
|
|
||||||
base += _this.data.datasets[i].metaData[index].value < base ? _this.data.datasets[i].metaData[index].value : 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (var i = 0; i < datasetIndex; i++) {
|
|
||||||
base += _this.data.datasets[i].metaData[index].value > base ? _this.data.datasets[i].metaData[index].value : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.calculateY(base);
|
|
||||||
}
|
|
||||||
|
|
||||||
base = this.endPoint;
|
|
||||||
|
|
||||||
if (this.beginAtZero || ((this.min <= 0 && this.max >= 0) || (this.min >= 0 && this.max <= 0))) {
|
|
||||||
base = this.calculateY(0);
|
|
||||||
base += _this.options.scaleGridLineWidth;
|
|
||||||
} else if (this.min < 0 && this.max < 0) {
|
|
||||||
// All values are negative. Use the top as the base
|
|
||||||
base = this.startPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base;
|
|
||||||
|
|
||||||
},
|
|
||||||
calculateBarX: function(datasetCount, datasetIndex, elementIndex) {
|
|
||||||
var xWidth = this.calculateBaseWidth(),
|
|
||||||
xAbsolute = this.calculateX(elementIndex) - (xWidth / 2),
|
|
||||||
barWidth = this.calculateBarWidth(datasetCount);
|
|
||||||
|
|
||||||
if (_this.options.stacked) {
|
|
||||||
return xAbsolute + barWidth / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth / 2;
|
|
||||||
},
|
|
||||||
calculateBarY: function(datasets, datasetIndex, barIndex, value) {
|
|
||||||
|
|
||||||
if (_this.options.stacked) {
|
|
||||||
|
|
||||||
var sumPos = 0,
|
|
||||||
sumNeg = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < datasetIndex; i++) {
|
|
||||||
if (datasets[i].metaData[barIndex].value < 0) {
|
|
||||||
sumNeg += datasets[i].metaData[barIndex].value || 0;
|
|
||||||
} else {
|
|
||||||
sumPos += datasets[i].metaData[barIndex].value || 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < 0) {
|
|
||||||
return this.calculateY(sumNeg + value);
|
|
||||||
} else {
|
|
||||||
return this.calculateY(sumPos + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if (options.relativeBars) {
|
|
||||||
offset = offset / sum * 100;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return this.calculateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset = 0;
|
|
||||||
|
|
||||||
for (i = datasetIndex; i < datasets.length; i++) {
|
|
||||||
if (i === datasetIndex && value) {
|
|
||||||
offset += value;
|
|
||||||
} else {
|
|
||||||
offset = offset + (datasets[i].metaData[barIndex].value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.calculateY(value);
|
|
||||||
},
|
|
||||||
calculateBaseWidth: function() {
|
|
||||||
return (this.calculateX(1) - this.calculateX(0)) - (2 * options.barValueSpacing);
|
|
||||||
},
|
|
||||||
calculateBaseHeight: function() {
|
|
||||||
return (this.calculateY(1) - this.calculateY(0));
|
|
||||||
},
|
|
||||||
calculateBarWidth: function(datasetCount) {
|
|
||||||
|
|
||||||
//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
|
|
||||||
var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
|
|
||||||
|
|
||||||
if (_this.options.stacked) {
|
|
||||||
return baseWidth;
|
|
||||||
}
|
|
||||||
return (baseWidth / datasetCount);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
helpers.bindEvents(this, this.options.tooltipEvents, this.onHover);
|
helpers.bindEvents(this, this.options.events, this.onHover);
|
||||||
|
|
||||||
//Declare the extension of the default point, to cater for the options passed in to the constructor
|
//Declare the extension of the default point, to cater for the options passed in to the constructor
|
||||||
this.BarClass = Chart.Rectangle.extend({
|
this.BarClass = Chart.Rectangle.extend({
|
||||||
ctx: this.chart.ctx,
|
ctx: this.chart.ctx,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build Scale
|
|
||||||
this.buildScale(this.data.labels);
|
|
||||||
|
|
||||||
//Create a new bar for each piece of data
|
//Create a new bar for each piece of data
|
||||||
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
||||||
dataset.metaData = [];
|
dataset.metaData = [];
|
||||||
helpers.each(dataset.data, function(dataPoint, index) {
|
helpers.each(dataset.data, function(dataPoint, index) {
|
||||||
dataset.metaData.push(new this.BarClass());
|
dataset.metaData.push(new this.BarClass());
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
// The bar chart only supports a single x axis because the x axis is always a dataset axis
|
||||||
|
dataset.xAxisID = this.options.scales.xAxes[0].id;
|
||||||
|
|
||||||
|
if (!dataset.yAxisID) {
|
||||||
|
dataset.yAxisID = this.options.scales.yAxes[0].id;
|
||||||
|
}
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
// Build and fit the scale. Needs to happen after the axis IDs have been set
|
||||||
|
this.buildScale();
|
||||||
|
Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
|
||||||
|
|
||||||
// Set defaults for bars
|
// Set defaults for bars
|
||||||
this.eachElement(function(bar, index, dataset, datasetIndex) {
|
this.eachElement(function(bar, index, dataset, datasetIndex) {
|
||||||
|
var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
|
||||||
|
var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
|
||||||
|
|
||||||
helpers.extend(bar, {
|
helpers.extend(bar, {
|
||||||
base: this.scale.zeroPoint,
|
base: yScale.getPixelForValue(0),
|
||||||
width: this.scale.calculateBarWidth(this.data.datasets.length),
|
width: xScale.calculateBarWidth(this.data.datasets.length),
|
||||||
x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index),
|
x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index),
|
||||||
y: this.scale.calculateBarY(this.data.datasets, datasetIndex, index, this.data.datasets[datasetIndex].data[index]),
|
y: yScale.calculateBarY(this.data.datasets, datasetIndex, index, this.data.datasets[datasetIndex].data[index]),
|
||||||
_datasetIndex: datasetIndex,
|
_datasetIndex: datasetIndex,
|
||||||
_index: index,
|
_index: index,
|
||||||
});
|
});
|
||||||
@ -321,22 +271,26 @@
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
update: function() {
|
update: function() {
|
||||||
|
// Update the scale sizes
|
||||||
this.scale.update();
|
Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
|
||||||
|
|
||||||
this.eachElement(function(bar, index, dataset, datasetIndex) {
|
this.eachElement(function(bar, index, dataset, datasetIndex) {
|
||||||
helpers.extend(bar, {
|
helpers.extend(bar, {
|
||||||
value: this.data.datasets[datasetIndex].data[index],
|
value: this.data.datasets[datasetIndex].data[index],
|
||||||
});
|
});
|
||||||
|
|
||||||
bar.pivot();
|
bar.pivot();
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.eachElement(function(bar, index, dataset, datasetIndex) {
|
this.eachElement(function(bar, index, dataset, datasetIndex) {
|
||||||
|
var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
|
||||||
|
var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
|
||||||
|
|
||||||
helpers.extend(bar, {
|
helpers.extend(bar, {
|
||||||
base: this.scale.calculateBarBase(datasetIndex, index),
|
base: yScale.calculateBarBase(datasetIndex, index),
|
||||||
x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index),
|
x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index),
|
||||||
y: this.scale.calculateBarY(this.data.datasets, datasetIndex, index, this.data.datasets[datasetIndex].data[index]),
|
y: yScale.calculateBarY(this.data.datasets, datasetIndex, index, this.data.datasets[datasetIndex].data[index]),
|
||||||
width: this.scale.calculateBarWidth(this.data.datasets.length),
|
width: xScale.calculateBarWidth(this.data.datasets.length),
|
||||||
label: this.data.labels[index],
|
label: this.data.labels[index],
|
||||||
datasetLabel: this.data.datasets[datasetIndex].label,
|
datasetLabel: this.data.datasets[datasetIndex].label,
|
||||||
borderColor: this.data.datasets[datasetIndex].borderColor,
|
borderColor: this.data.datasets[datasetIndex].borderColor,
|
||||||
@ -345,94 +299,214 @@
|
|||||||
_datasetIndex: datasetIndex,
|
_datasetIndex: datasetIndex,
|
||||||
_index: index,
|
_index: index,
|
||||||
});
|
});
|
||||||
|
|
||||||
bar.pivot();
|
bar.pivot();
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
buildScale: function(labels) {
|
buildScale: function(labels) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var dataTotal = function() {
|
// Function to determine the range of all the
|
||||||
var values = [];
|
var calculateYRange = function() {
|
||||||
|
this.min = null;
|
||||||
|
this.max = null;
|
||||||
|
|
||||||
|
var positiveValues = [];
|
||||||
var negativeValues = [];
|
var negativeValues = [];
|
||||||
|
|
||||||
if (self.options.stacked) {
|
if (self.options.stacked) {
|
||||||
self.eachValue(function(value, index) {
|
helpers.each(self.data.datasets, function(dataset) {
|
||||||
values[index] = values[index] || 0;
|
if (dataset.yAxisID === this.id) {
|
||||||
negativeValues[index] = negativeValues[index] || 0;
|
helpers.each(dataset.data, function(value, index) {
|
||||||
if (self.options.relativeBars) {
|
positiveValues[index] = positiveValues[index] || 0;
|
||||||
values[index] = 100;
|
negativeValues[index] = negativeValues[index] || 0;
|
||||||
} else {
|
|
||||||
if (value < 0) {
|
if (self.options.relativePoints) {
|
||||||
negativeValues[index] += value;
|
positiveValues[index] = 100;
|
||||||
|
} else {
|
||||||
|
if (value < 0) {
|
||||||
|
negativeValues[index] += value;
|
||||||
|
} else {
|
||||||
|
positiveValues[index] += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
var values = positiveValues.concat(negativeValues);
|
||||||
|
this.min = helpers.min(values);
|
||||||
|
this.max = helpers.max(values);
|
||||||
|
} else {
|
||||||
|
helpers.each(self.data.datasets, function(dataset) {
|
||||||
|
if (dataset.yAxisID === this.id) {
|
||||||
|
helpers.each(dataset.data, function(value, index) {
|
||||||
|
if (this.min === null) {
|
||||||
|
this.min = value;
|
||||||
|
} else if (value < this.min) {
|
||||||
|
this.min = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.max === null) {
|
||||||
|
this.max = value;
|
||||||
|
} else if (value > this.max) {
|
||||||
|
this.max = value;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map of scale ID to scale object so we can lookup later
|
||||||
|
this.scales = {};
|
||||||
|
|
||||||
|
// Build the x axis. The line chart only supports a single x axis
|
||||||
|
var ScaleClass = Chart.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType);
|
||||||
|
var xScale = new ScaleClass({
|
||||||
|
ctx: this.chart.ctx,
|
||||||
|
options: this.options.scales.xAxes[0],
|
||||||
|
id: this.options.scales.xAxes[0].id,
|
||||||
|
calculateRange: function() {
|
||||||
|
this.labels = self.data.labels;
|
||||||
|
this.min = 0;
|
||||||
|
this.max = this.labels.length;
|
||||||
|
},
|
||||||
|
calculateBaseWidth: function() {
|
||||||
|
return (this.getPixelForValue(null, 1, true) - this.getPixelForValue(null, 0, true)) - (2 * self.options.barValueSpacing);
|
||||||
|
},
|
||||||
|
calculateBarWidth: function(datasetCount) {
|
||||||
|
//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
|
||||||
|
var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * self.options.barDatasetSpacing);
|
||||||
|
|
||||||
|
if (self.options.stacked) {
|
||||||
|
return baseWidth;
|
||||||
|
}
|
||||||
|
return (baseWidth / datasetCount);
|
||||||
|
},
|
||||||
|
calculateBarX: function(datasetCount, datasetIndex, elementIndex) {
|
||||||
|
var xWidth = this.calculateBaseWidth(),
|
||||||
|
xAbsolute = this.getPixelForValue(null, elementIndex, true) - (xWidth / 2),
|
||||||
|
barWidth = this.calculateBarWidth(datasetCount);
|
||||||
|
|
||||||
|
if (self.options.stacked) {
|
||||||
|
return xAbsolute + barWidth / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * self.options.barDatasetSpacing) + barWidth / 2;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.scales[xScale.id] = xScale;
|
||||||
|
|
||||||
|
// Build up all the y scales
|
||||||
|
helpers.each(this.options.scales.yAxes, function(yAxisOptions) {
|
||||||
|
var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.scaleType);
|
||||||
|
var scale = new ScaleClass({
|
||||||
|
ctx: this.chart.ctx,
|
||||||
|
options: yAxisOptions,
|
||||||
|
calculateRange: calculateYRange,
|
||||||
|
calculateBarBase: function(datasetIndex, index) {
|
||||||
|
var base = 0;
|
||||||
|
|
||||||
|
if (self.options.stacked) {
|
||||||
|
var bar = self.data.datasets[datasetIndex].metaData[index];
|
||||||
|
|
||||||
|
if (bar.value < 0) {
|
||||||
|
for (var i = 0; i < datasetIndex; i++) {
|
||||||
|
if (self.data.datasets[i].yAxisID === this.id) {
|
||||||
|
base += self.data.datasets[i].metaData[index].value < base ? self.data.datasets[i].metaData[index].value : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
values[index] += value;
|
for (var i = 0; i < datasetIndex; i++) {
|
||||||
|
if (self.data.datasets[i].yAxisID === this.id) {
|
||||||
|
base += self.data.datasets[i].metaData[index].value > base ? self.data.datasets[i].metaData[index].value : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getPixelForValue(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
base = this.getPixelForValue(this.min);
|
||||||
|
|
||||||
|
if (this.beginAtZero || ((this.min <= 0 && this.max >= 0) || (this.min >= 0 && this.max <= 0))) {
|
||||||
|
base = this.getPixelForValue(0);
|
||||||
|
base += this.options.gridLines.lineWidth;
|
||||||
|
} else if (this.min < 0 && this.max < 0) {
|
||||||
|
// All values are negative. Use the top as the base
|
||||||
|
base = this.getPixelForValue(this.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
|
||||||
|
},
|
||||||
|
calculateBarY: function(datasets, datasetIndex, barIndex, value) {
|
||||||
|
|
||||||
|
if (self.options.stacked) {
|
||||||
|
|
||||||
|
var sumPos = 0,
|
||||||
|
sumNeg = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < datasetIndex; i++) {
|
||||||
|
if (datasets[i].metaData[barIndex].value < 0) {
|
||||||
|
sumNeg += datasets[i].metaData[barIndex].value || 0;
|
||||||
|
} else {
|
||||||
|
sumPos += datasets[i].metaData[barIndex].value || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 0) {
|
||||||
|
return this.getPixelForValue(sumNeg + value);
|
||||||
|
} else {
|
||||||
|
return this.getPixelForValue(sumPos + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (options.relativeBars) {
|
||||||
|
offset = offset / sum * 100;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return this.getPixelForValue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
|
for (i = datasetIndex; i < datasets.length; i++) {
|
||||||
|
if (i === datasetIndex && value) {
|
||||||
|
offset += value;
|
||||||
|
} else {
|
||||||
|
offset = offset + (datasets[i].metaData[barIndex].value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return values.concat(negativeValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.eachValue(function(value, index) {
|
return this.getPixelForValue(value);
|
||||||
values.push(value);
|
},
|
||||||
|
|
||||||
|
calculateBaseHeight: function() {
|
||||||
|
return (this.getPixelForValue(1) - this.getPixelForValue(0));
|
||||||
|
},
|
||||||
|
id: yAxisOptions.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
return values;
|
this.scales[scale.id] = scale;
|
||||||
|
}, this);
|
||||||
};
|
|
||||||
|
|
||||||
var scaleOptions = {
|
|
||||||
templateString: this.options.scaleLabel,
|
|
||||||
height: this.chart.height,
|
|
||||||
width: this.chart.width,
|
|
||||||
ctx: this.chart.ctx,
|
|
||||||
textColor: this.options.scaleFontColor,
|
|
||||||
fontSize: this.options.scaleFontSize,
|
|
||||||
fontStyle: this.options.scaleFontStyle,
|
|
||||||
fontFamily: this.options.scaleFontFamily,
|
|
||||||
valuesCount: labels.length,
|
|
||||||
beginAtZero: this.options.scaleBeginAtZero,
|
|
||||||
integersOnly: this.options.scaleIntegersOnly,
|
|
||||||
calculateYRange: function(currentHeight) {
|
|
||||||
var updatedRanges = helpers.calculateScaleRange(
|
|
||||||
dataTotal(),
|
|
||||||
currentHeight,
|
|
||||||
this.fontSize,
|
|
||||||
this.beginAtZero,
|
|
||||||
this.integersOnly
|
|
||||||
);
|
|
||||||
helpers.extend(this, updatedRanges);
|
|
||||||
},
|
|
||||||
xLabels: labels,
|
|
||||||
font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
|
|
||||||
lineWidth: this.options.scaleLineWidth,
|
|
||||||
lineColor: this.options.scaleLineColor,
|
|
||||||
showHorizontalLines: this.options.scaleShowHorizontalLines,
|
|
||||||
showVerticalLines: this.options.scaleShowVerticalLines,
|
|
||||||
gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
|
|
||||||
gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
|
|
||||||
padding: (this.options.showScale) ? 0 : this.options.borderWidth,
|
|
||||||
showLabels: this.options.scaleShowLabels,
|
|
||||||
display: this.options.showScale
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.options.scaleOverride) {
|
|
||||||
helpers.extend(scaleOptions, {
|
|
||||||
calculateYRange: helpers.noop,
|
|
||||||
steps: this.options.scaleSteps,
|
|
||||||
stepValue: this.options.scaleStepWidth,
|
|
||||||
min: this.options.scaleStartValue,
|
|
||||||
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scale = new this.ScaleClass(scaleOptions);
|
|
||||||
},
|
},
|
||||||
// This should be incorportated into the init as something like a default value. "Reflow" seems like a weird word for a fredraw function
|
// This should be incorportated into the init as something like a default value. "Reflow" seems like a weird word for a fredraw function
|
||||||
redraw: function() {
|
redraw: function() {
|
||||||
var base = this.scale.zeroPoint;
|
|
||||||
this.eachElement(function(element, index, datasetIndex) {
|
this.eachElement(function(element, index, datasetIndex) {
|
||||||
|
var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
|
||||||
|
var base = yScale.getPixelForValue(yScale.min);
|
||||||
|
|
||||||
|
if (yScale.min <= 0 && yScale.max >= 0) {
|
||||||
|
// have a 0 point
|
||||||
|
base = yScale.getPixelForValue(0);
|
||||||
|
} else if (yScale.min < 0 && yScale.max < 0) {
|
||||||
|
// all megative
|
||||||
|
base = yScale.getPixelForValue(yScale.max);
|
||||||
|
}
|
||||||
|
|
||||||
helpers.extend(element, {
|
helpers.extend(element, {
|
||||||
y: base,
|
y: base,
|
||||||
base: base
|
base: base
|
||||||
@ -445,7 +519,10 @@
|
|||||||
var easingDecimal = ease || 1;
|
var easingDecimal = ease || 1;
|
||||||
this.clear();
|
this.clear();
|
||||||
|
|
||||||
this.scale.draw(easingDecimal);
|
// Draw all the scales
|
||||||
|
helpers.each(this.scales, function(scale) {
|
||||||
|
scale.draw(this.chartArea);
|
||||||
|
}, this);
|
||||||
|
|
||||||
//Draw all the bars for each dataset
|
//Draw all the bars for each dataset
|
||||||
this.eachElement(function(bar, index, datasetIndex) {
|
this.eachElement(function(bar, index, datasetIndex) {
|
||||||
|
|||||||
@ -242,6 +242,37 @@
|
|||||||
args.unshift({});
|
args.unshift({});
|
||||||
return extend.apply(null, args);
|
return extend.apply(null, args);
|
||||||
},
|
},
|
||||||
|
// Need a special merge function to chart configs since they are now grouped
|
||||||
|
configMerge = helpers.configMerge = function(base) {
|
||||||
|
helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
|
||||||
|
helpers.each(extension, function(value, key) {
|
||||||
|
if (extension.hasOwnProperty(key)) {
|
||||||
|
if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
|
||||||
|
// In this case we have an array of objects replacing another array. Rather than doing a strict replace,
|
||||||
|
// merge. This allows easy scale option merging
|
||||||
|
var baseArray = base[key];
|
||||||
|
|
||||||
|
helpers.each(value, function(valueObj, index) {
|
||||||
|
if (index < baseArray.length) {
|
||||||
|
baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
|
||||||
|
} else {
|
||||||
|
baseArray.push(valueObj); // nothing to merge
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (base.hasOwnProperty(key) && typeof base[key] == "object" && typeof value == "object") {
|
||||||
|
// If we are overwriting an object with an object, do a merge of the properties.
|
||||||
|
base[key] = helpers.configMerge(base[key], value);
|
||||||
|
} else {
|
||||||
|
// can just overwrite the value in this case
|
||||||
|
base[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return base;
|
||||||
|
},
|
||||||
indexOf = helpers.indexOf = function(arrayToSearch, item) {
|
indexOf = helpers.indexOf = function(arrayToSearch, item) {
|
||||||
if (Array.prototype.indexOf) {
|
if (Array.prototype.indexOf) {
|
||||||
return arrayToSearch.indexOf(item);
|
return arrayToSearch.indexOf(item);
|
||||||
@ -330,6 +361,17 @@
|
|||||||
min = helpers.min = function(array) {
|
min = helpers.min = function(array) {
|
||||||
return Math.min.apply(Math, array);
|
return Math.min.apply(Math, array);
|
||||||
},
|
},
|
||||||
|
sign = helpers.sign = function(x) {
|
||||||
|
if (Math.sign) {
|
||||||
|
return Math.sign(x);
|
||||||
|
} else {
|
||||||
|
x = +x; // convert to a number
|
||||||
|
if (x === 0 || isNaN(x)) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
return x > 0 ? 1 : -1;
|
||||||
|
}
|
||||||
|
},
|
||||||
cap = helpers.cap = function(valueToCap, maxValue, minValue) {
|
cap = helpers.cap = function(valueToCap, maxValue, minValue) {
|
||||||
if (isNumber(maxValue)) {
|
if (isNumber(maxValue)) {
|
||||||
if (valueToCap > maxValue) {
|
if (valueToCap > maxValue) {
|
||||||
@ -360,9 +402,12 @@
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toRadians = helpers.radians = function(degrees) {
|
toRadians = helpers.toRadians = function(degrees) {
|
||||||
return degrees * (Math.PI / 180);
|
return degrees * (Math.PI / 180);
|
||||||
},
|
},
|
||||||
|
toDegrees = helpers.toDegrees = function(radians) {
|
||||||
|
return radians * (180 / Math.PI);
|
||||||
|
},
|
||||||
// Gets the angle from vertical upright to the point about a centre.
|
// Gets the angle from vertical upright to the point about a centre.
|
||||||
getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
|
getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
|
||||||
var distanceFromXCenter = anglePoint.x - centrePoint.x,
|
var distanceFromXCenter = anglePoint.x - centrePoint.x,
|
||||||
@ -482,6 +527,36 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
},
|
},
|
||||||
|
// Implementation of the nice number algorithm used in determining where axis labels will go
|
||||||
|
niceNum = helpers.niceNum = function(range, round) {
|
||||||
|
var exponent = Math.floor(Math.log10(range));
|
||||||
|
var fraction = range / Math.pow(10, exponent);
|
||||||
|
var niceFraction;
|
||||||
|
|
||||||
|
if (round) {
|
||||||
|
if (fraction < 1.5) {
|
||||||
|
niceFraction = 1;
|
||||||
|
} else if (fraction < 3) {
|
||||||
|
niceFraction = 2;
|
||||||
|
} else if (fraction < 7) {
|
||||||
|
niceFraction = 5;
|
||||||
|
} else {
|
||||||
|
niceFraction = 10;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (fraction <= 1.0) {
|
||||||
|
niceFraction = 1;
|
||||||
|
} else if (fraction <= 2) {
|
||||||
|
niceFraction = 2;
|
||||||
|
} else if (fraction <= 5) {
|
||||||
|
niceFraction = 5;
|
||||||
|
} else {
|
||||||
|
niceFraction = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return niceFraction * Math.pow(10, exponent);
|
||||||
|
},
|
||||||
/* jshint ignore:start */
|
/* jshint ignore:start */
|
||||||
// Blows up jshint errors based on the new Function constructor
|
// Blows up jshint errors based on the new Function constructor
|
||||||
//Templating methods
|
//Templating methods
|
||||||
@ -1086,13 +1161,13 @@
|
|||||||
|
|
||||||
var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
|
var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
|
||||||
|
|
||||||
Chart.defaults[chartName] = extend(baseDefaults, extensions.defaults);
|
Chart.defaults[chartName] = helpers.configMerge(baseDefaults, extensions.defaults);
|
||||||
|
|
||||||
Chart.types[chartName] = ChartType;
|
Chart.types[chartName] = ChartType;
|
||||||
|
|
||||||
//Register this new chart type in the Chart prototype
|
//Register this new chart type in the Chart prototype
|
||||||
Chart.prototype[chartName] = function(config) {
|
Chart.prototype[chartName] = function(config) {
|
||||||
helpers.extend(config.options, merge(Chart.defaults.global, Chart.defaults[chartName], config.options || {}));
|
config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[chartName], config.options || {});
|
||||||
return new ChartType(config, this);
|
return new ChartType(config, this);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -1332,7 +1407,7 @@
|
|||||||
},
|
},
|
||||||
draw: function() {
|
draw: function() {
|
||||||
|
|
||||||
var ctx = this._chart.ctx;
|
var ctx = this.ctx;
|
||||||
var vm = this._vm;
|
var vm = this._vm;
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@ -1707,542 +1782,6 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Chart.Scale = Chart.Element.extend({
|
|
||||||
initialize: function() {
|
|
||||||
this.fit();
|
|
||||||
},
|
|
||||||
buildYLabels: function() {
|
|
||||||
this.yLabels = [];
|
|
||||||
|
|
||||||
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
|
|
||||||
|
|
||||||
for (var i = 0; i <= this.steps; i++) {
|
|
||||||
this.yLabels.push(template(this.templateString, {
|
|
||||||
value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx, this.font, this.yLabels) + 10 : 0;
|
|
||||||
},
|
|
||||||
addXLabel: function(label) {
|
|
||||||
this.xLabels.push(label);
|
|
||||||
this.valuesCount++;
|
|
||||||
this.fit();
|
|
||||||
},
|
|
||||||
removeXLabel: function() {
|
|
||||||
this.xLabels.shift();
|
|
||||||
this.valuesCount--;
|
|
||||||
this.fit();
|
|
||||||
},
|
|
||||||
// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
|
|
||||||
fit: function() {
|
|
||||||
// First we need the width of the yLabels, assuming the xLabels aren't rotated
|
|
||||||
|
|
||||||
// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
|
|
||||||
this.startPoint = (this.display) ? this.fontSize : 0;
|
|
||||||
this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
|
|
||||||
|
|
||||||
// Apply padding settings to the start and end point.
|
|
||||||
this.startPoint += this.padding;
|
|
||||||
this.endPoint -= this.padding;
|
|
||||||
|
|
||||||
// Cache the starting endpoint, excluding the space for x labels
|
|
||||||
var cachedEndPoint = this.endPoint;
|
|
||||||
|
|
||||||
// Cache the starting height, so can determine if we need to recalculate the scale yAxis
|
|
||||||
var cachedHeight = this.endPoint - this.startPoint,
|
|
||||||
cachedYLabelWidth;
|
|
||||||
|
|
||||||
// Build the current yLabels so we have an idea of what size they'll be to start
|
|
||||||
/*
|
|
||||||
* This sets what is returned from calculateScaleRange as static properties of this class:
|
|
||||||
*
|
|
||||||
this.steps;
|
|
||||||
this.stepValue;
|
|
||||||
this.min;
|
|
||||||
this.max;
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
this.calculateYRange(cachedHeight);
|
|
||||||
|
|
||||||
// With these properties set we can now build the array of yLabels
|
|
||||||
// and also the width of the largest yLabel
|
|
||||||
this.buildYLabels();
|
|
||||||
|
|
||||||
this.calculateXLabelRotation();
|
|
||||||
|
|
||||||
while ((cachedHeight > this.endPoint - this.startPoint)) {
|
|
||||||
cachedHeight = this.endPoint - this.startPoint;
|
|
||||||
cachedYLabelWidth = this.yLabelWidth;
|
|
||||||
|
|
||||||
this.calculateYRange(cachedHeight);
|
|
||||||
this.buildYLabels();
|
|
||||||
|
|
||||||
// Only go through the xLabel loop again if the yLabel width has changed
|
|
||||||
if (cachedYLabelWidth < this.yLabelWidth) {
|
|
||||||
this.endPoint = cachedEndPoint;
|
|
||||||
this.calculateXLabelRotation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
calculateXLabelRotation: function() {
|
|
||||||
//Get the width of each grid by calculating the difference
|
|
||||||
//between x offsets between 0 and 1.
|
|
||||||
|
|
||||||
this.ctx.font = this.font;
|
|
||||||
|
|
||||||
var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
|
|
||||||
lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
|
|
||||||
firstRotated,
|
|
||||||
lastRotated;
|
|
||||||
|
|
||||||
|
|
||||||
this.xScalePaddingRight = lastWidth / 2 + 3;
|
|
||||||
this.xScalePaddingLeft = (firstWidth / 2 > this.yLabelWidth) ? firstWidth / 2 : this.yLabelWidth;
|
|
||||||
|
|
||||||
this.xLabelRotation = 0;
|
|
||||||
if (this.display) {
|
|
||||||
var originalLabelWidth = longestText(this.ctx, this.font, this.xLabels),
|
|
||||||
cosRotation,
|
|
||||||
firstRotatedWidth;
|
|
||||||
this.xLabelWidth = originalLabelWidth;
|
|
||||||
//Allow 3 pixels x2 padding either side for label readability
|
|
||||||
var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
|
|
||||||
|
|
||||||
//Max label rotate should be 90 - also act as a loop counter
|
|
||||||
while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)) {
|
|
||||||
cosRotation = Math.cos(toRadians(this.xLabelRotation));
|
|
||||||
|
|
||||||
firstRotated = cosRotation * firstWidth;
|
|
||||||
lastRotated = cosRotation * lastWidth;
|
|
||||||
|
|
||||||
// We're right aligning the text now.
|
|
||||||
if (firstRotated + this.fontSize / 2 > this.yLabelWidth) {
|
|
||||||
this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
|
|
||||||
}
|
|
||||||
this.xScalePaddingRight = this.fontSize / 2;
|
|
||||||
|
|
||||||
|
|
||||||
this.xLabelRotation++;
|
|
||||||
this.xLabelWidth = cosRotation * originalLabelWidth;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (this.xLabelRotation > 0) {
|
|
||||||
this.endPoint -= Math.sin(toRadians(this.xLabelRotation)) * originalLabelWidth + 3;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.xLabelWidth = 0;
|
|
||||||
this.xScalePaddingRight = this.padding;
|
|
||||||
this.xScalePaddingLeft = this.padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
// Needs to be overidden in each Chart type
|
|
||||||
// Otherwise we need to pass all the data into the scale class
|
|
||||||
calculateYRange: noop,
|
|
||||||
drawingArea: function() {
|
|
||||||
return this.startPoint - this.endPoint;
|
|
||||||
},
|
|
||||||
calculateY: function(value) {
|
|
||||||
var scalingFactor = this.drawingArea() / (this.min - this.max);
|
|
||||||
return this.endPoint - (scalingFactor * (value - this.min));
|
|
||||||
},
|
|
||||||
calculateX: function(index) {
|
|
||||||
var isRotated = (this.xLabelRotation > 0),
|
|
||||||
// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
|
|
||||||
innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
|
|
||||||
valueWidth = innerWidth / Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
|
|
||||||
valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
|
|
||||||
|
|
||||||
if (this.offsetGridLines) {
|
|
||||||
valueOffset += (valueWidth / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(valueOffset);
|
|
||||||
},
|
|
||||||
update: function(newProps) {
|
|
||||||
helpers.extend(this, newProps);
|
|
||||||
this.fit();
|
|
||||||
},
|
|
||||||
draw: function() {
|
|
||||||
var ctx = this.ctx,
|
|
||||||
yLabelGap = (this.endPoint - this.startPoint) / this.steps,
|
|
||||||
xStart = Math.round(this.xScalePaddingLeft);
|
|
||||||
if (this.display) {
|
|
||||||
ctx.fillStyle = this.textColor;
|
|
||||||
ctx.font = this.font;
|
|
||||||
each(this.yLabels, function(labelString, index) {
|
|
||||||
var yLabelCenter = this.endPoint - (yLabelGap * index),
|
|
||||||
linePositionY = Math.round(yLabelCenter),
|
|
||||||
drawHorizontalLine = this.showHorizontalLines;
|
|
||||||
|
|
||||||
ctx.textAlign = "right";
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
if (this.showLabels) {
|
|
||||||
ctx.fillText(labelString, xStart - 10, yLabelCenter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is X axis, so draw it
|
|
||||||
if (index === 0 && !drawHorizontalLine) {
|
|
||||||
drawHorizontalLine = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawHorizontalLine) {
|
|
||||||
ctx.beginPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index > 0) {
|
|
||||||
// This is a grid line in the centre, so drop that
|
|
||||||
ctx.lineWidth = this.gridLineWidth;
|
|
||||||
ctx.strokeStyle = this.gridLineColor;
|
|
||||||
} else {
|
|
||||||
// This is the first line on the scale
|
|
||||||
ctx.lineWidth = this.lineWidth;
|
|
||||||
ctx.strokeStyle = this.lineColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
linePositionY += helpers.aliasPixel(ctx.lineWidth);
|
|
||||||
|
|
||||||
if (drawHorizontalLine) {
|
|
||||||
ctx.moveTo(xStart, linePositionY);
|
|
||||||
ctx.lineTo(this.width, linePositionY);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.closePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.lineWidth = this.lineWidth;
|
|
||||||
ctx.strokeStyle = this.lineColor;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(xStart - 5, linePositionY);
|
|
||||||
ctx.lineTo(xStart, linePositionY);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.closePath();
|
|
||||||
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
each(this.xLabels, function(label, index) {
|
|
||||||
var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
|
|
||||||
// Check to see if line/bar here and decide where to place the line
|
|
||||||
linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
|
|
||||||
isRotated = (this.xLabelRotation > 0),
|
|
||||||
drawVerticalLine = this.showVerticalLines;
|
|
||||||
|
|
||||||
// This is Y axis, so draw it
|
|
||||||
if (index === 0 && !drawVerticalLine) {
|
|
||||||
drawVerticalLine = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawVerticalLine) {
|
|
||||||
ctx.beginPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index > 0) {
|
|
||||||
// This is a grid line in the centre, so drop that
|
|
||||||
ctx.lineWidth = this.gridLineWidth;
|
|
||||||
ctx.strokeStyle = this.gridLineColor;
|
|
||||||
} else {
|
|
||||||
// This is the first line on the scale
|
|
||||||
ctx.lineWidth = this.lineWidth;
|
|
||||||
ctx.strokeStyle = this.lineColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawVerticalLine) {
|
|
||||||
ctx.moveTo(linePos, this.endPoint);
|
|
||||||
ctx.lineTo(linePos, this.startPoint - 3);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.closePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ctx.lineWidth = this.lineWidth;
|
|
||||||
ctx.strokeStyle = this.lineColor;
|
|
||||||
|
|
||||||
|
|
||||||
// Small lines at the bottom of the base grid line
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(linePos, this.endPoint);
|
|
||||||
ctx.lineTo(linePos, this.endPoint + 5);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.closePath();
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(xPos, (isRotated) ? this.endPoint + 12 : this.endPoint + 8);
|
|
||||||
ctx.rotate(toRadians(this.xLabelRotation) * -1);
|
|
||||||
ctx.font = this.font;
|
|
||||||
ctx.textAlign = (isRotated) ? "right" : "center";
|
|
||||||
ctx.textBaseline = (isRotated) ? "middle" : "top";
|
|
||||||
ctx.fillText(label, 0, 0);
|
|
||||||
ctx.restore();
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
Chart.RadialScale = Chart.Element.extend({
|
|
||||||
initialize: function() {
|
|
||||||
this.size = min([this.height, this.width]);
|
|
||||||
this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2);
|
|
||||||
},
|
|
||||||
calculateCenterOffset: function(value) {
|
|
||||||
// Take into account half font size + the yPadding of the top value
|
|
||||||
var scalingFactor = this.drawingArea / (this.max - this.min);
|
|
||||||
|
|
||||||
return (value - this.min) * scalingFactor;
|
|
||||||
},
|
|
||||||
update: function() {
|
|
||||||
if (!this.lineArc) {
|
|
||||||
this.setScaleSize();
|
|
||||||
} else {
|
|
||||||
this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2);
|
|
||||||
}
|
|
||||||
this.buildYLabels();
|
|
||||||
},
|
|
||||||
buildYLabels: function() {
|
|
||||||
this.yLabels = [];
|
|
||||||
|
|
||||||
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
|
|
||||||
|
|
||||||
for (var i = 0; i <= this.steps; i++) {
|
|
||||||
this.yLabels.push(template(this.templateString, {
|
|
||||||
value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCircumference: function() {
|
|
||||||
return ((Math.PI * 2) / this.valuesCount);
|
|
||||||
},
|
|
||||||
setScaleSize: function() {
|
|
||||||
/*
|
|
||||||
* Right, this is really confusing and there is a lot of maths going on here
|
|
||||||
* The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
|
|
||||||
*
|
|
||||||
* Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
|
|
||||||
*
|
|
||||||
* Solution:
|
|
||||||
*
|
|
||||||
* We assume the radius of the polygon is half the size of the canvas at first
|
|
||||||
* at each index we check if the text overlaps.
|
|
||||||
*
|
|
||||||
* Where it does, we store that angle and that index.
|
|
||||||
*
|
|
||||||
* After finding the largest index and angle we calculate how much we need to remove
|
|
||||||
* from the shape radius to move the point inwards by that x.
|
|
||||||
*
|
|
||||||
* We average the left and right distances to get the maximum shape radius that can fit in the box
|
|
||||||
* along with labels.
|
|
||||||
*
|
|
||||||
* Once we have that, we can find the centre point for the chart, by taking the x text protrusion
|
|
||||||
* on each side, removing that from the size, halving it and adding the left x protrusion width.
|
|
||||||
*
|
|
||||||
* This will mean we have a shape fitted to the canvas, as large as it can be with the labels
|
|
||||||
* and position it in the most space efficient manner
|
|
||||||
*
|
|
||||||
* https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
|
|
||||||
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
|
|
||||||
var largestPossibleRadius = min([(this.height / 2 - this.pointLabelFontSize - 5), this.width / 2]),
|
|
||||||
pointPosition,
|
|
||||||
i,
|
|
||||||
textWidth,
|
|
||||||
halfTextWidth,
|
|
||||||
furthestRight = this.width,
|
|
||||||
furthestRightIndex,
|
|
||||||
furthestRightAngle,
|
|
||||||
furthestLeft = 0,
|
|
||||||
furthestLeftIndex,
|
|
||||||
furthestLeftAngle,
|
|
||||||
xProtrusionLeft,
|
|
||||||
xProtrusionRight,
|
|
||||||
radiusReductionRight,
|
|
||||||
radiusReductionLeft,
|
|
||||||
maxWidthRadius;
|
|
||||||
this.ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily);
|
|
||||||
for (i = 0; i < this.valuesCount; i++) {
|
|
||||||
// 5px to space the text slightly out - similar to what we do in the draw function.
|
|
||||||
pointPosition = this.getPointPosition(i, largestPossibleRadius);
|
|
||||||
textWidth = this.ctx.measureText(template(this.templateString, {
|
|
||||||
value: this.labels[i]
|
|
||||||
})).width + 5;
|
|
||||||
if (i === 0 || i === this.valuesCount / 2) {
|
|
||||||
// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
|
|
||||||
// of the radar chart, so text will be aligned centrally, so we'll half it and compare
|
|
||||||
// w/left and right text sizes
|
|
||||||
halfTextWidth = textWidth / 2;
|
|
||||||
if (pointPosition.x + halfTextWidth > furthestRight) {
|
|
||||||
furthestRight = pointPosition.x + halfTextWidth;
|
|
||||||
furthestRightIndex = i;
|
|
||||||
}
|
|
||||||
if (pointPosition.x - halfTextWidth < furthestLeft) {
|
|
||||||
furthestLeft = pointPosition.x - halfTextWidth;
|
|
||||||
furthestLeftIndex = i;
|
|
||||||
}
|
|
||||||
} else if (i < this.valuesCount / 2) {
|
|
||||||
// Less than half the values means we'll left align the text
|
|
||||||
if (pointPosition.x + textWidth > furthestRight) {
|
|
||||||
furthestRight = pointPosition.x + textWidth;
|
|
||||||
furthestRightIndex = i;
|
|
||||||
}
|
|
||||||
} else if (i > this.valuesCount / 2) {
|
|
||||||
// More than half the values means we'll right align the text
|
|
||||||
if (pointPosition.x - textWidth < furthestLeft) {
|
|
||||||
furthestLeft = pointPosition.x - textWidth;
|
|
||||||
furthestLeftIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xProtrusionLeft = furthestLeft;
|
|
||||||
|
|
||||||
xProtrusionRight = Math.ceil(furthestRight - this.width);
|
|
||||||
|
|
||||||
furthestRightAngle = this.getIndexAngle(furthestRightIndex);
|
|
||||||
|
|
||||||
furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
|
|
||||||
|
|
||||||
radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
|
|
||||||
|
|
||||||
radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
|
|
||||||
|
|
||||||
// Ensure we actually need to reduce the size of the chart
|
|
||||||
radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
|
|
||||||
radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
|
|
||||||
|
|
||||||
this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2;
|
|
||||||
|
|
||||||
//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
|
|
||||||
this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
|
|
||||||
|
|
||||||
},
|
|
||||||
setCenterPoint: function(leftMovement, rightMovement) {
|
|
||||||
|
|
||||||
var maxRight = this.width - rightMovement - this.drawingArea,
|
|
||||||
maxLeft = leftMovement + this.drawingArea;
|
|
||||||
|
|
||||||
this.xCenter = (maxLeft + maxRight) / 2;
|
|
||||||
// Always vertically in the centre as the text height doesn't change
|
|
||||||
this.yCenter = (this.height / 2);
|
|
||||||
},
|
|
||||||
|
|
||||||
getIndexAngle: function(index) {
|
|
||||||
var angleMultiplier = (Math.PI * 2) / this.valuesCount;
|
|
||||||
// Start from the top instead of right, so remove a quarter of the circle
|
|
||||||
|
|
||||||
return index * angleMultiplier - (Math.PI / 2);
|
|
||||||
},
|
|
||||||
getPointPosition: function(index, distanceFromCenter) {
|
|
||||||
var thisAngle = this.getIndexAngle(index);
|
|
||||||
return {
|
|
||||||
x: (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
|
|
||||||
y: (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
|
|
||||||
};
|
|
||||||
},
|
|
||||||
draw: function() {
|
|
||||||
if (this.display) {
|
|
||||||
var ctx = this.ctx;
|
|
||||||
each(this.yLabels, function(label, index) {
|
|
||||||
// Don't draw a centre value
|
|
||||||
if (index > 0) {
|
|
||||||
var yCenterOffset = index * (this.drawingArea / this.steps),
|
|
||||||
yHeight = this.yCenter - yCenterOffset,
|
|
||||||
pointPosition;
|
|
||||||
|
|
||||||
// Draw circular lines around the scale
|
|
||||||
if (this.lineWidth > 0) {
|
|
||||||
ctx.strokeStyle = this.lineColor;
|
|
||||||
ctx.lineWidth = this.lineWidth;
|
|
||||||
|
|
||||||
if (this.lineArc) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
} else {
|
|
||||||
ctx.beginPath();
|
|
||||||
for (var i = 0; i < this.valuesCount; i++) {
|
|
||||||
pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
|
|
||||||
if (i === 0) {
|
|
||||||
ctx.moveTo(pointPosition.x, pointPosition.y);
|
|
||||||
} else {
|
|
||||||
ctx.lineTo(pointPosition.x, pointPosition.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.showLabels) {
|
|
||||||
ctx.font = fontString(this.fontSize, this._fontStyle, this._fontFamily);
|
|
||||||
if (this.showLabelBackdrop) {
|
|
||||||
var labelWidth = ctx.measureText(label).width;
|
|
||||||
ctx.fillStyle = this.backdropColor;
|
|
||||||
ctx.fillRect(
|
|
||||||
this.xCenter - labelWidth / 2 - this.backdropPaddingX,
|
|
||||||
yHeight - this.fontSize / 2 - this.backdropPaddingY,
|
|
||||||
labelWidth + this.backdropPaddingX * 2,
|
|
||||||
this.fontSize + this.backdropPaddingY * 2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
ctx.fillStyle = this.fontColor;
|
|
||||||
ctx.fillText(label, this.xCenter, yHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
if (!this.lineArc) {
|
|
||||||
ctx.lineWidth = this.angleLineWidth;
|
|
||||||
ctx.strokeStyle = this.angleLineColor;
|
|
||||||
for (var i = this.valuesCount - 1; i >= 0; i--) {
|
|
||||||
if (this.angleLineWidth > 0) {
|
|
||||||
var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(this.xCenter, this.yCenter);
|
|
||||||
ctx.lineTo(outerPosition.x, outerPosition.y);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.closePath();
|
|
||||||
}
|
|
||||||
// Extra 3px out for some label spacing
|
|
||||||
var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
|
|
||||||
ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily);
|
|
||||||
ctx.fillStyle = this.pointLabelFontColor;
|
|
||||||
|
|
||||||
var labelsCount = this.labels.length,
|
|
||||||
halfLabelsCount = this.labels.length / 2,
|
|
||||||
quarterLabelsCount = halfLabelsCount / 2,
|
|
||||||
upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
|
|
||||||
exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
|
|
||||||
if (i === 0) {
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
} else if (i === halfLabelsCount) {
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
} else if (i < halfLabelsCount) {
|
|
||||||
ctx.textAlign = 'left';
|
|
||||||
} else {
|
|
||||||
ctx.textAlign = 'right';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the correct text baseline based on outer positioning
|
|
||||||
if (exactQuarter) {
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
} else if (upperHalf) {
|
|
||||||
ctx.textBaseline = 'bottom';
|
|
||||||
} else {
|
|
||||||
ctx.textBaseline = 'top';
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Chart.animationService = {
|
Chart.animationService = {
|
||||||
frameDuration: 17,
|
frameDuration: 17,
|
||||||
animations: [],
|
animations: [],
|
||||||
|
|||||||
@ -43,11 +43,7 @@
|
|||||||
defaults: defaultConfig,
|
defaults: defaultConfig,
|
||||||
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
|
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
|
||||||
//Config is automatically merged by the core of Chart.js, and is available at this.options
|
//Config is automatically merged by the core of Chart.js, and is available at this.options
|
||||||
initialize: function(data) {
|
initialize: function() {
|
||||||
|
|
||||||
// Save data as a source for updating of values & methods
|
|
||||||
this.data = data;
|
|
||||||
|
|
||||||
// Slice Type and defaults
|
// Slice Type and defaults
|
||||||
this.Slice = Chart.Arc.extend({
|
this.Slice = Chart.Arc.extend({
|
||||||
_chart: this.chart,
|
_chart: this.chart,
|
||||||
@ -57,7 +53,7 @@
|
|||||||
|
|
||||||
//Set up tooltip events on the chart
|
//Set up tooltip events on the chart
|
||||||
if (this.options.showTooltips) {
|
if (this.options.showTooltips) {
|
||||||
helpers.bindEvents(this, this.options.tooltipEvents, this.onHover);
|
helpers.bindEvents(this, this.options.events, this.onHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new slice for each piece of data
|
// Create new slice for each piece of data
|
||||||
|
|||||||
@ -7,29 +7,80 @@
|
|||||||
|
|
||||||
var defaultConfig = {
|
var defaultConfig = {
|
||||||
|
|
||||||
///Boolean - Whether grid lines are shown across the chart
|
scales: {
|
||||||
scaleShowGridLines: true,
|
xAxes: [{
|
||||||
//String - Colour of the grid lines
|
scaleType: "dataset", // scatter should not use a dataset axis
|
||||||
scaleGridLineColor: "rgba(0,0,0,.05)",
|
display: true,
|
||||||
//Number - Width of the grid lines
|
position: "bottom",
|
||||||
scaleGridLineWidth: 1,
|
id: "x-axis-1", // need an ID so datasets can reference the scale
|
||||||
//Boolean - Whether to show horizontal lines (except X axis)
|
|
||||||
scaleShowHorizontalLines: true,
|
// grid line settings
|
||||||
//Boolean - Whether to show vertical lines (except Y axis)
|
gridLines: {
|
||||||
scaleShowVerticalLines: true,
|
show: true,
|
||||||
//Boolean - Whether to horizontally center the label and point dot inside the grid
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
offsetGridLines: false,
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
offsetGridLines: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "left",
|
||||||
|
id: "y-axis-1",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true, // draw ticks extending towards the label
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
|
||||||
//Boolean - Whether to stack the lines essentially creating a stacked area chart.
|
//Boolean - Whether to stack the lines essentially creating a stacked area chart.
|
||||||
stacked: false,
|
stacked: false,
|
||||||
|
|
||||||
|
|
||||||
//Number - Tension of the bezier curve between points
|
//Number - Tension of the bezier curve between points
|
||||||
tension: 0.4,
|
tension: 0.4,
|
||||||
|
|
||||||
|
|
||||||
//Number - Radius of each point dot in pixels
|
//Number - Radius of each point dot in pixels
|
||||||
pointRadius: 3,
|
pointRadius: 3,
|
||||||
//Number - Pixel width of point dot border
|
//Number - Pixel width of point dot border
|
||||||
@ -51,9 +102,6 @@
|
|||||||
|
|
||||||
//String - A legend template
|
//String - A legend template
|
||||||
legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].borderColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>",
|
legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].borderColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -67,35 +115,6 @@
|
|||||||
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
// Build Scale
|
|
||||||
this.ScaleClass = Chart.Scale.extend({
|
|
||||||
calculatePointY: function(index, datasetIndex) {
|
|
||||||
|
|
||||||
var value = _this.data.datasets[datasetIndex].data[index];
|
|
||||||
|
|
||||||
if (_this.options.stacked) {
|
|
||||||
var offsetPos = 0;
|
|
||||||
var offsetNeg = 0;
|
|
||||||
for (var i = 0; i < datasetIndex; i++) {
|
|
||||||
if (_this.data.datasets[i].data[index] < 0) {
|
|
||||||
offsetNeg += _this.data.datasets[i].data[index];
|
|
||||||
} else {
|
|
||||||
offsetPos += _this.data.datasets[i].data[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (value < 0) {
|
|
||||||
return this.calculateY(offsetNeg + value);
|
|
||||||
} else {
|
|
||||||
return this.calculateY(offsetPos + value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.calculateY(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.buildScale(this.data.labels);
|
|
||||||
|
|
||||||
|
|
||||||
//Create a new line and its points for each dataset and piece of data
|
//Create a new line and its points for each dataset and piece of data
|
||||||
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
||||||
dataset.metaDataset = new Chart.Line();
|
dataset.metaDataset = new Chart.Line();
|
||||||
@ -103,8 +122,19 @@
|
|||||||
helpers.each(dataset.data, function(dataPoint, index) {
|
helpers.each(dataset.data, function(dataPoint, index) {
|
||||||
dataset.metaData.push(new Chart.Point());
|
dataset.metaData.push(new Chart.Point());
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
// The line chart only supports a single x axis because the x axis is always a dataset axis
|
||||||
|
dataset.xAxisID = this.options.scales.xAxes[0].id;
|
||||||
|
|
||||||
|
if (!dataset.yAxisID) {
|
||||||
|
dataset.yAxisID = this.options.scales.yAxes[0].id;
|
||||||
|
}
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
// Build and fit the scale. Needs to happen after the axis IDs have been set
|
||||||
|
this.buildScale();
|
||||||
|
Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
|
||||||
|
|
||||||
// Set defaults for lines
|
// Set defaults for lines
|
||||||
this.eachDataset(function(dataset, datasetIndex) {
|
this.eachDataset(function(dataset, datasetIndex) {
|
||||||
helpers.extend(dataset.metaDataset, {
|
helpers.extend(dataset.metaDataset, {
|
||||||
@ -120,9 +150,11 @@
|
|||||||
|
|
||||||
// Set defaults for points
|
// Set defaults for points
|
||||||
this.eachElement(function(point, index, dataset, datasetIndex) {
|
this.eachElement(function(point, index, dataset, datasetIndex) {
|
||||||
|
var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
|
||||||
|
|
||||||
helpers.extend(point, {
|
helpers.extend(point, {
|
||||||
x: this.scale.calculateX(index),
|
x: xScale.getPixelForValue(null, index, true),
|
||||||
y: this.scale.calculateY(0),
|
y: this.chartArea.bottom,
|
||||||
_datasetIndex: datasetIndex,
|
_datasetIndex: datasetIndex,
|
||||||
_index: index,
|
_index: index,
|
||||||
_chart: this.chart
|
_chart: this.chart
|
||||||
@ -155,21 +187,24 @@
|
|||||||
return collection[index + 1] || collection[index];
|
return collection[index + 1] || collection[index];
|
||||||
},
|
},
|
||||||
update: function() {
|
update: function() {
|
||||||
|
Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
|
||||||
// Update the scale
|
|
||||||
this.scale.update();
|
|
||||||
|
|
||||||
// Update the lines
|
// Update the lines
|
||||||
this.eachDataset(function(dataset, datasetIndex) {
|
this.eachDataset(function(dataset, datasetIndex) {
|
||||||
|
var yScale = this.scales[dataset.yAxisID];
|
||||||
|
|
||||||
helpers.extend(dataset.metaDataset, {
|
helpers.extend(dataset.metaDataset, {
|
||||||
// Utility
|
// Utility
|
||||||
_datasetIndex: datasetIndex,
|
_datasetIndex: datasetIndex,
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
_points: dataset.metaData,
|
_points: dataset.metaData,
|
||||||
|
|
||||||
// Geometry
|
// Geometry
|
||||||
scaleTop: this.scale.startPoint,
|
scaleTop: yScale.top,
|
||||||
scaleBottom: this.scale.endPoint,
|
scaleBottom: yScale.bottom,
|
||||||
scaleZero: this.scale.calculateY(0),
|
scaleZero: yScale.getPixelForValue(0),
|
||||||
|
|
||||||
// Appearance
|
// Appearance
|
||||||
tension: dataset.tension || this.options.tension,
|
tension: dataset.tension || this.options.tension,
|
||||||
backgroundColor: dataset.backgroundColor || this.options.backgroundColor,
|
backgroundColor: dataset.backgroundColor || this.options.backgroundColor,
|
||||||
@ -181,24 +216,31 @@
|
|||||||
|
|
||||||
// Update the points
|
// Update the points
|
||||||
this.eachElement(function(point, index, dataset, datasetIndex) {
|
this.eachElement(function(point, index, dataset, datasetIndex) {
|
||||||
|
var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
|
||||||
|
var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
|
||||||
|
|
||||||
helpers.extend(point, {
|
helpers.extend(point, {
|
||||||
// Utility
|
// Utility
|
||||||
_chart: this.chart,
|
_chart: this.chart,
|
||||||
_datasetIndex: datasetIndex,
|
_datasetIndex: datasetIndex,
|
||||||
_index: index,
|
_index: index,
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
label: this.data.labels[index],
|
label: this.data.labels[index],
|
||||||
value: this.data.datasets[datasetIndex].data[index],
|
value: this.data.datasets[datasetIndex].data[index],
|
||||||
datasetLabel: this.data.datasets[datasetIndex].label,
|
datasetLabel: this.data.datasets[datasetIndex].label,
|
||||||
|
|
||||||
// Geometry
|
// Geometry
|
||||||
offsetGridLines: this.options.offsetGridLines,
|
offsetGridLines: this.options.offsetGridLines,
|
||||||
x: this.scale.calculateX(index),
|
x: xScale.getPixelForValue(null, index, true), // value not used in dataset scale, but we want a consistent API between scales
|
||||||
y: this.scale.calculatePointY(index, datasetIndex),
|
y: yScale.getPointPixelForValue(this.data.datasets[datasetIndex].data[index], index, datasetIndex),
|
||||||
tension: this.data.datasets[datasetIndex].metaDataset.tension,
|
tension: this.data.datasets[datasetIndex].metaDataset.tension,
|
||||||
|
|
||||||
// Appearnce
|
// Appearnce
|
||||||
radius: this.data.datasets[datasetIndex].pointRadius || this.options.pointRadius,
|
radius: this.data.datasets[datasetIndex].pointRadius || this.options.pointRadius,
|
||||||
backgroundColor: this.data.datasets[datasetIndex].pointBackgroundColor || this.options.pointBackgroundColor,
|
backgroundColor: this.data.datasets[datasetIndex].pointBackgroundColor || this.options.pointBackgroundColor,
|
||||||
borderWidth: this.data.datasets[datasetIndex].pointBorderWidth || this.options.pointBorderWidth,
|
borderWidth: this.data.datasets[datasetIndex].pointBorderWidth || this.options.pointBorderWidth,
|
||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
hoverRadius: this.data.datasets[datasetIndex].pointHitRadius || this.options.pointHitRadius,
|
hoverRadius: this.data.datasets[datasetIndex].pointHitRadius || this.options.pointHitRadius,
|
||||||
});
|
});
|
||||||
@ -217,20 +259,21 @@
|
|||||||
point.controlPointNextX = controlPoints.next.x;
|
point.controlPointNextX = controlPoints.next.x;
|
||||||
|
|
||||||
// Prevent the bezier going outside of the bounds of the graph
|
// Prevent the bezier going outside of the bounds of the graph
|
||||||
|
|
||||||
// Cap puter bezier handles to the upper/lower scale bounds
|
// Cap puter bezier handles to the upper/lower scale bounds
|
||||||
if (controlPoints.next.y > this.scale.endPoint) {
|
if (controlPoints.next.y > this.chartArea.bottom) {
|
||||||
point.controlPointNextY = this.scale.endPoint;
|
point.controlPointNextY = this.chartArea.bottom;
|
||||||
} else if (controlPoints.next.y < this.scale.startPoint) {
|
} else if (controlPoints.next.y < this.chartArea.top) {
|
||||||
point.controlPointNextY = this.scale.startPoint;
|
point.controlPointNextY = this.chartArea.top;
|
||||||
} else {
|
} else {
|
||||||
point.controlPointNextY = controlPoints.next.y;
|
point.controlPointNextY = controlPoints.next.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap inner bezier handles to the upper/lower scale bounds
|
// Cap inner bezier handles to the upper/lower scale bounds
|
||||||
if (controlPoints.previous.y > this.scale.endPoint) {
|
if (controlPoints.previous.y > this.chartArea.bottom) {
|
||||||
point.controlPointPreviousY = this.scale.endPoint;
|
point.controlPointPreviousY = this.chartArea.bottom;
|
||||||
} else if (controlPoints.previous.y < this.scale.startPoint) {
|
} else if (controlPoints.previous.y < this.chartArea.top) {
|
||||||
point.controlPointPreviousY = this.scale.startPoint;
|
point.controlPointPreviousY = this.chartArea.top;
|
||||||
} else {
|
} else {
|
||||||
point.controlPointPreviousY = controlPoints.previous.y;
|
point.controlPointPreviousY = controlPoints.previous.y;
|
||||||
}
|
}
|
||||||
@ -240,85 +283,112 @@
|
|||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
buildScale: function(labels) {
|
buildScale: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var dataTotal = function() {
|
// Function to determine the range of all the
|
||||||
var values = [];
|
var calculateYRange = function() {
|
||||||
|
this.min = null;
|
||||||
|
this.max = null;
|
||||||
|
|
||||||
|
var positiveValues = [];
|
||||||
var negativeValues = [];
|
var negativeValues = [];
|
||||||
|
|
||||||
if (self.options.stacked) {
|
if (self.options.stacked) {
|
||||||
self.eachValue(function(value, index) {
|
helpers.each(self.data.datasets, function(dataset) {
|
||||||
values[index] = values[index] || 0;
|
if (dataset.yAxisID === this.id) {
|
||||||
negativeValues[index] = negativeValues[index] || 0;
|
helpers.each(dataset.data, function(value, index) {
|
||||||
if (self.options.relativePoints) {
|
positiveValues[index] = positiveValues[index] || 0;
|
||||||
values[index] = 100;
|
negativeValues[index] = negativeValues[index] || 0;
|
||||||
} else {
|
|
||||||
if (value < 0) {
|
if (self.options.relativePoints) {
|
||||||
negativeValues[index] += value;
|
positiveValues[index] = 100;
|
||||||
} else {
|
} else {
|
||||||
values[index] += value;
|
if (value < 0) {
|
||||||
}
|
negativeValues[index] += value;
|
||||||
|
} else {
|
||||||
|
positiveValues[index] += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
}
|
}
|
||||||
});
|
}, this);
|
||||||
return values.concat(negativeValues);
|
|
||||||
|
var values = positiveValues.concat(negativeValues);
|
||||||
|
this.min = helpers.min(values);
|
||||||
|
this.max = helpers.max(values);
|
||||||
|
} else {
|
||||||
|
helpers.each(self.data.datasets, function(dataset) {
|
||||||
|
if (dataset.yAxisID === this.id) {
|
||||||
|
helpers.each(dataset.data, function(value, index) {
|
||||||
|
if (this.min === null) {
|
||||||
|
this.min = value;
|
||||||
|
} else if (value < this.min) {
|
||||||
|
this.min = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.max === null) {
|
||||||
|
this.max = value;
|
||||||
|
} else if (value > this.max) {
|
||||||
|
this.max = value;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.eachValue(function(value, index) {
|
|
||||||
values.push(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return values;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var scaleOptions = {
|
// Map of scale ID to scale object so we can lookup later
|
||||||
templateString: this.options.scaleLabel,
|
this.scales = {};
|
||||||
height: this.chart.height,
|
|
||||||
width: this.chart.width,
|
// Build the x axis. The line chart only supports a single x axis
|
||||||
|
var ScaleClass = Chart.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType);
|
||||||
|
var xScale = new ScaleClass({
|
||||||
ctx: this.chart.ctx,
|
ctx: this.chart.ctx,
|
||||||
textColor: this.options.scaleFontColor,
|
options: this.options.scales.xAxes[0],
|
||||||
offsetGridLines: this.options.offsetGridLines,
|
calculateRange: function() {
|
||||||
fontSize: this.options.scaleFontSize,
|
this.labels = self.data.labels;
|
||||||
fontStyle: this.options.scaleFontStyle,
|
this.min = 0;
|
||||||
fontFamily: this.options.scaleFontFamily,
|
this.max = this.labels.length;
|
||||||
valuesCount: labels.length,
|
|
||||||
beginAtZero: this.options.scaleBeginAtZero,
|
|
||||||
integersOnly: this.options.scaleIntegersOnly,
|
|
||||||
calculateYRange: function(currentHeight) {
|
|
||||||
var updatedRanges = helpers.calculateScaleRange(
|
|
||||||
dataTotal(),
|
|
||||||
currentHeight,
|
|
||||||
this.fontSize,
|
|
||||||
this.beginAtZero,
|
|
||||||
this.integersOnly
|
|
||||||
);
|
|
||||||
helpers.extend(this, updatedRanges);
|
|
||||||
},
|
},
|
||||||
xLabels: this.data.labels,
|
id: this.options.scales.xAxes[0].id,
|
||||||
font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
|
});
|
||||||
lineWidth: this.options.scaleLineWidth,
|
this.scales[xScale.id] = xScale;
|
||||||
lineColor: this.options.scaleLineColor,
|
|
||||||
showHorizontalLines: this.options.scaleShowHorizontalLines,
|
|
||||||
showVerticalLines: this.options.scaleShowVerticalLines,
|
|
||||||
gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
|
|
||||||
gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
|
|
||||||
padding: (this.options.showScale) ? 0 : this.options.pointRadius + this.options.pointBorderWidth,
|
|
||||||
showLabels: this.options.scaleShowLabels,
|
|
||||||
display: this.options.showScale
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.options.scaleOverride) {
|
// Build up all the y scales
|
||||||
helpers.extend(scaleOptions, {
|
helpers.each(this.options.scales.yAxes, function(yAxisOptions) {
|
||||||
calculateYRange: helpers.noop,
|
var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.scaleType);
|
||||||
steps: this.options.scaleSteps,
|
var scale = new ScaleClass({
|
||||||
stepValue: this.options.scaleStepWidth,
|
ctx: this.chart.ctx,
|
||||||
min: this.options.scaleStartValue,
|
options: yAxisOptions,
|
||||||
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
calculateRange: calculateYRange,
|
||||||
|
getPointPixelForValue: function(value, index, datasetIndex) {
|
||||||
|
if (self.options.stacked) {
|
||||||
|
var offsetPos = 0;
|
||||||
|
var offsetNeg = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < datasetIndex; ++i) {
|
||||||
|
if (self.data.datasets[i].data[index] < 0) {
|
||||||
|
offsetNeg += self.data.datasets[i].data[index];
|
||||||
|
} else {
|
||||||
|
offsetPos += self.data.datasets[i].data[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 0) {
|
||||||
|
return this.getPixelForValue(offsetNeg + value);
|
||||||
|
} else {
|
||||||
|
return this.getPixelForValue(offsetPos + value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.getPixelForValue(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: yAxisOptions.id,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
this.scale = new this.ScaleClass(scaleOptions);
|
this.scales[scale.id] = scale;
|
||||||
|
}, this);
|
||||||
},
|
},
|
||||||
redraw: function() {
|
redraw: function() {
|
||||||
|
|
||||||
@ -328,7 +398,10 @@
|
|||||||
var easingDecimal = ease || 1;
|
var easingDecimal = ease || 1;
|
||||||
this.clear();
|
this.clear();
|
||||||
|
|
||||||
this.scale.draw(easingDecimal);
|
// Draw all the scales
|
||||||
|
helpers.each(this.scales, function(scale) {
|
||||||
|
scale.draw(this.chartArea);
|
||||||
|
}, this);
|
||||||
|
|
||||||
// reverse for-loop for proper stacking
|
// reverse for-loop for proper stacking
|
||||||
for (var i = this.data.datasets.length - 1; i >= 0; i--) {
|
for (var i = this.data.datasets.length - 1; i >= 0; i--) {
|
||||||
|
|||||||
@ -7,24 +7,6 @@
|
|||||||
helpers = Chart.helpers;
|
helpers = Chart.helpers;
|
||||||
|
|
||||||
var defaultConfig = {
|
var defaultConfig = {
|
||||||
//Boolean - Show a backdrop to the scale label
|
|
||||||
scaleShowLabelBackdrop : true,
|
|
||||||
|
|
||||||
//String - The colour of the label backdrop
|
|
||||||
scaleBackdropColor : "rgba(255,255,255,0.75)",
|
|
||||||
|
|
||||||
// Boolean - Whether the scale should begin at zero
|
|
||||||
scaleBeginAtZero : true,
|
|
||||||
|
|
||||||
//Number - The backdrop padding above & below the label in pixels
|
|
||||||
scaleBackdropPaddingY : 2,
|
|
||||||
|
|
||||||
//Number - The backdrop padding to the side of the label in pixels
|
|
||||||
scaleBackdropPaddingX : 2,
|
|
||||||
|
|
||||||
//Boolean - Show line for each value in the scale
|
|
||||||
scaleShowLine : true,
|
|
||||||
|
|
||||||
//Boolean - Stroke a line around each segment in the chart
|
//Boolean - Stroke a line around each segment in the chart
|
||||||
segmentShowStroke : true,
|
segmentShowStroke : true,
|
||||||
|
|
||||||
@ -34,6 +16,48 @@
|
|||||||
//Number - The width of the stroke value in pixels
|
//Number - The width of the stroke value in pixels
|
||||||
segmentStrokeWidth : 2,
|
segmentStrokeWidth : 2,
|
||||||
|
|
||||||
|
scale: {
|
||||||
|
scaleType: "radialLinear",
|
||||||
|
display: true,
|
||||||
|
|
||||||
|
//Boolean - Whether to animate scaling the chart from the centre
|
||||||
|
animate : false,
|
||||||
|
|
||||||
|
lineArc: true,
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: true,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
|
||||||
|
//Boolean - Show a backdrop to the scale label
|
||||||
|
showLabelBackdrop : true,
|
||||||
|
|
||||||
|
//String - The colour of the label backdrop
|
||||||
|
backdropColor : "rgba(255,255,255,0.75)",
|
||||||
|
|
||||||
|
//Number - The backdrop padding above & below the label in pixels
|
||||||
|
backdropPaddingY : 2,
|
||||||
|
|
||||||
|
//Number - The backdrop padding to the side of the label in pixels
|
||||||
|
backdropPaddingX : 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
//Number - Amount of animation steps
|
//Number - Amount of animation steps
|
||||||
animationSteps : 100,
|
animationSteps : 100,
|
||||||
|
|
||||||
@ -43,9 +67,6 @@
|
|||||||
//Boolean - Whether to animate the rotation of the chart
|
//Boolean - Whether to animate the rotation of the chart
|
||||||
animateRotate : true,
|
animateRotate : true,
|
||||||
|
|
||||||
//Boolean - Whether to animate scaling the chart from the centre
|
|
||||||
animateScale : false,
|
|
||||||
|
|
||||||
//String - A legend template
|
//String - A legend template
|
||||||
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
|
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
|
||||||
};
|
};
|
||||||
@ -58,10 +79,7 @@
|
|||||||
defaults : defaultConfig,
|
defaults : defaultConfig,
|
||||||
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
|
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
|
||||||
//Config is automatically merged by the core of Chart.js, and is available at this.options
|
//Config is automatically merged by the core of Chart.js, and is available at this.options
|
||||||
initialize: function(data){
|
initialize: function(){
|
||||||
// Save data as a source for updating of values & methods
|
|
||||||
this.data = data;
|
|
||||||
|
|
||||||
this.segments = [];
|
this.segments = [];
|
||||||
//Declare segment class as a chart instance specific class, so it can share props for this instance
|
//Declare segment class as a chart instance specific class, so it can share props for this instance
|
||||||
this.SegmentArc = Chart.Arc.extend({
|
this.SegmentArc = Chart.Arc.extend({
|
||||||
@ -73,47 +91,59 @@
|
|||||||
x : this.chart.width/2,
|
x : this.chart.width/2,
|
||||||
y : this.chart.height/2
|
y : this.chart.height/2
|
||||||
});
|
});
|
||||||
this.scale = new Chart.RadialScale({
|
|
||||||
display: this.options.showScale,
|
var self = this;
|
||||||
fontStyle: this.options.scaleFontStyle,
|
var ScaleClass = Chart.scales.getScaleConstructor(this.options.scale.scaleType);
|
||||||
fontSize: this.options.scaleFontSize,
|
this.scale = new ScaleClass({
|
||||||
fontFamily: this.options.scaleFontFamily,
|
options: this.options.scale,
|
||||||
fontColor: this.options.scaleFontColor,
|
|
||||||
showLabels: this.options.scaleShowLabels,
|
|
||||||
showLabelBackdrop: this.options.scaleShowLabelBackdrop,
|
|
||||||
backdropColor: this.options.scaleBackdropColor,
|
|
||||||
backdropPaddingY : this.options.scaleBackdropPaddingY,
|
|
||||||
backdropPaddingX: this.options.scaleBackdropPaddingX,
|
|
||||||
lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
|
|
||||||
lineColor: this.options.scaleLineColor,
|
|
||||||
lineArc: true,
|
lineArc: true,
|
||||||
width: this.chart.width,
|
width: this.chart.width,
|
||||||
height: this.chart.height,
|
height: this.chart.height,
|
||||||
xCenter: this.chart.width/2,
|
xCenter: this.chart.width/2,
|
||||||
yCenter: this.chart.height/2,
|
yCenter: this.chart.height/2,
|
||||||
ctx : this.chart.ctx,
|
ctx : this.chart.ctx,
|
||||||
templateString: this.options.scaleLabel,
|
valuesCount: this.data.length,
|
||||||
valuesCount: data.length
|
calculateRange: function() {
|
||||||
|
this.min = null;
|
||||||
|
this.max = null;
|
||||||
|
|
||||||
|
helpers.each(self.data, function(data) {
|
||||||
|
if (this.min === null) {
|
||||||
|
this.min = data.value;
|
||||||
|
} else if (data.value < this.min) {
|
||||||
|
this.min = data.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.max === null) {
|
||||||
|
this.max = data.value;
|
||||||
|
} else if (data.value > this.max) {
|
||||||
|
this.max = data.value;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateScaleRange(data);
|
this.updateScaleRange();
|
||||||
|
this.scale.calculateRange();
|
||||||
|
this.scale.generateTicks();
|
||||||
|
this.scale.buildYLabels();
|
||||||
|
|
||||||
this.scale.update();
|
helpers.each(this.data,function(segment,index){
|
||||||
|
|
||||||
helpers.each(data,function(segment,index){
|
|
||||||
this.addData(segment,index,true);
|
this.addData(segment,index,true);
|
||||||
},this);
|
},this);
|
||||||
|
|
||||||
//Set up tooltip events on the chart
|
//Set up tooltip events on the chart
|
||||||
if (this.options.showTooltips){
|
if (this.options.showTooltips){
|
||||||
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
helpers.bindEvents(this, this.options.events, function(evt){
|
||||||
var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
|
var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
|
||||||
helpers.each(this.segments,function(segment){
|
helpers.each(this.segments,function(segment){
|
||||||
segment.restore(["fillColor"]);
|
segment.restore(["fillColor"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
helpers.each(activeSegments,function(activeSegment){
|
helpers.each(activeSegments,function(activeSegment){
|
||||||
activeSegment.fillColor = activeSegment.highlightColor;
|
activeSegment.fillColor = activeSegment.highlightColor;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.showTooltip(activeSegments);
|
this.showTooltip(activeSegments);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -122,12 +152,12 @@
|
|||||||
},
|
},
|
||||||
getSegmentsAtEvent : function(e){
|
getSegmentsAtEvent : function(e){
|
||||||
var segmentsArray = [];
|
var segmentsArray = [];
|
||||||
|
|
||||||
var location = helpers.getRelativePosition(e);
|
var location = helpers.getRelativePosition(e);
|
||||||
|
|
||||||
helpers.each(this.segments,function(segment){
|
helpers.each(this.segments,function(segment){
|
||||||
if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
|
if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
|
||||||
},this);
|
},this);
|
||||||
|
|
||||||
return segmentsArray;
|
return segmentsArray;
|
||||||
},
|
},
|
||||||
addData : function(segment, atIndex, silent){
|
addData : function(segment, atIndex, silent){
|
||||||
@ -160,37 +190,13 @@
|
|||||||
},this);
|
},this);
|
||||||
this.scale.valuesCount = this.segments.length;
|
this.scale.valuesCount = this.segments.length;
|
||||||
},
|
},
|
||||||
updateScaleRange: function(datapoints){
|
updateScaleRange: function(){
|
||||||
var valuesArray = [];
|
helpers.extend(this.scale, {
|
||||||
helpers.each(datapoints,function(segment){
|
size: helpers.min([this.chart.width, this.chart.height]),
|
||||||
valuesArray.push(segment.value);
|
xCenter: this.chart.width/2,
|
||||||
|
yCenter: this.chart.height/2
|
||||||
});
|
});
|
||||||
|
|
||||||
var scaleSizes = (this.options.scaleOverride) ?
|
|
||||||
{
|
|
||||||
steps: this.options.scaleSteps,
|
|
||||||
stepValue: this.options.scaleStepWidth,
|
|
||||||
min: this.options.scaleStartValue,
|
|
||||||
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
|
||||||
} :
|
|
||||||
helpers.calculateScaleRange(
|
|
||||||
valuesArray,
|
|
||||||
helpers.min([this.chart.width, this.chart.height])/2,
|
|
||||||
this.options.scaleFontSize,
|
|
||||||
this.options.scaleBeginAtZero,
|
|
||||||
this.options.scaleIntegersOnly
|
|
||||||
);
|
|
||||||
|
|
||||||
helpers.extend(
|
|
||||||
this.scale,
|
|
||||||
scaleSizes,
|
|
||||||
{
|
|
||||||
size: helpers.min([this.chart.width, this.chart.height]),
|
|
||||||
xCenter: this.chart.width/2,
|
|
||||||
yCenter: this.chart.height/2
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
},
|
},
|
||||||
update : function(){
|
update : function(){
|
||||||
|
|
||||||
@ -223,8 +229,11 @@
|
|||||||
x : this.chart.width/2,
|
x : this.chart.width/2,
|
||||||
y : this.chart.height/2
|
y : this.chart.height/2
|
||||||
});
|
});
|
||||||
this.updateScaleRange(this.segments);
|
|
||||||
this.scale.update();
|
this.updateScaleRange();
|
||||||
|
this.scale.calculateRange();
|
||||||
|
this.scale.generateTicks();
|
||||||
|
this.scale.buildYLabels();
|
||||||
|
|
||||||
helpers.extend(this.scale,{
|
helpers.extend(this.scale,{
|
||||||
xCenter: this.chart.width/2,
|
xCenter: this.chart.width/2,
|
||||||
@ -232,8 +241,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
helpers.each(this.segments, function(segment){
|
helpers.each(this.segments, function(segment){
|
||||||
segment.update({
|
//segment.update({
|
||||||
outerRadius : this.scale.calculateCenterOffset(segment.value)
|
// outerRadius : this.scale.calculateCenterOffset(segment.value)
|
||||||
|
//});
|
||||||
|
helpers.extend(segment, {
|
||||||
|
outerRadius: this.scale.calculateCenterOffset(segment.value)
|
||||||
});
|
});
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
|||||||
@ -10,47 +10,88 @@
|
|||||||
Chart.Type.extend({
|
Chart.Type.extend({
|
||||||
name: "Radar",
|
name: "Radar",
|
||||||
defaults:{
|
defaults:{
|
||||||
//Boolean - Whether to show lines for each scale point
|
|
||||||
scaleShowLine : true,
|
|
||||||
|
|
||||||
//Boolean - Whether we show the angle lines out of the radar
|
scale: {
|
||||||
angleShowLineOut : true,
|
scaleType: "radialLinear",
|
||||||
|
display: true,
|
||||||
|
|
||||||
|
//Boolean - Whether to animate scaling the chart from the centre
|
||||||
|
animate : false,
|
||||||
|
|
||||||
//Boolean - Whether to show labels on the scale
|
lineArc: false,
|
||||||
scaleShowLabels : false,
|
|
||||||
|
|
||||||
// Boolean - Whether the scale should begin at zero
|
// grid line settings
|
||||||
scaleBeginAtZero : true,
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
},
|
||||||
|
|
||||||
//String - Colour of the angle line
|
angleLines: {
|
||||||
angleLineColor : "rgba(0,0,0,.1)",
|
show: true,
|
||||||
|
color: "rgba(0,0,0,.1)",
|
||||||
|
lineWidth: 1
|
||||||
|
},
|
||||||
|
|
||||||
//Number - Pixel width of the angle line
|
// scale numbers
|
||||||
angleLineWidth : 1,
|
beginAtZero: true,
|
||||||
|
|
||||||
//String - Point label font declaration
|
// label settings
|
||||||
pointLabelFontFamily : "'Arial'",
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
|
||||||
//String - Point label font weight
|
//Boolean - Show a backdrop to the scale label
|
||||||
pointLabelFontStyle : "normal",
|
showLabelBackdrop : true,
|
||||||
|
|
||||||
//Number - Point label font size in pixels
|
//String - The colour of the label backdrop
|
||||||
pointLabelFontSize : 10,
|
backdropColor : "rgba(255,255,255,0.75)",
|
||||||
|
|
||||||
//String - Point label font colour
|
//Number - The backdrop padding above & below the label in pixels
|
||||||
pointLabelFontColor : "#666",
|
backdropPaddingY : 2,
|
||||||
|
|
||||||
|
//Number - The backdrop padding to the side of the label in pixels
|
||||||
|
backdropPaddingX : 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
pointLabels: {
|
||||||
|
//String - Point label font declaration
|
||||||
|
fontFamily : "'Arial'",
|
||||||
|
|
||||||
|
//String - Point label font weight
|
||||||
|
fontStyle : "normal",
|
||||||
|
|
||||||
|
//Number - Point label font size in pixels
|
||||||
|
fontSize : 10,
|
||||||
|
|
||||||
|
//String - Point label font colour
|
||||||
|
fontColor : "#666",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
//Boolean - Whether to show a dot for each point
|
//Boolean - Whether to show a dot for each point
|
||||||
pointDot : true,
|
pointDot : true,
|
||||||
|
|
||||||
//Number - Radius of each point dot in pixels
|
//Number - Radius of each point dot in pixels
|
||||||
pointDotRadius : 3,
|
pointRadius: 3,
|
||||||
|
|
||||||
//Number - Pixel width of point dot stroke
|
//Number - Pixel width of point dot border
|
||||||
pointDotStrokeWidth : 1,
|
pointBorderWidth: 1,
|
||||||
|
|
||||||
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
|
//Number - Pixel width of point on hover
|
||||||
pointHitDetectionRadius : 20,
|
pointHoverRadius: 5,
|
||||||
|
|
||||||
|
//Number - Pixel width of point dot border on hover
|
||||||
|
pointHoverBorderWidth: 2,
|
||||||
|
pointBackgroundColor: Chart.defaults.global.defaultColor,
|
||||||
|
pointBorderColor: Chart.defaults.global.defaultColor,
|
||||||
|
|
||||||
|
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
|
||||||
|
pointHitRadius: 20,
|
||||||
|
|
||||||
//Boolean - Whether to show a stroke for datasets
|
//Boolean - Whether to show a stroke for datasets
|
||||||
datasetStroke : true,
|
datasetStroke : true,
|
||||||
@ -66,25 +107,19 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function(data){
|
initialize: function(){
|
||||||
// Save data as a source for updating of values & methods
|
|
||||||
this.data = data;
|
|
||||||
|
|
||||||
this.PointClass = Chart.Point.extend({
|
this.PointClass = Chart.Point.extend({
|
||||||
strokeWidth : this.options.pointDotStrokeWidth,
|
|
||||||
radius : this.options.pointDotRadius,
|
|
||||||
display: this.options.pointDot,
|
display: this.options.pointDot,
|
||||||
hitDetectionRadius : this.options.pointHitDetectionRadius,
|
_chart: this.chart
|
||||||
ctx : this.chart.ctx
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.datasets = [];
|
this.datasets = [];
|
||||||
|
|
||||||
this.buildScale(data);
|
this.buildScale(this.data);
|
||||||
|
|
||||||
//Set up tooltip events on the chart
|
//Set up tooltip events on the chart
|
||||||
if (this.options.showTooltips){
|
if (this.options.showTooltips){
|
||||||
helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
|
helpers.bindEvents(this, this.options.events, function(evt){
|
||||||
var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
|
var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
|
||||||
|
|
||||||
this.eachPoints(function(point){
|
this.eachPoints(function(point){
|
||||||
@ -100,7 +135,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Iterate through each of the datasets, and build this into a property of the chart
|
//Iterate through each of the datasets, and build this into a property of the chart
|
||||||
helpers.each(data.datasets,function(dataset){
|
helpers.each(this.data.datasets,function(dataset){
|
||||||
|
|
||||||
var datasetObject = {
|
var datasetObject = {
|
||||||
label: dataset.label || null,
|
label: dataset.label || null,
|
||||||
@ -121,14 +156,22 @@
|
|||||||
}
|
}
|
||||||
datasetObject.points.push(new this.PointClass({
|
datasetObject.points.push(new this.PointClass({
|
||||||
value : dataPoint,
|
value : dataPoint,
|
||||||
label : data.labels[index],
|
label : this.data.labels[index],
|
||||||
datasetLabel: dataset.label,
|
datasetLabel: dataset.label,
|
||||||
x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
|
x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
|
||||||
y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
|
y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
|
||||||
strokeColor : dataset.pointStrokeColor,
|
strokeColor : dataset.pointStrokeColor,
|
||||||
fillColor : dataset.pointColor,
|
fillColor : dataset.pointColor,
|
||||||
highlightFill : dataset.pointHighlightFill || dataset.pointColor,
|
highlightFill : dataset.pointHighlightFill || dataset.pointColor,
|
||||||
highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
|
highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor,
|
||||||
|
|
||||||
|
// Appearance
|
||||||
|
radius: dataset.pointRadius || this.options.pointRadius,
|
||||||
|
backgroundColor: dataset.pointBackgroundColor || this.options.pointBackgroundColor,
|
||||||
|
borderWidth: dataset.pointBorderWidth || this.options.pointBorderWidth,
|
||||||
|
|
||||||
|
// Tooltip
|
||||||
|
hoverRadius: dataset.pointHitRadius || this.options.pointHitRadius,
|
||||||
}));
|
}));
|
||||||
},this);
|
},this);
|
||||||
|
|
||||||
@ -168,78 +211,47 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
buildScale : function(data){
|
buildScale : function(data){
|
||||||
this.scale = new Chart.RadialScale({
|
var self = this;
|
||||||
display: this.options.showScale,
|
|
||||||
fontStyle: this.options.scaleFontStyle,
|
var ScaleConstructor = Chart.scales.getScaleConstructor(this.options.scale.scaleType);
|
||||||
fontSize: this.options.scaleFontSize,
|
this.scale = new ScaleConstructor({
|
||||||
fontFamily: this.options.scaleFontFamily,
|
options: this.options.scale,
|
||||||
fontColor: this.options.scaleFontColor,
|
|
||||||
showLabels: this.options.scaleShowLabels,
|
|
||||||
showLabelBackdrop: this.options.scaleShowLabelBackdrop,
|
|
||||||
backdropColor: this.options.scaleBackdropColor,
|
|
||||||
backdropPaddingY : this.options.scaleBackdropPaddingY,
|
|
||||||
backdropPaddingX: this.options.scaleBackdropPaddingX,
|
|
||||||
lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
|
|
||||||
lineColor: this.options.scaleLineColor,
|
|
||||||
angleLineColor : this.options.angleLineColor,
|
|
||||||
angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
|
|
||||||
// Point labels at the edge of each line
|
|
||||||
pointLabelFontColor : this.options.pointLabelFontColor,
|
|
||||||
pointLabelFontSize : this.options.pointLabelFontSize,
|
|
||||||
pointLabelFontFamily : this.options.pointLabelFontFamily,
|
|
||||||
pointLabelFontStyle : this.options.pointLabelFontStyle,
|
|
||||||
height : this.chart.height,
|
height : this.chart.height,
|
||||||
width: this.chart.width,
|
width: this.chart.width,
|
||||||
xCenter: this.chart.width/2,
|
xCenter: this.chart.width/2,
|
||||||
yCenter: this.chart.height/2,
|
yCenter: this.chart.height/2,
|
||||||
ctx : this.chart.ctx,
|
ctx : this.chart.ctx,
|
||||||
templateString: this.options.scaleLabel,
|
|
||||||
labels: data.labels,
|
labels: data.labels,
|
||||||
valuesCount: data.datasets[0].data.length
|
valuesCount: data.datasets[0].data.length,
|
||||||
|
calculateRange: function() {
|
||||||
|
this.min = null;
|
||||||
|
this.max = null;
|
||||||
|
|
||||||
|
helpers.each(self.data.datasets, function(dataset) {
|
||||||
|
if (dataset.yAxisID === this.id) {
|
||||||
|
helpers.each(dataset.data, function(value, index) {
|
||||||
|
if (this.min === null) {
|
||||||
|
this.min = value;
|
||||||
|
} else if (value < this.min) {
|
||||||
|
this.min = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.max === null) {
|
||||||
|
this.max = value;
|
||||||
|
} else if (value > this.max) {
|
||||||
|
this.max = value;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scale.setScaleSize();
|
this.scale.setScaleSize();
|
||||||
this.updateScaleRange(data.datasets);
|
this.scale.calculateRange();
|
||||||
|
this.scale.generateTicks();
|
||||||
this.scale.buildYLabels();
|
this.scale.buildYLabels();
|
||||||
},
|
},
|
||||||
updateScaleRange: function(datasets){
|
|
||||||
var valuesArray = (function(){
|
|
||||||
var totalDataArray = [];
|
|
||||||
helpers.each(datasets,function(dataset){
|
|
||||||
if (dataset.data){
|
|
||||||
totalDataArray = totalDataArray.concat(dataset.data);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
helpers.each(dataset.points, function(point){
|
|
||||||
totalDataArray.push(point.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return totalDataArray;
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
var scaleSizes = (this.options.scaleOverride) ?
|
|
||||||
{
|
|
||||||
steps: this.options.scaleSteps,
|
|
||||||
stepValue: this.options.scaleStepWidth,
|
|
||||||
min: this.options.scaleStartValue,
|
|
||||||
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
|
|
||||||
} :
|
|
||||||
helpers.calculateScaleRange(
|
|
||||||
valuesArray,
|
|
||||||
helpers.min([this.chart.width, this.chart.height])/2,
|
|
||||||
this.options.scaleFontSize,
|
|
||||||
this.options.scaleBeginAtZero,
|
|
||||||
this.options.scaleIntegersOnly
|
|
||||||
);
|
|
||||||
|
|
||||||
helpers.extend(
|
|
||||||
this.scale,
|
|
||||||
scaleSizes
|
|
||||||
);
|
|
||||||
|
|
||||||
},
|
|
||||||
addData : function(valuesArray,label){
|
addData : function(valuesArray,label){
|
||||||
//Map the values array for each of the datasets
|
//Map the values array for each of the datasets
|
||||||
this.scale.valuesCount++;
|
this.scale.valuesCount++;
|
||||||
@ -311,8 +323,9 @@
|
|||||||
xCenter: this.chart.width/2,
|
xCenter: this.chart.width/2,
|
||||||
yCenter: this.chart.height/2
|
yCenter: this.chart.height/2
|
||||||
});
|
});
|
||||||
this.updateScaleRange(this.datasets);
|
|
||||||
this.scale.setScaleSize();
|
this.scale.calculateRange();
|
||||||
|
this.scale.generateTicks();
|
||||||
this.scale.buildYLabels();
|
this.scale.buildYLabels();
|
||||||
},
|
},
|
||||||
draw : function(ease){
|
draw : function(ease){
|
||||||
@ -326,7 +339,7 @@
|
|||||||
//Transition each point first so that the line and point drawing isn't out of sync
|
//Transition each point first so that the line and point drawing isn't out of sync
|
||||||
helpers.each(dataset.points,function(point,index){
|
helpers.each(dataset.points,function(point,index){
|
||||||
if (point.hasValue()){
|
if (point.hasValue()){
|
||||||
point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
|
point.transition(easeDecimal);
|
||||||
}
|
}
|
||||||
},this);
|
},this);
|
||||||
|
|
||||||
|
|||||||
1155
src/Chart.Scale.js
Normal file
1155
src/Chart.Scale.js
Normal file
File diff suppressed because it is too large
Load Diff
502
src/Chart.Scatter.js
Normal file
502
src/Chart.Scatter.js
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var root = this,
|
||||||
|
Chart = root.Chart,
|
||||||
|
helpers = Chart.helpers;
|
||||||
|
|
||||||
|
var defaultConfig = {
|
||||||
|
hoverMode: 'single',
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
scaleType: "linear", // scatter should not use a dataset axis
|
||||||
|
display: true,
|
||||||
|
position: "bottom",
|
||||||
|
id: "x-axis-1", // need an ID so datasets can reference the scale
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true,
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
scaleType: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
|
||||||
|
display: true,
|
||||||
|
position: "left",
|
||||||
|
id: "y-axis-1",
|
||||||
|
|
||||||
|
// grid line settings
|
||||||
|
gridLines: {
|
||||||
|
show: true,
|
||||||
|
color: "rgba(0, 0, 0, 0.05)",
|
||||||
|
lineWidth: 1,
|
||||||
|
drawOnChartArea: true,
|
||||||
|
drawTicks: true, // draw ticks extending towards the label
|
||||||
|
zeroLineWidth: 1,
|
||||||
|
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// scale numbers
|
||||||
|
beginAtZero: false,
|
||||||
|
integersOnly: false,
|
||||||
|
override: null,
|
||||||
|
|
||||||
|
// label settings
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
template: "<%=value%>",
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontColor: "#666",
|
||||||
|
fontFamily: "Helvetica Neue",
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
|
||||||
|
//Number - Tension of the bezier curve between points
|
||||||
|
tension: 0.4,
|
||||||
|
|
||||||
|
//Number - Radius of each point dot in pixels
|
||||||
|
pointRadius: 4,
|
||||||
|
|
||||||
|
//Number - Pixel width of point dot border
|
||||||
|
pointBorderWidth: 1,
|
||||||
|
|
||||||
|
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
|
||||||
|
pointHoverRadius: 20,
|
||||||
|
|
||||||
|
//Number - Pixel width of dataset border
|
||||||
|
borderWidth: 2,
|
||||||
|
|
||||||
|
//String - A legend template
|
||||||
|
legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].borderColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>",
|
||||||
|
|
||||||
|
tooltipTemplate: "(<%= dataX %>, <%= dataY %>)",
|
||||||
|
multiTooltipTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= dataX %>, <%= dataY %>)",
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Chart.Type.extend({
|
||||||
|
name: "Scatter",
|
||||||
|
defaults: defaultConfig,
|
||||||
|
initialize: function() {
|
||||||
|
//Custom Point Defaults
|
||||||
|
this.PointClass = Chart.Point.extend({
|
||||||
|
_chart: this.chart,
|
||||||
|
offsetGridLines: this.options.offsetGridLines,
|
||||||
|
borderWidth: this.options.pointBorderWidth,
|
||||||
|
radius: this.options.pointRadius,
|
||||||
|
hoverRadius: this.options.pointHoverRadius,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Events
|
||||||
|
helpers.bindEvents(this, this.options.events, this.events);
|
||||||
|
|
||||||
|
// Build Scale
|
||||||
|
this.buildScale();
|
||||||
|
Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
|
||||||
|
|
||||||
|
//Create a new line and its points for each dataset and piece of data
|
||||||
|
helpers.each(this.data.datasets, function(dataset, datasetIndex) {
|
||||||
|
dataset.metaDataset = new Chart.Line();
|
||||||
|
dataset.metaData = [];
|
||||||
|
helpers.each(dataset.data, function(dataPoint, index) {
|
||||||
|
dataset.metaData.push(new this.PointClass());
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Make sure each dataset is bound to an x and a y axis
|
||||||
|
if (!dataset.xAxisID) {
|
||||||
|
dataset.xAxisID = this.options.scales.xAxes[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dataset.yAxisID) {
|
||||||
|
dataset.yAxisID = this.options.scales.yAxes[0].id;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Set defaults for lines
|
||||||
|
this.eachDataset(function(dataset, datasetIndex) {
|
||||||
|
dataset = helpers.merge(this.options, dataset);
|
||||||
|
helpers.extend(dataset.metaDataset, {
|
||||||
|
_points: dataset.metaData,
|
||||||
|
_datasetIndex: datasetIndex,
|
||||||
|
_chart: this.chart,
|
||||||
|
});
|
||||||
|
// Copy to view model
|
||||||
|
dataset.metaDataset.save();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Set defaults for points
|
||||||
|
this.eachElement(function(point, index, dataset, datasetIndex) {
|
||||||
|
var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
|
||||||
|
|
||||||
|
helpers.extend(point, {
|
||||||
|
x: xScale.getPixelForValue(index),
|
||||||
|
y: this.chartArea.bottom,
|
||||||
|
_datasetIndex: datasetIndex,
|
||||||
|
_index: index,
|
||||||
|
_chart: this.chart
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default bezier control points
|
||||||
|
helpers.extend(point, {
|
||||||
|
controlPointPreviousX: this.previousPoint(dataset, index).x,
|
||||||
|
controlPointPreviousY: this.nextPoint(dataset, index).y,
|
||||||
|
controlPointNextX: this.previousPoint(dataset, index).x,
|
||||||
|
controlPointNextY: this.nextPoint(dataset, index).y,
|
||||||
|
});
|
||||||
|
// Copy to view model
|
||||||
|
point.save();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Create tooltip instance exclusively for this chart with some defaults.
|
||||||
|
this.tooltip = new Chart.Tooltip({
|
||||||
|
_chart: this.chart,
|
||||||
|
_data: this.data,
|
||||||
|
_options: this.options,
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
},
|
||||||
|
nextPoint: function(collection, index) {
|
||||||
|
return collection[index - 1] || collection[index];
|
||||||
|
},
|
||||||
|
previousPoint: function(collection, index) {
|
||||||
|
return collection[index + 1] || collection[index];
|
||||||
|
},
|
||||||
|
events: function(e) {
|
||||||
|
// If exiting chart
|
||||||
|
if (e.type == 'mouseout') {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastActive = this.lastActive || [];
|
||||||
|
|
||||||
|
// Find Active Elements
|
||||||
|
this.active = function() {
|
||||||
|
switch (this.options.hoverMode) {
|
||||||
|
case 'single':
|
||||||
|
return this.getElementAtEvent(e);
|
||||||
|
case 'label':
|
||||||
|
return this.getElementsAtEvent(e);
|
||||||
|
case 'dataset':
|
||||||
|
return this.getDatasetAtEvent(e);
|
||||||
|
default:
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}.call(this);
|
||||||
|
|
||||||
|
// On Hover hook
|
||||||
|
if (this.options.onHover) {
|
||||||
|
this.options.onHover.call(this, this.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove styling for last active (even if it may still be active)
|
||||||
|
if (this.lastActive.length) {
|
||||||
|
switch (this.options.hoverMode) {
|
||||||
|
case 'single':
|
||||||
|
this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].pointBackgroundColor;
|
||||||
|
this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderColor;
|
||||||
|
this.lastActive[0].borderWidth = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth;
|
||||||
|
break;
|
||||||
|
case 'label':
|
||||||
|
for (var i = 0; i < this.lastActive.length; i++) {
|
||||||
|
this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].pointBackgroundColor;
|
||||||
|
this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].pointBorderColor;
|
||||||
|
this.lastActive[i].borderWidth = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'dataset':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Don't change anything
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Built in hover styling
|
||||||
|
if (this.active.length && this.options.hoverMode) {
|
||||||
|
switch (this.options.hoverMode) {
|
||||||
|
case 'single':
|
||||||
|
this.active[0].backgroundColor = this.data.datasets[this.active[0]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.5).darken(0.35).rgbString();
|
||||||
|
this.active[0].borderColor = this.data.datasets[this.active[0]._datasetIndex].hoverBorderColor || helpers.color(this.active[0].borderColor).saturate(0.5).darken(0.35).rgbString();
|
||||||
|
this.active[0].borderWidth = this.data.datasets[this.active[0]._datasetIndex].borderWidth + 10;
|
||||||
|
break;
|
||||||
|
case 'label':
|
||||||
|
for (var i = 0; i < this.active.length; i++) {
|
||||||
|
this.active[i].backgroundColor = this.data.datasets[this.active[i]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[i].backgroundColor).saturate(0.5).darken(0.35).rgbString();
|
||||||
|
this.active[i].borderColor = this.data.datasets[this.active[i]._datasetIndex].hoverBorderColor || helpers.color(this.active[i].borderColor).saturate(0.5).darken(0.35).rgbString();
|
||||||
|
this.active[i].borderWidth = this.data.datasets[this.active[i]._datasetIndex].borderWidth + 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'dataset':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Don't change anything
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Built in Tooltips
|
||||||
|
if (this.options.showTooltips) {
|
||||||
|
|
||||||
|
// The usual updates
|
||||||
|
this.tooltip.initialize();
|
||||||
|
|
||||||
|
// Active
|
||||||
|
if (this.active.length) {
|
||||||
|
helpers.extend(this.tooltip, {
|
||||||
|
opacity: 1,
|
||||||
|
_active: this.active,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tooltip.update();
|
||||||
|
} else {
|
||||||
|
// Inactive
|
||||||
|
helpers.extend(this.tooltip, {
|
||||||
|
opacity: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover animations
|
||||||
|
this.tooltip.pivot();
|
||||||
|
|
||||||
|
if (!this.animating) {
|
||||||
|
var changed;
|
||||||
|
|
||||||
|
helpers.each(this.active, function(element, index) {
|
||||||
|
if (element !== this.lastActive[index]) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// If entering, leaving, or changing elements, animate the change via pivot
|
||||||
|
if ((!this.lastActive.length && this.active.length) ||
|
||||||
|
(this.lastActive.length && !this.active.length) ||
|
||||||
|
(this.lastActive.length && this.active.length && changed)) {
|
||||||
|
|
||||||
|
this.stop();
|
||||||
|
this.render(this.options.hoverAnimationDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember Last Active
|
||||||
|
this.lastActive = this.active;
|
||||||
|
return this;
|
||||||
|
|
||||||
|
},
|
||||||
|
update: function() {
|
||||||
|
Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height);
|
||||||
|
|
||||||
|
// Update the lines
|
||||||
|
this.eachDataset(function(dataset, datasetIndex) {
|
||||||
|
helpers.extend(dataset.metaDataset, {
|
||||||
|
backgroundColor: dataset.backgroundColor || this.options.backgroundColor,
|
||||||
|
borderWidth: dataset.borderWidth || this.options.borderWidth,
|
||||||
|
borderColor: dataset.borderColor || this.options.borderColor,
|
||||||
|
tension: dataset.tension || this.options.tension,
|
||||||
|
scaleTop: this.chartArea.top,
|
||||||
|
scaleBottom: this.chartArea.bottom,
|
||||||
|
_points: dataset.metaData,
|
||||||
|
_datasetIndex: datasetIndex,
|
||||||
|
});
|
||||||
|
dataset.metaDataset.pivot();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the points
|
||||||
|
this.eachElement(function(point, index, dataset, datasetIndex) {
|
||||||
|
var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID];
|
||||||
|
var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID];
|
||||||
|
|
||||||
|
helpers.extend(point, {
|
||||||
|
x: xScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].x),
|
||||||
|
y: yScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].y),
|
||||||
|
dataX: this.data.datasets[datasetIndex].data[index].x,
|
||||||
|
dataY: this.data.datasets[datasetIndex].data[index].y,
|
||||||
|
label: '', // so that the multitooltip looks ok
|
||||||
|
value: this.data.datasets[datasetIndex].data[index].y, // for legacy reasons
|
||||||
|
datasetLabel: this.data.datasets[datasetIndex].label,
|
||||||
|
// Appearance
|
||||||
|
hoverBackgroundColor: this.data.datasets[datasetIndex].pointHoverBackgroundColor || this.options.pointHoverBackgroundColor,
|
||||||
|
hoverBorderColor: this.data.datasets[datasetIndex].pointHoverBorderColor || this.options.pointHoverBorderColor,
|
||||||
|
hoverRadius: this.data.datasets[datasetIndex].pointHoverRadius || this.options.pointHoverRadius,
|
||||||
|
radius: this.data.datasets[datasetIndex].pointRadius || this.options.pointRadius,
|
||||||
|
borderWidth: this.data.datasets[datasetIndex].pointBorderWidth || this.options.pointBorderWidth,
|
||||||
|
borderColor: this.data.datasets[datasetIndex].pointBorderColor || this.options.pointBorderColor,
|
||||||
|
backgroundColor: this.data.datasets[datasetIndex].pointBackgroundColor || this.options.pointBackgroundColor,
|
||||||
|
tension: this.data.datasets[datasetIndex].metaDataset.tension,
|
||||||
|
_datasetIndex: datasetIndex,
|
||||||
|
_index: index,
|
||||||
|
});
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Update control points for the bezier curve
|
||||||
|
this.eachElement(function(point, index, dataset, datasetIndex) {
|
||||||
|
var controlPoints = helpers.splineCurve(
|
||||||
|
this.previousPoint(dataset, index),
|
||||||
|
point,
|
||||||
|
this.nextPoint(dataset, index),
|
||||||
|
point.tension
|
||||||
|
);
|
||||||
|
|
||||||
|
point.controlPointPreviousX = controlPoints.previous.x;
|
||||||
|
point.controlPointNextX = controlPoints.next.x;
|
||||||
|
|
||||||
|
// Prevent the bezier going outside of the bounds of the graph
|
||||||
|
|
||||||
|
// Cap puter bezier handles to the upper/lower scale bounds
|
||||||
|
if (controlPoints.next.y > this.chartArea.bottom) {
|
||||||
|
point.controlPointNextY = this.chartArea.bottom;
|
||||||
|
} else if (controlPoints.next.y < this.chartArea.top) {
|
||||||
|
point.controlPointNextY = this.chartArea.top;
|
||||||
|
} else {
|
||||||
|
point.controlPointNextY = controlPoints.next.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap inner bezier handles to the upper/lower scale bounds
|
||||||
|
if (controlPoints.previous.y > this.chartArea.bottom) {
|
||||||
|
point.controlPointPreviousY = this.chartArea.bottom;
|
||||||
|
} else if (controlPoints.previous.y < this.chartArea.top) {
|
||||||
|
point.controlPointPreviousY = this.chartArea.top;
|
||||||
|
} else {
|
||||||
|
point.controlPointPreviousY = controlPoints.previous.y;
|
||||||
|
}
|
||||||
|
// Now pivot the point for animation
|
||||||
|
point.pivot();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
buildScale: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var calculateXRange = function() {
|
||||||
|
this.min = null;
|
||||||
|
this.max = null;
|
||||||
|
|
||||||
|
helpers.each(self.data.datasets, function(dataset) {
|
||||||
|
// Only set the scale range for datasets that actually use this axis
|
||||||
|
if (dataset.xAxisID === this.id) {
|
||||||
|
helpers.each(dataset.data, function(value) {
|
||||||
|
if (this.min === null) {
|
||||||
|
this.min = value.x;
|
||||||
|
} else if (value.x < this.min) {
|
||||||
|
this.min = value.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.max === null) {
|
||||||
|
this.max = value.x;
|
||||||
|
} else if (value.x > this.max) {
|
||||||
|
this.max = value.x;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
var calculateYRange = function() {
|
||||||
|
this.min = null;
|
||||||
|
this.max = null;
|
||||||
|
|
||||||
|
helpers.each(self.data.datasets, function(dataset) {
|
||||||
|
if (dataset.yAxisID === this.id) {
|
||||||
|
helpers.each(dataset.data, function(value) {
|
||||||
|
if (this.min === null) {
|
||||||
|
this.min = value.y;
|
||||||
|
} else if (value.y < this.min) {
|
||||||
|
this.min = value.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.max === null) {
|
||||||
|
this.max = value.y;
|
||||||
|
} else if (value.y > this.max) {
|
||||||
|
this.max = value.y;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map of scale ID to scale object so we can lookup later
|
||||||
|
this.scales = {};
|
||||||
|
|
||||||
|
helpers.each(this.options.scales.xAxes, function(xAxisOptions) {
|
||||||
|
var ScaleClass = Chart.scales.getScaleConstructor(xAxisOptions.scaleType);
|
||||||
|
var scale = new ScaleClass({
|
||||||
|
ctx: this.chart.ctx,
|
||||||
|
options: xAxisOptions,
|
||||||
|
calculateRange: calculateXRange,
|
||||||
|
id: xAxisOptions.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.scales[scale.id] = scale;
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
helpers.each(this.options.scales.yAxes, function(yAxisOptions) {
|
||||||
|
var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.scaleType);
|
||||||
|
var scale = new ScaleClass({
|
||||||
|
ctx: this.chart.ctx,
|
||||||
|
options: yAxisOptions,
|
||||||
|
calculateRange: calculateYRange,
|
||||||
|
id: yAxisOptions.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.scales[scale.id] = scale;
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
redraw: function() {
|
||||||
|
|
||||||
|
},
|
||||||
|
draw: function(ease) {
|
||||||
|
|
||||||
|
var easingDecimal = ease || 1;
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
// Draw all the scales
|
||||||
|
helpers.each(this.scales, function(scale) {
|
||||||
|
scale.draw(this.chartArea);
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.eachDataset(function(dataset, datasetIndex) {
|
||||||
|
// Transition Point Locations
|
||||||
|
helpers.each(dataset.metaData, function(point, index) {
|
||||||
|
point.transition(easingDecimal);
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Transition and Draw the line
|
||||||
|
dataset.metaDataset.transition(easingDecimal).draw();
|
||||||
|
|
||||||
|
// Draw the points
|
||||||
|
helpers.each(dataset.metaData, function(point) {
|
||||||
|
point.draw();
|
||||||
|
});
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
// Finally draw the tooltip
|
||||||
|
this.tooltip.transition(easingDecimal).draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
Loading…
x
Reference in New Issue
Block a user