在React Native中创建胜利的图表

934 阅读4分钟

当谈到构建一个应用程序时,拥有有效的数据可视化通过使数据易于阅读和理解来吸引用户。

在这篇文章中,我们将介绍如何使用Victory在React Native中实现数据可视化。

什么是Victory?

Victory是一个React的数据可视化库。它包含各种基于组件的数据可视化工具,这些工具是交互式的,有观点的,也是完全可重写的。它很强大,而且在React和React Native应用中都能比较简单地实现。

这篇文章假设你有React Native的工作知识。如果你没有,这里是如何开始的。

在React Native中创建一个胜利的图表

现在,让我们来创建你的第一个图表!首先,初始化一个React Native项目。

npx react-native init VictoryRN

接下来,我们可以安装victory-native

npm install victory-native

你还应该安装react-native-svg,以便将React Native SVG的本地依赖性链接到iOS和Android项目中。

react-native install react-native-svg

现在你已经初始化了你的React Native应用并安装了你的依赖项,让我们开始添加一些图表。首先,让我们导入一些victory-native模块,我们将用下面的代码来使用。

import { VictoryBar, VictoryChart, VictoryTheme } from "victory-native";

我们需要一些假的数据来提供给我们的图表。这些数据由一个对象的数组来表示。

const data = [
 { year: '2011', earnings: 13000 },
 { year: '2012', earnings: 16500 },
 { year: '2013', earnings: 14250 },
 { year: '2014', earnings: 19000 }
];

最后,让我们添加我们的第一个Victory图表组件,像这样。

<VictoryChart width={350} theme={VictoryTheme.material}>
 <VictoryBar data={data} x="quarter" y="earnings" />
</VictoryChart>

在网站上说,VictoryChart 是一个包装组件,可以在一组笛卡尔轴或极坐标轴上显示一组给定的孩子。VictoryBar 是一个由胜利公司提供的柱状图组件。添加上述代码后,我们的应用程序应该是这样的。

Code With A Mobile Device Displaying Bar Chart

Viola,你的第一个Victory图表!现在我们有了基本的图表实现,让我们来试验一下动态图表。我们希望我们的图表能够随着数据的更新而实时更新。我们还可以添加单选按钮来改变正在使用的图表类型。

在React Native中制作动态的Victory图表

胜利图表的一个伟大之处在于它们可以动态地工作于更新的状态值。"它们可以对状态变化做出反应,并以动画方式显示任何新数据。与其他库如D3.js不同,它不需要直接控制DOM,"Matt Crouch说。

这一点很重要,因为对于React来说,直接与DOM交互是一种反模式的做法。决定是React还是你的第三方库应该更新图表会变得有点模糊。

现在,让我们看看这个动态的行动吧首先,让我们使用React Hooks将我们的数据移入我们组件的状态,并将其存储为chartData

const [chartData, setData] = useState(data)

接下来,让我们添加一个按钮,当点击时,将另一个数据对象添加到我们的chartData

const addData = () => {
 var d = [...chartData]
 var obj = {year: `${maxYear}`, earnings: Math.random() * (20000 - 10000) + 10000}
 d.push(obj)
 setData(d)
 maxYear++
}

在这个函数中,我们要创建一个新的对象添加到chartData ,年份是maxYear ,这是一个全局变量,是最近使用的年份之后的年份。对于收益,我正在利用Math.random() ,生成一个介于10,000和20,000之间的随机数。

一旦我们构建了我们的对象,我们就可以把它添加到d (使用这个变量是为了不直接突变chartData ),然后把它设置为我们新的chartData 。我们需要做的最后一件事是将maxYear 增加1。现在我们可以将我们的新函数应用于React Native Button组件。见下图。

<Button onPress={addData} title="Add Earnings"/>

为了整洁,我们可以包括另一个按钮,将我们的图表重置到原来的状态。onPress 将调用我们的重置函数,它只是将chartData 重置为datamaxYear 重置为2015。

现在,我们的应用程序动态地将数据添加到图表中,让我们更进一步,允许用户改变用于可视化可用数据的图表类型。为此,我们可以在我们的组件状态中存储我们的图表类型的值。

