D3.js 教程: 使用 JavaScript 建立可交互的柱狀圖

原文連接:D3.js Tutorial: Building Interactive Bar Charts with JavaScriptjavascript

譯者:OFEDcss

最近,咱們有幸參與了一個機器學習項目,該項目涉及 React 和 D3.js 之類的庫。在許多任務中,我開發了幾個圖表用來展現諸如樸素貝葉斯這樣的機器學習模型的處理結果,圖表以折線圖或分組柱狀圖的形式呈現。html

我會在此文中介紹使用 D3.js 的過程,以及經過一個簡單的柱狀圖示例演示庫的基本使用。前端

讀完此文後,你將學到如何輕鬆建立相似的 D3.js 圖表:java

bar chart

這裏有完整的源代碼git

咱們在 RisingStack(公司)喜歡 JavaScript 生態,前端,後端都喜歡。就我我的而言,我對先後端都感興趣。經過後端開發,我能夠看透應用程序的底層業務邏輯,同時也有機會在前端建立使人驚歎的效果。這正是 D3.js 的用武之地!github

D3.js 是什麼?

D3.js 是一個能夠基於數據來操做文檔的 JavaScript 庫。編程

「D3 能夠幫助你使用 HTML, CSS, SVG 以及 Canvas 來展現數據。D3 遵循現有的 Web 標準,能夠不須要其餘任何框架獨立運行在現代瀏覽器中,它結合強大的可視化組件來驅動 DOM 操做。」 - d3js.org後端

首先考慮爲何要用 D3.js 建立圖表?爲何不僅顯示圖片呢?api

圖表是基於第三方資源的信息,在渲染時須要動態可視化。此外,SVG 是一個很是強大的工具,很是適合這個應用場景。

讓咱們先看看 SVG 有什麼好。

SVG 的優勢

SVG 表明可縮放矢量圖形,從技術上講,這是一種基於 XML 的標記語言。

它一般用於繪製矢量圖形,好比線條和形狀或修改現有圖像。你能夠在這裏找到可用元素的列表。

優勢:

  • 支持全部主流瀏覽器;
  • 有 DOM 接口,不須要第三方庫;
  • 可伸縮,可保持高分辨率;
  • 和其餘圖像格式相比,體積更小。

缺點:

  • 只能顯示二維圖像;
  • 學習曲線長;
  • 對於計算密集型操做,渲染可能須要很長時間。

SVG 儘管有缺點,但它還是顯示圖標,logo,插圖或者此文說起的圖表的優良工具。

開始使用 D3.js

我選擇以柱狀圖做爲開始,由於它表明了一個低複雜度的視覺元素,同時它還能教會 D3.js 自己的基本應用。沒騙你,D3 提供了一套很棒的可視化數據的工具。看看它的 github page 頁面,欣賞一些很是好的用例!

柱狀圖能夠是水平或垂直的,取決於它的方向。咱們從垂直的柱狀圖開始。

在這個圖表中,我將根據 Stack Overlow 2018年開發者調查結果顯示前十個最受歡迎的編程語言。

畫起來!

SVG 的座標系從左上角開始(0;0)。正 x 軸向右,正 y 軸向下。所以,在計算元素的 y 座標時,必須考慮 SVG 的高度。

axis

背景知識差很少了,讓咱們擼代碼吧!

我想建立一個寬1000像素、高600像素的圖表。

<body>
    <svg />
</body>
<script> const margin = 60; const width = 1000 - 2 * margin; const height = 600 - 2 * margin; const svg = d3.select('svg'); </script>
複製代碼

以上代碼片斷中,我用 d3 select 選擇了 HTML 建立的 <svg> 元素。此選擇方法接收各類類型的選擇器字符串並返回第一個匹配元素。若是想獲取全部匹配元素,使用 selectAll

我還定義了一個邊距值,它給圖表提供了一點間距。間距也能夠應用到 <g> 元素上,經過 translate 移動指望的值。從如今起,我將在這個分組中繪製,確保與頁面其它內容保持合理的間距。

const chart = svg.append('g')
    .attr('transform', `translate(${margin}, ${margin})`);
複製代碼

往元素添加屬性就像調用 attr 方法同樣簡單。方法的第一個參數接收用於所選 DOM 元素的屬性。第二個參數是屬性值或返回其值的回調函數。以上代碼簡單將圖表的原點移到 SVG 的 (60;60) 位置。

D3.js 支持的數據源格式

要開始繪圖,我須要定義使用的數據源。本教程中,我使用了一個簡單的 JavaScript 數組,該數組保存了語言名稱及其所佔百分比率的對象,可是這裏着重提到一點,D3.js 支持多種數據格式。

該庫具有從 XMLHttpRequest,.csv 文件,文本文件等數據源加載數據的內置功能。每一種數據源均可能包含 D3.js 可用的數據,最重要的是把它們構建成數組。注意,從 版本5.0 開始,D3 庫使用 Promise 取代回調來加載數據,這是一次不向後兼容的更改。

