七、可视化数据

能够可视化数据使其更容易理解。它允许您看到原本可能隐藏的模式,并讲述有关您的数据的故事。您可以用纯 JavaScript 创建数据可视化,但是有许多 JavaScript 库可以使这变得更容易。您将在本书中使用的库是 D3.js。在本章中,您将使用 D3.js 创建一个条形图,其数据收集方式与第 6 章相同。在本章的最后,您将看到一个条形图,其中包含来自一个电位计的数据。

D3.js 简介

D3 代表数据驱动文档;这是一个数据可视化 JavaScript 库。它允许您将数据绑定到文档对象模型(DOM ),以便用 DOM 元素在网页上显示可视化效果。这些可以是任何 DOM 元素:例如,text 或 div。在本书的例子中,SVG DOM 元素将用于显示数据。

D3.js 有许多函数可以构建不同类型的数据可视化。这些包括条形图,折线图,choropleth 图,气泡图,等等。它通过数学运算将数据转化为可视化数据。

它允许您使用许多不同的数据文件类型,包括 JSON、GeoJSON 和 CSV。

您可以使用其他库在 JavaScript 中创建数据可视化;我在附录 b 中列出了其中的一些。

D3.js 如何工作

使用 D3.js,首先选择一个 HTML 元素来保存整个可视化,然后决定要使用哪种类型的 DOM 元素来保存每个数据:例如,text、div 或 SVG。接下来,将数据绑定到这些元素,然后添加显示数据的 SVG 或 DOM 元素。然后,您可以对形状进行定位、缩放和着色。

D3.js 会帮你计算。如果你有一个 2、5 和 9 的数据集,你想做三个条形图,你知道这是你最小的数字,9 是最大的。你可以创建一个从 0 到 9 的图表。但是如果数据被更新,从-4 变成 22,或者引入第四个数字,会发生什么呢?轴线会是错的。当处理实时数据时,你需要一个可以随数据伸缩的轴,D3.js 有这样的功能。

数据值是输入域,经过缩放后成为输出范围。输入域的最低和最高输入数字被映射到输出范围的最低和最高数字。图 7-1 显示输入域,0 到 55 的输入数据,0 到 500 的输出范围。

A453258_1_En_7_Fig1_HTML.jpg

图 7-1

The input domain and output range

D3.js 对数据使用一般的更新模式:进入、更新和退出。Enter 用于新数据,它添加一个 DOM 元素,当数据改变时更新,不添加或删除任何 DOM 元素,当 DOM 元素数量减少时退出。当数据更新时,如果 DOM 元素的数量需要增加,或者如果 DOM 元素的数量需要减少,使用这种模式允许 DOM 元素的动画转换。该模式的功能如图 7-2 所示。

A453258_1_En_7_Fig2_HTML.png

图 7-2

The functions for the general update pattern

如果存在没有 DOM 元素的数据,则使用函数 enter()。当有一个不再有数据的 DOM 元素时,使用 exit()。当 DOM 元素的数量与数据的数量相同,但数据发生了变化时,使用 update()函数。图 7-3 显示了这些功能是如何工作的。

A453258_1_En_7_Fig3_HTML.jpg

图 7-3

How elements are added, updated, and removed from the DOM with D3.js Try D3.JS

理解 D3.js 最好的方法就是开始使用它。在将 D3.js 与 Arduino 数据一起使用之前,最好先通过构建一个条形图来了解一下基础知识。清单 7-1 中的代码将使用一些随机数据制作一个条形图。该数据将定义条的高度、轴的标签和轴的刻度。id 为“viz”的 div 将包含可视化效果。打开文本编辑器,复制清单 7-1 中的代码。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>D3.js</title>
    <script type="text/javascript" src="https://d3js.org/d3.v4.js"></script>

    <style type="text/css">
        body{
            font-family: arial;
        }
        h1{
            font-size: 22px;

           margin: 0px;
        }
        h2{
            font-size: 16px;
            margin: 0px;
            margin-top: 2px;
        }
        .axis text {
            font-family: arial;
            font-size: 12px;
            font-weight: normal;
            color: pink;
        }
        .axis path,
        .axis line {
            fill: none;
            stroke: #000;
            shape-rendering: crispEdges;
        }
        .bar {
            fill: #fd8f00;
       }
       h1, h2, p{
          margin-left: 40px;
      }
      p{
         font-size: 12px;
      }
  </style>
