D3.js

D3 v3 vs v4

DOM操作(重要!)

d3.select("body") //取得 body
    .selectAll("p")     //取得裡面所有的 <p>,不存在,就建立1個
    .data([1,2,3,4,5])  //依資料量,後面的動作會執行 n 次
    .enter()            //分析 若資料量 > body內 div 的數量,在背景建立 n 個虛擬 p
    .append("p")        //將 enter 產生的虛擬元件 p,加到畫面中 
    .text(function(d){  //分別塞文字到個別元素
        return "haha-"+d;
    })
    .css('color',function(d){  //對每個元素設定顏色
        return d > 3 ? red : blue;
    });
    
    //function(d,i){return xxx;},就是對單筆送進來的資料,做個別處理
選元件->綁資料->比對數量、增減虛擬元件->實踐元件->其他操作

Enter、Update、Exit

//body中有3個div1,2,3
var data = [1,2,3,4,5];
var d = d3.select('body').selectAll('div');
//update
d.data(data).text(function (d) {return 'update'+d;});
//enter
d.data(data).enter().append('div').text(function (d) {return 'enter'+d;});
//exit
d3.select('body').selectAll('div').data([1,2,3,4]).exit().remove();

在更新畫面元件前,須先比對「新資料」、「畫面元件」兩邊數量
  • update - 兩邊都有的部分,[更新]
  • enter() - 「新資料」多出來的部分,[塞入],通常搭配 append()
  • exit() - 「畫面元件」多出來的部分,[超出],通常搭配 remove()


SVG 形狀(元件)

//html呈現
<svg width="400" height="400">
 <形狀 座標資料 屬性(樣式)></形狀>
</svg>
  • 方形 svg:rect x="左上角x座標" y="左上角y座標" width="寬" height="高" rx="0" ry="0"
  • 圓形 svg:circle cx="0" cy="0" r="0"
  • 橢圓 svg:ellipse cx="0" cy="0" rx="0" ry="0"
  • 直線 svg:line x1="0" y1="0" x2="0" y2="0"
  • 折線 svg:polyline points=""
  • 多邊形 svg:polygon points=""
  • 文字 svg:text x="0" y="0" dx="0" dy="0" text-anchor="start"
  • 路徑 svg:path d="" transform=""
    • 可用來畫任何東西

其他 DOM操作

  • .append('rect') 加到後方
  • .insert('table', ':first-child') 加到最前面
z-index
似乎沒看到類似屬性,而是依照元件先後順序,排越後面就越上層

Selector




Selector


  • .style(參數名稱,值)
同 css 效果,直接對上層指定樣式,子元素會繼承,除非自己有另外訂

群組 g

當畫面上小元件太多時,可先建立 group,將關聯的元件放在此 group 下。
內部的元件位置與外部不相關。 再使用 translate 調整位置、sacle 調整比例。
var svg=target.append('svg');
var group = svg.append('g').attr('id', 'my_group')
.attr({
    transform: "translate(10,100)scale(1.5)"
});
group.append('text');
group.append('rect');

svg.select('#my_group');

繪圖

幾個一般的步驟
  1. 座標0,0為左上,往下越大、往右越大
  2. 寫資料生成器,將自己的 data 對應 svg 屬性
  3. 選區域、大小,append上去
  4. 調整形狀的 style
若不建立資料生成器,那就直接對 <形狀> 加入 data 屬性
var data_format = d3.svg.line() //生成器
    .x(function (d) {
        return d.x;
    })
    .y(function (d) {
        return d.y;
    })
    .defined(function(d) { return d.y; }) //如果為 null,傳什麼回去(可用此達成"斷線"效果) http://jsfiddle.net/G5z4N/2/
    .interpolate('linear-closed'); //補插點,此參數為封閉線

d3.select('body') //區域
    .append('svg').append('path') //塞圖型
    .attr('d', data_format(data)) //資料經過生成器後,塞入 attr
    //以下修改樣式
    .attr('stroke', 'black') 
    .attr('fill', 'none');

Important : 整個繪圖區,左上角為(0,0)、xy向右向下遞增,倒轉資料請見 scale

畫布規劃

  • 要同步縮放的元素,都要放置在同個 svg
  • 善用 svg:g 劃分出區域,group 內的元素位置定義較為方便
  • 若內部需保留 padding,則繪圖區域須先剪掉 左右、上下 padding
    • viewbox 、root元素、scale...,都吃 減去後的 width、height

svg:path

基礎參數
http://www.oxxostudio.tw/articles/201406/svg-04-path-1.html
interpolate 13種
http://www.oxxostudio.tw/articles/201411/svg-d3-02-line.html
  • transform

svg:circle


  • cx, cy 中心點位置
  • r 半徑

比例尺 scale