縮放,座標軸

讓咱們繼續討論圖表的座標軸。爲了畫 y 軸,我須要設定最小和最大值,分別設置爲0和100。

本教程中,我正在研究使用百分比,可是除了數字以外,還有其餘數據類型的實用函數,我將在後面解釋。

我必須將圖表的高度在這兩個值之間均分。爲此,我建立了一個縮放函數。

const yScale = d3.scaleLinear()
    .range([height, 0])
    .domain([0, 100]);
複製代碼

線性縮放是最多見的縮放類型。它將連續輸入範圍轉換爲連續輸出範圍。請注意 rangedomain 方法。第一個 range 方法取的長度應該在 domain 的邊界值之間。

記住,SVG 座標系從左上角開始,這就是爲何 range 將高度做爲第一個參數而不是零。

在左側建立一個座標軸跟添加另外一個分組同樣簡單,調用 d3 的 axisLeft 方法,並把縮放函數做爲參數。

chart.append('g')
    .call(d3.axisLeft(yScale));
複製代碼

如今,繼續添加 x 軸。

const xScale = d3.scaleBand()
    .range([0, width])
    .domain(sample.map((s) => s.language))
    .padding(0.2)

chart.append('g')
    .attr('transform', `translate(0, ${height})`)
    .call(d3.axisBottom(xScale));
複製代碼

請注意,我使用 scaleBand 方法建立 x 軸,它將 x 軸 分紅多段,而且使用餘下的間隙計算柱狀圖的座標和寬度。

D3.js 還能處理許多其餘日期類型。scaleTimescaleLinear 很是類似,只是這裏的 domain 是一個日期數組。

使用 D3.js 繪製柱狀圖

想一想咱們須要什麼樣的輸入來畫柱條。它們各自表明一個用簡單形狀,特別是矩形來展現的值。下一段代碼中,我把它們添加到已建立的分組元素中了。

chart.selectAll()
    .data(goals)
    .enter()
    .append('rect')
    .attr('x', (s) => xScale(s.language))
    .attr('y', (s) => yScale(s.value))
    .attr('height', (s) => height - yScale(s.value))
    .attr('width', xScale.bandwidth())
複製代碼

首先,我 selectAll 圖表上的全部元素,返回結果爲空。而後,data 函數根據數組長度通知 DOM 應該更新多少元素。若是數據個數多於 DOM 個數時,則 enter 會標識出缺乏的元素。enter 會返回須要添加的元素。 一般,後面緊跟 append 方法會把元素添加到 DOM 中。

基本上,我用 D3.js 給數組每一項都追加了一個矩形。

當前只在彼此頂部添加了沒有寬高的矩形。這兩個屬性必須經過以前的縮放函數計算所得。

我調用 attr 方法添加了矩形座標。第二個參數能夠是回調,它返回3個參數:當前綁定的數據,索引和全部數據數組。

.attr(’x’, (actual, index, array) =>
    xScale(actual.value))
複製代碼

縮放函數返回給定範圍值的座標。計算座標就是小菜一碟,訣竅是利用柱子的高度。必須從圖表的高度減去計算出的 y 座標,才能獲得正確的列值。

定義矩形的寬度也會用到縮放函數。scaleBand 有一個 bandwidth 函數,它基於設置的間距返回一個元素的計算寬度。

幹得不錯,但沒那麼花哨,對吧?

爲了防止觀衆視覺疲勞,讓咱們添加一些信息改善下視覺效果!

製做柱狀圖的技巧

有一些基本規則值得一提。

  • 避免使用 3D 效果;
  • 直觀地排序數據點 - 按字母順序或按數字排序;
  • 柱條之間保持必定距離;
  • y 軸從 0 開始,而不是從最小值開始;
  • 使用統一的顏色;
  • 添加軸標籤、標題、導引線。

D3.js 網格系統

我想在背景中添加柵格線突出那些值。

垂直和水平的線均可以添加,個人建議是隻添加一種。過多的線會分散注意力。如下代碼片斷演示瞭如何添加水平和垂直的柵格。

chart.append('g')
    .attr('class', 'grid')
    .attr('transform', `translate(0, ${height})`)
    .call(d3.axisBottom()
        .scale(xScale)
        .tickSize(-height, 0, 0)
        .tickFormat(''))

chart.append('g')
    .attr('class', 'grid')
    .call(d3.axisLeft()
        .scale(yScale)
        .tickSize(-width, 0, 0)
        .tickFormat(''))
複製代碼

此例中,我更喜歡垂直柵格線,由於它能夠引導視線,保持總體畫面簡介明快。

D3.js 中的標籤

我還想添加一些文字指導,從而使圖表更加全面。讓咱們給圖表命個名,併爲座標軸添加標籤吧。

文本是 SVG 元素,一樣能夠添加到 SVG 或者分組中。它們可使用 x 和 y 座標定位,文本對齊是經過 text-anchor 屬性實現的。 添加標籤文字,只需調用文本元素上的 text 方法。