</head>
<body>
  <h1>A D3 bar chart</h1>
  <div id = "viz"></div>

  <script type="text/javascript">
      var margin = {top: 20, right: 20, bottom: 40, left: 40};
      var width = 480 - margin.left - margin.right;
      var height = 500 - margin.top - margin.bottom;

      var data = [
        {"amount": 5, "name": "column1"},

        {"amount": 11, "name": "column2"},
        {"amount": 55, "name": "column3"},
        {"amount": 23, "name": "column4"}
      ]

        var x = d3.scaleBand()
        .domain(data.map(function(d) { return d.name; }))
        .range([0, width], .1)
        .padding(0.1);

        var y = d3.scaleLinear()

        .domain([0, d3.max(data, function(d) { return +d.amount; })])
        .range([height, 0]);

        var dataviz = d3.select("#viz").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 + ")");

        dataviz.selectAll(".bar")
          .data(data)
        .enter()
          .append("rect")
          .attr("class", "bar")
          .attr("x", function(d) { return x(d.name); })
          .attr("width", x.bandwidth())
          .attr("y", function(d) { return y(+d.amount); })
          .attr("height", function(d) { return height - y(+d.amount); });

          dataviz.append("g")

          .attr("transform", "translate(0," + height + ")")
          .call(d3.axisBottom(x));
          dataviz.append("g")
          .call(d3.axisLeft(y));
     </script>
</body>
</html>

Listing 7-1bar_chart.html

打开浏览器,打开 bar_chart.html 文件;您将看到数据可视化。

代码解释

表 7-1

bar_chart.html explained

