深入淺出d3.js數(shù)據(jù)可視化之道(5)

在經(jīng)過(guò)了一個(gè)階段的學(xué)習(xí)之后讨越,我們對(duì)基本的坐標(biāo)軸和比例尺都有了很好的了解,今天我們結(jié)合之前的內(nèi)容永毅,配合節(jié)流函數(shù)來(lái)制作一款精美的可交互折線圖把跨。

準(zhǔn)備的數(shù)據(jù)


const  line_data = [
    {
        country: "china",
        gdp: [
                [2008, 2033],
                [2009, 2400],
                [2010, 4333],
                [2011, 5600],
                [2012, 6500],
                [2013, 6700],
                [2014, 6933],
                [2015, 7400],
                [2016, 7733],
                [2017, 8200]
             ]
    },
    {
        country: "japan",
        gdp: [
                [2008, 3333],
                [2009, 4400],
                [2010, 5233],
                [2011, 5800],
                [2012, 6333],
                [2013, 6400],
                [2014, 6533],
                [2015, 6700],
                [2016, 7033],
                [2017, 7200]
             ]
    }

]

添加坐標(biāo)軸

坐標(biāo)軸的建立在前幾節(jié)已多次介紹,這里就不再贅述沼死。

    const data = line_data;

    var initWidth = 340
    var initHeight = 500

    var padding = { left:40, top:10, right:20, bottom: 20}

    var height = initWidth - padding.top - padding.bottom
    var width  = initHeight - padding.left - padding.right


    var svg = d3.select("body")
                .append("svg")
                .attr("id", "chart")
                .attr("width", width)
                .attr("height", height)
                .style("padding-left", padding.left)
                .style("padding-right", padding.right)
                .style("padding-top", padding.top)
                .style("padding-bottom", padding.bottom)


     //添加y軸坐標(biāo)軸

        //y軸比例尺
        let nums = [...data[0]["gdp"], ...data[1]["gdp"]].map(function(e){
            return e[1]
        })
        let yScale = d3.scaleLinear()
         .domain([0, d3.max(nums)])
         .range([height , 0]);

         let _yScale = d3.scaleLinear()
         .domain([0, d3.max(nums)])
         .range([0, height]);

        //定義y軸
        let yAxis = d3.axisLeft(yScale)
                      .tickFormat(d3.format("d")); //把x,xxx 的數(shù)據(jù)計(jì)數(shù)方式格式化着逐,轉(zhuǎn)化為不帶逗號(hào)的格式

        //添加y軸
        svg.append("g")
        .attr("class","axis")
        .attr("transform","translate(" + 0 + "," + 0 + ")")
        .call(yAxis);

     //添加x軸坐標(biāo)軸

         //x軸比例尺
         let years = data[0]["gdp"].map(function(e){
                    return e[0]
          })

         let xScale =  d3.scaleLinear()
                        .domain([2008,2017])
                        .rangeRound([0, width])

        let _xScale =  d3.scaleLinear()
                       .domain([0,width])
                       .rangeRound([2008, 2017])

        //定義x軸
        let xAxis = d3.axisBottom(xScale)
                            .tickFormat(d3.format("d"))

        //添加x軸
         svg.append("g")
            .attr("class","axis")
            .attr("transform","translate(" + "0 ," + height + ")")
            .call(xAxis);


坐標(biāo)軸的樣式

    .axis path {
        stroke: steelblue;
        stroke-width: 1
    }
    .axis .tick line{
      stroke: steelblue;
      stroke-width: 3
    }
生成的坐標(biāo)軸

添加背景間隔線

添加網(wǎng)線的內(nèi)容上一節(jié)已經(jīng)介紹過(guò)了,這次只使用了y軸方向的網(wǎng)線意蛀,來(lái)幫助使用者確立數(shù)據(jù)位置

        //添加x軸
         svg.append("g")
            .attr("class","axis")
            .attr("transform","translate(" + "0 ," + height + ")")
            .call(xAxis);


        //添加
        // gridlines in x axis function
        function make_x_gridlines() {
            return d3.axisBottom(xScale)
                .ticks(years.length)

        }

        // add the X gridlines
      var grid =  svg.append("g")
            .attr("id", "grid")
            .attr("transform", "translate(0," + height + ")")
            .call(make_x_gridlines()
                .tickSize(-height)
                .tickFormat("")
            )

樣式

    #grid .tick:nth-child(2) {
      display: none
    }
    #grid path {
      display: none
    }

效果展示


