canvas-柱狀

學(xué)習(xí)了這么多Canvas中的API瞄勾,是時(shí)候出來溜溜了脂崔,寫一個(gè)low版的柱狀圖吧!
先瞜一眼效果圖:


column.gif

分析一下簡版思路:

  1. 上個(gè)canvas
    寬高為600* 600

  2. 繪制刻度軸

  3. 繪制單根柱子

  4. 單個(gè)tip的繪制

  5. 根據(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();
軸.PNG

好了猪落,這樣我們基本的刻度軸在此時(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ù)測試一下吧~


simpleC.gif

實(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;
    }
simpleTip.PNG

到這就已經(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é)一下這些功能

  1. 來個(gè)刻度
    scaleLine(context, isColumn, isPlus, step, length)
  2. 數(shù)據(jù)來一打
    Var arr;
  3. 將數(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的寫完了~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末压恒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子错邦,更是在濱河造成了極大的恐慌探赫,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撬呢,死亡現(xiàn)場離奇詭異伦吠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)魂拦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門讨勤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晨另,你說我怎么就攤上這事潭千。” “怎么了借尿?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵刨晴,是天一觀的道長。 經(jīng)常有香客問我路翻,道長狈癞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任茂契,我火速辦了婚禮蝶桶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掉冶。我一直安慰自己真竖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布厌小。 她就那樣靜靜地躺著恢共,像睡著了一般。 火紅的嫁衣襯著肌膚如雪璧亚。 梳的紋絲不亂的頭發(fā)上讨韭,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天,我揣著相機(jī)與錄音癣蟋,去河邊找鬼透硝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疯搅,可吹牛的內(nèi)容都是我干的濒生。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼秉撇,長吁一口氣:“原來是場噩夢啊……” “哼甜攀!你這毒婦竟也來了秋泄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤规阀,失蹤者是張志新(化名)和其女友劉穎恒序,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谁撼,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歧胁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厉碟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喊巍。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖箍鼓,靈堂內(nèi)的尸體忽然破棺而出崭参,到底是詐尸還是另有隱情,我是刑警寧澤款咖,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布何暮,位于F島的核電站,受9級特大地震影響铐殃,放射性物質(zhì)發(fā)生泄漏海洼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一富腊、第九天 我趴在偏房一處隱蔽的房頂上張望坏逢。 院中可真熱鬧,春花似錦赘被、人聲如沸是整。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贰盗。三九已至,卻和暖如春阳欲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陋率。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工球化, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓦糟。 一個(gè)月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓筒愚,卻偏偏與公主長得像炕吸,于是被迫代替她去往敵國和親整胃。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容