示例代碼托管在: "http://www.github.com/dashnowords/blogs" 博客園地址: "《大史住在大前端》原創博文目錄" 華為雲社區地址: "【你要的前端打怪升級指南】" [TOC] 一. 任務說明 使用原生 繪製柱狀圖。(柱狀圖截圖來自於百度Echarts官方示例庫 ...
目錄
示例代碼托管在:http://www.github.com/dashnowords/blogs
博客園地址:《大史住在大前端》原創博文目錄
華為雲社區地址:【你要的前端打怪升級指南】
一. 任務說明
使用原生canvasAPI
繪製柱狀圖。(柱狀圖截圖來自於百度Echarts官方示例庫【查看示例鏈接】)
二. 重點提示
柱狀圖或許是最容易實現的圖表類型了,矩形的部分直接使用fillRect()
來繪製即可,為了將坐標軸標簽文字繪製在小分割線中間,需要用measureText()
來測量文本的寬度,然後進行相應的偏移,否則直接繪製的話文字的左邊界會和直線相對齊。其他部分都是一些基本API的使用,希望各位小伙伴通過做練習來熟悉這些API的用法。
三. 示例代碼
提示:代碼中將個別圖表參數直接寫在了函數里(也就是所謂的“魔鬼數字”),這種做法是不提倡的,因為它違反了開發的基本原則之一“開放封閉原則”。如果你使用過Echarts
圖表庫就會發現,圖表中幾乎所有要素都可以通過參數來定製,此處只需要關註canvasAPI
的實現方法即可。
/**
* 獲取canvas繪圖上下文
* @type {[type]}
*/
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
//繪圖配置
let options = {
chartZone:[50,50,1000,700],//標識繪圖區域
yAxisLabel:['0','100','200','300','400'],//標示Y軸坐標
yMax:400,//Y軸最大值
xAxisLabel:['Mon','Tue','Wed','Thu','Fri','Sat','Sun'],//X軸坐標
data:[10,50,200,330,390,320,220],//柱狀圖數據
barStyle:{
width:70,//柱狀圖寬度
color:'#1abc9c'//柱狀圖顏色
}
}
/*Echarts使用時,會調用實例方法echartsInstance.setOptions(options)來啟動繪圖*/
drawBarChart(options);
/**
* 繪製柱狀圖
*/
function drawBarChart(options) {
drawAxis(options); //繪製坐標軸
drawYLabels(options); //繪製y軸坐標
drawXLabels(options); //繪製x軸坐標
//drawData(options);//繪製柱狀圖
drawDataGradient(options);//繪製漸變色柱狀圖
}
/**
* 繪製坐標軸
*/
function drawAxis(options) {
let chartZone = options.chartZone;
context.strokeWidth = 4;
context.strokeStyle = '#353535';
context.moveTo(chartZone[0],chartZone[1]);
context.lineTo(chartZone[0],chartZone[3]); //y軸總高從50到700
context.lineTo(chartZone[2],chartZone[3]); //x軸總長從50到1000
context.stroke();
}
/**
* 繪製y軸坐標
*/
function drawYLabels(options) {
let labels = options.yAxisLabel;
let yLength = (options.chartZone[3] - options.chartZone[1])*0.98;
let gap = yLength / (labels.length - 1);
labels.forEach(function (label, index) {
//繪製坐標文字
let offset = context.measureText(label).width + 20;
context.strokeStyle = '#eaeaea';
context.font = '16px';
context.fillText(label, options.chartZone[0] - offset ,options.chartZone[3] - index * gap);
//繪製小間隔
context.beginPath();
context.strokeStyle = '#353535';
context.moveTo(options.chartZone[0] - 10, options.chartZone[3] - index * gap);
context.lineTo(options.chartZone[0], options.chartZone[3] - index * gap);
context.stroke();
//繪製輔助線
context.beginPath();
context.strokeStyle = '#eaeaea';
context.strokeWidth = 2;
context.moveTo(options.chartZone[0], options.chartZone[3] - index * gap);
context.lineTo(options.chartZone[2], options.chartZone[3] - index * gap);
context.stroke();
});
}
/**
* 繪製x軸坐標
*/
function drawXLabels(options) {
let labels = options.xAxisLabel;
let xLength = (options.chartZone[2] - options.chartZone[0])*0.96;
let gap = xLength / labels.length;
labels.forEach(function (label, index) {
//繪製坐標文字
let offset = context.measureText(label).width;
context.strokeStyle = '#eaeaea';
context.font = '18px';
context.fillText(label, options.chartZone[0] + (index + 1) * gap - offset ,options.chartZone[3] + 20);
//繪製小間隔
context.beginPath();
context.strokeStyle = '#353535';
context.moveTo(options.chartZone[0] + (index + 1) * gap - offset / 2 ,options.chartZone[3]);
context.lineTo(options.chartZone[0] + (index + 1) * gap - offset / 2,options.chartZone[3]+5);
context.stroke();
//存儲偏移量
options.offsetXLabel = offset / 2;
});
}
/**
* 繪製數據
*/
function drawData(options) {
let data = options.data;
let xLength = (options.chartZone[2] - options.chartZone[0])*0.96;
let yLength = (options.chartZone[3] - options.chartZone[1])*0.98;
let gap = xLength / options.xAxisLabel.length;
//繪製矩形
data.forEach(function (item, index) {
context.fillStyle = options.barStyle.color || '#1abc9c'; //02BAD4
let x0 = options.chartZone[0] + (index + 1) * gap - options.barStyle.width / 2 - options.offsetXLabel;
let height = item / options.yMax * (options.chartZone[3] - options.chartZone[1])*0.98;
let y0 = options.chartZone[3] - height;
let width = options.barStyle.width;
context.fillRect(x0,y0,width,height);
});
}
/**
* 繪製線性漸變色柱狀圖
*/
function drawDataGradient(options) {
let data = options.data;
let xLength = (options.chartZone[2] - options.chartZone[0])*0.96;
let yLength = (options.chartZone[3] - options.chartZone[1])*0.98;
let gap = xLength / options.xAxisLabel.length;
//創建漸變色
let fillStyleGradient = context.createLinearGradient(50,50,50,700);
fillStyleGradient.addColorStop(0, options.barStyle.color);
fillStyleGradient.addColorStop(1, 'rgba(1,176,241,0.6)');
//繪製矩形
data.forEach(function (item, index) {
context.fillStyle = fillStyleGradient;
let x0 = options.chartZone[0] + (index + 1) * gap - options.barStyle.width / 2 - options.offsetXLabel;
let height = item / options.yMax * (options.chartZone[3] - options.chartZone[1])*0.98;
let y0 = options.chartZone[3] - height;
let width = options.barStyle.width;
context.fillRect(x0,y0,width,height);
});
}
瀏覽器中可查看效果:
四. 思考題
如果希望在坐標軸末端加一個箭頭,需要怎麼做呢?
/*x軸箭頭示例*/
//1.options中增加箭頭顏色和大小的設置
let options = {
//...
axisArrow:{
size:2,
color:'#DA5961'
}
}
//箭頭繪製函數
/**
* x軸繪製箭頭
*/
function drawArrow(options) {
let factor = options.axisArrow.size;//獲取箭頭大小因數
context.save();//保存當前設置的繪圖上下文
context.translate(options.chartZone[2], options.chartZone[3]);//移動坐標系原點至x軸末端
context.beginPath();//開始繪製箭頭
context.moveTo(0,0);//移動至新原點
context.lineTo(2 * factor,-3 * factor);
context.lineTo(10 * factor,0);
context.lineTo(2 * factor, 3 * factor);
context.lineTo(0,0);
context.globalAlpha = 0.7; //設置填充色透明度
context.fillStyle = options.axisArrow.color;//獲取箭頭顏色
context.fill();//填充箭頭路徑
context.restore();//恢復繪圖上下文樣式設置
}
箭頭效果:
y軸的箭頭請自行完成即可。