使用D3.js構(gòu)建實(shí)時(shí)圖形的示例代碼
首先你需要在計(jì)算機(jī)上安裝Node和npm。
數(shù)據(jù)的可視化表示是傳遞復(fù)雜信息的最有效手段之一,D3.js提供了創(chuàng)建這些數(shù)據(jù)可視化的強(qiáng)大工具和靈活性。
D3.js是一個(gè)JavaScript庫,用于使用SVG,HTML和CSS在Web瀏覽器中生成動(dòng)態(tài)的交互式數(shù)據(jù)可視化。
D3 提供了各種簡單易用的函數(shù),大大簡化了 JavaScript 操作數(shù)據(jù)的難度。由于它本質(zhì)上是 JavaScript ,所以用 JavaScript 也是可以實(shí)現(xiàn)所有功能的,但它能大大減小你的工作量,尤其是在數(shù)據(jù)可視化方面,D3 已經(jīng)將生成可視化的復(fù)雜步驟精簡到了幾個(gè)簡單的函數(shù),你只需要輸入幾個(gè)簡單的數(shù)據(jù),就能夠轉(zhuǎn)換為各種絢麗的圖形。有過 JavaScript 基礎(chǔ)的朋友一定很容易理解它。
在本教程中,我們將探討如何使用D3.js和Pusher Channels構(gòu)建實(shí)時(shí)圖形。如果您在閱讀本教程時(shí)想要使用代碼,請查看此GitHub存儲(chǔ)庫,其中包含代碼的最終版本。
準(zhǔn)備
要完成本教程,您需要安裝Node.js和npm。我在創(chuàng)建本教程時(shí)使用的版本如下:
- Node.js v10.4.1
- npm v6.3.0
您還需要在計(jì)算機(jī)上安裝http-server。它可以通過運(yùn)行以下命令通過npm安裝:npm install http-server。
雖然不需要Pusher知識(shí),但如果熟悉它后,對學(xué)習(xí)JavaScript和D3.js會(huì)很有幫助。
開始
首先,為我們要構(gòu)建的應(yīng)用程序創(chuàng)建一個(gè)新目錄。將其稱為實(shí)時(shí)圖形或任何您喜歡的圖形。在新創(chuàng)建的目錄中,創(chuàng)建一個(gè)新的index.html文件并粘貼以下代碼:
//index.html <!DOCTYPE html> <hml lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" href="style.css" rel="external nofollow" > <title>Realtime D3 Chart</title> </head> <body> <script src="https://js.pusher.com/4.2/pusher.min.js"></script> <script src="https://d3js.org/d3.v5.min.js"></script> <script src="app.js"></script> </body> </html>
如您所見,HTML文件只是提取構(gòu)建圖形所需的樣式和腳本。我們正在利用D3.js來構(gòu)建圖表,并使用Pusher來添加實(shí)時(shí)功能。app.js文件是應(yīng)用程序前端代碼的寫入位置。
在我們開始實(shí)現(xiàn)圖表之前,讓我們在style.css中添加應(yīng)用程序的樣式:
// style.css
html {
height: 100%;
box-sizing: border-box;
padding: 0;
margin: 0;
}
*, *::before, *::after {
box-sizing: inherit;
}
body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
overflow: hidden;
background: linear-gradient(135deg, #ffffff 0%,#e8f1f5 100%);
}
.container {
position: absolute;
padding: 20px;
top: 50%;
left: 50%;
background-color: white;
border-radius: 4px;
transform: translate(-50%, -50%);
box-shadow: 0px 50px 100px 0px rgba(0,0,102,0.1);
text-align: center;
}
.container h1 {
color: #333;
}
.bar {
fill: #6875ff;
border-radius: 2px;
}
.bar:hover {
fill: #1edede;
}
.tooltip {
opacity: 0;
background-color: rgb(170, 204, 247);
padding: 5px;
border-radius: 4px;
transition: opacity 0.2s ease;
}
安裝服務(wù)器依賴項(xiàng)
假設(shè)您安裝了Node和npm,請運(yùn)行以下命令來安裝應(yīng)用程序的服務(wù)器組件所需的所有依賴項(xiàng):
npm install express dotenv cors pusher
Pusher 設(shè)置
前往Pusher網(wǎng)站并注冊一個(gè)免費(fèi)帳戶。選擇側(cè)欄上的Channels apps,然后點(diǎn)擊Create Channels app以創(chuàng)建新應(yīng)用。
創(chuàng)建應(yīng)用程序后,從API Keys選項(xiàng)卡中檢索憑據(jù),然后在項(xiàng)目目錄根目錄中創(chuàng)建一個(gè)variables.env文件,將以下內(nèi)容添加到這個(gè)文件中。
// variables.env PUSHER_APP_ID=<your app id> PUSHER_APP_KEY=<your app key> PUSHER_APP_SECRET=<your app secret> PUSHER_APP_CLUSTER=<your app cluster>
設(shè)置服務(wù)器
現(xiàn)在我們已經(jīng)安裝了相關(guān)的依賴項(xiàng)并且已經(jīng)設(shè)置了我們的Pusher帳戶,我們可以開始構(gòu)建服務(wù)器。
在項(xiàng)目目錄的根目錄中創(chuàng)建一個(gè)名為server.js的新文件,并粘貼以下代碼:
// server.js
require('dotenv').config({ path: 'variables.env' });
const express = require('express');
const cors = require('cors');
const poll = [
{
name: 'Chelsea',
votes: 100,
},
{
name: 'Arsenal',
votes: 70,
},
{
name: 'Liverpool',
votes: 250,
},
{
name: 'Manchester City',
votes: 689,
},
{
name: 'Manchester United',
votes: 150,
},
];
const app = express();
app.use(cors());
app.get('/poll', (req, res) => {
res.json(poll);
});
app.set('port', process.env.PORT || 4000);
const server = app.listen(app.get('port'), () => {
console.log(Express running → PORT ${server.address().port});
});
保存文件并從項(xiàng)目目錄的根目錄運(yùn)行節(jié)點(diǎn)server.js以啟動(dòng)服務(wù)器。
設(shè)置前端應(yīng)用程序
應(yīng)用程序的前端將寫在我們之前引用的app.js文件中。在項(xiàng)目目錄的根目錄中創(chuàng)建此文件,并在其中粘貼以下代碼:
// app.js
// set the dimensions and margins of the graph
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const width = 960 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
// set the ranges for the graph
const x = d3
.scaleBand()
.range([0, width])
.padding(0.1);
const y = d3.scaleLinear().range([height, 0]);
// append the container for the graph to the page
const container = d3
.select('body')
.append('div')
.attr('class', 'container');
container.append('h1').text('Who will win the 2018/19 Premier League Season?');
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
const svg = container
.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 + ')');
// Create a skeleton structure for a tooltip and append it to the page
const tip = d3
.select('body')
.append('div')
.attr('class', 'tooltip');
// Get the poll data from the /poll endpoint
fetch('http://localhost:4000/poll')
.then(response => response.json())
.then(poll => {
// add the x Axis
svg
.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'x-axis')
.call(d3.axisBottom(x));
// add the y Axis
svg
.append('g')
.attr('class', 'y-axis')
.call(d3.axisLeft(y));
update(poll);
});
function update(poll) {
// Scale the range of the data in the x axis
x.domain(
poll.map(d => {
return d.name;
})
);
// Scale the range of the data in the y axis
y.domain([
0,
d3.max(poll, d => {
return d.votes + 200;
}),
]);
// Select all bars on the graph, take them out, and exit the previous data set.
// Enter the new data and append the rectangles for each object in the poll array
svg
.selectAll('.bar')
.remove()
.exit()
.data(poll)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', d => {
return x(d.name);
})
.attr('width', x.bandwidth())
.attr('y', d => {
return y(d.votes);
})
.attr('height', d => {
return height - y(d.votes);
})
.on('mousemove', d => {
tip
.style('position', 'absolute')
.style('left', ${d3.event.pageX + 10}px)
.style('top', ${d3.event.pageY + 20}px)
.style('display', 'inline-block')
.style('opacity', '0.9')
.html(
<div><strong>${d.name}</strong></div> <span>${d.votes} votes</span>
);
})
.on('mouseout', () => tip.style('display', 'none'));
// update the x-axis
svg.select('.x-axis').call(d3.axisBottom(x));
// update the y-axis
svg.select('.y-axis').call(d3.axisLeft(y));
}
在上面的代碼塊中,我們使用通過/ poll端點(diǎn)接收的初始數(shù)據(jù)創(chuàng)建了一個(gè)基本條形圖。如果您熟悉D3的工作原理,那么您應(yīng)該熟悉這些代碼。我在代碼的關(guān)鍵部分添加了注釋,以指導(dǎo)您構(gòu)建圖表的方式。
在新終端中,啟動(dòng)開發(fā)服務(wù)器以提供index.html文件:
npx http-server
我在這里使用http-server,但你可以使用你想要的任何服務(wù)器。您甚至可以直接在瀏覽器中打開index.html。
此時(shí),您的圖表應(yīng)如下所示:

