使用 ReactJS、Nivo 和 InfluxDB 轻松实现数据可视化
作者:Charles Mahler / 产品,用例,开发者
2021年12月01日
导航至
如果一张图胜过千言万语,那么精心制作的数据可视化就胜过百万。仪表板的质量可以决定一个应用程序的成功或失败。在本教程中,您将学习如何通过使用 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
}
]
}
];
结果是以下图表
您可以尝试调整上述图表 props,看看会发生什么,以更好地了解 Nivo。
设置 InfluxDB
接下来,您需要设置一个可工作的 InfluxDB 实例,以便您可以写入和查询数据。InfluxDB 是开源的,可以托管在您的计算机上或部署到服务器。根据您使用的操作系统,设置可能略有不同,因此在本教程中,我建议您创建一个 免费的 InfluxDB Cloud 账户,这大约需要 2 分钟。如果您想使用开源版本,您可以从 这里 下载它,并按照 文档 中的说明操作。
一旦您的 InfluxDB 实例运行起来,现在您需要执行以下步骤来设置使用 InfluxDB 客户端库:
- 创建一个组织 — 您将在创建账户时这样做。
- 创建一个用于存储数据的 存储桶 — 存储桶的名称将用于我们的代码中查询数据。
- 为您的存储桶创建一个具有读写访问权限的 API 令牌 — 此令牌将用于对客户端库进行身份验证。
安装项目需求
现在 InfluxDB 准备就绪,您需要设置应用程序的前端。本教程的唯一需求是 NodeJS 开发环境。Create React App 将用作本项目的起点,但您可以选择使用自定义的 ReactJS 设置。如果您不想在计算机上本地安装任何东西,您可以使用 Replit 或 CodeSandbox 在浏览器中获取 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" )
点击保存,返回到任务仪表板。点击您刚刚创建的任务的轮形图标,然后点击“运行”,以便它立即运行。
现在回到数据探索器并检查您的存储桶,提交查询后,您应该能看到可见的数据。如果您看不到任何数据,请尝试刷新浏览器并确保您点击了正确的存储桶。如果您仍然看不到数据,请再次运行这些步骤并查看文档,以确保您没有错过任何内容。
从 influxDB 查询数据
您现在已有数据存储,是时候学习如何查询这些数据了。开始的最简单方法是使用 数据探索器。您可以在 UI 中直观地修改查询,并查看根据这些更改图表如何变化。
在底层,数据探索器使用 Flux,这是从 InfluxDB 获取数据的查询语言。Flux 是从头开始为处理时间序列数据而设计的,并包括许多用于常见时间序列用例的内置函数。其语法与 JavaScript 非常相似,并不难掌握。通过点击数据探索器中的脚本编辑标签,您可以看到数据探索器 UI 实际使用的 Flux 查询。这种迭代可能是学习 Flux 的最快方式。有关 Flux 的完整介绍和概述,请查看 文档。
一般来说,Flux 查询将包含 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提供的其他一些适用于时间序列数据的图表类型包括热图、日历和地图图表。
向您的项目添加功能
现在您已经有一个相当基础的图表,该图表从InfluxDB中获取数据,接下来是什么?您可以通过利用InfluxDB提供的一些功能来扩展此演示或对其他数据集进行操作。
例如,您可以通过设置一个钩子来每隔几分钟查询InfluxDB一次,使您的Nivo图表实时更新。Nivo图表是动画的,并且当数据prop更新时将渲染新数据。您还可以使用空气传感器数据集的SQL配套数据来丰富您正在获取的数据。Flux提供查询外部数据源的能力,并将其与存储在InfluxDB中的数据相结合。在这种情况下,您可以通过查询关系型数据库来获取有关每条数据的更多信息。这些数据将与您的Flux查询一起返回,并可以添加到您的Nivo图表中。
对于特定的空气传感器数据集,您可以根据数据创建一个触发动作的警报。例如,如果一氧化碳水平达到某个阈值,您可以使用webhook警报并通过发送API请求到类似Twilio的东西来发送短信警报。如果您有温度传感器,您可以根据需要动态调整家庭中的智能恒温器以节省金钱。
关键是,一旦数据在InfluxDB中,您现在就可以对数据进行操作。您可以自动化任务、监视事物和可视化数据。
其他资源
希望通过本教程,您已经对Nivo和InfluxDB有了扎实的理解。该项目是构建在可扩展性的基础上,您可以构建一些酷炫的东西来展示。同时,这只是一个起点,以下是一些您可以查看以了解更多信息的附加资源
- InfluxDB云文档
- Nivo文档
- InfluxDB网络研讨会和活动
- InfluxDB物联网中心 - 使用InfluxDB客户端库构建的更高级应用程序
在完成本教程后,“清理”也是一个好主意。如果您不会定期检查您的账户,请确保您删除或停用API密钥。取消运行空气传感器数据任务也是一个好主意,这样您就不会下载和存储不需要的数据。