使用 ReactJS、Nivo 和 InfluxDB 轻松实现数据可视化

导航至

如果一张图胜过千言万语,那么精心制作的数据可视化就胜过百万。仪表板的质量可以决定一个应用程序的成功或失败。在本教程中,您将学习如何通过使用 ReactJS 和 Nivo 绘图库轻松制作高质量的数据可视化。您还将学习如何查询存储在 InfluxDB 中的数据,以使您的图表动态且多样化。(如果您正在寻找使用 Chart.js 和 InfluxDB 可视化时间序列数据的教程,请点击链接。)

什么是 Nivo?

Nivo 是一个流行的数据可视化库,专为与 ReactJS 一起使用而构建。虽然在性能方面表现优异,但 Nivo 最大的卖点,在我看来,是开发者体验。Nivo 包含了一系列有用的实用工具和功能,可以在创建应用程序中的图表时节省您的时间,并且由于有大量示例的出色文档,因此易于学习。以下是 Nivo 最有用的功能之一

  • 27 种不同的图表类型
  • 同构和服务器端渲染
  • SVG、Canvas 和 HTML 图表支持
  • 演示数据生成实用工具函数
  • 核心包具有可选的图表导入以减少包大小
  • 带有交互示例的 Storybook
  • 预设颜色模式和模板可用

Nivo 还有一个强大的社区,在 Github 上有超过 9500 个星标,并有 150 名贡献者。这意味着如果您需要帮助,您将能够找到它,并且随着时间的推移,该项目将不断添加更多功能和功能。

什么是 InfluxDB?

InfluxDB 是一个针对处理时间序列数据进行优化的数据库。时间序列数据是一组按时间顺序索引的数据点序列。这类数据具有独特的属性,传统的关系型数据库和非关系型数据库往往难以处理。一些常见的时间序列数据示例包括

  • 物联网传感器
  • 监控指标
  • 金融数据
  • 应用程序事件
  • 分析

除了能够以大规模存储和查询时间序列数据之外,InfluxDB 还是一个用于处理时间序列数据的完整平台。该平台使自动化任务变得容易,可以根据数据设置警报,并提供了 Flux 查询语言中的预构建函数,用于常见的查询。

使用 Nivo 入门:组件和属性

Nivo 库提供了 27 种不同的图表类型,适用于几乎任何用例或数据类型。在本教程中,我们将重点介绍一些特别适合可视化时间序列数据的图表。

由于 Nivo 是为在 ReactJS 应用中制作图表而设计的,因此您的图表的所有配置和样式都是通过传递给特定图表组件的 props 来完成的。每种图表类型都有一些自己特有的 props,因为不同的图表需要不同的数据,但以下是一些您需要了解的主要 props:

  • 数据 - 数据对象是必需的,如果您没有向图表组件传递任何内容,它显然无法显示任何内容。数据结构将取决于图表类型。
  • 宽度/高度 - 这些是图表的必需 props。如果您正在使用响应式图表组件,则高度和宽度将来自父元素。
  • - 键 props 可以用来确定图表中每个序列的排列方式。

除了上述 props,还有数十个其他 props 用于控制样式、布局、刻度、坐标轴、颜色等。您还可以向图表添加交互式功能,如工具提示和事件处理器。

以下是一个示例折线图配置

import { ResponsiveLine } from "@nivo/line";
import { lineData } from "./data";

const MyResponsiveLine = ({ data /* see data tab */ }) => (
  <ResponsiveLine
    colors={{ scheme: "dark2" }}
    data={lineData}
    margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
    xScale={{ type: "point" }}
    yScale={{
      type: "linear",
      min: "auto",
      max: "auto",
      stacked: true,
      reverse: false
    }}
    axisTop={null}
    axisRight={null}
    axisBottom={{
      orient: "bottom",
      tickSize: 5,
      tickPadding: 5,
      tickRotation: 0,
      legend: "transportation",
      legendOffset: 36,
      legendPosition: "middle"
    }}
    axisLeft={{
      orient: "left",
      tickSize: 5,
      tickPadding: 5,
      tickRotation: 0,
      legend: "count",
      legendOffset: -40,
      legendPosition: "middle"
    }}
    pointSize={10}
    pointColor={{ theme: "background" }}
    pointBorderWidth={2}
    pointBorderColor="black"
    pointLabelYOffset={-12}
    useMesh={true}
    legends={[
      {
        anchor: "bottom-right",
        direction: "column",
        justify: false,
        translateX: 100,
        translateY: 0,
        itemsSpacing: 0,
        itemDirection: "left-to-right",
        itemWidth: 80,
        itemHeight: 20,
        itemOpacity: 0.75,
        symbolSize: 12,
        symbolShape: "circle",
        symbolBorderColor: "rgba(0, 0, 0, .5)",
        effects: [
          {
            on: "hover",
            style: {
              itemBackground: "rgba(0, 0, 0, .03)",
              itemOpacity: 1
            }
          }
        ]
      }
    ]}
  />
);

