D3 JS study notes

世有因果知因求果發表於2017-03-23

如何使用d3來解析自定義格式的資料來源?

var psv = d3.dsvFormat("|");
// This parser can parse pipe-delimited text:
var output = psv.parse("first|last\nabe|lincoln")
console.log(output[0])
// 將輸出以下內容:
=> {first: "abe", last: "lincoln"}

這樣我們通過js filereader獲取檔案內容,之後通過上面的程式碼格式化就能形成javascript的物件以供我們使用

javascript以及D3中的陣列操作方法

在javascript中對陣列的操作總體上分為兩類:一種是mutation的也就是執行對應函式後原陣列將會被修改,一種是accessor,就是返回源陣列的部分資料,不會對原陣列做任何的修改。

mutation類的函式有: 

array.reverse, array.splice, array.shift, array.unshift, array.sort

accessor類的函式有 

array.concat, array.indexOf, array.join, array.lastIndexOf, array.slice 

d3庫中陣列操作相關的函式有:

d3.min, d3.max, d3.sum,d3.mean, d3.extent(獲取最大最小值並形成一個陣列)

d3.median

D3資料視覺化基礎pattern:

var svg = d3.select("body").append("svg").attr("width", diamter).attr("height",diameter).attr("class","bubble");

svg.select/.selectAll

.data

.enter

.append 

d3.select

d3.select selects only the FIRST MATCHING ELEMENT that lives in the document

d3.selectAll

d3.selectAll selects ALL MATCHING Elements in the document.

d3 selections的結果是包含dom陣列的陣列,而這個陣列除了javascript的native函式功能外,d3還定義了新的array處理函式可以對這些selection結果進行特殊操作

select elements by:

1. tags - "div", "span"

2. class - ".inner-div",".outer-span", ".my-class-element"

3. id - "#id_of_my_element"

4. attribute, containment, union/intersection of selectors chosen

注意:部分老的瀏覽器對於CSS3定義的selector可能不支援,這時可以使用一個叫做sizzle的pure js工具

每一個selected elements lives in the selection array

d3.selection.append(name): append operator

append a new element with the specified named element as the last child in the each of the current selection, then returning the new selection with the new appended element.返回的就是新增的元素陣列 (注意name可以是普通的html元素,也可以是svg圖形類:rect, circle,ellapse, polyline,polygon等等)

另外需要注意的是:如果使用svg元素來做視覺化,那麼由於svg本身不像普通html元素屬性那樣適用document flow,像margin, padding等css屬性在svg中是不起作用的,這樣所有的svg元素都會一個一個地堆砌在一起,因此必須我們自己來通過設定svg元素的transform屬性或者x,y,cx,cy,width等svg元素屬性來自行調整元素在svg container中的position,也就是將資料繫結到svg並且應用到svg類似x,y,cx,cy,width的屬性上,否則就將是一團堆疊在一起的東西

d3.selection.insert(name, (before)) : insert operator

insert a new element with the specified name before the element the before selector matching element, 如果before selector沒有匹配的selection,則會作為append operator使用 

d3.selection.remove(): remove operator

對selection執行remove操作,從dom中去除

SVG VS canvas

SVG: vector based向量圖,可無損無限放大, SVG本身就在DOM中,可以針對區域性css修飾,比較適合簡單的幾何圖形, SVG元素可以接受js event api

Canvas: raster based數點陣圖, canvas可以用js繪製出複雜的圖形

D3使用svg來繪製圖形

d3.selection.attr(name [,value]).attr(name [,value])   attr operator

對所有selection中的element設定屬性

d3.selection.style(name [,value][,priority])  style operator

雖然d3可以非常方便地使用鏈式函式呼叫,但是這帶來另外一個問題是程式碼很難理清哪些被select了,操作了哪些屬性。而幸運的是,由於d3的以下特性使得這個問題易於解決:

1. d3在javascript之上,那麼js的所有特性都能在d3中使用

