本章建議學習時間4小時
學習方式:詳細閱讀,並手動實現相關代碼(如果沒有canvas基礎,需要先學習前面的canvas基礎筆記)
學習目標:此教程將教會大家如何使用canvas繪制各種圖表,詳細分解步驟,本次講解柱狀圖。
源文件下載地址:https://github.com/sutianbinde/charts
柱狀圖
柱狀圖是前端最基本的圖表之一,我們的案例展示效果如下
功能:橫軸年份,縱軸產量,圖表會根據年份的多少自動分配柱的寬度,高度會有由低到高的運動效果,當鼠標移入時,當前柱會顏色加深。點擊圖表會有刷新重載動畫效果。
實現步驟
--新建Html文件,寫入canvas標簽,並且定義繪制圖表的方法
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <canvas id="barChart" height="400" width="600" style="margin:50px"> 你的瀏覽器不支持HTML5 canvas </canvas> <script type="text/javascript"> //封裝繪制圖表的方法 function goBarChart(dataArr){ } //調用方法,並傳入需要顯示的數據 goBarChart( [[2007, 750], [2008, 425], [2009, 960], [2010, 700], [2011, 800], [2012, 975], [2013, 375], [2014, 775]] ) </script> </body> </html>
--在 goBarChart方法中定義需要使用的變量 並獲取 canvas上下文
// 聲明所需變量 var canvas,ctx; // 圖表屬性 var cWidth, cHeight, cMargin, cSpace; var originX, originY; // 柱狀圖屬性 var bMargin, tobalBars, bWidth, maxValue; var totalYNomber; var gradient; // 運動相關變量 var ctr, numctr, speed; //鼠標移動 var mousePosition = {}; // 獲得canvas上下文 canvas = document.getElementById("barChart"); if(canvas && canvas.getContext){ ctx = canvas.getContext("2d"); }
--初始化圖表 (接着上一步的代碼寫在 goBarChart方法中 )
initChart(); // 圖表初始化 // 圖表初始化 function initChart(){ // 圖表信息 cMargin = 30; cSpace = 60; cHeight = canvas.height - cMargin*2 - cSpace; cWidth = canvas.width - cMargin*2 - cSpace; originX = cMargin + cSpace; originY = cMargin + cHeight; // 柱狀圖信息 bMargin = 15; tobalBars = dataArr.length; bWidth = parseInt( cWidth/tobalBars - bMargin ); maxValue = 0; for(var i=0; i<dataArr.length; i++){ var barVal = parseInt( dataArr[i][1] ); if( barVal > maxValue ){ maxValue = barVal; } } maxValue += 50; totalYNomber = 10; // 運動相關 ctr = 1; numctr = 100; speed = 10; //柱狀圖漸變色 gradient = ctx.createLinearGradient(0, 0, 0, 300); gradient.addColorStop(0, 'green'); gradient.addColorStop(1, 'rgba(67,203,36,1)'); }
--繪制圖表的軸和標記 (接着上一步的代碼寫在 goBarChart方法中 )
drawLineLabelMarkers(); // 繪制圖表軸、標簽和標記 // 繪制圖表軸、標簽和標記 function drawLineLabelMarkers(){ ctx.translate(0.5,0.5); // 當只繪制1像素的線的時候,坐標點需要偏移,這樣才能畫出1像素實線 ctx.font = "12px Arial"; ctx.lineWidth = 1; ctx.fillStyle = "#000"; ctx.strokeStyle = "#000"; // y軸 drawLine(originX, originY, originX, cMargin); // x軸 drawLine(originX, originY, originX+cWidth, originY); // 繪制標記 drawMarkers(); ctx.translate(-0.5,-0.5); // 還原位置 } // 畫線的方法 function drawLine(x, y, X, Y){ ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(X, Y); ctx.stroke(); ctx.closePath(); } // 繪制標記 function drawMarkers(){ ctx.strokeStyle = "#E0E0E0"; // 繪制 y var oneVal = parseInt(maxValue/totalYNomber); ctx.textAlign = "right"; for(var i=0; i<=totalYNomber; i++){ var markerVal = i*oneVal; var xMarker = originX-5; var yMarker = parseInt( cHeight*(1-markerVal/maxValue) ) + cMargin; //console.log(xMarker, yMarker+3,markerVal/maxValue,originY); ctx.fillText(markerVal, xMarker, yMarker+3, cSpace); // 文字 if(i>0){ drawLine(originX, yMarker, originX+cWidth, yMarker); } } // 繪制 x ctx.textAlign = "center"; for(var i=0; i<tobalBars; i++){ var markerVal = dataArr[i][0]; var xMarker = parseInt( originX+cWidth*(i/tobalBars)+bMargin+bWidth/2 ); var yMarker = originY+15; ctx.fillText(markerVal, xMarker, yMarker, cSpace); // 文字 } // 繪制標題 y ctx.save(); ctx.rotate(-Math.PI/2); ctx.fillText("產 量", -canvas.height/2, cSpace-10); ctx.restore(); // 繪制標題 x ctx.fillText("年份", originX+cWidth/2, originY+cSpace/2+10); };
-- 繪制柱狀圖(接着上一步的代碼寫在 goBarChart方法中 )
drawBarAnimate(); // 繪制柱狀圖的動畫 //繪制柱形圖 function drawBarAnimate(mouseMove){ for(var i=0; i<tobalBars; i++){ var oneVal = parseInt(maxValue/totalYNomber); var barVal = dataArr[i][1]; var barH = parseInt( cHeight*barVal/maxValue * ctr/numctr ); var y = originY - barH; var x = originX + (bWidth+bMargin)*i + bMargin; drawRect( x, y, bWidth, barH, mouseMove ); //高度減一避免蓋住x軸 ctx.fillText(parseInt(barVal*ctr/numctr), x+15, y-8); // 文字 } if(ctr<numctr){ ctr++; setTimeout(function(){ ctx.clearRect(0,0,canvas.width, canvas.height); drawLineLabelMarkers(); drawBarAnimate(); }, speed); } } //繪制方塊 function drawRect( x, y, X, Y, mouseMove ){ ctx.beginPath(); ctx.rect( x, y, X, Y ); if(mouseMove && ctx.isPointInPath(mousePosition.x, mousePosition.y)){ //如果是鼠標移動的到柱狀圖上,重新繪制圖表 ctx.fillStyle = "green"; }else{ ctx.fillStyle = gradient; ctx.strokeStyle = gradient; } ctx.fill(); ctx.closePath(); }
--檢測鼠標移動並顯示當前項(接着上一步的代碼寫在 goBarChart方法中 )
注:這里鼠標移動的檢測在有文字縮放顯示的高清屏幕上會有偏差不准確的情況,而且在高清屏幕中canvas中的文字會略顯模糊,以后的章節中會說明如何處理這個問題,大家可以先不管這個問題。(github上的 柱狀圖-高清.html也已經解決了這個問題,你可以點擊上面的下載鏈接去查看源碼)
//檢測鼠標移動 var mouseTimer = null; canvas.addEventListener("mousemove",function(e){ e = e || window.event; if( e.offsetX || e.offsetX==0 ){ mousePosition.x = e.offsetX; mousePosition.y = e.offsetY; }else if( e.layerX || e.layerX==0 ){ mousePosition.x = e.layerX; mousePosition.y = e.layerY; } clearTimeout(mouseTimer); mouseTimer = setTimeout(function(){ ctx.clearRect(0,0,canvas.width, canvas.height); drawLineLabelMarkers(); drawBarAnimate(true); },10); });
--當點擊canvas的時候重新刷新圖表(接着上一步的代碼寫在 goBarChart方法中 )
//點擊刷新圖表 canvas.onclick = function(){ initChart(); // 圖表初始化 drawLineLabelMarkers(); // 繪制圖表軸、標簽和標記 drawBarAnimate(); // 繪制折線圖的動畫 };
這樣我們整個代碼就編寫完成了,為了代碼更便於閱讀,我們可以將所有方法放到后面,把調用方法的代碼放到前面,經過調整的全部代碼如下
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <canvas id="barChart" height="400" width="600" style="margin:50px"> 你的瀏覽器不支持HTML5 canvas </canvas> <script type="text/javascript"> function goBarChart(dataArr){ // 聲明所需變量 var canvas,ctx; // 圖表屬性 var cWidth, cHeight, cMargin, cSpace; var originX, originY; // 柱狀圖屬性 var bMargin, tobalBars, bWidth, maxValue; var totalYNomber; var gradient; // 運動相關變量 var ctr, numctr, speed; //鼠標移動 var mousePosition = {}; // 獲得canvas上下文 canvas = document.getElementById("barChart"); if(canvas && canvas.getContext){ ctx = canvas.getContext("2d"); } initChart(); // 圖表初始化 drawLineLabelMarkers(); // 繪制圖表軸、標簽和標記 drawBarAnimate(); // 繪制柱狀圖的動畫 //檢測鼠標移動 var mouseTimer = null; canvas.addEventListener("mousemove",function(e){ e = e || window.event; if( e.layerX || e.layerX==0 ){ mousePosition.x = e.layerX; mousePosition.y = e.layerY; }else if( e.offsetX || e.offsetX==0 ){ mousePosition.x = e.offsetX; mousePosition.y = e.offsetY; } clearTimeout(mouseTimer); mouseTimer = setTimeout(function(){ ctx.clearRect(0,0,canvas.width, canvas.height); drawLineLabelMarkers(); drawBarAnimate(true); },10); }); //點擊刷新圖表 canvas.onclick = function(){ initChart(); // 圖表初始化 drawLineLabelMarkers(); // 繪制圖表軸、標簽和標記 drawBarAnimate(); // 繪制折線圖的動畫 }; // 圖表初始化 function initChart(){ // 圖表信息 cMargin = 30; cSpace = 60; cHeight = canvas.height - cMargin*2 - cSpace; cWidth = canvas.width - cMargin*2 - cSpace; originX = cMargin + cSpace; originY = cMargin + cHeight; // 柱狀圖信息 bMargin = 15; tobalBars = dataArr.length; bWidth = parseInt( cWidth/tobalBars - bMargin ); maxValue = 0; for(var i=0; i<dataArr.length; i++){ var barVal = parseInt( dataArr[i][1] ); if( barVal > maxValue ){ maxValue = barVal; } } maxValue += 50; totalYNomber = 10; // 運動相關 ctr = 1; numctr = 100; speed = 10; //柱狀圖漸變色 gradient = ctx.createLinearGradient(0, 0, 0, 300); gradient.addColorStop(0, 'green'); gradient.addColorStop(1, 'rgba(67,203,36,1)'); } // 繪制圖表軸、標簽和標記 function drawLineLabelMarkers(){ ctx.translate(0.5,0.5); // 當只繪制1像素的線的時候,坐標點需要偏移,這樣才能畫出1像素實線 ctx.font = "12px Arial"; ctx.lineWidth = 1; ctx.fillStyle = "#000"; ctx.strokeStyle = "#000"; // y軸 drawLine(originX, originY, originX, cMargin); // x軸 drawLine(originX, originY, originX+cWidth, originY); // 繪制標記 drawMarkers(); ctx.translate(-0.5,-0.5); // 還原位置 } // 畫線的方法 function drawLine(x, y, X, Y){ ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(X, Y); ctx.stroke(); ctx.closePath(); } // 繪制標記 function drawMarkers(){ ctx.strokeStyle = "#E0E0E0"; // 繪制 y var oneVal = parseInt(maxValue/totalYNomber); ctx.textAlign = "right"; for(var i=0; i<=totalYNomber; i++){ var markerVal = i*oneVal; var xMarker = originX-5; var yMarker = parseInt( cHeight*(1-markerVal/maxValue) ) + cMargin; //console.log(xMarker, yMarker+3,markerVal/maxValue,originY); ctx.fillText(markerVal, xMarker, yMarker+3, cSpace); // 文字 if(i>0){ drawLine(originX, yMarker, originX+cWidth, yMarker); } } // 繪制 x ctx.textAlign = "center"; for(var i=0; i<tobalBars; i++){ var markerVal = dataArr[i][0]; var xMarker = parseInt( originX+cWidth*(i/tobalBars)+bMargin+bWidth/2 ); var yMarker = originY+15; ctx.fillText(markerVal, xMarker, yMarker, cSpace); // 文字 } // 繪制標題 y ctx.save(); ctx.rotate(-Math.PI/2); ctx.fillText("產 量", -canvas.height/2, cSpace-10); ctx.restore(); // 繪制標題 x ctx.fillText("年份", originX+cWidth/2, originY+cSpace/2+10); }; //繪制柱形圖 function drawBarAnimate(mouseMove){ for(var i=0; i<tobalBars; i++){ var oneVal = parseInt(maxValue/totalYNomber); var barVal = dataArr[i][1]; var barH = parseInt( cHeight*barVal/maxValue * ctr/numctr ); var y = originY - barH; var x = originX + (bWidth+bMargin)*i + bMargin; drawRect( x, y, bWidth, barH, mouseMove ); //高度減一避免蓋住x軸 ctx.fillText(parseInt(barVal*ctr/numctr), x+15, y-8); // 文字 } if(ctr<numctr){ ctr++; setTimeout(function(){ ctx.clearRect(0,0,canvas.width, canvas.height); drawLineLabelMarkers(); drawBarAnimate(); }, speed); } } //繪制方塊 function drawRect( x, y, X, Y, mouseMove ){ ctx.beginPath(); ctx.rect( x, y, X, Y ); if(mouseMove && ctx.isPointInPath(mousePosition.x, mousePosition.y)){ //如果是鼠標移動的到柱狀圖上,重新繪制圖表 ctx.fillStyle = "green"; }else{ ctx.fillStyle = gradient; ctx.strokeStyle = gradient; } ctx.fill(); ctx.closePath(); } } goBarChart( [[2007, 750], [2008, 425], [2009, 960], [2010, 700], [2011, 800], [2012, 975], [2013, 375], [2014, 775]] ) </script> </body> </html>
好了,今天就講到這里,希望大家把代碼都自己敲一遍。
關注公眾號,博客更新即可收到推送