D3視覺化:(1)初次見面,SVG與D3的魅力

Vincent Ko發表於2019-02-16

D3.js是一個基於HTML/SVG/CSS的資料視覺化庫,是領域內非常強大的存在了。

今後會在工作使用,也因此開始了自學之旅。學習過程中,我主要通過Curran Kelleher老師的系列教程進行學習,這個筆記用於學習、整理和分享,陸續學習、更新和記錄中....

原文連結

學習目的:

  • 熟悉和認識D3.js
  • 練習使用SVG畫圖,熟悉操作
  • 練習D3.js的基本函式和操作

完成效果圖: 線上demo

D3視覺化:(1)初次見面,SVG與D3的魅力

D3初印象

選擇集

D3是實現資料視覺化,仍然離不開傳統DOM的選擇和操作,也因此,D3提供了類似Jquery的DOM操作指令:

  • d3.select:選擇第一個指定元素
  • d3.selectAll : 選擇所有的元素
const svg = d3.select('svg') //選擇svg
const p = svg.selectAll('p') //選擇svg下所有的p標籤

複製程式碼

當然,可以使用#id 以及 .class 對id和類進行選擇

檢視狀態

d3.selectd3.selectAll返回的都是選擇集,新增、刪除以及修改都需要用到選擇集,檢視狀態有三個函式可以使用:

  • selection.empty() 選擇集為空,返回true,否則返回false
  • selection.node() 返回第一個非空元素,如果選擇集為空,返回null
  • selection.size() 返回選擇集中的元素個數

設定和獲取屬性

使用selectselectAll選擇後,可以通過attr獲取和設定屬性,可以使用append方法新增元素

const svg = select('svg');
svg.append('circle')
    .attr('r','30')

複製程式碼

使用D3和SVG畫圖

html檔案

<html lang="en">
<head>
    <title>Smile face with d3</title>
</head>
<body>
    <svg width="960" height="500"></svg>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="index.js"></script>
</body>
</html>
複製程式碼

主要內容通過index.js實現:

第一步:通過d3選擇SVG,使用circle構建輪廓

D3視覺化:(1)初次見面,SVG與D3的魅力

const svg = d3.select('svg');
const height = +svg.attr('height');
const width = +svg.attr('width');
svg.append('circle')
    .attr('r',height / 2)
    .attr('cx', width / 2)
    .attr('cy', height / 2)
    .attr('fill', 'yellow')
    .attr('stroke','black')

const leftEye = svg.append('circle')
    .attr('r', 30)
    .attr('cx', width / 2 - 100)
    .attr('cy', height / 2 - 80)
    .attr('fill', 'black')

const rightEye = svg.append('circle')
    .attr('r', 30)
    .attr('cx', width / 2 + 100)
    .attr('cy', height / 2 - 80)
    .attr('fill', 'black')
複製程式碼

要點:

  • 通過attr獲取的屬性是string型別的,通過parseFloat或者+轉換為number型別
  • 對圓形circle的屬性cx cy等,使用變數作為其大小設定的值,而不用一個數字,方便日後的維護
  • leftEye和RightEye用類似的方法,創造出來,並將眼睛放置在合適的位置。

程式碼優化

1)可以發現,對三個圓圓心的操作cxcy出現了多次,而作用也僅僅是為了將圓放在中心。因此,可以通過SVG中的<g>來分組來實現一次性操作。

const g = svg.append('g')
    .attr('transform',`translate(${ width / 2}, ${ height / 2})`)

//這樣,在畫圓的時候,就可以去掉對其圓心的操作,因為預設的位置就在中心了
const circle = g.append('circle')
    .attr('r',height / 2)
    .attr('fill', 'yellow')
    .attr('stroke','black')
複製程式碼

2)同樣的,對於眼睛的操作,也有點繁瑣,可以通過變數和分組,提高程式碼的可維護效能。

const eyeSpacing = 100;
const eyeYoffset = -80
const eyeRadius = 30;

const eyesG = g.append('g')
    .attr('transform', `translate(0, ${eyeYoffset})`);

const leftEye = eyesG.append('circle')
    .attr('r', eyeRadius)
    .attr('cx', - eyeSpacing)

const rightEye = eyesG.append('circle')
    .attr('r', eyeRadius)
    .attr('cx', + eyeSpacing)
複製程式碼

第二步 嘴巴

D3視覺化:(1)初次見面,SVG與D3的魅力