預(yù)覽

繪制圖形

繪制網(wǎng)線的時(shí)候我們使用了直線生成器,這里簡(jiǎn)要介紹一下直線生成器耸别。

首先,在svg中县钥,線段元素放的寫(xiě)法是

<line x1="20" y1="20" x2="100" y2="100" />

或者是

<path d="M20,20L100,100>

兩個(gè)點(diǎn)形成的一條線段很容易手寫(xiě)生成出來(lái)秀姐,但是如果有成千上萬(wàn)個(gè)點(diǎn)就很不方便了,因此d3 引入了 路徑生成器 這個(gè)概念魁蒜,能夠自動(dòng)根據(jù)數(shù)據(jù)生成路徑囊扳。用于生成線段的生成器也就叫做 線段生成器吩翻。

例如:

       var  lines = [  [80, 80], [200, 100],  [200, 200], [100, 200]  ]
       var linePath = d3.line()

        svg.append("path")
             .attr("d", linePath(lines))
             .attr("stroke-width", "2px")
             .attr("stroke",  "red")
             .attr("fill", "none")

生成效果


生成的路徑

了解了這個(gè)知識(shí)點(diǎn)之后,繼續(xù)繪制折線圖

        //創(chuàng)建一個(gè)直線生成器
        var linePath = d3.line()
                          .curve(d3.curveCardinal.tension(0.5))
                         .x( function(d){ return xScale(d[0]) })
                         .y( function(d){ return yScale(d[1])})


        var colors = ["rgb(0, 188, 212)", "rgb(255, 64, 129)"]


        //添加路徑
        svg.append("g").selectAll("path")
            .data(data)
            .enter()
            .append("path")
            .attr("transform","translate(0, 0)")
            .attr("d", function(d){
                return linePath(d.gdp)
            })
            .attr("fill", "none")
            .attr("stroke-width", "2px")
            .attr("stroke", function(d, i){
              return colors[i]
            })

添加路徑之后的效果


預(yù)覽

添加左側(cè)指示欄

類(lèi)別指示欄在上一節(jié)已經(jīng)介紹過(guò)了锥咸,這一節(jié)也就不過(guò)多介紹狭瞎,代碼示下

      
        var cover =svg.append("g")

            cover.selectAll("rect")
                .data(data)
                .enter()
                .append("rect")
                .attr("width", 10)
                .attr("height", 10)
                .attr("fill", function(d, i){
                  return i%2 == 0 ? colors[0] : colors[1]
                })
                .attr("transform", function(d, i){
                    return `translate(10, ${(i)*20})`
                })


            cover.selectAll("text")
                  .data(data)
                  .enter()
                  .append("text")
                  .text(function(d, i){
                    return d.country
                  })
                  .attr("transform", function(d, i){
                      return `translate(27, ${(i)*20})`
                  })
                  .attr("font-size", '12px')
                  .attr("dy",function(){
                    return '0.75em'
                  })
                  .attr("fill", function(){
                    return '#333'
                  })

此時(shí)的效果


預(yù)覽

添加提示欄和準(zhǔn)線

截至到這里,已經(jīng)完成了折線圖的基本制作搏予。接下來(lái)添加一些交互效果熊锭。

  • 滑動(dòng)準(zhǔn)線
  • 內(nèi)容提示框

最后要達(dá)到的效果,鼠標(biāo)在圖表移動(dòng)時(shí)雪侥,準(zhǔn)線吸附到最近的參考線碗殷,并且提示欄內(nèi)顯示該參考線位置上折線的數(shù)據(jù)

其實(shí)思路很簡(jiǎn)單速缨,只要計(jì)算出每?jī)蓚€(gè)參考線之間的距離singleStep锌妻,就可以根據(jù)鼠標(biāo)位置找到當(dāng)前鼠標(biāo)距離哪兩個(gè)參考線之間,并且距離哪個(gè)參考線更近旬牲,判斷出來(lái)之后準(zhǔn)線就吸到相應(yīng)的參考線仿粹。

這里采用了mousemove事件來(lái)實(shí)時(shí)判斷當(dāng)前鼠標(biāo)位置并進(jìn)行運(yùn)算,事實(shí)上原茅,并不需要實(shí)時(shí)觸發(fā)這個(gè)函數(shù)吭历,準(zhǔn)線吸附速度只要流暢就可以,大量觸發(fā)會(huì)極大浪費(fèi)計(jì)算機(jī)性能擂橘。這里采用了一個(gè)高級(jí)函數(shù)晌区,通過(guò)控制函數(shù)在多少毫秒內(nèi)只執(zhí)行一次,來(lái)幫助解決這個(gè)問(wèn)題通贞。節(jié)流函數(shù)詳細(xì)講解在這里