| <script type="text/javascript" src=" https://d3js.org/d3.v4.js "> | 您可以从 URL 下载 D3.js 或将其包含在您的页面中。 | | <style type="text/css"></style> | 由于 D3.js 将数据附加到 DOM 元素上,所以这些元素可以用 CSS 进行样式化。通常你会创建一个单独的 CSS 文件,但是在这个例子中 CSS 是在 HTML 页面上。 | | var margin = {top: 20, right: 20, bottom: 40, left: 40}; | 如果可视化与 SVG 视口的大小相同,就没有空间来读取轴。变量 margin 保存一个对象,该对象具有您想要在可视化周围留下的空间量(以像素为单位)。 | | var width = 480 - margin.left - margin.right; var height = 500 - margin.top - margin.bottom; | width 和 height 变量保存 SVG 画布的宽度和高度——边距。 | | var data = [``{"amount": 5, "name": "column1"},``...T3】 | 变量数据包含一个对象数组;在这种情况下,每个对象中有两个键/值对。 | | var x = d3.scaleBand()``.domain(data.map(function(d) { return d.name; }))``.range([0, width])T3】 | 变量 x 保存 x 轴的比例计算。D3.js 函数 scaleBand()用于非数字数据,如标签或序号数据。输入域是名称数据;它需要一个参数,数据。它遍历数据并计算出有多少个值。 | | var y = d3.scaleLinear()``.domain([0, d3.max(data, function(d) { return +d.amount; })])T2】 | y 变量保存 y 轴的刻度。这次使用 scaleLinear()函数,因为数据是数字。输入域是数据集中从 0 到最大数的数组。d3.max()函数在数据集中寻找最大的数字。 | | var dataviz = d3.select("#viz").append("svg")``.attr("width", width + margin.left + margin.right)``.attr("height", height + margin.top + margin.bottom)``.append("g")T4】 | d3.select()函数允许您选择一个 DOM 元素来附加可视化。append()函数向元素添加一个 SVG。接下来的两个 attr()函数设置元素的宽度和高度。添加从宽度和高度中移除的边距。append(“g”)将“g”元素添加到可视化中。“g”元素不是 D3.js 特有的;它是一个容器元素,允许您将图形元素组合在一起。 | | dataviz.selectAll(".bar") | selectAll()函数选择所有的条形对象,即使还没有任何对象,它为条形创建一个占位符。 | | .data(data) | data()函数将数据附加到可视化中。 | | .enter() .append("rect") | enter()函数是更新模式的一部分,append 添加了一个 SVG。 | | .attr("class", "bar") | 一个类被添加到每个酒吧,所以他们可以被设计,它也允许你再次选择他们。 | | .attr("x", function(d) { return x(d.name); }) | 这将设置每个条形的 x 轴位置。 | | .attr("width", x.bandwidth()) | 宽度属性设置每个条形的宽度。它是使用前面设置的 x 刻度计算出来的。它知道有多少数据项和可视化的宽度。 | | .attr("y", function(d) { return y(+d.amount); }) | 这将设置矩形顶部的位置。 | | +d.amount | 您可以在值前使用+将数量数据转换为数字。有时你认为数据是一个数字,但它实际上是一个字符串。 | | .attr("height", function(d) { return height - y(+d.amount); }); | 这设置了酒吧的高度。SVG 的坐标从左上角的 0 0 开始,任何高度都是从上到下。在此图中,您希望条形从轴的底部向上增长,高度–y(+d . amount)解决了这个问题。 | | dataviz.append("g")``.attr("transform", "translate(0," + height + ")")T2】 | 这将向包含 x 轴的 SVG 追加一个新组。D3.js 有一个 axisBottom()函数,它在 SVG 的底部创建一个水平轴。 | | dataviz.append("g") .call(d3.axisLeft(y)); | 这将向 SVG 追加一个包含 y 轴的新组。D3.js 有一个函数 axisLeft(),它在 SVG 的左边创建一个垂直轴。 |

7-1 解释了 bar_chart.html 中的代码。

方法链接

您可能已经注意到,在 D3.js 中,有一些函数是用“.”一个接一个地调用的他们之间。这被称为方法链接,在 JavaScript 和 JavaScript 库中使用。代码“dataviz . append(“g”)。attr("transform "," translate(0,"+ height +")。call(D3 . axis bottom(x));"是一个接一个调用的三个函数,append()、attr()和 call()。它使代码更容易阅读,并创建自然组合在一起的函数调用块。

用 D3.js 可视化来自 Arduino 的数据

本章将使用与第 6 章相同的 Arduino 设置和相同的基础 JavaScript 代码。JavaScript 将被更新以包含新的可视化。图 7-4 显示了这一章的最终结果,一个条形图显示了第一个问题“在今晚的活动中,你和新认识的人说话了吗?”

A453258_1_En_7_Fig4_HTML.jpg

图 7-4

The web application with a bar chart Set Up the Arduino

Arduino 的设置与第 6 章中的设置完全相同。Arduino 的设置如图 6-3 所示。一旦组件连接完毕,用清单 6-1 中的代码刷新 Arduino。您想要使用原始代码,而不是在第 6 章中添加的更新。

Set Up the Node.js Server

该代码也将基于第 6 章中的原始代码。应用程序的目录结构将是:

/chapter_07
    /node_modules
    /public
        /css
            main.css
        /javascipt
            main.js
    /views
        index.ejs
    index.js

创建 skeleton Node.js 应用程序与前几章相同:

  1. 创建一个新文件夹来存放应用程序。我把我的叫做 chapter_07。
  2. 打开命令提示符(Windows 操作系统)或终端窗口(Mac)并导航到新创建的文件夹。
  3. 在正确的目录中,键入 npm init 创建一个新的应用程序;您可以按下 return 键浏览每个问题,或者对它们进行更改。
  4. 您现在可以开始添加必要的库;要在命令行下载 Express.js,请键入 npm install express@4.15.3 - save。
  5. 然后安装 ejs,键入 npm install ejs@2.5.6 save。
  6. 下载完成后,安装串口。在 Mac 上键入 NPM install serial port @ 4 . 0 . 7–save,在 Windows PC 上键入 NPM install serial port @ 4 . 0 . 7-build-from-source。
  7. 然后最后安装 socket.io,输入 npm install socket.io@1.7.3 - save。

在 index.js 中,文件复制清单 6-2 中的代码。你想使用代码的原始清单,而不是在第 6 章中更新的版本。将清单 6-3 中的代码复制到 index.ejs 文件,然后将清单 6-4 中的 CSS 复制到 main.css 文件,最后将清单 6-5 中的代码复制到 main.js 文件。

确保在新的 serial port()函数中将更新为您自己的串口。

现在,您应该已经在 chapter_7 应用程序中复制了上一章的基本设置。您可以通过在控制台窗口中导航到 chapter_07 应用程序并使用 nodemon index.js 或 node index.js 启动该应用程序来测试您是否拥有它。只要连接了 Arduino,您就应该能够与电位计和按钮进行交互,并在打开浏览器并转到 http://localhost:3000 时看到结果。

Update the Application

现在您可以升级 main.js 了。打开文件,添加清单 7-2 中粗体显示的代码。

(function(){
    var socket = io();

    var accumulatorArrayA0 = [0,0,0,0,0,0,0,0,0,0,0];
        var accumulatorArrayA1 = [0,0,0,0,0,0,0,0,0,0,0];

    var margin = {top: 20, right: 20, bottom: 40, left: 40};
    var width = 480 - margin.left - margin.right;
    var height = 500 - margin.top - margin.bottom;

    var x = d3.scaleBand()
    .range([0, width], .1)
    .padding(0.1);

    var y = d3.scaleLinear()
    .range([height, 0]);

    var bars = d3.select("#bar-chart").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 + ")");

    bars.selectAll(".bar")
      .data(accumulatorArrayA0)
    .enter()
      .append("rect")
      .attr("class", "bar")
      .attr("x", function(d, i) { return x(i); })
      .attr("width", x.bandwidth())
      .attr("y", function(d) {return y(d); })
      .attr("height", function(d) {  return height - y(+d); });

      bars.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x));

    bars.append("g")
      .attr("class", "y axis")
      .call(d3.axisLeft(y)
        .ticks(0));

      bars.append("text")            
      .attr("transform",
            "translate(" + (width/2) + " ," +
                           (height + margin.top + 20) + ")")
      .style("text-anchor", "middle")
      .text("score");

    bars.append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 0 - margin.left)
      .attr("x",0 - (height / 2))
      .attr("dy", "1em")
      .style("text-anchor", "middle")
      .text("frequency");  

    socket.on("bar-data", function(data){

        var current = data.dataKey;
        var svgBar = document.getElementById(current);

        var newWidth = data.dataString * 40;

        svgBar.setAttribute("width", newWidth);

        currentInputValue(data);
        addRemoveClass("add");
    });

    socket.on("button-data", function(data){
        accumulatorArrayA0[data[0]] = accumulatorArrayA0[data[0]] + 1;
        accumulatorArrayA1[data[1]] = accumulatorArrayA1[data[1]] + 1;

        updateBar(accumulatorArrayA0);
        addRemoveClass("remove");
    });

    function updateBar(data){

        x.domain(d3.range(data.length));
        y.domain([0, d3.max(data)]);

        var rect = bars.selectAll(".bar")
            .data(data);

        rect.enter().append("rect");

          rect.attr("class", "bar")
          .transition()
          .duration(1000)
          .attr("x", function(d, i) { return x(i); })
          .attr("width", x.bandwidth())
          .attr("y", function(d) {return y(d); })
          .attr("height", function(d) {  return height - y(+d); });

      bars.select(".x.axis")
                .transition()
                .duration(1000)
            .call(d3.axisBottom(x));

            bars.select(".y.axis")
                .transition()
                .duration(1000)

            .call(d3.axisLeft(y)
        .ticks(d3.max(data))
        .tickFormat(d3.format("d")));

    }

    function addRemoveClass(action){
        var buttonResponse = document.getElementById("bar-A0").getElementsByClassName("text-block-response")[0];

        buttonResponse.classList[action]("hidden");

        buttonResponse = document.getElementById("bar-A1").getElementsByClassName("text-block-response")[0];

        buttonResponse.classList[action]("hidden");
    }

    function currentInputValue(data){
        var targetP = document.getElementById("bar-" + data.dataKey).getElementsByClassName("text-block")[0].getElementsByTagName("p")[0];

        targetP.innerHTML = data.dataString;

    }
})();

