如何向此React Native Chart添加渐变色?

问题描述

当前实施:

enter image description here

这是一个使用react-native-svg库的图表,并在其中添加了工具提示。我想使线条的颜色成为渐变色,而不是单一颜色

代码

import React,{ useState } from 'react'
import { View,Text,Dimensions } from 'react-native'
import { LineChart } from 'react-native-chart-kit'
import { Rect,Text as TextSVG,Svg } from "react-native-svg";

const Charts = () => {
    let [tooltipPos,setTooltipPos] = useState({ x: 0,y: 0,visible: false,value: 0 })

    return (
        <View>
            <LineChart
                data={{
                    labels: ["January","February","march","April","May","June"],datasets: [
                        {
                            data: [
                                100,110,90,130,80,103
                            ]
                        }
                    ]
                }}
                width={Dimensions.get("window").width}
                height={250}
                yAxisLabel="$"
                yAxisSuffix="k"
                yAxisInterval={1}
                chartConfig={{
                    backgroundColor: "white",backgroundGradientFrom: "#fbfbfb",backgroundGradientTo: "#fbfbfb",decimalPlaces: 2,color: (opacity = 1) => `rgba(0,${opacity})`,labelColor: (opacity = 1) => `rgba(0,style: {
                        borderRadius: 0
                    },propsForDots: {
                        r: "6",strokeWidth: "0",stroke: "#fbfbfb"
                    }
                }}
                bezier
                style={{
                    marginVertical: 8,borderRadius: 6
                }}

                decorator={() => {
                    return tooltipPos.visible ? <View>
                        <Svg>
                            <Rect x={tooltipPos.x - 15} 
                                y={tooltipPos.y + 10} 
                                width="40" 
                                height="30"
                                fill="black" />
                                <TextSVG
                                    x={tooltipPos.x + 5}
                                    y={tooltipPos.y + 30}
                                    fill="white"
                                    fontSize="16"
                                    fontWeight="bold"
                                    textAnchor="middle">
                                    {tooltipPos.value}
                                </TextSVG>
                        </Svg>
                    </View> : null
                }}

                onDataPointClick={(data) => {

                    let isSamePoint = (tooltipPos.x === data.x 
                                        && tooltipPos.y === data.y)

                    isSamePoint ? setTooltipPos((prevIoUsstate) => {
                        return { 
                                  ...prevIoUsstate,value: data.value,visible: !prevIoUsstate.visible
                               }
                    })
                        : 
                    setTooltipPos({ x: data.x,y: data.y,visible: true });

                }}
            />
        </View>
    )
}

export default Charts

问题: 我希望图表的颜色超过特定阈值时不再是灰色,而是希望图表颜色变为红色,另一个值则为黄色,最低值为绿色。

有点像:

enter image description here

我发现的东西:

render() {

    const data = [ 50,10,40,95,-4,-24,85,91,35,53,-53,24,50,-20,-80 ]

    const Gradient = () => (
      <Defs key={'gradient'}>
        <LinearGradient id={'gradient'} x1={'0'} y={'0%'} x2={'100%'} y2={'0%'}>
          <Stop offset={'0%'} stopColor={'rgb(134,65,244)'}/>
          <Stop offset={'100%'} stopColor={'rgb(66,194,244)'}/>
        </LinearGradient>
      </Defs>
    )

    return (
      <LineChart
        style={ { height: 200 } }
        data={ data }
        contentInset={ { top: 20,bottom: 20 } }
        svg={{
          strokeWidth: 2,stroke: 'url(#gradient)',}}
      >
        <Grid/>
        <Gradient/>
      </LineChart>
    )
  }

我无法将两者整合。请帮忙。

解决方法

这很复杂,但是我们可以创建自己的CustomLineChart组件,该组件继承自LineChart react-native-chart-kit提供的内容:

class CustomLineChart extends LineChart {
  render() {
    const {
      width,height,data,withScrollableDot = false,withShadow = true,withDots = true,withInnerLines = true,withOuterLines = true,withHorizontalLines = true,withVerticalLines = true,withHorizontalLabels = true,withVerticalLabels = true,style = {},decorator,onDataPointClick,verticalLabelRotation = 0,horizontalLabelRotation = 0,formatYLabel = (yLabel) => yLabel,formatXLabel = (xLabel) => xLabel,segments,transparent = false,chartConfig,} = this.props;

    const {scrollableDotHorizontalOffset} = this.state;
    const {labels = []} = data;
    const {
      borderRadius = 0,paddingTop = 16,paddingRight = 64,margin = 0,marginRight = 0,paddingBottom = 0,} = style;

    const config = {
      width,verticalLabelRotation,horizontalLabelRotation,};

    const datas = this.getDatas(data.datasets);

    let count = Math.min(...datas) === Math.max(...datas) ? 1 : 4;
    if (segments) {
      count = segments;
    }

    const legendOffset = this.props.data.legend ? height * 0.15 : 0;

    return (
      <View style={style}>
        <Svg
          height={height + paddingBottom + legendOffset}
          width={width - margin * 2 - marginRight}>
          <Defs>
            <LinearGradient id="grad" x1="0" y1="0" x2="0" y2="1">
              <Stop offset="0" stopColor="red" stopOpacity="1" />
              <Stop offset="1" stopColor="blue" stopOpacity="1" />
            </LinearGradient>
          </Defs>
          <Rect
            width="100%"
            height={height + legendOffset}
            rx={borderRadius}
            ry={borderRadius}
            fill="white"
            fillOpacity={transparent ? 0 : 1}
          />
          {this.props.data.legend &&
            this.renderLegend(config.width,legendOffset)}
          <G x="0" y={legendOffset}>
            <G>
              {withHorizontalLines &&
                (withInnerLines
                  ? this.renderHorizontalLines({
                      ...config,count: count,paddingTop,paddingRight,})
                  : withOuterLines
                  ? this.renderHorizontalLine({
                      ...config,})
                  : null)}
            </G>
            <G>
              {withHorizontalLabels &&
                this.renderHorizontalLabels({
                  ...config,data: datas,paddingTop: paddingTop,paddingRight: paddingRight,formatYLabel,decimalPlaces: chartConfig.decimalPlaces,})}
            </G>
            <G>
              {withVerticalLines &&
                (withInnerLines
                  ? this.renderVerticalLines({
                      ...config,data: data.datasets[0].data,})
                  : withOuterLines
                  ? this.renderVerticalLine({
                      ...config,})
                  : null)}
            </G>
            <G>
              {withVerticalLabels &&
                this.renderVerticalLabels({
                  ...config,labels,formatXLabel,})}
            </G>
            <G>
              {this.renderLine({
                ...config,...chartConfig,data: data.datasets,})}
            </G>
            <G>
              {withDots &&
                this.renderDots({
                  ...config,})}
            </G>
            <G>
              {withScrollableDot &&
                this.renderScrollableDot({
                  ...config,scrollableDotHorizontalOffset,})}
            </G>
            <G>
              {decorator &&
                decorator({
                  ...config,})}
            </G>
          </G>
        </Svg>
        {withScrollableDot && (
          <ScrollView
            style={StyleSheet.absoluteFill}
            contentContainerStyle={{width: width * 2}}
            showsHorizontalScrollIndicator={false}
            scrollEventThrottle={16}
            onScroll={Animated.event([
              {
                nativeEvent: {
                  contentOffset: {x: scrollableDotHorizontalOffset},},])}
            horizontal
            bounces={false}
          />
        )}
      </View>
    );
  }
}

function App() {
  return (
    <CustomLineChart
      data={{
        labels: ['January','February','March','April','May','June'],datasets: [
          {
            data: [100,110,90,130,80,103],],}}
      width={Dimensions.get('window').width}
      height={250}
      chartConfig={{
        backgroundGradientFrom: '#fbfbfb',backgroundGradientTo: '#fbfbfb',color: (opacity = 1) => 'url(#grad)',labelColor: (opacity = 1) => `rgba(0,${opacity})`,}}
      bezier
      withInnerLines={false}
      withOuterLines={false}
    />
  );
}

渲染中的大多数代码与官方LineChart组件(https://github.com/indiespirit/react-native-chart-kit/blob/master/src/line-chart/LineChart.tsx)相同。

此代码中的重要部分是添加的线性渐变和chartConfig中的该行:

color: (opacity = 1) => 'url(#grad)'

颜色是指ID LinearGradient所定义的grad


结果看起来像这样:

showcase


您还可以添加更多颜色并使用每种颜色的offset道具。

您也可以为梯度使用百分比,但是建议使用精确值,因为根据文档https://github.com/react-native-community/react-native-svg#lineargradient,它比使用百分比具有更好的性能。


要注意的重要一点是,此实现没有以fillShadowGradient为背景,也没有实现阴影。我还省略了一些其他代码,例如与问题无关的工具提示代码。