D3.js
D3 v3 vs v4
- v4 沒有 .svg.xxx()
- https://iros.github.io/d3-v4-whats-new/
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');
繪圖
幾個一般的步驟- 座標0,0為左上,往下越大、往右越大
- 寫資料生成器,將自己的 data 對應 svg 屬性
- 選區域、大小,append上去
- 調整形狀的 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');
畫布規劃
- 要同步縮放的元素,都要放置在同個 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 使用?
只要給2個範圍,scale 會幫 data「等比例放大」
- domain([0,10]) 原始資料的範圍
- range(0,1000) 期望顯示的範圍
要給 domain、range 各一個陣列,陣列「元素數量相同」、「同位置彼此對應」
但 range 通常不會自己手動打,所以有方式來處理
- scale.range(data.map(function(d){return(d.x);}) 讀成陣列
- scale.rangePoints([0, 邊界], padding) 點狀自動分隔
- scale.rangeRoundPoints() 點狀自動分隔(整數)
- scale. rangeBand() 帶狀自動分隔
- scale. rangeRoundBand() 帶狀自動分隔(整數)
padding、outerPadding
倒轉資料
其他
- 傳 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);
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.htmlresize 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
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)
- 讀成 blob
- 存擋
- canvas.toDataUrl()
- canvas.toBlob()
- canvas.toBlobHD()
- mozGetAsFile()
- 支援度不佳
- server-side
- 作法
- nodejs + jsdom 或直接用 PhantomJS 渲染 js
- 若要從前端送回 server-side,須先讀成 SVG/XML,並完整回傳 (link)
- 將 svg 轉存成檔案
- 在前端以 image 形式載入
- 套件
- jsdom
- *待解決 load img, font-family
- cheerio
- canvg
- svg->canvas->png
- 安裝流程 (mac 為例)
- 用 canvas 套件實踐,但 canvas 不好安裝
- 到 node-canvas git 依照不同 OS 先安裝圖片 lib (link)
- Mac 很容易遇到 "node-gyp rebuild" 相關問題
- 狀況
- 不支援 .transition().duration(100),動態圖調整到 0 仍然出不來
- 需拿掉
- svg:image 圖片出不來
- .attr('xlink:href', xxx.png') 改成
- .attr('xlink:xlink:href', xxx.png')
- d3.xml (XMLHttpRequest) 會報錯,目前沒有修復
- 改從外部送入,用 fs.readFileSync
$ brew uninstall jpeg && brew install jpeg
$ brew uninstall cairo
$ xcode-select --install
$ brew install cairo
圖片實作概念
- 曲線圖
- line
- 區塊曲線圖
- area + line
- 長條圖
- rect + 寬高 + 位置
- 光譜圖
- 長條圖、無 padding,顏色光譜對應資料流水號陣列
- Slider
- 參數 max、min、step
- 事件 drag、click
- 當事件發生,call() function 去操作其他物件
- 實作
- 用 svg + d3.behavier.drag
- HTML5 input type=range
- 在手機上會慢
- jQuery UI slider
Resource
- 抓取圖表背後的資料
- 將資料快速產成圖
- C3.js 延伸外掛
dx,dy
axis font size, 斜(text的transform rotate), title
元件相對位置,append在某 el 後,在設定內部的 xy
rotate 後的 xy
元件相對位置,append在某 el 後,在設定內部的 xy
rotate 後的 xy
d3.range() 產假資料
資料轉換
http://blog.infographics.tw/2016/02/data-restructure-with-d3js/
android 上,快速更新文字,可能導致效能低落
資料轉換
http://blog.infographics.tw/2016/02/data-restructure-with-d3js/
android 上,快速更新文字,可能導致效能低落
D3.js
Reviewed by Wild
on
4/27/2017 10:35:00 上午
Rating:
沒有留言:
沒有Google帳號也可發表意見唷!