const [chart, setChart] = useState(0)

我们现在可以安装react-native-simple-radio-button,然后添加该库中提供的组件,RadioForm

<RadioForm
 radio_props={radio_props}
 formHorizontal={true}
 labelHorizontal={false}
 buttonColor={'#2196f3'}
 onPress={(value) => setChart(value)}
/>

我们的radio_props 是一个对象的数组。每个对象都有一个标签和一个值,它们将对应于我们RadioForm 组件中的单选按钮。

const radio_props = [
 {label: 'Bar', value: 0}, 
 {label: 'Line', value: 1}, 
 {label: 'Area', value: 2}
]

现在我们可以改变我们的图表类型,我们可以根据选择的内容从不同的进口图表类型中选择。

<VictoryChart width={350}>
 {chart === 0
   ? <VictoryBar data={chartData} x="year" y="earnings" />
   : null
 }
 {chart === 1
   ? <VictoryLine data={chartData} x="year" y="earnings" />
   : null
 }
 {chart === 2
   ? <VictoryArea data={chartData} x="year" y="earnings" />
   : null
 }
</VictoryChart>

让我们看看我们的应用程序现在看起来像什么,我们已经添加了一些新的功能。

Code With A Mobile Device Displaying Various Dynamic Features Of Victory Charts

太完美了!现在我们有了一个具有多种可视化选项的动态应用程序。这样做很好,但条件性渲染有点重复了。让我们试着完善一下。

干净的条件渲染