export default function App() {
  return (
    <div className="App">
      <MyResponsiveLine />
    </div>
  );
}

数据正在从名为 data.js 的文件中导入,该文件导出包含对象的数组

export const lineData = [
  {
    id: "japan",
    data: [
      {
        x: "plane",
        y: 102
      },
      {
        x: "helicopter",
        y: 83
      },
      {
        x: "boat",
        y: 177
      },
      {
        x: "train",
        y: 130
      },
      {
        x: "subway",
        y: 275
      },
      {
        x: "bus",
        y: 243
      },
      {
        x: "car",
        y: 80
      },
      {
        x: "moto",
        y: 60
      },
      {
        x: "bicycle",
        y: 241
      },
      {
        x: "horse",
        y: 211
      },
      {
        x: "skateboard",
        y: 189
      },
      {
        x: "others",
        y: 111
      }
    ]
  }
];

结果是以下图表

Nivo chart - transportation

您可以尝试调整上述图表 props,看看会发生什么,以更好地了解 Nivo。

设置 InfluxDB

接下来,您需要设置一个可工作的 InfluxDB 实例,以便您可以写入和查询数据。InfluxDB 是开源的,可以托管在您的计算机上或部署到服务器。根据您使用的操作系统,设置可能略有不同,因此在本教程中,我建议您创建一个 免费的 InfluxDB Cloud 账户,这大约需要 2 分钟。如果您想使用开源版本,您可以从 这里 下载它,并按照 文档 中的说明操作。

一旦您的 InfluxDB 实例运行起来,现在您需要执行以下步骤来设置使用 InfluxDB 客户端库:

  1. 创建一个组织 — 您将在创建账户时这样做。
  2. 创建一个用于存储数据的 存储桶 — 存储桶的名称将用于我们的代码中查询数据。
  3. 为您的存储桶创建一个具有读写访问权限的 API 令牌 — 此令牌将用于对客户端库进行身份验证。

安装项目需求

现在 InfluxDB 准备就绪,您需要设置应用程序的前端。本教程的唯一需求是 NodeJS 开发环境。Create React App 将用作本项目的起点,但您可以选择使用自定义的 ReactJS 设置。如果您不想在计算机上本地安装任何东西,您可以使用 ReplitCodeSandbox 在浏览器中获取 NodeJS 环境设置,这两个平台都提供 ReactJS 模板,因此您可以在几秒钟内开始运行。

如果您使用 VS Code 作为您的编辑器,您可以选择使用 Flux VS Code 扩展,该扩展提供 Flux 语法高亮、自动完成和编辑器中 InfluxDB 的直接集成。这将使测试和运行查询变得更加容易。

接下来,您需要使用以下命令安装 InfluxDB JavaScript 客户端库和 Nivo 核心包:

npm install --save @influxdata/influxdb-client @nivo/core

您还需要从InfluxDB获取API令牌,并将其存储为环境变量或常量。请注意,这是一个安全问题,不应该在生产环境中使用,因为您的API令牌将被嵌入到公共前端源代码中。

在生产环境中,您可能希望服务器端处理这些API调用,以确保您的令牌安全。另一个选项是在客户端授予API令牌只读访问权限,并设置一个较短的寿命,使其快速过期。

将数据写入InfluxDB

因此,现在InfluxDB已启动并运行,您需要存储一些数据,然后才能开始查询和可视化这些数据。InfluxDB提供多种不同的选项来写入数据,具体取决于您的用例。

Telegraf

Telegraf是由InfluxData创建的一个开源服务器代理,用于收集、处理数据并将其输出到存储,而无需任何代码。您只需创建一个配置文件,并对其进行稍微修改,以适应您特定的需求。

Telegraf通过其250个输入插件轻松地将几乎任何数据源与InfluxDB集成,这些插件涵盖了几乎所有最流行的框架、工具和服务提供商。一旦收集了数据,Telegraf提供了一些处理器插件,可用于转换或丰富数据。处理后的数据可以输出到40多种不同的数据存储中,其中InfluxDB是最受欢迎的选项。

客户端库和REST API

InfluxDB通过REST API直接提供所有功能,以给开发者提供最大的灵活性,以便如何存储和查询数据。

