如何使用 React Native - Victory Charts 教程制作数据可视化

导航至

一个漂亮的仪表盘可以使您的应用程序成功或失败。在本教程中,您将学习如何使用 React Native 和 Victory Native 绘图库制作 iOS 和 Android 图表和数据可视化。

要求

要跟随本教程,您需要对 JavaScript 有基本的了解,并最好熟悉 ReactJS 以及 NodeJS 开发环境。您还需要一个正在运行的 InfluxDB 实例,用于存储在本教程中创建的时序数据,以便创建图表进行可视化。最简单的方法是创建一个免费的 InfluxDB Cloud 账户。

在开发过程中预览您的 React Native 应用程序,您需要在您的手机上安装 Expo Go 应用。您也可以使用 Android Studio 模拟器iOS XCode 模拟器,尽管这些方法有一些局限性。

在浏览器中运行 Victory Native

Expo 还提供了将您的 React Native 应用程序部署到网络的能力。如果您想使用网络浏览器而不是使用手机或模拟器来预览在本教程中制作的图表,您可以根据文档中的这些 说明 来配置您的 expo 项目,以确保 Victory Native 正确编译。

React Native 是什么?

React Native 是由 Facebook 创建的框架,它允许网页开发者使用 ReactJS 和 JavaScript 生态系统创建原生应用程序,同时获得接近原生移动应用程序的性能和用户体验。结果是开发时间更快,因为大量的代码可以在 Android 和 iOS 应用程序之间共享,在某些情况下,网页开发者甚至可以以最少的培训需求帮助开发移动应用程序。React Native 的另一个优点是,您不必在完整的应用程序中使用它——您可以选择在需要真正原生功能的 App 位置使用 Swift 或 Java。

对于本教程,您将使用 Expo,这是一个使使用 React Native 更容易的开发工具。安装过程将在本文后面介绍。

Victory Native 库简介

Victory Native 是一个图表库,可用于您的 React Native 应用程序。它具有与用于 Web 应用程序的 ReactJS Victory 库几乎相同的 API。Victory Native 随带一些预构建的图表组件、交互元素和样式选项。Victory 在 GitHub 上有近 10,000 个星标,超过 150 名贡献者,并被 Airbnb、FiveThirtyEight、Zillow 和 Viacom 等公司使用。

Victory 图表基础

Victory 提供了多个基本图表组件,涵盖了大多数常见用例。一个可以尝试的组件是 VictoryChart 组件,它作为子组件的包装器。这在您想将多种图表类型组合在一起的情况下很有用,例如条形图和折线图。

Victory-Native

上述图表的代码如下所示

VictoryChart domainPadding={{ x: 8 }}>
  <VictoryBar
    style={{
      data: { fill: "#c43a31" }
    }}
    data={sampleData}
  />
  <VictoryLine
    data={sampleData}
  />
</VictoryChart>
```

The sample data:
```Javascript
const sampleData = [
  {x:1, y:4},
  {x:2, y:7},
  {x:3, y:3},
  {x:4, y:4},
  {x:5, y:6}
]

有用的 Victory 图表属性

图表组件之间存在许多常见的属性。以下是一些值得了解的属性

  • data - data 属性是显示任何内容所必需的。您可以传递一个对象数组或嵌套数组作为您的数据。
  • xy - xy 属性用于指定图表的 x 轴和 y 轴值。如果数据格式为对象,您可以传递一个字符串作为值;如果使用嵌套数组,您将传递一个整数值作为索引。您还可以传递一个函数给这些属性,根据输入计算值。
  •  style - 这个属性用于定义图表的样式,并使用 CSS in JS 样式。对于动态样式,许多值可以传递函数来更改颜色或标签等。

要了解更多关于这些属性如何工作,您可以查看 Victory 文档 – 它们有许多图表的实时预览功能,您可以在其中修改每个属性并查看效果。

Victory 图表教程

要开始教程,您首先需要使用以下命令安装 Expo CLI

npm install --global expo-cli

现在导航到您希望项目所在的任何文件夹,并运行以下命令

expo init my-app

导航到新创建的项目文件夹,并通过运行 expo start 启动您的 react native 应用程序。这应该会打开一个浏览器窗口,您可以在其中使用 Expo Go 应用程序扫描 QR 码,在您的移动设备上查看应用程序。

接下来,您将安装InfluxDB客户端库和Victory图表库,以及Victory图表工作所需的SVG库

expo install victory-native expo install react-native-svg npm install –save @influxdata/influxdb-client

