这篇文章本应该昨天写出来的,只是昨天在本地demo测试的时候发现一个bug,当时差点要到Chart.js的github上添加issue了。
Demo需求:
- 在柱状体click之后,当前堆叠柱状体更换背景颜色,以及添加虚线边框。然后点击其他的堆叠柱状图或者空白区域,原先的堆叠柱状图还要更新到原先的背景颜色。
解决方案:
- 更换背景颜色需要将datasets内当前点击的index的所有bar进行背景替换。同时也要记忆原先的背景颜色,用于后面背景颜色的还原。
- 虚线边框,由于Chart自身没有虚线边框的功能,所以只有自己去绘画出虚线边框。
第一版代码:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet/less" href="less/common.less" type="text/css">
<script type="text/javascript" src="Chart.js" ></script>
</head>
<body>
<canvas id="myChart" width="500px" height="400px"></canvas>
<script>
var ctx = document.getElementById("myChart").getContext('2d');
Chart.defaults.derivedBubble = Chart.defaults.bar;
// I think the recommend using Chart.controllers.bubble.extend({ extensions here });
var custom = Chart.controllers.bar.extend({
draw: function(ease) {
// Call super method first
Chart.controllers.bar.prototype.draw.call(this, ease);
// Now we can do some custom drawing for this dataset. Here we'll draw a red box around the first point in each dataset
var meta = this.getMeta();
var pt0 = meta.data[0];
//Only click the bar will draw the dash border
if (lastClickBarArrayStyle.index != null) {
var pt0 = meta.data[lastClickBarArrayStyle.index];
var ctx = this.chart.chart.ctx;
ctx.save();
//The top bar need to draw the top border.
if (pt0._model.datasetLabel == this.chart.data.datasets[this.chart.data.datasets.length - 1].label) {
ctx.moveTo(pt0._view.x - pt0._view.width/2, pt0._view.y);
ctx.lineTo(pt0._view.x + pt0._view.width/2, pt0._view.y);
}
//All bar need to draw the right border.
ctx.moveTo(pt0._view.x + pt0._view.width/2, pt0._view.y);
ctx.lineTo(pt0._view.x + pt0._view.width/2, pt0._view.base);
//The bottom bar need to draw the bottom border.
if (pt0._model.datasetLabel == this.chart.data.datasets[0].label) {
ctx.moveTo(pt0._view.x + pt0._view.width/2, pt0._view.base);
ctx.lineTo(pt0._view.x - pt0._view.width/2, pt0._view.base);
}
//All bar need to draw the left border.
ctx.moveTo(pt0._view.x - pt0._view.width/2, pt0._view.base);
ctx.lineTo(pt0._view.x - pt0._view.width/2, pt0._view.y);
ctx.setLineDash([5]);
ctx.lineWidth = 1;
ctx.strokeStyle = 'gray';
ctx.stroke();
}
}
});
// Stores the controller so that the chart initialization routine can look it up with
// Chart.controllers[type]
Chart.controllers.derivedBubble = custom;
//Cache store the background color and the index.
var lastClickBarArrayStyle = {
index: null,
style: [],
reset: function() {
this.index = null;
this.style= [];
}
};
var myChart = new Chart(ctx, {
type: 'derivedBubble',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '1# of Votes',
data: [12, 19, 3, 5, 2, 3],
stack: 'Stack 0',
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(255, 99, 132, 0.2)',
'rgba(255, 99, 132, 0.2)',
'rgba(255, 99, 132, 0.2)',
'rgba(255, 99, 132, 0.2)',
'rgba(255, 99, 132, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(255,99,132,1)',
'rgba(255,99,132,1)',
'rgba(255,99,132,1)',
'rgba(255,99,132,1)',
'rgba(255,99,132,1)'
],
borderWidth: 0
},
{
label: '2# of Votes',
data: [1, 2, 3, 4, 5, 6],
stack: 'Stack 0',
backgroundColor: [
'rgba(54, 162, 235, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(54, 162, 235, 0.2)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(54, 162, 235, 1)',
'rgba(54, 162, 235, 1)',
'rgba(54, 162, 235, 1)',
'rgba(54, 162, 235, 1)',
'rgba(54, 162, 235, 1)'
],
borderWidth: 0
},
{
label: '3# of Votes',
data: [10, 9, 8, 7, 6, 5],
stack: 'Stack 0',
backgroundColor: [
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)'
],
borderColor: [
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)',
'rgba(200, 162, 235, 1)'
],
borderWidth: 0
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
},
stacked: true
}],
yAxes: [{
stacked: true
}]
},
onClick: function(event, bars) {
var points = this.getElementAtEvent(event);
if (points.length > 0) {
var index = points[0]._index;
//Restore last update bar backgroundcolor.
if (lastClickBarArrayStyle.index != null) {
var dataset = this.data.datasets;
for (i = 0; i < dataset.length; i++) {
dataset[i].backgroundColor[lastClickBarArrayStyle.index] = lastClickBarArrayStyle.style[i];
}
this.update();
lastClickBarArrayStyle.reset();
}
//Update the bar to highlight backgroundcolor and cache the backgroundcolor
var dataset = this.data.datasets;
for (i = 0; i < dataset.length; i++) {
lastClickBarArrayStyle.index = index;
lastClickBarArrayStyle.style.push(dataset[i].backgroundColor[index]);
dataset[i].backgroundColor[index] = 'rgba(0,175,170, 0.3)';
}
this.update(this, {lazy:true});
} else {
var dataset = this.data.datasets;
for (i = 0; i < dataset.length; i++) {
dataset[i].backgroundColor[lastClickBarArrayStyle.index] = lastClickBarArrayStyle.style[i];
}
this.update();
lastClickBarArrayStyle.reset();
}
}
}
});
</script>
</body>
</html>
如图:可以看到基本的需求已经实现了,但是可以看到最后的一个堆叠柱状图却会有边框,这不是我们想要的,这个效果存在的原因还不知道为什么,再加上自己确实chart.js和canvans不是很了解。所以昨天当时也是怎么测试都不行。最后想起来画虚线步骤,网上查看了一下,发现有一个步骤:
ctx.beginPath();
完整代码:
这里就不把所有的代码都贴出来了,只将draw的代码贴出来:
draw: function(ease) {
// Call super method first
Chart.controllers.bar.prototype.draw.call(this, ease);
// Now we can do some custom drawing for this dataset. Here we'll draw a red box around the first point in each dataset
var meta = this.getMeta();
var pt0 = meta.data[0];
//Only click the bar will draw the dash border
if (lastClickBarArrayStyle.index != null) {
var pt0 = meta.data[lastClickBarArrayStyle.index];
var ctx = this.chart.chart.ctx;
ctx.save();
ctx.beginPath();
//The top bar need to draw the top border.
if (pt0._model.datasetLabel == this.chart.data.datasets[this.chart.data.datasets.length - 1].label) {
ctx.moveTo(pt0._view.x - pt0._view.width/2, pt0._view.y);
ctx.lineTo(pt0._view.x + pt0._view.width/2, pt0._view.y);
}
//All bar need to draw the right border.
ctx.moveTo(pt0._view.x + pt0._view.width/2, pt0._view.y);
ctx.lineTo(pt0._view.x + pt0._view.width/2, pt0._view.base);
//The bottom bar need to draw the bottom border.
if (pt0._model.datasetLabel == this.chart.data.datasets[0].label) {
ctx.moveTo(pt0._view.x + pt0._view.width/2, pt0._view.base);
ctx.lineTo(pt0._view.x - pt0._view.width/2, pt0._view.base);
}
//All bar need to draw the left border.
ctx.moveTo(pt0._view.x - pt0._view.width/2, pt0._view.base);
ctx.lineTo(pt0._view.x - pt0._view.width/2, pt0._view.y);
ctx.setLineDash([5]);
ctx.lineWidth = 1;
ctx.strokeStyle = 'gray';
ctx.stroke();
}
}