使用D3.js構建實時圖形

首先你須要在計算機上安裝Node和npm。css

數據的可視化表示是傳遞複雜信息的最有效手段之一,D3.js提供了建立這些數據可視化的強大工具和靈活性。html

D3.js是一個JavaScript庫,用於使用SVG,HTML和CSS在Web瀏覽器中生成動態的交互式數據可視化。前端

在本教程中,咱們將探討如何使用D3.js和Pusher Channels構建實時圖形。若是您在閱讀本教程時想要使用代碼,請查看此GitHub存儲庫,其中包含代碼的最終版本。node

準備

要完成本教程,您須要安裝Node.jsnpm。我在建立本教程時使用的版本以下:git

  • Node.js v10.4.1
  • npm v6.3.0

您還須要在計算機上安裝http-server。它能夠經過運行如下命令經過npm安裝:npm install http-server。github

雖然不須要Pusher知識,但若是熟悉它後,對學習JavaScript和D3.js會頗有幫助。express

開始

首先,爲咱們要構建的應用程序建立一個新目錄。將其稱爲實時圖形或任何您喜歡的圖形。在新建立的目錄中,建立一個新的index.html文件並粘貼如下代碼:npm

//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">
      <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文件只是提取構建圖形所需的樣式和腳本。咱們正在利用D3.js來構建圖表,並使用Pusher來添加實時功能。app.js文件是應用程序前端代碼的寫入位置。json

在咱們開始實現圖表以前,讓咱們在style.css中添加應用程序的樣式:瀏覽器

// 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;
    }

安裝服務器依賴項

假設您安裝了Node和npm,請運行如下命令來安裝應用程序的服務器組件所需的全部依賴項:

npm install express dotenv cors pusher

Pusher 設置

前往Pusher網站並註冊一個免費賬戶。選擇側欄上的Channels apps,而後點擊Create Channels app以建立新應用。

建立應用程序後,從API Keys選項卡中檢索憑據,而後在項目目錄根目錄中建立一個variables.env文件,將如下內容添加到這個文件中。

// 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>

設置服務器

如今咱們已經安裝了相關的依賴項而且已經設置了咱們的Pusher賬戶,咱們能夠開始構建服務器。

在項目目錄的根目錄中建立一個名爲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});
    });

保存文件並從項目目錄的根目錄運行節點server.js以啓動服務器。

設置前端應用程序

應用程序的前端將寫在咱們以前引用的app.js文件中。在項目目錄的根目錄中建立此文件,並在其中粘貼如下代碼:

// 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端點接收的初始數據建立了一個基本條形圖。若是您熟悉D3的工做原理,那麼您應該熟悉這些代碼。我在代碼的關鍵部分添加了註釋,以指導您構建圖表的方式。

在新終端中,啓動開發服務器以提供index.html文件:

npx http-server

我在這裏使用http-server,但你可使用你想要的任何服務器。您甚至能夠直接在瀏覽器中打開index.html。

此時,您的圖表應以下所示:

clipboard.png

使用Pusher實時更新圖表

讓咱們確保輪詢的更新能夠經過Pusher Channels實時反映在應用程序的前端中。將如下代碼粘貼到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()方法訂閱了一個名爲poll-channel的新頻道。經過bind方法監聽輪詢更新,並在收到更新後使用最新數據調用update()函數,以便從新呈現圖形。

不要忘記使用Pusher賬戶信息中心中的相應詳細信息替換佔位符。

從服務器觸發更新

咱們將模擬每秒更新一次的輪詢,並在數據發生變化時使用Pusher觸發更新,以便輪詢的訂閱者(客戶端)能夠實時接收更新的數據。

在其餘導入下面的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端點更改成以下所示:

app.get('/poll', (req, res) => {
      res.json(poll);
      updatePoll();
    });

/ poll路由將初始輪詢數據發送到客戶端並調用updatePoll()函數,該函數以三秒爲間隔遞增隨機俱樂部的投票,並觸發咱們在最後一步中在客戶端上建立的輪詢頻道的更新。

經過從項目目錄的根目錄運行節點server.js,終止服務器並從新啓動它。此時,您應該有一個實時更新的條形圖。

clipboard.png

結論

您已經看到了使用D3.js建立條形圖的過程以及如何使用Pusher Channels實時建立條形圖。

咱們已經爲Pusher和D3提供了一個簡單的用例,但其中一個僅僅是表面上的問題。我建議深刻研究docs,瞭解更多有關Pusher及其餘功能的信息。

謝謝閱讀!您能夠在GitHub存儲庫中找到本教程的完整源代碼。

相關文章
相關標籤/搜索