2. 幾乎everything都是一個selection

3. 返回的selection那麼可以賦值給js變數

d3.selection.data([values[,key]) :d3 data operator

上面提過d3會對selection陣列及元素提供很多擴充套件的函式或者屬性,其中非常重要的一點就是__data__屬性,一旦執行了下面的程式碼 d3.select("p").data([1])則就會對所有的p元素繫結上資料1,在console中你可以看到__data__屬性就建立了並且設定為1

d3.select("p").data([function returnThree(){return 3;}])

d3.select("p").data([ superWoman = {"gender": "female","transportation": "fly"}])

 d3.selectAll("p").data([1,2]) =>這個兩個p元素selection分別繫結1和2這兩個資料!~

d3.selectAll("p").data([[1,2,3],[4,5,6]) =>這個兩個p元素selection分別繫結[1,2,3]和[4,5,6]這兩個陣列!~

d3.selectAll("p").data([function returnThree(){return 3;},function returnFour(){return 4;}]) 這個兩個p元素selection分別繫結returnThree和returnFour兩個函式

d3.selectAll("p").data([ superWoman = {"gender": "female","transportation": "fly"},superMan = {"gender": "male","transportation": "ICE"}])

 如果data資料個數和selection的元素個數不匹配會出現什麼情況?

d3.select("body").insert("p");
d3.select("body").insert("p");
// 兩個p插入body
d3.selectAll("p");//將返回兩個p
d3.selectAll("p").data([1]); //  selection數量大於資料個數: 這時只有第一個p元素被繫結了資料1,並且返回的selection中只包含這個p!!! 
// 也就是返回已經成功繫結了資料的selection,而沒有繫結資料的第2個p則存在於上述selection的exist selection中, 如下右圖所示,一般接著就應該是remove()掉
d3.select("p").data([1,2]); // 資料個數大於selection的元素個數, 這時第一個元素被繫結為1,那麼第二個資料2如何體現呢? // 這時selection將返回兩個元素(其中一個為Placeholder,存在於enter selection中,一般緊接著就是append建立元素並繫結placeholder的資料),
// 但是由於只存在一個元素,所以並沒有第二個元素存在於selection中,但是selection的length為2,這個就是指示還可以繼續append元素,如下左圖所示

       

上面的data函式將dom和資料join起來,如果data是一系列的objects陣列,則可以傳入第二個引數key用於指定用到資料的哪一個欄位來繫結

http://javascript.tutorialhorizon.com/2014/11/20/a-visual-explanation-of-the-enter-update-and-exit-selections-in-d3js/

 

 

d3 update selection = de3.select("p").data(xx)返回的selection

返回前面的selection中成功繫結了資料的實體elements,同時該update selection可以跟著enter,exit來訪問也包含了enter, exit兩個selection分別對應未繫結資料的實體元素和已繫結資料的placeholder元素

https://bost.ocks.org/mike/join/

d3 exit selection: d3.selectAll("p").data([1]) (兩個p只給一個資料)

d3.select("body").insert["p"].attr("class","p_one");
d3.select("body").insert["p"].attr("class","p_two");
var updateSelection = d3.select("body").selectAll("p").data([1]);
// 注意updateSelection只返回成功繫結了資料的p_one元素
updateSelection.exit(); // 注意這個exit selection包含p_two元素(沒有繫結資料的)

只能緊跟在.data()返回的update selection後面使用, prior selection沒有成功繫結資料的elements

一句話: exit selection返回prior selection中還沒有繫結資料的實體元素

d3 enter selection:

返回prior selection中還沒有繫結資料的placeholder元素, enter selection只定義了append, insert,select operator,一旦這三個operator中的任何一個使用了,你就可以修改select的content了

d3.select("body").insert["p"].attr("class","p_one");
var updateSelection = d3.select("body").select["p"].data([1,2]);
// 返回繫結了資料的第一個p元素
updateSelection.enter(); 
// 返回placeholder元素(還沒有繫結資料的placeholder)

https://bost.ocks.org/mike/selection/

https://bost.ocks.org/mike/constancy/

Binding data to dom elements

我們來回顧一下基本的pattern: d3.selectAll('div').data([yy] ).enter().append('div')

首先選擇根本不存在的'div',通過data operator繫結(join)資料,通過enter獲取到這些placeholder元素(js object),隨後通過append operator實際生成這些div,注意這時每個實體的div都將繼承enter selection中placeholder元素的資料

d3.selection.text([value/function]) d3 text operator

設定html元素的textContent屬性為value,value可以是常量,也可以返回string的函式。對於傳入function的情況,這個function對selection中的所有元素都會分別被呼叫,並且在該function中可以訪問該元素繫結的data

callback函式的可用引數

d- the __data__ property

i- the element index 

this- the DOM reference to the element in question

var fivePData = d3.select("body").selectAll("p").data([1,2,3,4,5]).enter().append("p");
function textFunc (d,i){ return "data is" + d + " and index is: " +i;}
fivePdata.text(textFunc);
// 上面的程式碼將產生5個p元素,並且將p元素的textContent設定為包含對應資料的title

selection.style(name [, value][,priority])  style operator

var myData = [1,2,3,4,5];
var svgViewport = d3.select("body").append("svg").attr("width","400").attr("height","300");
var circleSelection = svgViewport.selectAll("circle").data(myData)
var circleElements = circleSelection.enter().append("circle");
function greenRed(d,i){
  if(i%2===0){return "green";}else{return "red";}
}
var circleStyle = circleElements.style("fill",greenRed);
// 上面的程式碼對奇數和偶數個數circle應用不同顏色

SVG basic shapes

rect,:x,y,width,height

circle,: cx,cy,r

ellipse,: cx,cy,rx,ry

straight line,: x1,y1,x2,y2,stroke,stroke-width

polyline,: stroke,stroke-width,points

<svg width="50" height="50">
  <polyline
       stroke="blue" stroke-width="2"
       points="05,30 15,30 15,20 25,20" />
</svg>

polygon, :fill,stroke,stroke-width,points

<svg width="50" height="50">
  <polygon
       fill="yellow"
       stroke="blue" stroke-width="2"
       points="05,30 15,30 15,20 25,20" />
</svg>

從上面的polyline和polygon可以看出稍稍複雜一點的graph都會有很繁瑣的point資料來描述的,隨著影像複雜度增大,如何維護polyline中的這個points屬性值將是一個噩夢,幸運的是由於svg path可以產生任意的graph,而d3又可以通過path generator來為我們自動產生path所需要的d屬性字串,從而替我們來處理這些瑣碎無聊的工作。

path: svg path is the shape to make all shapes

上面的例子中lineFunction對points陣列變換成了path的d屬性。

但是問題是:d3js v4好像沒有了svg函式??!===>d3 v4已經全部使用了扁平的命名方式,不用再使用d3.svg.line了,取而代之的是d3.line了!

下圖列出通過svg path來畫出rect,circle,line這幾種最常見的svg shape方法來:

注意cx,cy,r資料都是unit而不是px, svg的width,height也是以unit為單位的,而非px,因此在svg中非常重要的一點是scale,比例尺

coordinates in svg vs math

 

注意svg元素中的任何子元素其x,y,cx,cy等都是相對於其container元素的座標系的

<svg width="300" height="200" style="
    stroke: aliceblue;
    stroke-width: 1px;
    border: 1px solid;
">
        <rect x="0" y="0" width="50" height="50" style="fill:green"></rect>
        <g transform="translate(100,30)">
            <rect x="0" y="0" width="100" height="100" style="fill:blue"></rect>
            <text x="15" y="58" fill="White" font-family="arial" font-size="16">
                In the box
            </text>
        </g>
    </svg>

上面的程式碼中rect, text的x,y都是相對於g元素的哦!而g元素又做了transform:translate如下圖

注意:svg中沒有類似css中的z-index的概念,在後面的元素總會覆蓋前面的元素,要實現類似z-index的概念,我們必須在render之前對selection做好排序!

 d3js by examples: https://bl.ocks.org/d3byex

d3 key, values,entries utility function

SVG Path Mini-Language

-moveto

-lineto

-horizontal lineto

-vertical lineto

-curveto

-smooth curveto

-quadratic bezier curveto

-smooth quadratic bezier curveto

-elliptical arc

-closepath

D3 Path Data Generator

上面提到通過svg path mini language來做svg path我們可以畫出任何簡單或者複雜的圖形來,但是要維護這個path的資料簡直就是一個災難,不過好在d3可以幫助我們簡化這個工作,上面也提到過通過line generator來生成path所需要的d引數,下面我們詳細看看d3中提供了哪些個path generator

D3 path data generator functions

d3.svg.line

d3.svg.line.radial

d3.svg.area

d3.svg.area.radial

d3.svg.arc

d3.svg.symbol

d3.svg.chord

d3.svg.diagonal

d3.svg.diagonal.radial

D3 associative Array Utilities 

d3.keys(object) : 返回一個物件的字典陣列

d3.values(object):返回一個物件所有key對應的value

d3.entries(object): 返回key/value pair 物件陣列

給svg container 新增座標軸

d3.select("body").append("svg").attr("width",200)
    .attr("height",200).append("g").call(d3.svg.axis())

d3 scale for data and axis:

到底什麼是scale呢?按照d3作者的說法

"

If visualization is constructing “visual representations of abstract data to amplify cognition”, then perhaps the most important concept in D3 is the scale, which maps a dimension of abstract data(domain) to a visual variable(range)

"

也就說scale用來將抽象資料的一個緯度對映成一個可視域的變數。那麼什麼又是資料的一個緯度呢?什麼又是可視域變數呢?

對於一個execel表格資料來說,每一row行可以被認為是一個vector向量,而每一column列就可以被認為是一個緯度。比如價格,銷售額等

而visual variables可以這樣理解: how graphical marks(比如雜湊圖中的一個dot)can represent data using planar position(x,y)和一個多變的z緯度

在 Semiology of Graphics 這本符號圖形理論書籍中,作者這樣舉例:

"

在一個平面裡,一個mark可以在上面或者下面,左面或者右面。人類的眼睛能夠感知到獨立正交的x和y兩個緯度。而mark本身光學能量則產生了一個完全獨立於x和y的z緯度.

人眼是敏感的,在這個和光學相關的z緯度,又可以分解為6個獨立的visual variables可以疊加在x,y平面上: the size of the marks, their value, texture, color, orientation, and shape.他們可以代表diffrences(≠)不同, similarities(≡)相似,a quantified order(Q),或者a nonquantified order(O),and can express groups, hierachies,or vertical movements.

"

根據上面的理論,我們可以得出下面的例項化定義:

"

Thus, a scale is a function that takes an abstract value of data, such as the mass of a diamond in carats, and returns a visual value such as the horizontal position of a dot in pixels. With two scales (one each for x and y),(y軸可以拿對應的price做對映) we have the basis for a scatterplot.

"

scale就是將input domain對映成為range的函式

The input domain is an interval in the abstract dimension of data, often the extent of the observed values. The output range is an interval in the visual variable, such as the visible area defined by the chart size

d3js built-in支援以下種類的scale

 

quantitative scales: d3.scale.linear() : for continuous input domains, such as numbers: y=kx+b

d3.scale.linear().domain([0,100]).range([0,1]).clamp(true) // 對於input資料不在domain這個範圍時,只取其最靠近的range,比如linearfunction(500)也只能返回1

sequential scale:

 

var rainbow = d3.scaleSequential(function(t) {
  return d3.hsl(t * 360, 1, 0.5) + "";
}).range([0,1]);
rainbow(0) // "rgb(255, 0, 0)"
rainbow(0.1) // rgb(255, 153, 0)"

 

 threashold scale:

var color = d3.scaleThreshold()
    .domain([0, 1])
    .range(["red", "white", "green"]);

color.invertExtent("red"); // [undefined, 0]
color.invertExtent("white"); // [0, 1]
color.invertExtent("green"); // [1, undefined]

Band Scale:

var bands = d3.scaleBand().domain([0,1,2]).range([0,100]).round(true)
bands.paddingOuter()
// 0
bands.bandwidth()
// 33
bands(1)
//34
bands(2)
//67
bands(0)
//1

PointScale

 

 

 

Quantize scale

注意如果希望將連續的domain對映成離散的output輸出,需要使用scaleQuantize,而不是scaleLinear (注意這些是d3js v4的api,而d3 v3則對應是d3.scale.linear)

var quantizescale = d3.scaleQuantize().domain([0,100]).range(["red","white","green"])
quantizescale(10)
//"red"
quantizescale(60)
//"white"
quantizescale(80)
//"green"
quantizescale(20)
//"red"

quantizescale.invertExtent('white')
// [33.333333333333336, 66.66666666666667]
quantizescale.invertExtent('red')
// [0, 33.333333333333336]
quantizescale.invertExtent('green')
// [66.66666666666667, 100]

 

ordinal scale: 離散的資料: d3.scale.ordinal().domain([values]).range([values]) for discrete input domains, such as names or categories

rangePoints()

.rangeBands(interval[,padding[,outerPadding]])

.rangeRoundBands

柱狀圖應該使用scaleBand

scatter plot散佈圖可以呈現三維資訊,其中圓radius應該使用 rScale = d3.scaleSqrt() (//因為是平方的關係)

 

v4 api中應該使用d3.scaleOrdinal = d3.scale.ordinal (V3 api)

 

time scale: for time domain

Scale完成的工作是將domain對映為range,它就是一個函式,可以被應用在axis上

var myScale = d3.scale.linear().domain([0,10]).range([0,200]);
var myAxis = d3.svg.axis().scale(myScale);
var mySVG = d3.select("body").append("svg").attr("width",200).attr("height",200);
var axisGroup = mySVG.append("g").call(myAxis);

scale.invert函式返回反向計算的結果:給定一個range值,返回對應的domain值

linear scale
methods:
sqrt
A square root scale.
pow
A power scale (good for the gym, er, I mean, useful when working with exponential
series of values, as in “to the power of ” some exponent).
log
A logarithmic scale.
Other Methods | 119
quantize
A linear scale with discrete values for its output range, for when you want to sort
data into “buckets.”
quantile
Similar to quantize, but with discrete values for its input domain (when you already
have “buckets”).
ordinal
Ordinal scales use nonquantitative values (like category names) for output; perfect
for comparing apples and oranges.
d3.scale.category10(), d3.scale.category20(), d3.scale.category20b(), and
d3.scale.category20c()
Handy preset ordinal scales that output either 10 or 20 categorical colors.
d3.

take into account the margin arrangement

上面的一段程式碼通過svg container append一個group element,在這個g元素selection中直接呼叫myAxis函式建立座標軸,這時axis都是緊挨著svg conatiner的邊緣的,因此部分資料會由於不在svg container的viewport中而無法顯示。如何解決這個問題?d3給了標準的方案:

var margin = {top: 50, right: 50, bottom: 50, left: 50},
    width = 300-margin.left-margin.right,
    height = 300-margin.top-margin.bottom
var myScale = d3.scale.linear().domain([0,10]).range([0,width]);
var myAxis = d3.svg.axis().scale(myScale).orient("bottom"); // 預設就是bottom orientation,文字在軸的下面
var mySVG = d3.select("body").append("svg").attr("width",width+margin.left+margin.right).attr("height",height+margin.top+margin.bottom).append("g").attr("transform","translate("+margin.left+","+margin.top+")");
var axisGroup = mySVG.append("g").call(myAxis);

d3 axis orientation with x軸,y軸及margin convention

上面我們定義並建立了axis,但是座標軸上的文字在軸的上面?下面?左面?右面?以及這個軸是在上面下面左面右面,橫向或者縱向?這個是怎麼定義的呢?答案是由axis的.orient來定義文字和軸的相對方向,而由transform來將軸整體移動到上下左右不同的角落

d3.svg.axis().orient([orientation) 

 "top": 位置在最上面,自左往右橫向,文字也在軸的上面

"left":位置在最左面,自上往下縱向,文字也在軸的左邊

"right":位置在最左面,自上往下縱向,文字在軸的右面

"bottom":位置在最上面,自左往右橫向,文字在軸的下面

 一個svg元素container設定為viewport,其widht,height設定為包含margin-top,left,

chart area則由一個g元素來做容器包裹,該g元素通過transform對應margin來實現和viewport之間的間距

var margin = {top: 50, right: 50, bottom: 50, left: 50},
    width = 300-margin.left-margin.right,
    height = 300-margin.top-margin.bottom
var myScale = d3.scale.linear().domain([0,10]).range([0,width]);
var myXAxis = d3.svg.axis().scale(myScale).orient("top");
var myYAxis = d3.svg.axis().scale(myScale).orient("left");
var mySVG = d3.select("body").append("svg").attr("width",width+margin.left+margin.right).attr("height",height+margin.top+margin.bottom).append("g").attr("transform","translate("+margin.left+","+margin.top+")");
// 需要注意的是g元素只是作為一個container元素來使用,其大小是有其內部元素來決定的,也就是auto的,由於g沒有width,height屬性,因此不能通過設定widht,height來指定大小

var axisX = mySVG.append("g").call(myXAxis); var axisY = mySVG.append("g").call(myYAxis);

上面的程式碼將產生以下座標

 d3 scale for data

我們通過scale應用到axis建立的過程後,座標軸就實現了domain 到 range的自動對映,但是這時我們建立visual component時,比如bar chart的高度如果直接傳入繫結的domain資料,那麼我們看到的結果並不會如我們預期,這時也必須應用axis相同的scale function.我們也需要更多地考慮和決定到底哪些data需要應用這個scale,比如bubble chart我們可能只對bubble所在的x,y座標使用scale,而bubble circle的直徑要麼使用未經過scale變換的真實資料,要麼我們可能需要定義另外一個scale function來專門應用到這個circle radius的值變換上去。

elementAttributes = elements.attr(...,function(d,i){return scaleFunction(d)})
                                               .attr(...,function(d,i){return d.r})
                                               .attr(...,function(d,i){return anotherScaleFunc(d)})
// 這個例子中我們依據繫結的真實資料對svg內的elements屬性做對映設定時部分使用原資料,
// 部分使用一個scaleFunc,部分使用另外的scaleFunction
// 這個就需要我們仔細去思考和設計並決定哪些應該返回真實資料,哪些返回

d3.xhr(url[,mimeType][,callback(error,data)])) ajax functions to retrieve data from server

d3.xhr按照不同的mimeType又有幾個對應的子函式,如下:

d3.text

d3.html

d3.json

d3.xml

d3.csv

d3.tsv

javascript async/sync

javascript本身是single threaded執行模式的,如果某個js模組執行時間過長,那麼整個頁面就處於block狀態,這對於從較慢的網路獲取資料時尤其顯得問題突出,javascript ajax技術的發明就是解決這個問題的。

使用selection.call(callback,additionalParameters)來梳理程式碼

function scaleSelected(selection, scale){
   selection.style('transform', 'scaleX(' + scale + ')');
}
var bar = d3.select('.chart').append('g').insert('rect').data(myData).enter().on('mouseout',function(d,i,elements){
  d3.select(this).call(scaleSelected,2).call(anothercallback);
// 注意call呼叫依然返回selection,這樣就可以鏈式操作了 }

 

d3->d4最重大的變化

d4支援模組化,雖然預設的bundle基本上已經包含了30多個小的模組基本夠用,但是我們也可以方便地使用擴充套件的其他模組,或者使用webpack打包出我們定製的包

d4 symbol都使用了扁平化,而不是以前的nested mode,比如d3.scale.linear  = d3.scaleLinear, and d3.layout.treemap = now d3.treemap.

https://github.com/d3/d3/blob/master/CHANGES.md

chart creation steps

 建立xScale,yScale:

var xScale = d3.scaleTime().domain([

d3.min(data,company=>d3.min(company.values,d=>d.date)),

d3.max(data,company=>d3.max(company.values,d=>d.date))])

=>建立axisBottom, axisLeft:

svg.append('g').attr('transform',`translate(0,${height})`).call(d3.axisBottom(xScale).ticks(5);

svg.append('g').call(d3.axisLeft(yScale);

=> svgcontainerselection.call(axisBottom)放置到container中, 建立line/path generator:

var line = d3.line().x(d=>xScale(d.date).y(d=>yScale(d.close)).curve(d3.curveCatmullRom.alpaha(0.5));,

selectAll('.line').data(data).enter().append('path').attr('class','.line').attr('d'=>line(d.values))

SVG responsive

var margin = { top: 10, right: 20, bottom: 30, left: 30 };
var width = 400 - margin.left - margin.right;
var height = 600 - margin.top - margin.bottom;

var svg = d3.select('.chart')
  .append('svg')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .call(responsivefy)
  .append('g')
    .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');

svg.append('rect')
  .attr('width', width)
  .attr('height', height)
  .style('fill', 'lightblue')
  .style('stroke', 'green');

var yScale = d3.scaleLinear()
  .domain([0, 100])
  .range([height, 0]);
var yAxis = d3.axisLeft(yScale);
svg.call(yAxis);

var xScale = d3.scaleTime()
  .domain([new Date(2016, 0, 1, 6), new Date(2016, 0, 1, 9)])
  .range([0, width]);

var xAxis = d3.axisBottom(xScale)
  .ticks(5)
  .tickSize(10)
  .tickPadding(5);
svg
  .append('g')
    .attr('transform', `translate(0, ${height})`)
  .call(xAxis);

function responsivefy(svg) {
  // get container + svg aspect ratio
  var container = d3.select(svg.node().parentNode),
      width = parseInt(svg.style("width")),
      height = parseInt(svg.style("height")),
      aspect = width / height;

  // add viewBox and preserveAspectRatio properties,
  // and call resize so that svg resizes on inital page load
  svg.attr("viewBox", "0 0 " + width + " " + height)
      .attr("preserveAspectRatio", "xMinYMid")
      .call(resize);

  // to register multiple listeners for same event type,
  // you need to add namespace, i.e., 'click.foo'
  // necessary if you call invoke this function for multiple svgs
  // api docs: https://github.com/mbostock/d3/wiki/Selections#on
  d3.select(window).on("resize." + container.attr("id"), resize);

  // get width of container and resize svg to fit it
  function resize() {
      var targetWidth = parseInt(container.style("width"));
      svg.attr("width", targetWidth);
      svg.attr("height", Math.round(targetWidth / aspect));
  }
}

d3js v3和v4中如何一次性獲取和處理enter以及update selections統一處理相應邏輯?

在d3js中,我們已經很熟悉通過select.data().append().attr這種pattern來處理視覺化,我們也知道data操作符呼叫後返回update selection並且通過update selection也可以訪問exit和enter selection,這些selection加起來才是整個視覺化的dom集合,在d3中無須任何其他呼叫,只要你執行了update.enter().append()之後enter selection自然就merge到update selection中去了,因此我們對update selection的任何操作都會影響到整個dom集。但是在v4中,這個特性消失了,取而代之的是必須使用merge呼叫。但是要注意的是通過merge獲取到整個dom集合後,update段的資料更新(比如在.each函式中對每個資料dataum增加一個欄位,而在其他地方使用該資料)在該場景下可能存在問題。

<html>    
    <head>
        <script src="d3.v3.min.js"></script>        
    </head>
    <body>
        <p>p1</p>
        <p>p2</p>
        <p>p3</p>
    </body>
</html>
var update = d3.select("body").selectAll("p").data([4,5,6,7,8])
update.enter().append("p");
update.text(function(d,i){return d}) // V3下一次呼叫就可以對所有5個dom元素執行操作
// 而v4下則必須使用merge函式呼叫,比如:
var update = d3.select("body").selectAll("p").data([4,5,6,7,8])
var enter = update.enter().append("p")
enter.merge(update).text(function(d){return d;}) // 一次呼叫text就可以對所有的selection設定對應資料了

 

常用資料視覺化的圖表種類及應用場景(從excel中梳理出來)

佈局和路徑生成器

佈局的目的是生成便於繪製圖形所需的資料,比如要繪製餅圖就需要startAngle, endAngle,而輸入的資料卻只是一個類似[1,3,,5,2,32]的陣列,因此要繪製必須將輸入的陣列轉換為對應的[{startAngle:0,endAngle:20,data:1},{}...]的陣列。

在得到適宜於繪圖的資料後,還需要路徑生成器,來生成最終繪圖的svg圖形,餅圖一般就用弧生成器。

 

var dataset = [ 30 , 10 , 43 , 55 , 13 ];
var pie = d3.layout.pie();
var piedata = pie(dataset);
var outerRadius = 150; //外半徑
var innerRadius = 0; //內半徑,為0則中間沒有空白

var arc = d3.svg.arc()  //弧生成器
    .innerRadius(innerRadius)   //設定內半徑
    .outerRadius(outerRadius);  //設定外半徑
var arcsg = svg.selectAll("g")
    .data(piedata)
    .enter()
    .append("g")
    .attr("transform","translate("+ (width/2) +","+ (width/2) +")");
arcsg.append("path")
    .attr("fill",function(d,i){
        return color(i);
    })
    .attr("d",function(d){
        return arc(d);   //呼叫弧生成器,得到路徑值
    });
var color = d3.scale.category10();   //有十種顏色的顏色比例尺
arcsg.append("text")
    .attr("transform",function(d){
        return "translate(" + arc.centroid(d) + ")";
    })
    .attr("text-anchor","middle")
    .text(function(d){
        return d.data;
    });

 如何建立帶有刻度尺的圖表?

最重要的思路是將那些橫向和縱向的刻度當作axis的tick來處理。分別建立一個axisLeft和axisBottom但是transform到right和top,將tick的長度設定為圖形的寬度和高度,將座標軸上的數字隱藏起來

var yGridlinesAxis = d3.svg.axis().scale(scale).orient("left");
            var yGridlineNodes = svg.append('g')
                .attr('transform', 'translate(' + (margins.left + graphWidth) + ',' + margins.top + ')')
                .call(yGridlinesAxis.tickSize(graphWidth + axisPadding, 0, 0).tickFormat(""));
            styleGridlineNodes(yGridlineNodes);
function styleGridlineNodes(axisNodes) {
            axisNodes.selectAll('.domain')
                .attr({
                    fill: 'none',
                    stroke: 'none'
                });
            axisNodes.selectAll('.tick line')
                .attr({
                    fill: 'none',
                    'stroke-width': 1,
                    stroke: 'lightgray'
                });
        }

 

相關文章