svg.append('text')
    .attr('x', -(height / 2) - margin)
    .attr('y', margin / 2.4)
    .attr('transform', 'rotate(-90)')
    .attr('text-anchor', 'middle')
    .text('Love meter (%)')

svg.append('text')
    .attr('x', width / 2 + margin)
    .attr('y', 40)
    .attr('text-anchor', 'middle')
    .text('Most loved programming languages in 2018')
複製代碼

與 D3.js 交互

咱們的圖表內容已然豐富,可是仍然能夠添加些互動效果。

如下的代碼演示瞭如何給 SVG 元素添加事件監聽。

svgElement
    .on('mouseenter', function (actual, i) {
        d3.select(this).attr(‘opacity’, 0.5)
    })
    .on('mouseleave’, function (actual, i) {
        d3.select(this).attr(‘opacity’, 1)
    })
複製代碼

注意,我用了函數表達式而不是箭頭函數,由於我經過 this 關鍵字訪問元素。

當鼠標滑過選中的 SVG 元素時,它的透明度變爲原始值的一半,鼠標離開元素時透明度恢復原始值。

你也能夠經過 d3.mouse 獲取鼠標座標。它返回一個具備 x 和 y 座標的數組。在光標所在位置顯示提示,就能夠經過這個實現。

建立使人瞠目結舌的圖表並沒那麼簡單。

可能須要圖形設計師,UX 研究員和其餘牛人的智慧。如下例子展現了幾個提高圖表效果的可能性!

咱們的圖表顯示了很是類似的值,因此爲了突出條形值之間的差別,我添加了一個 mouseenter 事件。每當用戶懸停在特定的列時,該欄的頂部就會畫一條水平線。此外,我還計算了與其餘柱條的差別,並顯示在了相應的柱條上。

很整齊吧?我還在此例中增長了透明度,加大了柱條的寬度。

.on(‘mouseenter’, function (s, i) {
    d3.select(this)
        .transition()
        .duration(300)
        .attr('opacity', 0.6)
        .attr('x', (a) => xScale(a.language) - 5)
        .attr('width', xScale.bandwidth() + 10)

    chart.append('line')
        .attr('x1', 0)
        .attr('y1', y)
        .attr('x2', width)
        .attr('y2', y)
        .attr('stroke', 'red')

    // 部分實現,總體效果見源碼
})
複製代碼

transition 方法代表我想把 DOM 改變繪製成動畫。它的時間間隔是用 duration 函數設置的,該函數以毫秒做爲參數。上面的過渡會淡化帶狀顏色,並加寬條形的寬度。

要畫一條 SVG 線,我須要起點和終點。這能夠經過 x1y1x2y2 座標來設置。直到我用 stroke 屬性設置線條的顏色,線條纔可見。

這裏只展現了 mouseenter 事件這部分,切記,必須在 mouseout 事件上恢復或刪除更改。本文末尾提供了完整的源代碼。

讓咱們給圖表添加一些樣式吧!

回顧下咱們目前爲止完成了那些功能,以及如何經過樣式裝扮圖表。能夠經過先前用過的 attr 方法給 SVG 元素添加 class 屬性。

咱們的圖表功能豐富,而不是死板的靜態圖片,鼠標懸停時能夠顯示各個柱條的差值。標題交表明格的背景,標籤幫助識別座標軸的測量單位。我還在右下角添加了新的標籤,註明數據來源。

剩下的事情就差顏色和字體了!

深色背景的圖表使亮色柱條看起來很酷。我還使用了 open Sans 字體,並給不一樣的標籤設置不一樣的大小和粗細。

注意到那條虛線了嗎?它是經過 stroke-widthstroke-dasharray 屬性實現的。使用 stroke-dasharray,能夠定義虛線的圖案和間距,從而改變形狀的輪廓。

line#limit {
    stroke: #FED966;
    stroke-width: 3;
    stroke-dasharray: 3 6;
}

.grid path {
    stroke-width: 3;
}

.grid .tick line {
    stroke: #9FAAAE;
    stroke-opacity: 0.2;
}
複製代碼

網格線比較討巧,我給分組中的路徑元素使用了 stroke-width: 0,爲了隱藏表格的框架,我還經過設置線條的透明度下降它們的可見性。

全部其它有關字體大小和顏色的 CSS 能夠參照源碼。

收尾咱們的 D3.js 柱狀圖教程

D3.js 是一個使人驚歎的 DOM 操做庫。它的內部埋藏了無數的寶藏等待你去探索(確切的說,不是埋藏,文檔也很齊全)。此文僅僅使用了它的工具集的冰山一角,就建立了一個不一樣凡響的柱狀圖。

繼續探索吧,定能創造出無比壯觀的視覺效果!

這是本文示例源代碼 的連接。

你用 D3.js 作過一些炫酷的東西嗎?和咱們分享一下!你有任何問題,或者想要關於這個主題的另外一個教程,歡迎留言!

謝謝閱讀,下次再見!

相關文章
相關標籤/搜索