还提供了13种最流行编程语言的客户端库。这些客户端库作为API的包装器,为开发者提供了一组开箱即用的功能,以提高开发速度,例如请求批处理和错误处理。

在本教程中,我们将使用JavaScript客户端库查询InfluxDB,然后使用Nivo图表库可视化这些数据。

文件上传

InfluxDB UI允许您上传CSV文件或已经按照行协议组织好的文件。如果您只想快速查看一些数据点,也可以使用UI直接输入一些数据。

使用示例数据

在本教程中,我们将使用InfluxDB内直接提供的示例数据集之一。有多个时间序列数据集可供选择,但本教程我们将使用空气传感器数据,该数据提供了建筑物多个房间的温度、湿度和一氧化碳水平。

为了确保数据保持最新,我们将使用InfluxDB的任务功能创建一个任务,每15分钟更新一次数据。为此,点击侧边栏上的“任务”图标,然后点击“创建任务”。

为您的任务命名,然后在“每隔”列中输入15m ,使任务每15分钟运行一次。将以下Flux代码粘贴到编辑器中

import "influxdata/influxdb/sample"

sample.data(set: "airSensor")
  |> to(bucket: "your-bucket-name"  )

Tasks dashboard - create task

点击保存,返回到任务仪表板。点击您刚刚创建的任务的轮形图标,然后点击“运行”,以便它立即运行。

Task Dashboard - run task

现在回到数据探索器并检查您的存储桶,提交查询后,您应该能看到可见的数据。如果您看不到任何数据,请尝试刷新浏览器并确保您点击了正确的存储桶。如果您仍然看不到数据,请再次运行这些步骤并查看文档,以确保您没有错过任何内容。

从 influxDB 查询数据

您现在已有数据存储,是时候学习如何查询这些数据了。开始的最简单方法是使用 数据探索器。您可以在 UI 中直观地修改查询,并查看根据这些更改图表如何变化。

在底层,数据探索器使用 Flux,这是从 InfluxDB 获取数据的查询语言。Flux 是从头开始为处理时间序列数据而设计的,并包括许多用于常见时间序列用例的内置函数。其语法与 JavaScript 非常相似,并不难掌握。通过点击数据探索器中的脚本编辑标签,您可以看到数据探索器 UI 实际使用的 Flux 查询。这种迭代可能是学习 Flux 的最快方式。有关 Flux 的完整介绍和概述,请查看 文档

一般来说,Flux 查询将包含 4 个关键部分

  1. 源:您正在从中提取数据的特定存储桶。
  2. 过滤器:您很少需要存储桶中的所有数据,因此您将使用过滤器功能从特定时间框架中选择数据,也许只选择几个字段值。
  3. 形状:在这个阶段,您可能想要修改数据的格式以进行进一步处理。这可能包括将列转换为行,删除列或合并键。
  4. 处理:最终阶段是数据处理。这包括一些示例,例如聚合行,选择特定数据点(如最小值或最大值),使用 map 函数重写每一行的值,或分析数据并将警报发送到外部 API 端点。

以下是该结构的示例

from(bucket: "example-bucket")            // ?? Source
  |> range(start: -1d)                    // ?? Filter on time
  |> filter(fn: (r) => r._field == "foo") // ?? Filter on column values
  |> group(columns: ["sensorID"])         // ?? Shape
  |> mean()                               // ?? Process

使用 Nivo 可视化数据

现在是时候将所有这些放在一起,使用客户端库查询 InfluxDB,然后使用 Nivo 显示这些数据了。首先,您需要创建图表组件,然后将其导入到 App.js 文件中。

以下是我们的图表组件的代码。下面我将说明正在进行的事情

import React, { useState, useEffect } from "react";
import { InfluxDB } from "@influxdata/influxdb-client";
import { ResponsiveLine } from "@nivo/line";

const token =  "YOUR-TOKEN";
const org = "org-name";
const bucket = "bucket-name";
const url = "INFLUX-DB-URL";

let query = `from(bucket: "air-sensor")
  |> range(start: -1h)
  |> filter(fn: (r) => r["_measurement"] == "airSensors")
  |> filter(fn: (r) => r["_field"] == "co")
  |> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
  |> yield(name: "mean")`;

