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

导航至

如果说一图胜千言,那么一个出色的数据可视化就价值百万。仪表板的质量可以成就或毁掉一个应用程序。在本教程中,您将学习如何通过使用 Nivo 图表库和 ReactJS 轻松制作高质量的数据可视化。您还将学习如何查询存储在 InfluxDB 中的数据,使您的图表动态且用途广泛。(如果您正在寻找关于使用 Chart.js 和 InfluxDB 可视化时序数据的教程,请点击链接。)

什么是 Nivo?

Nivo 是一个流行的用于数据可视化的库,它从头开始构建,用于 ReactJS。虽然它在性能方面表现出色,但在我看来,Nivo 最大的卖点是开发者体验。Nivo 包含大量有用的实用程序和功能,可以在您的应用程序中创建图表时节省您的时间,并且由于文档完善且示例丰富,因此易于学习。以下是 Nivo 一些最有用的功能:

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

Nivo 还拥有强大的社区,在 Github 上拥有超过 9500 个星标和 150 位项目贡献者。这意味着如果您需要帮助,您可以找到帮助,并且随着时间的推移,更多功能和特性将继续添加到项目中。

什么是 InfluxDB?

InfluxDB 是一个针对时序数据优化的数据库。时序数据是按时间顺序索引的数据点序列。这种类型的数据具有传统的关系型和 noSQL 数据库难以处理的独特属性。以下是一些常见的时序数据示例:

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

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

Nivo 入门:组件和属性

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

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

  • Data(数据) - 数据对象是必需的,如果您不向图表组件传递任何内容,显然它将无法显示任何内容。数据的结构将取决于图表类型。
  • Width/Height(宽度/高度) - 这些是图表必需的属性。如果您使用的是响应式图表组件,则高度和宽度将取自父元素。
  • Keys(键) - 键属性可用于确定图表中每个系列是如何组织的。

除了上面介绍的属性之外,还有数十个其他属性可以控制样式、布局、比例、轴、颜色等。您还可以向图表添加交互式功能,例如工具提示和事件处理程序。

这是一个折线图配置示例

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

您可以尝试使用上面的图表属性,看看会发生什么,以便更好地了解 Nivo。

设置 InfluxDB

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

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

  1. 创建一个组织 — 您将在创建帐户时执行此操作。
  2. 创建一个存储桶 (bucket) 以存储数据 — 存储桶的名称将在我们的代码中用于查询数据。
  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 已经启动并运行,您需要存储一些数据,然后才能开始查询数据并使用 Nivo 可视化数据。InfluxDB 提供了几种不同的选项来写入数据,具体取决于您的用例。

Telegraf

Telegraf 是 InfluxData 创建的开源服务器代理,用于收集、处理和输出数据到存储,而无需任何代码。相反,您只需要创建一个配置文件并稍微修改它以适应您的特定情况。

Telegraf 使集成几乎任何数据源变得容易,它拥有 250 个输入插件,几乎涵盖了所有最流行的框架、工具和服务提供商。收集数据后,Telegraf 提供了许多处理器插件,可用于转换或丰富数据。处理完成后,数据可以输出到 40 多个不同的数据存储,其中 InfluxDB 是最受欢迎的选择。

客户端库和 REST API

InfluxDB 通过 REST API 直接访问所有功能,为开发人员提供最大的灵活性,让他们可以控制如何存储和查询数据。

客户端库也适用于 13 种最流行的编程语言。这些客户端库充当 API 的包装器,并为开发人员提供了许多开箱即用的功能,以提高开发速度,例如请求批处理和错误处理。

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

文件上传

InfluxDB UI 允许您上传 CSV 文件或已按 行协议 组织的文件。如果您只想快速查看几个数据点,您还可以选择直接在 UI 中键入一些数据。

使用示例数据

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

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

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

import "influxdata/influxdb/sample"

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

Tasks dashboard - create task

