Как добавить цвет градиента в эту диаграмму React Native?

Текущая реализация:

введите здесь описание изображения

Это диаграмма, использующая библиотеку 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, 0, 0, ${opacity})`,
                    labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
                    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, value: data.value, y: data.y, visible: true });

                }}
            />
        </View>
    )
}

export default Charts

ВОПРОС: Вместо того, чтобы диаграмма была только серой, я хочу, чтобы цвет диаграммы был красным, когда он превышает определенный порог, желтым для другого значения и зеленым для самого низкого значения.

Примерно так: введите здесь описание изображения

Что я нашел:

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>
    )
  }

Я не могу интегрировать два. Пожалуйста помоги.


person Blake    schedule 12.09.2020    source источник
comment
Это довольно сложно, но это возможно только при создании собственного пользовательского компонента диаграммы, который расширяет AbstractChart github.com/indiespirit/react-native-chart-kit#abstract-chart.   -  person Bas van der Linden    schedule 24.09.2020


Ответы (1)


Это довольно сложно, но мы можем создать свой собственный компонент 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,
      height,
      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,
                      paddingTop,
                      paddingRight,
                    })
                  : null)}
            </G>
            <G>
              {withHorizontalLabels &&
                this.renderHorizontalLabels({
                  ...config,
                  count: count,
                  data: datas,
                  paddingTop: paddingTop,
                  paddingRight: paddingRight,
                  formatYLabel,
                  decimalPlaces: chartConfig.decimalPlaces,
                })}
            </G>
            <G>
              {withVerticalLines &&
                (withInnerLines
                  ? this.renderVerticalLines({
                      ...config,
                      data: data.datasets[0].data,
                      paddingTop: paddingTop,
                      paddingRight: paddingRight,
                    })
                  : withOuterLines
                  ? this.renderVerticalLine({
                      ...config,
                      paddingTop: paddingTop,
                      paddingRight: paddingRight,
                    })
                  : null)}
            </G>
            <G>
              {withVerticalLabels &&
                this.renderVerticalLabels({
                  ...config,
                  labels,
                  paddingTop: paddingTop,
                  paddingRight: paddingRight,
                  formatXLabel,
                })}
            </G>
            <G>
              {this.renderLine({
                ...config,
                ...chartConfig,
                paddingRight: paddingRight,
                paddingTop: paddingTop,
                data: data.datasets,
              })}
            </G>
            <G>
              {withDots &&
                this.renderDots({
                  ...config,
                  data: data.datasets,
                  paddingTop: paddingTop,
                  paddingRight: paddingRight,
                  onDataPointClick,
                })}
            </G>
            <G>
              {withScrollableDot &&
                this.renderScrollableDot({
                  ...config,
                  ...chartConfig,
                  data: data.datasets,
                  paddingTop: paddingTop,
                  paddingRight: paddingRight,
                  onDataPointClick,
                  scrollableDotHorizontalOffset,
                })}
            </G>
            <G>
              {decorator &&
                decorator({
                  ...config,
                  data: data.datasets,
                  paddingTop,
                  paddingRight,
                })}
            </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, 0, 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)'

Цвет относится к LinearGradient, который мы определили по его идентификатору grad.


Результат выглядит примерно так:

витрина


Вы также можете добавить больше цветов и поэкспериментировать с реквизитом offset каждого из них.

Вы также можете использовать проценты для градиента, но рекомендуется использовать точные значения, поскольку это дает преимущества в производительности по сравнению с использованием процентов в соответствии с документацией: https://github.com/react-native-community/react-native-svg#lineargradient.


Важно отметить, что в этой реализации нет fillShadowGradient в качестве фона и не реализована тень. Я также пропустил часть вашего другого кода, например код всплывающей подсказки, который не имеет отношения к вопросу.

person Bas van der Linden    schedule 21.09.2020
comment
Но будет ли это работать и с всплывающей подсказкой? Типа мне нужно, чтобы и в диаграмме были, и в градиенте, и во всплывающей подсказке. - person Blake; 23.09.2020
comment
Я получаю сообщение об ошибке URL-адрес (# grad) не является допустимым цветом или 0 не является допустимым смещением/url (# grad) не является допустимым цветом или 1 не является допустимым смещением - person Blake; 23.09.2020
comment
Он не может получить цвет по идентификатору градиента. - person Blake; 23.09.2020
comment
Эй, так это отлично работает на моей закуске в Интернете, но когда я запускаю ее на своем устройстве, линейная диаграмма просто не отображается. Любые идеи? Ссылка: snack.expo.io/@vijayr0753/chart-draft - person Blake; 23.09.2020
comment
Я заставил его работать, создав компонент, расширяющий LineChart, см. мой обновленный ответ. - person Bas van der Linden; 29.09.2020
comment
Может ли это иметь всплывающую подсказку? - person Blake; 18.11.2020
comment
Также можно ли остановить интерполяцию на этом? Скажем, если значения для определенных значений x отсутствуют, мы не строим график для этих значений. Можно ли это сделать? - person Blake; 18.11.2020
comment
Да, у вас может быть всплывающая подсказка, декоратор в моем коде отображается так же, как и обычный LineChart. Я думаю, что ваш вопрос об интерполяции лучше подходит как отдельный вопрос. Я не думаю, что есть простой способ сделать это, но это возможно, если переопределить функцию renderLine LineChart. - person Bas van der Linden; 22.11.2020