Listing 7-2main.js

代码解释

应用程序中使用的 D3.js 类似于清单 7-1 中的例子,但是有一些新的功能。表 7-2 详细介绍了 main.js 中的代码

表 7-2

main.js explained

| .ticks(0)); | 这个函数在 y 轴上创建刻度,因为在创建图形时没有任何数据,并且它被设置为 0;这个会在有数据的时候更新。 | | bars.append("text") .attr("transform", "translate(" + (width/2) + " ," + (height + margin.top + 20) + ")") .style("text-anchor", "middle") | 这些函数为 x 轴添加并对齐标签,对于 y 轴也有一组类似的函数。 | | updateBar(data); | 当按下按钮时,需要用新数据更新条形图。更新的代码在一个名为 updateBar()的函数中,传递给它的是数据数组。 | | x.domain(d3.range(data.length)); y.domain([0, d3.max(data)]); | 以前,x 和 y 的定义域和值域是同时声明的。现在,随着新数据添加到阵列中,域将不断变化。这意味着 x 和 y 的定义域也需要改变。在这种情况下,x 的数据长度不会改变,但数组中的最大值会改变。这用于改变条形的高度,因此 y.domain 从 0 到数组中的最大值。这使用了 d3.max()函数,它可以将一个数组作为它的数据,并计算出数组中的最大值。 | | .transition() .duration(1000) | 这些函数使条形的新值从旧值变为动画,持续时间以毫秒为单位。 | | .ticks(d3.max(data)) | 您需要 y 轴上的刻度,但是最大数量需要随着数组中最大数量的增加而改变。使用 d3.max()函数意味着刻度将始终与数组中的最大数量相同。 | | .tickFormat(d3.format("d"))); | 有许多方法可以格式化您的分笔成交点;使用“d”使它们成为整数。 |