export const InfluxChart = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    let res = [];
    const influxQuery = async () => {
      //create InfluxDB client
      const queryApi = await new InfluxDB({ url, token }).getQueryApi(org);
      //make query
      await queryApi.queryRows(query, {
        next(row, tableMeta) {

          const o = tableMeta.toObject(row);
         //push rows from query into an array object
          res.push(o);
        },
        complete() {

          let finalData = []

          //variable is used to track if the current ID already has a key
          var exists = false

          //nested for loops aren't ideal, this could be optimized but gets the job done
          for(let i = 0; i < res.length; i++) {
            for(let j =0; j < finalData.length; j++) {
              //check if the sensor ID is already in the array, if true we want to add the current data point to the array
              if(res[i]['sensor_id'] === finalData[j]['id']) {
                exists = true

                let point = {}
                point["x"] = res[i]["_time"];
                point["y"] = res[i]["_value"];
                finalData[j]["data"].push(point)
              }

            }
             //if the ID does not exist, create the key and append first data point to array
              if(!exists) {
                let d = {}
              d["id"] = res[i]["sensor_id"];
              d['data'] = []
              let point = {}
              point["x"] = res[i]["_time"];
              point["y"] = res[i]["_value"];
              d['data'].push(point)
              finalData.push(d)
              }
              //need to set this back to false
              exists = false

          }

          setData(finalData);

        },
        error(error) {
          console.log("query failed- ", error);
        }
      });

    };

    influxQuery();
  }, []);

  return (
  <ResponsiveLine 
  data={data}
   />
  )
};

以下是此文件中的内容

  • 顶部是我们从 ReactJS 中需要导入的导入,用于使用 useEffect 钩子从 InfluxDB 获取数据并将其存储在状态中。我们还导入了 InfluxDB 客户端和 Nivo 的响应式折线图组件。接下来是连接到 InfluxDB 所需的常数。您可能希望将这些存储为环境变量。
  • 下面是我们将使用的 Flux 查询。该查询获取最新的一个小时的值得数据,获取 airSensor 测量,过滤数据以仅获取一氧化碳数据,然后获取该数据的 5 分钟平均值。
  • 图表组件相当复杂,所以我添加了内联注释,如果您想了解每个步骤发生了什么。简而言之,当组件在页面上渲染并查询InfluxDB时,useEffect钩子会运行。在complete 函数中,数据被转换以适应Nivo图表数据格式。使用setData钩子将转换后的数据存储到我们的React状态中,并将其传递给Nivo折线图组件。如果您对此感到困惑,可以在代码的各个位置添加一些console.log 语句以查看数据如何在组件中移动。

与App.js文件相比,这很简单。我们只需导入我们制作的图表组件。这里的关键是要确保父元素设置了高度和宽度。Nivo响应式组件从父元素继承。如果没有设置高度,它将不会渲染。

import "./styles.css";
import { InfluxChart } from "./Influx";

export default function App() {
  return (
    <div className="App">
      <InfluxChart />
    </div>
  );
}

这就是最终的结果

Nivo chart tutorial

请随意尝试调整图表轴、颜色和其他样式选项。

Nivo提供的其他一些适用于时间序列数据的图表类型包括热图日历地图图表。

向您的项目添加功能

现在您已经有一个相当基础的图表,该图表从InfluxDB中获取数据,接下来是什么?您可以通过利用InfluxDB提供的一些功能来扩展此演示或对其他数据集进行操作。

例如,您可以通过设置一个钩子来每隔几分钟查询InfluxDB一次,使您的Nivo图表实时更新。Nivo图表是动画的,并且当数据prop更新时将渲染新数据。您还可以使用空气传感器数据集的SQL配套数据来丰富您正在获取的数据。Flux提供查询外部数据源的能力,并将其与存储在InfluxDB中的数据相结合。在这种情况下,您可以通过查询关系型数据库来获取有关每条数据的更多信息。这些数据将与您的Flux查询一起返回,并可以添加到您的Nivo图表中。

对于特定的空气传感器数据集,您可以根据数据创建一个触发动作的警报。例如,如果一氧化碳水平达到某个阈值,您可以使用webhook警报并通过发送API请求到类似Twilio的东西来发送短信警报。如果您有温度传感器,您可以根据需要动态调整家庭中的智能恒温器以节省金钱。

关键是,一旦数据在InfluxDB中,您现在就可以对数据进行操作。您可以自动化任务、监视事物和可视化数据。

其他资源

希望通过本教程,您已经对Nivo和InfluxDB有了扎实的理解。该项目是构建在可扩展性的基础上,您可以构建一些酷炫的东西来展示。同时,这只是一个起点,以下是一些您可以查看以了解更多信息的附加资源

在完成本教程后,“清理”也是一个好主意。如果您不会定期检查您的账户,请确保您删除或停用API密钥。取消运行空气传感器数据任务也是一个好主意,这样您就不会下载和存储不需要的数据。