使用Pusher實(shí)時(shí)更新圖表
讓我們確保輪詢的更新可以通過Pusher Channels實(shí)時(shí)反映在應(yīng)用程序的前端中。將以下代碼粘貼到app.js文件的末尾。
// app.js
const pusher = new Pusher('<your app key>', {
cluster: '<your app cluster>',
encrypted: true,
});
const channel = pusher.subscribe('poll-channel');
channel.bind('update-poll', data => {
update(data.poll);
});
在這里,我們打開了與Channels的連接,并使用Pusher的subscribe()方法訂閱了一個(gè)名為poll-channel的新頻道。通過bind方法監(jiān)聽輪詢更新,并在收到更新后使用最新數(shù)據(jù)調(diào)用update()函數(shù),以便重新呈現(xiàn)圖形。
不要忘記使用Pusher帳戶信息中心中的相應(yīng)詳細(xì)信息替換占位符。
從服務(wù)器觸發(fā)更新
我們將模擬每秒更新一次的輪詢,并在數(shù)據(jù)發(fā)生變化時(shí)使用Pusher觸發(fā)更新,以便輪詢的訂閱者(客戶端)可以實(shí)時(shí)接收更新的數(shù)據(jù)。
在其他導(dǎo)入下面的server.js頂部添加以下代碼:
const Pusher = require('pusher');
const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_APP_KEY,
secret: process.env.PUSHER_APP_SECRET,
cluster: process.env.PUSHER_APP_CLUSTER,
encrypted: true,
});
function getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
function increment() {
const num = getRandomNumber(0, poll.length);
poll[num].votes += 20;
}
function updatePoll() {
setInterval(() => {
increment();
pusher.trigger('poll-channel', 'update-poll', {
poll,
});
}, 1000);
}
然后將/ poll端點(diǎn)更改為如下所示:
app.get('/poll', (req, res) => {
res.json(poll);
updatePoll();
});
/ poll路由將初始輪詢數(shù)據(jù)發(fā)送到客戶端并調(diào)用updatePoll()函數(shù),該函數(shù)以三秒為間隔遞增隨機(jī)俱樂部的投票,并觸發(fā)我們在最后一步中在客戶端上創(chuàng)建的輪詢頻道的更新。
通過從項(xiàng)目目錄的根目錄運(yùn)行節(jié)點(diǎn)server.js,終止服務(wù)器并重新啟動(dòng)它。此時(shí),您應(yīng)該有一個(gè)實(shí)時(shí)更新的條形圖。