您需要将可视化的创建与更新分开。如果不这样做,每次添加新数据时,都会创建新的 SVG。通过分离创建和更新,您可以用新数据更新同一个 SVG。

Update the Front End

更新前端不需要做太多。打开 index.ejs 文件,添加清单 7-3 中的粗体代码。这是第六章的代码,有小的更新,所以我没有完整地写旧代码。

<!DOCTYPE html>
<html>
<head>
    ...
    <link href="/css/main.css" rel="stylesheet" type="text/css">
    <script type="text/javascript" src="https://d3js.org/d3.v4.js"></script>

</head>
<body>
    <header>
        <h1>EVENT METRICS</h1>
        <h2>getting information through an Arduino</h2>

    </header>
    <div id="content">
        <h2>AT TONIGHTS EVENT DID YOU ...</H2>
        <p>Answer the questions by turning the knobs, to submit your answer press the button.</p>

...
            <div class="text-block-response hidden">
                <h3>Thanks<h3>
                <p></p>
            </div>
        </div>

        <div id="bar-chart">
            <h2>Talk to someone new?</h2>
            <p>did people meet new people tonight?</p>

        </div>

    </div>
    <script src="/socket.io/socket.io.js"></script>
    <script src="javascript/main.js"></script>

</body>
</html>

Listing 7-3Index.ejs

更新将包括 D3.js 库,并添加一个 div 来保存可视化。

Update the CSS

打开 main.css 文件;您应该已经从第 6 章复制了 CSS,所以只需要添加以下代码:

.bar {
  fill: #6BCAE2;
}

这为图表中的条形添加了颜色。

在您的浏览器上,如果您刷新页面,您应该能够看到条形上的颜色。

整理代码

您可能已经注意到,添加 D3.js 代码已经创建了许多全局变量。出于多种原因,使用全局变量并不是一个好主意,这些原因包括:

  1. 它们位于全局名称空间中。很容易忘记你称之为变量的东西,并创建多个同名的变量。这会给你带来意想不到的结果。任何函数也可以使用该变量。
  2. 如果您引入其他库,它们可能与您的全局变量同名。
  3. 很难看出什么变量属于什么函数。

