學(xué)習(xí)了這么多Canvas中的API瞄勾,是時(shí)候出來溜溜了脂崔,寫一個(gè)low版的柱狀圖吧!
先瞜一眼效果圖:
分析一下簡版思路:
上個(gè)canvas
寬高為600* 600繪制刻度軸
繪制單根柱子
單個(gè)tip的繪制
根據(jù)數(shù)據(jù)個(gè)數(shù)循環(huán)繪制
第一步: 上個(gè)Canvas
// 創(chuàng)建canvas
var canvas = document.createElement('canvas');
// 設(shè)置寬高
canvas.width = 600;
canvas.height = 600;
// 背景顏色
canvas.style.backgroundColor = '#eee';
// 添加至body中
document.body.appendChild(canvas);
// 獲取2d上下文
var ctx = canvas.getContext('2d');
第二步:繪制刻度軸
1.繪制刻度軸的時(shí)候土浸,我們的的軸心(0, 0)在canvas中的(50, 400)上昼弟,因此我們可以translate移動(dòng)原點(diǎn)丘损,當(dāng)然芍碧,需要提前保存當(dāng)前的狀態(tài)
2.繪制Y軸刻度時(shí),需要考慮到刻度值是反著的号俐,并且文案繪制的時(shí)候泌豆,水平對齊方式,垂直對齊方需要稍微注意一下
繪制刻度線的函數(shù)
/**
* 繪制刻度線
* @param {*} context
* @param {*} isColumn : 是否垂直
* @param {*} isPlus : 是否為正
* @param {*} step : 刻度值
* @param {*} length : 刻度個(gè)數(shù)
*/
function scaleLine(context, isColumn, isPlus, step, length) {
context.save();
context.lineWidth = 2;
context.strokeStyle = '#000';
context.textAlign = 'right';
context.textBaseline = 'middle';
context.beginPath();
context.moveTo(0, 0);
if (isColumn) {
// 垂直繪制Y軸
for (var i = 0; i < length; i++) {
// 正負(fù)軸的判斷
var y = isPlus ? -i * step : i * step;
// 繪制每段刻度
context.lineTo(0, y);
// 刻度值的突出線
context.lineTo(-5, y);
// 刻度值
context.fillText(-y, -10, y)
context.lineTo(0, y);
}
} else {
// 水平繪制X軸
for (var i = 0; i < length; i++) {
// 正負(fù)軸的判斷
var x = isPlus ? -i * step : i * step;
context.lineTo(x, 0);
}
}
context.stroke();
context.restore();
}
通過調(diào)用scaleLine函數(shù) 吏饿,我們可以另寫一個(gè)函數(shù)踪危,統(tǒng)一調(diào)用,并且統(tǒng)一的將原點(diǎn)移動(dòng)至(50, 400)位置
// 繪制坐標(biāo)刻度線
function scaleXY(context) {
context.save();
// 移動(dòng)原點(diǎn), 將刻度線坐標(biāo)(0, 0) 移動(dòng)到 (50,400)
context.translate(50, 400);
// 繪制刻度
// +y軸
scaleLine(context, true, true, 50, 7);
// -y軸
scaleLine(context, true, false, 50, 3);
// x軸
scaleLine(context, false, false, 50, 9);
context.restore();
好了猪落,這樣我們基本的刻度軸在此時(shí)就會(huì)出現(xiàn)在畫布上贞远,是不是很簡單~
第三步: 繪制單根柱子
在繪制單根柱子的時(shí)候,頂部會(huì)有彈性的表現(xiàn)笨忌,采用最簡單的思路蓝仲,
1.畫一幀:畫高于當(dāng)前數(shù)據(jù)值
2.擦一幀,擦高于當(dāng)前數(shù)據(jù)值
3.畫一幀:畫低于當(dāng)前數(shù)據(jù)值
4.擦一幀官疲,擦低于當(dāng)前數(shù)據(jù)值
5.畫一幀:畫高于當(dāng)前數(shù)據(jù)值
這四個(gè)步驟循環(huán)袱结,直到最后回到當(dāng)前數(shù)據(jù)值,我們需要的就是控制其步長途凫,那么我們完全可以使用比例來畫垢夹,并且用數(shù)組存儲比例,數(shù)組的長度就是步長维费,每幀按順序畫一次數(shù)組中的比例及實(shí)現(xiàn)了果元,就是這么簡單,就是這么的low(其實(shí)是因?yàn)樽约簩憦椥詣?dòng)畫的時(shí)候犀盟,邊界值的判斷卡著自己腦殼了而晒,如果有更好的思路希望能提供一下,感謝~)
// 每一幀的比例阅畴,畫多少幀倡怎,取決于比例數(shù)組的長度
var scaleStep = [0.2, 0.3, 0.4, 0.5, 0.6, 0.75, 0.85, 0.95, 1, 1.05, 1.1, 1.15, 1.1, 1.05, 1, 0.975, 0.950, 0.925, 0.90, 0.875, 0.850, 0.825, 0.80, 0.825, 0.850, 0.875, 0.90, 0.925, 0.950, 0.975, 1];
按照每一幀畫上去,肯定是需要擦除上一幀
因此:
當(dāng)畫第二幀比例的時(shí)候恶阴,需要擦去第一幀所畫的
當(dāng)畫第三幀比例的時(shí)候诈胜,需要擦去第二幀所畫的
當(dāng)畫第四幀比例的時(shí)候,需要擦去第三幀所畫的
……
代碼就是:
擦
Height: 為數(shù)據(jù)的高度
var clearH = height * (scaleStep[i === 0 ? 0 : i - 1] + 0.1);
畫
var fillH = height * scaleStep[i];
為什么擦的時(shí)候這判斷冯事?
(scaleStep[i === 0 ? 0 : i - 1] + 0.1)
這個(gè)判斷是考慮到焦匈,當(dāng)為第一幀的時(shí)候,我們沒有上一幀了呀昵仅,還擦個(gè)球球缓熟,因此第一幀的時(shí)候累魔,擦的話就擦自己吧。擦完了自己就將自己畫上够滑,當(dāng)執(zhí)行第二幀的時(shí)候去擦掉第一幀
好滴垦写,好奇為什么擦要+ 0.1 的比例呢?
哈哈彰触,好像是精度不足梯投,擦不完,可能會(huì)有點(diǎn)漏了况毅,因此擦的時(shí)候就多擦點(diǎn)吧
在畫柱子的時(shí)候呢分蓖,X軸會(huì)稍稍有點(diǎn)被蓋住,因此需要重繪一下X軸
scaleLine(context, false, false, 50, 9);
然后呢尔许? 這柱子畫那呢么鹤?
當(dāng)然是從x軸開始畫呀,所以又要移動(dòng)一下原點(diǎn)啦味廊,這個(gè)是每一幀都需要的移動(dòng)的蒸甜,不可能在定時(shí)器外面使用(定時(shí)器是異步的)
// 移動(dòng)原點(diǎn), 將刻度線坐標(biāo)(0, 0) 移動(dòng)到 (50,400)
context.translate(50, 400);
好了,綜上所述余佛,來個(gè)定時(shí)器吧柠新,把他們裝起來,每17毫秒來一下衙熔,就實(shí)現(xiàn)的彈性的效果了
/**
* 繪制單根樹狀
* @param {*} context
* @param {*} x x軸坐標(biāo)
* @param {*} width 寬度
* @param {*} height 高度
* @param {*} bgColor 填充顏色
*/
function drawRect(context, x, width, height, bgColor) {
// 每一幀的比例登颓,畫多少幀搅荞,取決于比例數(shù)組的長度
var scaleStep = [0.2, 0.3, 0.4, 0.5, 0.6, 0.75, 0.85, 0.95, 1, 1.05, 1.1, 1.15, 1.1, 1.05, 1, 0.975, 0.950, 0.925, 0.90, 0.875, 0.850, 0.825, 0.80, 0.825, 0.850, 0.875, 0.90, 0.925, 0.950, 0.975, 1];
var i = 0;
var timer = setInterval(function () {
context.save();
// 移動(dòng)原點(diǎn), 將刻度線坐標(biāo)(0, 0) 移動(dòng)到 (50,400)
context.translate(50, 400);
// 清除是上一根柱子的高度
var clearH = height * (scaleStep[i === 0 ? 0 : i - 1] + 0.1);
context.clearRect(x, -clearH, width, clearH);
// 柱子的顏色
context.fillStyle = bgColor;
// 繪制柱子的高度
var fillH = height * scaleStep[i];
context.fillRect(x, -fillH, width, fillH);
// 重新繪制一下X軸: 因?yàn)橹訒?huì)遮住X軸
scaleLine(context, false, false, 50, 9);
// 下一幀
i++;
// 當(dāng)循環(huán)步長數(shù)組結(jié)束時(shí)
if (i === scaleStep.length) {
clearInterval(timer);
timer = i = scaleStep = null;
}
context.restore();
}, 17);
}
來個(gè)參數(shù)測試一下吧~
實(shí)現(xiàn)起來也很簡單
只不過多個(gè)之間需要保持間距红氯,那么下一個(gè)tip是前面所有tip的間距以及高度之和就可以了
來一個(gè)起始間距高度
來一個(gè)起始間距高度
var allHeight = 10;
每繪制一個(gè)tip高度就需要疊加一次(我就來了個(gè)死的, 畢竟low嘛)
allHeight += 20;
好吧,上代碼
/ 每繪制一個(gè)提示咕痛,則需要疊加計(jì)算一次痢甘,下一次繪制的坐標(biāo)是之前繪制過后的高度之和
// 起始高度間距
var allHeight = 10;
/**
*
* @param {*} context
* @param {*} text 文案名字
* @param {*} color 填充顏色
*/
function drawTips(context, text, color) {
context.save();
// 填充顏色
context.fillStyle = color;
// 小色塊的繪制
context.fillRect(500, allHeight, 10, 10);
// 繪制文字
context.font = '14px bold';
context.textBaseline = 'middle';
// x軸的位置隨意定義一個(gè)
context.fillText(text, 520, allHeight + 6);
context.restore();
// 高度每次畫完一個(gè)需要疊加一次
allHeight += 20;
}
到這就已經(jīng)完成前面四步了,就剩下數(shù)據(jù)了~
好滴:我準(zhǔn)備了一組low版數(shù)據(jù)
var arr = [
{
name: '項(xiàng)目一',
height: 50,
color: 'purple'
},
{
name: '項(xiàng)目二',
height: 100,
color: 'skyblue'
},
{
name: '項(xiàng)目三',
height: 120,
color: 'rgb(252, 157, 154)'
},
{
name: '項(xiàng)目四',
height: 200,
color: 'rgb(244, 208, 4)'
},
{
name: '項(xiàng)目五',
height: -50,
color: 'orange'
},
{
name: '項(xiàng)目六',
height: -100,
color: 'rgb(254, 67, 101)'
},
{
name: '項(xiàng)目七',
height: 170,
color: 'rgb(204, 200, 169)'
},
{
name: '項(xiàng)目八',
height: 250,
color: 'rgb(240, 205, 173)'
},
{
name: '項(xiàng)目九',
height: -20,
color: 'rgb(131, 175, 155)'
},
{
name: '項(xiàng)目十',
height: -100,
color: 'rgb(220, 87, 18)'
}
];
繪制每根柱子都需要有間距茉贡,也和繪制tip一樣塞栅,需要依次循環(huán)疊加x坐標(biāo)值
每次繪制柱子之間需要有時(shí)間的間隔
繪制柱子的同時(shí),需要繪制tip腔丧,那么我們可以整合至一個(gè)功能里面
/**
* 繪制數(shù)據(jù)步驟
* @param {*} context
* @param {*} arr 數(shù)據(jù)
* @param {*} time 每繪制一根柱子的間隔時(shí)間
*/
function drawData(context, arr, time) {
// 從第一個(gè)數(shù)據(jù)開始放椰,每隔500毫秒繪制下一個(gè)數(shù)據(jù)
var i = 0;
// 每繪制一根柱子,則需要疊加計(jì)算一次愉粤,下一次繪制的坐標(biāo)是之前繪制過后的寬度之和
var allWidth = 10;
var timer = setInterval(function () {
// 繪制每一根數(shù)據(jù)
drawRect(context, allWidth, 20, arr[i].height, arr[i].color);
// 繪制提示
drawTips(context, arr[i].name, arr[i].color);
// 每次都加30 柱子的寬度以及間隔10
allWidth += 30;
i++;
if (i === arr.length) {
clearInterval(timer);
timer = null;
};
}, time);
// 每繪制一個(gè)提示砾医,則需要疊加計(jì)算一次,下一次繪制的坐標(biāo)是之前繪制過后的高度之和
// 起始高度間距
var allHeight = 10;
/**
*
* @param {*} context
* @param {*} text 文案名字
* @param {*} color 填充顏色
*/
function drawTips(context, text, color) {
context.save();
// 填充顏色
context.fillStyle = color;
// 小色塊的繪制
context.fillRect(500, allHeight, 10, 10);
// 繪制文字
context.font = '14px bold';
context.textBaseline = 'middle';
// x軸的位置隨意定義一個(gè)
context.fillText(text, 520, allHeight + 6);
context.restore();
// 高度每次畫完一個(gè)需要疊加一次
allHeight += 20;
}
}
好了衣厘,總結(jié)一下這些功能
- 來個(gè)刻度
scaleLine(context, isColumn, isPlus, step, length) - 數(shù)據(jù)來一打
Var arr; - 將數(shù)據(jù)傳入
drawData(context, arr, time)
該方法里面調(diào)用了:
3.1 單個(gè)tip的繪制功能
drawTips(context, text, color)
單個(gè)柱子的繪制
3.2 drawRect(context, x, width, height, bgColor)
low的柱狀圖就這么low如蚜,low的寫完了~