嘴巴實際上是一個弧線,使用SVG中的path進行繪製。熟悉path的,當然可以直接給引數進行繪製,但是d3對圓弧有更好的支援,可以使用d3.arc函式,方便的繪製圓弧。

arc函式

根據官方的API手冊,函式的使用方法如下:

var arc = d3.arc();

arc({
  innerRadius: 0,
  outerRadius: 100,
  startAngle: 0,
  endAngle: Math.PI / 2
}); // "M0,-100A100,100,0,0,1,100,0L0,0Z"
複製程式碼

其中,innerRadius是內圓半徑,outerRadius是外圓半徑,startAngle和endAngle分別是開始和結束的弧度(完整的圓是0~2PI)

笑臉的實現

根據以上內容,程式碼如下:

const mouth = g.append('path')
    .attr('d',d3.arc()({
        innerRadius: 150,
        outerRadius: 170,
        startAngle: Math.PI /2,
        endAngle: Math.PI * 3 / 2
    }))
複製程式碼

眉毛

眉毛用簡單的長方形來代替,也就是svg中的<rect>,使用前文的程式設計風格,將眉毛歸為一個group,並將位置設定。

const eyebrowWidth = 50;
const eyebrowHeight = 10;
const eyebrowYoffset = -150;
const BrowG = g.append('g')
    .attr('transform',`translate(${-eyebrowWidth / 2},${eyebrowYoffset})`);

const leftEyebrow = BrowG.append('rect')
    .attr('width',eyebrowWidth)
    .attr('height',eyebrowHeight)
    .attr('x',-eyeSpacing)

const rightEyebrow = BrowG.append('rect')
    .attr('width', eyebrowWidth)
    .attr('height', eyebrowHeight)
    .attr('x', eyeSpacing)
複製程式碼

加入動畫

使用transition函式設定眉毛的動畫,讓笑臉動起來

const BrowG = g.append('g')
    .attr('transform',`translate(${-eyebrowWidth / 2},${eyebrowYoffset})`);
BrowG.transition().duration(2000)
    .attr('transform', `translate(${-eyebrowWidth / 2},${eyebrowYoffset - 50})`)
    .transition().duration(2000)
    .attr('transform', `translate(${-eyebrowWidth / 2},${eyebrowYoffset})`).duration(2000)

複製程式碼

注意點:

  • transition函式要對號入座,即如果加在了<g>上,對應的增加的屬性動畫應該在transform上; 如果動畫在<rect>上,動畫應該在y屬性上。
  • 新增動畫不能在append函式之後連線,比如BrowG.append('g').attr(...).transition()是不行的。因為過度動畫無法繫結在append的元素上,需要分別操作。

完整程式碼

code downlaod here

const svg = d3.select('svg');
const height = +svg.attr('height');
const width = +svg.attr('width');
const g = svg.append('g')
    .attr('transform',`translate(${ width / 2}, ${ height / 2})`)

const circle = g.append('circle')
    .attr('r',height / 2)
    .attr('fill', 'yellow')
    .attr('stroke','black')

const eyeSpacing = 100;
const eyeYoffset = -80
const eyeRadius = 30;
const eyebrowWidth = 50;
const eyebrowHeight = 10;
const eyebrowYoffset = -150;

const eyesG = g.append('g')
    .attr('transform', `translate(0, ${eyeYoffset})`);

const leftEye = eyesG.append('circle')
    .attr('r', eyeRadius)
    .attr('cx', - eyeSpacing)

const rightEye = eyesG.append('circle')
    .attr('r', eyeRadius)
    .attr('cx', + eyeSpacing)

const mouth = g.append('path')
    .attr('d',d3.arc()({
        innerRadius: 150,
        outerRadius: 170,
        startAngle: Math.PI /2,
        endAngle: Math.PI * 3 / 2
    }))

const BrowG = g.append('g')
    .attr('transform',`translate(${-eyebrowWidth / 2},${eyebrowYoffset})`);
BrowG.transition().duration(2000)
    .attr('transform', `translate(${-eyebrowWidth / 2},${eyebrowYoffset - 50})`)
    .transition().duration(2000)
    .attr('transform', `translate(${-eyebrowWidth / 2},${eyebrowYoffset})`).duration(2000)

const leftEyebrow = BrowG.append('rect')
    .attr('width',eyebrowWidth)
    .attr('height',eyebrowHeight)
    .attr('x',-eyeSpacing)

const rightEyebrow = BrowG.append('rect')
    .attr('width', eyebrowWidth)
    .attr('height', eyebrowHeight)
    .attr('x', eyeSpacing)


複製程式碼

相關文章