做法是產生一個轉換 function,套用到資料產生器中,通過這個 function 的值會自動被放大縮小

分為兩種
  • Quantitative 定量縮放,通常用在數字、日期
  • Ordinal 自定義縮放依據,多搭配 Axis 使用?
Quantitative (d3.scale.xxx)
只要給2個範圍,scale 會幫 data「等比例放大」
  • domain([0,10]) 原始資料的範圍
  • range(0,1000)  期望顯示的範圍
Qrdinal (d3.scale.ordinal)
要給 domain、range 各一個陣列,陣列「元素數量相同」、「同位置彼此對應」
但 range 通常不會自己手動打,所以有方式來處理

  • scale.range(data.map(function(d){return(d.x);}) 讀成陣列
  • scale.rangePoints([0, 邊界], padding) 點狀自動分隔
  • scale.rangeRoundPoints() 點狀自動分隔(整數)
  • scale. rangeBand() 帶狀自動分隔
  • scale. rangeRoundBand() 帶狀自動分隔(整數)
padding、outerPadding

  • point 點狀
    • padding 在兩端
  • band 帶狀

    • 一個 step = band + padding
    • outpadding 在兩端,類似 point 的 padding
倒轉資料
  • 傳 data 進元件時,用 scale.invert(yourdata.x)
  • 將 .range() 大小互換


其他
  • ordinal.rangeBand() 取得與邊界的距離,-xx 為超過邊界
  • ordinal.rangeExtent() 取得 range 總範圍

svg:g 座標軸

使用 d3.svg.axis(),賦予 scale 資料,並與 g 元件關聯
用 transform 屬性,調整元件自己的位置,在 g 或一般圖形都有
調整座標「文字」的位置,用 axis().orient("上下左右")
    var s = d3...
        .append('path')
        .......
        .attr({'transform':'translate(25,20)'}); //調整圖表位置,來吻合座標軸

    var axisX = d3.svg.axis()
      .scale(scaleX) //給予比例尺資料
      .orient("bottom") //文字在軸線下方
      .ticks(10) //只顯示10個軸線值?
      .tickValues([10,50,90]) //手動指定軸線值
      .tickFormat(function(d){return d+'n';}) //指定軸線值的格式,如在值前後加文字
      .tickFormat(d3.format(",%")) //也可配合 format() 變更單位
      .tickPadding(-20); //值與軸線距離,越小越靠近

      axisX.selectAll('text')
      .attr('transform', 'translate(-5, 7)') //也可用此方式微調文字位置

    var axisY = d3.svg.axis().scale(scaleY);

    s.append('g')
        .call(axisX) //關聯 axis()
        .attr({
            'fill':'none',
            'stroke':'#000',
            'transform':'translate(25,'+(height+20)+')' //調整x軸位置,(left=25,top=內部圖型高度+20)
        });
    s.append('g').call(axisY);

自動產 tickValues 陣列
function _getTicksArray(RangeSt, RangeEnd, tickNumber) {
    var range = parseInt(RangeEnd - RangeSt)/(tickNumber-1);
    var re = [];
    for(var ti = 0; ti < tickNumber; ti++) {
        re.push(ti*range);
    }
    return re;
}

Grid 座標格線
需另外再建立一組沒有文字,只有格線的 axis(),並與 g 元件關聯
    var axisXGrid = d3.svg.axis()
      .scale(scaleX) //基本參數與座標軸一樣
      .orient("bottom")
      .ticks(10)
      .tickFormat('') //把座標值清空
      .tickSize(-軸內長度,軸外長度); //畫格線
      //X座標為例,向左 -xx | 軸線0 |向右 +xx

    var axisYGrid = d3.svg.axis()
      ...
      .tickSize(-width,0);

    var axis = s.append('g')
     .call(axisXGrid)
     .attr({
      ... //與軸線屬性一樣
      'stroke':'rgba(0,0,0,.1)', //指定格線顏色
      'stroke-width': '0.5', //線粗細
      'fill':'none' //線太粗務必指定為 none,並調整字型(顏色、填充)
     })
     .style({
      'stroke-dasharray' :('3, 3') //虛線(單數值為線條、雙數值為空格)
     });

    //調整文字
    axis.selectAll('text')
        .style({ stroke: 'none', 'fill': 'gray', 'font-family':'Arial', 'font-weight':300})

    s.append('g')
     .call(axisYGrid)
     .attr({
      ...
      'stroke':'rgba(0,0,0,.1)',
     });
座標 label,中文垂直顯示
function verticalAxisText(svg, textArr, x, y, TextHeight, classname){
    textArr.forEach(function (t, i) {
        svg.append('text')
            .attr('class', classname)
            .text(t)
            .attr('transform', 'translate('+ x +', '+( y + (i*TextHeight))+')');
    })

//用法
verticalAxisText(mySvg, ['中','文','座','標','(%)'], 0, 10, 20, 'yaxis');
mySvg.selectAll('.yaxis')
    .attr('font-size', '16px')
    .style({ fill: 'black', stroke: 'none'});

mySvg.selectAll('.yaxis:last-of-type')
    .attr('font-size', '10px');
}

Area 區域

區域是由 2 條線組成,通常會是
  • x0,x1,y(垂直) - 2 條垂直線
  • x,y0,y1(水平) - 2 條水平線
2條線其中一條通常與 x/y 軸重疊,如 y 為 0,圖會黏在上面,y=hight則會黏在下方
曲線圖,使用 line + area 兩張圖組成,會比較漂亮

area().interpolate('cardinal') 圓滑

顏色套用

D3有預設的顏色陣列可直接使用,也可以用 scale.range 自訂

  • d3.scale.category10()
  • d3.scale.category20()
  • d3.scale.category20b()
  • d3.scale.category20c()
//預設
    var color = d3.scale.category20b();

    ...
    .attr({
        fill:color(2), //直接指定
        stroke:function(d,i){
            return color(i) //依流水號
        }
    });

//自訂
    color = d3..scale.linear()
              .domain([1,100])
              .range(['blue', 'lightblue',...]);

    ....
    .attr({
        fill:function(d,i){
            return color(i) //會產生深藍到淺藍
        }
    });

動畫

...
.append('rect')
.attr({
    'width': 20,
    'height': 0 //先歸零
})
.transition() //漸變
.delay(200) //延遲
.duration(1500) //總長1.5秒
.attr({
    'height': function (d) {
        return d.h; //最終值
    }
}
http://www.oxxostudio.tw/articles/201501/svg-d3-14-transition-1.html

補間動畫
用 .interpolate 系列,產生一個範圍間的陣列,再由 .tween 去依照指定效果、時間去變化
...
.transition()
.duration(1500)
.tween("number", function(yourdata) { //指定效果
  var i = d3.interpolateRound('0', yourdata.x); //產生補差資料
  return function(t) {
    this.textContent = i(t);
  };
})
補差系列(用法)
  • interpolateRound(0, 150)
  • interpolate('#ffff00','#00cccc');
  • interpolateString('Hello World', 'YoYo 123')
  • interpolateRgb('#f00', '#23e'); 
  • interpolateArray([0,1], [1,50,200])
  • interpolateObject({x:0,y:1},{x:1,y:20,z:40})
  • var x1 = 1, x2 = 5;
    var ip = d3.interpolateObject({x:x1,y:10}, {x:x2, y:50}); //1,10 2,20 3,30 4,40 5,50
    var x = 3;
    console.log(ip(0~1));
    var obj = ip(1/(x2-x1)*(x-x1));
    console.log(obj);
    
  • interpolateTransform(transform_start, transform_stop);

http://www.oxxostudio.tw/articles/201509/svg-d3-15-transition-tween.html

RWD

通常使用兩個手段,SVG 的 viewbox 參數,還有 window on Resize 事件來重繪

viewbox / viewport
viewport 為定義的 svg 長寬
viewbox 像是 crop 效果,最多與原圖 1:1,可設定對齊的位置,作用非放大
...
.append('svg')
.attr({
    //width:400,
    //height:200,
    viewBox:'0 0 400 200', //min-x min-y width height
    preserveAspectRatio:'xMinYMin meet' //align meet|slice
})
http://www.oxxostudio.tw/articles/201409/svg-23-viewpoint-viewBox.html

resize event
function renderAxisY(){
    var axisY = d3.svg.axis()
        .scale(scale)
        .orient('left')
        //.ticks(10)
        .ticks(yourSvg.node().getBoundingClientRect().height/50);
        yourSvg.select('g').remove();
        yourSvg.append('g').call(axisY);
}
d3.select(window).on('resize', renderAxisY);

文字

  • 自適大小
    • 須在 svg 中
    • 使用 text 元件
    • *若使用 div,不會有 RWD
  • 字型
    • 可使用 Adobe illusrator 將文字轉成 Path
      • 點選元件->按右鍵->建立外框->輸出 SVG

With JQuery

  • jquery to d3
    • d3.select($('#hahaha').get(0))
  • d3 to jquery
    • $(d3obj[0])

外部載入 SVG

.SVG 內容為 xml,可使用 xml 讀出,或者直接用 html 格式將整個印到畫面上
若使用 svg:image 物件,則不會將內容印在畫面上,只會呈現一個 image 連結
//image形式
svg.append('g').append('svg:image').attr('xlink:href','*.svg路徑')
//印到畫面上 jquery
$.get('*.svg路徑', null, function(data){
    $('#mydiv').append(data.documentElement);
    var svg = d3.select('#mydiv svg').attr({
    ....
    });
}, 'xml');
//OR HTML format
$.get('*.svg路徑', null, function(data){
    $('#mydiv').html(data);
}, 'html');

//印到畫面上 d3
d3.xml('*.svg路徑').mimeType("image/svg+xml").get(function(error, xml) {
    if (error) throw error;
    $('#mydiv').append(xml.documentElement);
});

Image

  • svg:image 有一個參數可「滿版」
    • .attr('preserveAspectRatio', 'none')

數值相關

  • d3.max 數值陣列的最大值
  • d3.min 數值陣列的最小值
  • d3.range(1,100) 產生 1~99的陣列
    • d3.range([start, ]stop[, step])
    • *注意:產出的陣列「不包括 stop 值」
  • d3.format 數值格式化
  • var nformat = d3.format('.2f');
    console.log(nformat(1.2345)); //1.23
    //limit length
    var nformat = d3.format('.7n');
    console.log(nformat(1234.5600)); //1,234.560
    console.log(nformat(1234.5600).replace(/\.?0+$/, '')); //1,234.56
    console.log(nformat(1234.5600).replace(/,/, '').replace(/\.?0+$/, '')); //1234.56
    
  • d3.time.scale 產生日期陣列
  • var dateArray = d3.time.scale()
      .domain([new Date(2019, 1, 1), new Date(2019, 5, 1)])
      .ticks(d3.time.days, 1);
    //會產生以天為單位的日期清單
    

事件

.on(type,[, listener[, capture]])
click.btn
.btn
input

d3.behavior.drag
var drag = d3.behavior.drag()
    .on("dragstart", function(){
        //d3.event.x 取得x座標
    })
    .on("drag", function(){
        //d3.event.x 取得x座標
        //myXScale.invert() //換算座標
    })
    .on("dragend", function(){
        //d3.mouse(this)[0] 取得x座標
        //可用 click 事件取代
    });

    svg.append('rect').attr('width',20).attr('height',20).style('fill', 'red')
        .call(drag)
        .on('click', function(){
            //d3.mouse(this)[0] 取得x座標
        });
注意:在手機上 click 與 dragend 事件似乎是同步,與 PC 效果不同

Call

.call(funcName, param1, param2....)
有重複的片段,可以將片段獨立放置某個 function,再使用 call 呼叫
例如當改變 scale 後,後面重繪的過程,可直接包裹來重複使用,init 也用同一個function

轉存圖片

  • client-side
    • 作法 (link, link2, link3)
      1. 讀成 blob
      2. 存擋
    • canvas.toDataUrl()
    • canvas.toBlob()
    • canvas.toBlobHD()
    • mozGetAsFile()
    • 支援度不佳
  • server-side 
    • 作法
      1. nodejs + jsdom 或直接用 PhantomJS 渲染 js
        • 若要從前端送回 server-side,須先讀成 SVG/XML,並完整回傳 (link)
      2. 將 svg 轉存成檔案
      3. 在前端以 image 形式載入
    • 套件
      • jsdom
        • *待解決 load img, font-family
      • cheerio
    • canvg
      • svg->canvas->png
      • 安裝流程 (mac 為例)
        • 用 canvas 套件實踐,但 canvas 不好安裝
        1. 到 node-canvas git 依照不同 OS 先安裝圖片 lib (link)
        2. Mac 很容易遇到 "node-gyp rebuild" 相關問題
        3. $ brew uninstall jpeg && brew install jpeg
          $ brew uninstall cairo
          $ xcode-select --install
          $ brew install cairo
          
    • 狀況
      • 不支援 .transition().duration(100),動態圖調整到 0 仍然出不來
        • 需拿掉
      • svg:image 圖片出不來
        • .attr('xlink:href', xxx.png') 改成
        • .attr('xlink:xlink:href', xxx.png')
      • d3.xml (XMLHttpRequest) 會報錯,目前沒有修復
        • 改從外部送入,用 fs.readFileSync

圖片實作概念

  • 曲線圖
    • line
  • 區塊曲線圖
    • area + line
  • 長條圖
    • rect + 寬高 + 位置
  • 光譜圖
    • 長條圖、無 padding,顏色光譜對應資料流水號陣列
  • Slider

Resource


dx,dy
axis font size, 斜(text的transform rotate), title
元件相對位置,append在某 el 後,在設定內部的 xy
rotate 後的 xy
d3.range() 產假資料
資料轉換
http://blog.infographics.tw/2016/02/data-restructure-with-d3js/
android 上,快速更新文字,可能導致效能低落
D3.js D3.js Reviewed by Wild on 4/27/2017 10:35:00 上午 Rating: 5

沒有留言:

沒有Google帳號也可發表意見唷!

技術提供:Blogger.