節(jié)流函數(shù)

//節(jié)流函數(shù)
var throttle = function (fn, interval) {
        var  timer, firstTime = true;

        return function () {
          var args = arguments;
          var _me = this;

           if ( firstTime ) {
             fn.apply(_me, args)
             return firstTime = false;
           }

           if ( timer ) {
             return false
           }

           timer = setTimeout(function () {
             clearTimeout(timer);
             timer=null;
             fn.apply(_me, args)
           }, interval || 500)
        }

    }

繪制提示欄和準(zhǔn)線(無(wú)刻度的y軸)



        var detailLine = svg.append("g")
                            .attr("class","line_y")
                            .attr("transform","translate(" + width + "," + 0 + ")")
                            .call(yAxis.ticks(0).tickSize(0).tickFormat(""));

        //添加提示欄
        var tooltip = d3.select("body")
                        .append("div")
                        .attr("class", "tooltip")
                        .style("opacity", 0)

        //計(jì)算位置 便于吸附
        let singleStep = width / (years.length-1)

        //這里使用節(jié)流函數(shù)朗若,避免過(guò)多運(yùn)算導(dǎo)致瀏覽器卡頓
        document.getElementById('chart').onmousemove =throttle(function(e){
              console.log(e.offsetX)
              e.stopPropagation();
              let t = Math.round((e.offsetX - padding.left) / singleStep)*singleStep
              detailLine.attr("transform","translate(" + t + "," + 0 + ")")
              let year = _xScale(t)
              let currentHtml = []
              data.forEach( (e) => {
                  e.gdp.forEach( (ev, i) =>{
                    if(ev[0]==year){
                      currentHtml.push(`<div>${e.country}: ${ev[1]}</div>`);
                    }
                  })
              })
            currentHtml.unshift(`<div>${year}</div>`)
            tooltip.html(currentHtml.join(""))
                   .style("left", e.pageX + 20+ "px")
                   .style("top", e.pageY + 20 + "px")
                   .style("opacity", 1)
        },50)


        //隱藏顯示欄
        document.onclick= function(){
          tooltip.style("opacity", 0)
        }

提示欄樣式

    .tooltip {
        position: absolute;
        min-width: 100px;
        height: auto;
        font-size: 14px;
        text-align: center;
        border: 1px solid #666;
        border-radius: 5px;
        color: #fff;
        background:rgba(0, 0, 0, 0.8);
        padding-bottom: 5px;
        transition: transform 0.2s
    }

最終效果

效果

源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市滑频,隨后出現(xiàn)的幾起案子捡偏,更是在濱河造成了極大的恐慌椎椰,老刑警劉巖成洗,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件占哟,死亡現(xiàn)場(chǎng)離奇詭異署穗,居然都是意外死亡贯底,警方通過(guò)查閱死者的電腦和手機(jī)喂击,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)荐类,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)麦乞,“玉大人夯辖,你說(shuō)我怎么就攤上這事琉预。” “怎么了蒿褂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵圆米,是天一觀的道長(zhǎng)卒暂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)娄帖,這世上最難降的妖魔是什么也祠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮近速,結(jié)果婚禮上诈嘿,老公的妹妹穿的比我還像新娘。我一直安慰自己削葱,他們只是感情好奖亚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著析砸,像睡著了一般昔字。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上干厚,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天李滴,我揣著相機(jī)與錄音螃宙,去河邊找鬼蛮瞄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谆扎,可吹牛的內(nèi)容都是我干的挂捅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼堂湖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼闲先!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起无蜂,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤伺糠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后斥季,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體训桶,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年酣倾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舵揭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡躁锡,死狀恐怖午绳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情映之,我是刑警寧澤拦焚,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布蜡坊,位于F島的核電站,受9級(jí)特大地震影響赎败,放射性物質(zhì)發(fā)生泄漏算色。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一螟够、第九天 我趴在偏房一處隱蔽的房頂上張望灾梦。 院中可真熱鬧,春花似錦妓笙、人聲如沸若河。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萧福。三九已至,卻和暖如春辈赋,著一層夾襖步出監(jiān)牢的瞬間鲫忍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工钥屈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悟民,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓篷就,卻偏偏與公主長(zhǎng)得像射亏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子竭业,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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