现在,您应该可以使用React Native了,接下来您需要转到您正在运行的InfluxDB实例,创建一个桶并在其中存储数据。对于本教程,我们将使用InfluxDB提供的部分示例数据集。

本教程将使用空气传感器数据集。转到InfluxDB用户界面的任务标签页,粘贴以下Flux查询,将桶名称更改为您刚刚创建的桶名称

import "influxdata/influxdb/sample"

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

tasks tab

确保给任务起一个名字,并设置为每15分钟运行一次,因为样本数据就是以这种方式更新的,然后点击保存。点击您创建的任务的齿轮图标,然后点击运行,这样它将立即设置数据,而不是可能等待15分钟。

create-task

如果您转到InfluxDB中的数据资源管理器并检查您的桶,您应该会看到一些可以可视化的数据。在进入下一步之前,您需要创建一个API令牌,以便您可以从React Native应用程序查询数据。如果您在任何时候感到困惑或想了解更多关于用户界面中的内容,请查看文档

从React Native查询InfluxDB

在您的expo项目的App.js文件中,您可以粘贴以下代码

import React, {useState, useEffect} from "react";
import { InfluxDB } from "@influxdata/influxdb-client";
import '@expo/browser-polyfill';
import { StyleSheet, View, Text } from "react-native";
import { VictoryBar, VictoryChart, VictoryLine } from "victory-native";

//from https://github.com/facebook/react-native/issues/21209
FileReader.prototype.readAsArrayBuffer = function (blob) {
	if (this.readyState === this.LOADING) throw new Error("InvalidStateError");
	this._setReadyState(this.LOADING);
	this._result = null;
	this._error = null;
	const fr = new FileReader();
	fr.onloadend = () => {
		const content = atob(fr.result.substr("data:application/octet-stream;base64,".length));
		const buffer = new ArrayBuffer(content.length);
		const view = new Uint8Array(buffer);
		view.set(Array.from(content).map(c => c.charCodeAt(0)));
		this._result = buffer;
		this._setReadyState(this.DONE);
	};
	fr.readAsDataURL(blob);
}
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const atob = (input = '') => {
	let str = input.replace(/=+$/, '');
	let output = '';

	if (str.length % 4 == 1) {
		throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
	}
	for (let bc = 0, bs = 0, buffer, i = 0;
		buffer = str.charAt(i++);

		~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
			bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
	) {
		buffer = chars.indexOf(buffer);
	}

	return output;
}

const token = 'your-api-token'
const org = 'org-id'
const url = 'cloud-url'
const bucket = 'bucket-name'

let query = `from(bucket: "bucket-name")
  |> range(start: -30m)
  |> filter(fn: (r) => r["_measurement"] == "airSensors")
  |> filter(fn: (r) => r["_field"] == "humidity" or r["_field"] == "temperature" )
  |> filter(fn: (r) => r["sensor_id"] == "TLM0102")
  |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
  |> yield(name: "results")`
const InfluxChart = () => {
  const [data, setData] = useState([])

  useEffect(() => {

    const influxQuery = async () => {
      //create InfluxDB client
      const queryApi =  new InfluxDB({ url, token }).getQueryApi(org);
      //make query
       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 = []

          for (let i = 0; i < res.length; i++) {
            let point = {};
            point["humidity"] = res[i]["humidity"];
            point["temperature"] = res[i]["temperature"];

            point["name"] = res[i]["_time"];
            finalData.push(point);
          }
          setData(finalData); 
        },
        error(error) {

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

    influxQuery();
  }, []);

  return(
    <View>
      <VictoryChart height={600} domainPadding={20}>
        <VictoryLine style={{data: {stroke: '#34d5eb', strokeWidth: 1}}} data={data} x="name" y='humidity' />
        <VictoryLine style={{data: {stroke: '#a534eb', strokeWidth: 1}}} data={data} x="name" y="temperature"/>
      </VictoryChart>
    </View>
  )
}

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Victory Native</Text>
        <InfluxChart />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#f5fcff"
  }
});

此代码将对InfluxDB进行查询,以获取过去30分钟的空气传感器数据,转换返回的结果,将数据放入ReactJS状态中,然后将数据传递给Victory图表组件。结果将如下所示

Querying-InfluxDB-from-React-Native

下一步

Victory是一个功能强大的图表库,它还有一个额外的优点,即可以用于Web或使用React Native在移动应用程序中。如果您不打算长期使用此项目,请确保关闭您之前创建的任务,以便它不会持续运行。

本教程仅涵盖了一些基本示例,用于使用Victory和InfluxDB。如果您想了解更多,请查看以下资源。