上面的条件渲染不是很DRY(Don't Repeat Yourself)。我发现最好的解决办法是初始化一个变量,然后在返回语句之前有条件地决定哪个图表应用于我们的变量。

var MyChart = <VictoryBar data={chartData} x="year" y="earnings" alignment="start"/>

if (chart === 1){
  MyChart =  <VictoryLine data={chartData} x="year" y="earnings" />
} else if (chart === 2){
  MyChart = <VictoryArea data={chartData} x="year" y="earnings" />
}

一旦我们有了我们的图表变化逻辑,让我们看看它与我们的图表包装器VictoryChart

<VictoryChart width={350} domainPadding={10}>
         {MyChart}
 </VictoryChart>

完美。VictoryChart 有一个道具需要注意,那就是domainPadding 。它很简单,但我发现它很重要,因为如果不把它设置为10,条形图就不能正确放置。注意下面的第一个例子是没有domainPadding ,而第二个例子的domainPadding 是10。

Two Mobile Devices One Displaying A Bar Starting On The Y-Axis And The Other Displaying Space In Between The First Bar

现在让我们来看看data 这个道具。在我们的例子中,我们的数据对象相当简单,但如果你想得到更多的东西,你可以在数据对象中添加更多的键/值对。

{ x: 1, y: 1, label: "first", symbol: "star", opacity: 0.5, fill: "blue" }

在这里,我们的xy 属性指向了一个数字,而不是一个字符串。图表可以按字面意思呈现xy 的值(就像上面的情况),或者它们可以根据指定的键来查找值(就像我们的例子,使用earningsyear )。

这对自定义数据来说是很好的,因为我们不需要转换键值对来遵守字面的xy 的解释。我们所要做的就是指定哪些键对应于x轴和y轴,这可以在有条件渲染的图表组件上声明。

你还可以包括数据对象的样式和符号。然而,在最新版本的Victory中,这些样式不会自动应用,所以你需要给你的图表添加一个样式道具,这是一个可以渲染样式功能的对象。

见下文。

style={{
    data: {
      fill: ({ datum }) => datum.fill,
      opacity: ({ datum }) => datum.opacity
    }
  }}

需要注意的是,对于将数据渲染为一个数据集的图表(如VictoryAreaVictoryLine ),datum 不是一个有效的道具。相反,你应该这样写样式。

style={{ data: { fill: "#c43a31" } }}

如果你想更好地定制颜色,为你的VictoryArea ,而不是使用一种颜色,你可以使用react-native-svg提供的一些组件。

import {Defs, LinearGradient, Stop} from "react-native-svg";

Defs 将成为我们的说唱者。这个组件 "用于嵌入可在SVG图像内重复使用的定义"。LinearGradient 嵌套在Defs 组件内,用于生成应用程序中渲染的实际LinearGradient 。在LinearGradient ,我们将有多个Stop 组件。A [<stop>](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop)元素 "定义了一种颜色和它在渐变上的位置"。

下面是我们的代码,为我们的VictoryArea 图表加入线性梯度。它将被放置在VictoryChart 内的{MyChart} 上面。

<Defs>
   <LinearGradient id="gradientStroke" >
     <Stop offset="0%" stopColor="red"/>
     <Stop offset="25%" stopColor="orange"/>
     <Stop offset="50%" stopColor="gold"/>
     <Stop offset="75%" stopColor="yellow"/>
     <Stop offset="100%" stopColor="green"/>
  </LinearGradient>
</Defs>

现在,为了让它与我们的区域图一起工作,我们需要在我们的样式道具中为VictoryArea 组件添加一些东西。

style={{
  data: {
    fill: 'url(#gradientStroke)',
    ...  
  }
}}

url(#gradientStroke) 是将我们的区域图的填充样式与我们的LinearGradient 组件(ID为gradientStroke )中定义的各种色块连接起来。下面是我们的VictoryArea ,当我们实现当前的线性梯度UI时,我们的图表将是什么样子。

Area Chart With A Gradient

现在,这个梯度并不对应于我们的具体类别。它将简单地把颜色渲染成整个图表的一部分,增量为25%。如果你想让它挑选一种颜色,并对每个类别的大小做出反应(比如你动态地添加更多的数据),你可以使用下面的代码,以及预定义的颜色列表。

const genStops = () => {
    var percentage = 100 / chartData.length
    //initialize at 0%
    var stops = [<Stop offset="0%" stopColor={colorArray[0]}/>]
    chartData.forEach((x, i)=>{
      console.log(pickColor(i))
      var stop = <Stop offset={`${percentage * (i + 1)}%`}
                       stopColor={pickColor(i)}
                 />
      stops.push(stop)
    })
    return stops
  }

  const pickColor = (i) => {
    var index = indexHelper(i, colorArray.length)
    return colorArray[index]
  }

  const indexHelper = (a, b) => {
    //start from beginning of color array when chartData gets too long
    return a >= b ? indexHelper(a - b, b) : a
  }

  const stops = genStops()

然后在你的LinearGradient 组件里面,不要包括单独的Stop 组件,你可以只包括{stops} 。现在你的梯度图的颜色与类别相对应,而不是硬编码并应用于整个图表。

我想介绍的最后一个功能是动画。这是你可以传递给你的图表的道具,以控制它们将被渲染的动画风格(而不是仅仅让它们立即渲染)。

为了给你的图表添加动画,"道具 "是一个很好的开始。 [animate](https://formidable.com/open-source/victory/docs/common-props/#animate)prop是一个很好的开始。这个道具是一个接受一些属性的对象,其中我们将使用durationeasing ,和onLoad

Duration 指的是动画需要多长时间(ms),easing 是动画风格,onLoad 是一个对象,也可以指定animate 属性。这些道具被应用于加载或更新的数据。

animate={{
  onLoad: {duration: 1000},
  duration: 1000,
  easing: "bounce"
}}

你可以将animate 道具应用到VictoryChart 或子图表上。就我个人而言,我发现当我把动画应用到VictoryChart ,在图表类型之间过渡时有问题,而如果我把它们直接添加到子图表中,就没有问题了。请看下面的图表与animate 属性。

Code With A Mobile Device Displaying Animation Of Additional Bars Appearing On Chart

关于动画的更多信息,请查看《胜利图表的动画指南》。

总结

胜利是一个强大而有效的数据可视化库,它的构成并不太僵化。它很有主见,但也提供了足够的灵活性,这样开发者就可以在内置功能之外从图表中获得更多的东西。

胜利的优势还在于它不像其他一些库那样直接与DOM交互。这允许更好的运行时间,更少的UI冲突,并且是React Native的最佳选择。

这个项目的所有源代码都可以在GitHub上找到。

The postCreating Victory charts in React Nativeappeared first onLogRocket Blog.