結(jié)論
您已經(jīng)看到了使用D3.js創(chuàng)建條形圖的過程以及如何使用Pusher Channels實(shí)時(shí)創(chuàng)建條形圖。
我們已經(jīng)為Pusher和D3提供了一個(gè)簡單的用例,但其中一個(gè)僅僅是表面上的問題。我建議深入研究docs,了解更多有關(guān)Pusher及其他功能的信息。
謝謝閱讀!您可以在GitHub存儲(chǔ)庫中找到本教程的完整源代碼。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
通用javascript代碼判斷版本號(hào)是否在版本范圍之間
通用判斷版本號(hào)是否在兩者之間,也可以搭配判斷是否大于某版本號(hào),小于取反即可,本文給大家介紹通用javascript代碼判斷版本號(hào)是否在版本范圍之間,需要的朋友參考下2015-11-11
淺談?lì)愃朴?function(){}).call()的js語句
這篇文章主要介紹了淺談?lì)愃朴?function(){}).call()的js語句,的相關(guān)資料,需要的朋友可以參考下2015-03-03
不刷新網(wǎng)頁就能鏈接新的js文件方法總結(jié)
在本篇文章里小編給大家整理的是關(guān)于不刷新網(wǎng)頁就能鏈接新的js文件方法總結(jié),需要的朋友們參考下。2020-03-03
JavaScript中常見的七種繼承及實(shí)現(xiàn)
JS的繼承方式在我們面試的時(shí)候經(jīng)常會(huì)被問到,所以深入理解js繼承方式以及它們的優(yōu)缺點(diǎn)是非常有必要的。本文為大家整理了JavaScript中常見的七種繼承及實(shí)現(xiàn),需要的可以參考一下2023-03-03
整理關(guān)于Bootstrap模態(tài)彈出框的慕課筆記
這篇文章主要為大家整理了關(guān)于Bootstrap模態(tài)彈出框的慕課筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
JS基于正則實(shí)現(xiàn)數(shù)字千分位用逗號(hào)分隔的方法
這篇文章主要介紹了JS基于正則實(shí)現(xiàn)數(shù)字千分位用逗號(hào)分隔的方法,涉及javascript正則表達(dá)式操作數(shù)字的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-06-06