创建可视化效果的代码与页面其余部分使用的代码非常不同,因此它很适合在单独的 JavaScript 文件中拥有自己的空间。您确实需要将数据从 main.js 传递到可视化,这可以通过多种方式完成。在这一章中,你将使用揭示模块模式来做这件事。

显示模块模式

JavaScript 中有许多编程模式。启示模块模式就是其中之一。它使用一个变量来保存一个立即调用的函数表达式。函数在加载时被调用。在这个函数中,您可以创建变量和函数,它们被封装在 main 函数中。您可以允许在模块外部访问这些函数和变量。在函数结束时返回它们可以做到这一点。任何未返回的函数或变量都不能在模块外部调用。

分离数据可视化

在 chapter_07 应用程序中,在 public/javascript 文件夹中创建一个名为 BarChart.js 的新文件。这意味着 chapter_07 应用程序的目录结构如下所示:

/chapter_07
    /node_modules
    /public
        /css
            main.css
        /javascipt
            main.js
            BarChart.js
    /views
        index.ejs
    index.js

Use the Revealing Module Pattern

打开 BarChart.js 并复制清单 7-4 中的代码。

var BarChart = (function(){

    var margin = {top: 20, right: 20, bottom: 40, left: 40};
    var width = 480 - margin.left - margin.right;
    var height = 500 - margin.top - margin.bottom;
    var x;
    var y;
    var bars;

    function setup(data){
        x = d3.scaleBand()
        .range([0, width], .1)
        .padding(0.1);

        y = d3.scaleLinear()
        .range([height, 0]);

        bars = d3.select("#bar-chart").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 + ")");

        bars.selectAll(".bar")
          .data(data)
        .enter()
          .append("rect")
          .attr("class", "bar")
          .attr("x", function(d, i) { return x(i); })
          .attr("width", x.bandwidth())
          .attr("y", function(d) {return y(d); })
          .attr("height", function(d) {  return height - y(+d); });

          bars.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")")
          .call(d3.axisBottom(x));
          bars.append("g")
          .attr("class", "y axis")
          .call(d3.axisLeft(y)
            .ticks(0));

          bars.append("text")          
          .attr("transform",
                "translate(" + (width/2) + " ," +
                               (height + margin.top + 20) + ")")
          .style("text-anchor", "middle")
          .text("score");

        bars.append("text")
          .attr("transform", "rotate(-90)")
          .attr("y", 0 - margin.left)
          .attr("x",0 - (height / 2))
          .attr("dy", "1em")
          .style("text-anchor", "middle")
          .text("freqency");  
    }

function updateBar(data){
        x.domain(d3.range(data.length));
        y.domain([0, d3.max(data)]);

        var test = d3.max(data);

        var rect = bars.selectAll(".bar")
            .data(data);

        rect.enter().append("rect");

          rect.attr("class", "bar")
          .transition()
          .duration(1000)
          .attr("x", function(d, i) { return x(i); })
          .attr("width", x.bandwidth())
          .attr("y", function(d) {return y(d); })
          .attr("height", function(d) {  return height - y(+d); });

        bars.select(".x.axis")
            .transition()
            .duration(1000)
            .call(d3.axisBottom(x));

        bars.select(".y.axis")

            .transition()
            .duration(1000)
            .call(d3.axisLeft(y)
                .ticks(test)
                .tickFormat(d3.format("d")));
    }

    return{
        setup: setup,
        updateBar: updateBar
    }
})();

Listing 7-4BarChart.js

代码解释

应用程序中使用的 D3.js 类似于清单 7-1 中的例子,但是有一些新的功能。表 7-3 解释了 BarChart.js 中的代码

表 7-3

BarChart.js explained