单击“save(保存)”并返回到任务仪表板。单击您刚刚创建的任务的齿轮图标,然后单击“run(运行)”,以便立即运行它。

Task Dashboard - run task

现在返回到 Data Explorer(数据浏览器)并检查您的存储桶,提交查询后,您应该会看到现在有数据可见。如果您没有看到任何数据,请尝试刷新浏览器并确保您单击了正确的存储桶。如果您仍然看不到数据,请尝试再次执行步骤并查看文档,以确保您没有遗漏任何内容。

从 InfluxDB 查询数据

现在您已经存储了数据,是时候学习如何查询该数据了。最简单的入门方法是使用Data Explorer(数据浏览器)。您可以在 UI 中可视化地修改查询,并查看图表如何根据这些更改而变化。

在底层,Data Explorer 使用 Flux,这是一种用于从 InfluxDB 获取数据的查询语言。Flux 从头开始设计,用于处理时序数据,并包含许多内置函数,用于常见的时序用例。语法与 JavaScript 非常相似,并且不难上手。通过单击 Data Explorer 中的脚本编辑器选项卡,您可以看到 Data Explorer UI 用于获取数据的实际 Flux 查询。这种类型的迭代可能是掌握 Flux 最快的方法。有关 Flux 可以做什么的完整介绍和概述,请查看文档

一般来说,Flux 查询将有 4 个关键部分:

  1. Source(来源):您从中提取数据的特定存储桶。
  2. Filter(过滤器):您很少需要存储桶中的所有数据,因此您将使用过滤器函数从特定时间范围以及可能仅从几个字段值中选择数据。
  3. Shape(形状):在此阶段,您可能想要修改数据的格式以进行进一步处理。这可以包括将列透视为行、删除列或将键分组在一起。
  4. Process(处理):最后阶段是处理数据。这方面的一些示例包括聚合行、选择特定数据点(如最小值或最大值)、使用 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 获取数据,然后将其存储在状态中。我们还从 Nivo 导入了 InfluxDB 客户端和 Responsive line chart(响应式折线图)组件。接下来是连接到 InfluxDB 所需的常量。您可能应该将这些存储为环境变量。
  • 下面是我们将使用的 Flux 查询。该查询抓取最近一小时的数据,抓取 airSensor 测量,过滤数据以仅抓取一氧化碳数据,然后取该数据的 5 分钟平均值。
  • 图表组件相当复杂,因此如果您想知道每个步骤发生了什么,我添加了内联注释。简而言之,useEffect 钩子在组件在页面上呈现时运行并查询 InfluxDB。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 图表是动画的,并且将在数据属性更新时呈现新数据。您还可以使用 空气传感器数据集的 SQL 伴随数据来丰富您正在提取的数据。Flux 提供了查询外部数据源并将其与存储在 InfluxDB 中的数据相结合的能力。在这种情况下,您可以查询关系数据库以获取有关每个数据点的更多信息。此数据将与您的 Flux 查询一起返回,并且可以添加到您的 Nivo 图表中。

对于空气传感器数据集,您可以创建一个警报,根据数据触发操作。例如,如果一氧化碳水平达到特定阈值,您可以使用 Webhook 警报并向 Twilio 之类的东西发送 API 请求,通过 SMS 提醒人们。如果您有温度传感器,您可以动态调整家中的智能恒温器以节省资金。

关键的要点是,一旦您的数据进入 InfluxDB,您现在就可以对该数据采取行动。您可以自动化任务、监控事物和可视化您的数据。

其他资源

希望在阅读完本教程后,您对 Nivo 和 InfluxDB 都有了扎实的了解。构建此项目的想法是使其具有可扩展性,以便您可以构建一些很酷的东西来展示。同时,这只是一个起点,因此以下是一些您可以查看的其他资源以了解更多信息:

教程结束后,最好进行“清理”。如果您不经常查看您的帐户,请务必删除或停用您的 API 密钥。最好也停止空气传感器数据任务,这样您就不会下载和存储您不需要的数据。