| var BarChart = (function(){ })(); | 创建一个匿名函数来保存用于创建可视化的变量和函数。它保存在一个名为条形图的变量中。它在加载时调用自己。 | | var margin = {top: 20, right: 20, bottom: 40, left: 40}; var width = 480 - margin.left - margin.right; var height = 500 - margin.top - margin.bottom; var x; var y; | BarChart 内有不同函数会用到的变量,所以在 BarChart.js 内全局添加,只能在变量 BarChart 内保存的函数范围内看到。 | | function setup(data){} | setup 函数包含 main.js 中全局可视化的所有设置代码,并将其放在自己的函数中。 | | function updateBar(data){} | updateBar()函数具有 updateBar()函数中 main.js 中可视化的所有更新代码。 | | return{``setup: setup,``updateBar: updateBarT3】 | 您决定哪些函数和变量可以在函数外部看到。为此,您需要返回函数。“:”之前的名称是其他函数调用该函数的方式,“:”之后的名称是当前函数中的命名函数。您可以返回多个函数和变量,它们之间用“,”分隔。 |

Update Main.js

main.js 文件必须更新;首先,需要删除与可视化连接的所有代码,然后需要添加对 setup()和 updateBar()函数的调用。从清单 7-2 中打开 main.js,并用清单 7-5 中的代码更新它。

(function(){
    var socket = io();

    var accumulatorArrayA0 = [0,0,0,0,0,0,0,0,0,0,0];
    var accumulatorArrayA1 = [0,0,0,0,0,0,0,0,0,0,0];

    BarChart.setup(accumulatorArrayA0);

    socket.on("bar-data", function(data){
        var current = data.dataKey;
        var svgBar = document.getElementById(current);

        var newWidth = data.dataString * 40;

        svgBar.setAttribute("width", newWidth);

        currentInputValue(data);
        addRemoveClass("add");
    });

    socket.on("button-data", function(data){

        accumulatorArrayA0[data[0]] = accumulatorArrayA0[data[0]] + 1;
        accumulatorArrayA1[data[1]] = accumulatorArrayA1[data[1]] + 1;

        addRemoveClass("remove");

        BarChart.updateBar(accumulatorArrayA0);
    });

    function addRemoveClass(action){
        var buttonResponse = document.getElementById("bar-A0").getElementsByClassName("text-block-response")[0];

        buttonResponse.classList[action]("hidden");

        buttonResponse = document.getElementById("bar-A1").getElementsByClassName("text-block-response")[0];

        buttonResponse.classList[action]("hidden");
    }

    function currentInputValue(data){
        var targetP = document.getElementById("bar-" + data.dataKey).getElementsByClassName("text-block")[0].getElementsByTagName("p")[0];

        targetP.innerHTML = data.dataString;
    }
})();

Listing 7-5Updated main.js code

您会注意到,在创建可视化代码的地方,有两个对新函数的调用。调用该函数的格式如图 7-5 所示。

A453258_1_En_7_Fig5_HTML.jpg

图 7-5

Calling a function within a revealing module pattern Update Index.ejs

最后,需要更新 index.ejs 文件以包含新的 JavaScript 文件。由于 main.js 使用的是 BarChart.js,所以需要在 main.js 之前调用 BarChart.js,打开清单 7-3 中的 index.ejs,加入清单 7-6 中粗体显示的代码。

<!DOCTYPE html>
<html>
...
        <div id="bar-chart">
            <h2>Talk to someone new?</h2>
            <p>did people meet new people tonigt?</p>

        </div>

    </div>
    <script src="/socket.io/socket.io.js"></script>
    <script src="javascript/BarChart.js"></script>
    <script src="javascript/main.js"></script>

</body>
</html>

Listing 7-6Adding BarChart.js to index.ejs

如果您正在运行 localhost,请刷新浏览器或重新启动服务器。该页面应该以完全相同的方式工作,但现在代码更加模块化,这使得它更安全、更易于阅读。

您可以添加第二个条形图来显示第二个电位计的数据。

摘要

在本章中,你开始使用 D3.js 来可视化来自 Arduino 的数据。您还尝试了一些新的 JavaScript 概念,现在应该对 JavaScript 的结构有了更好的理解。在下一章中,您将创建一个仪表板,并使用 D3.js 创建圆环图。