software-mansion / react-native-svg

SVG library for React Native, React Native Web, and plain React web projects.
MIT License
7.44k stars 1.12k forks source link

Best way to dynamic chart transitions? #793

Closed hoffmannjan closed 5 years ago

hoffmannjan commented 6 years ago

Hi,

I've got simple chart like this in my React Native App drawn using react-native-svg and I'm wondering what is the cleanest solution to animate this chart.

screenshot 2018-09-26 15 18 38

In standard web React application I would use just css-transitions, but in react-native I've read about Animated and other solution but all seems like overhead.

The code for drawing bar is simple, just:

<Svg.G key={index} x={x - 1}>
  <Svg.Rect x={0} y={height - y} width={2} height={y} fill={GREEN_COLOR} />
  <Svg.Circle cx={1} cy={height - y} r="4" stroke="rgba(85, 198, 103, 0.8)" strokeWidth="2" fill="white" />
</Svg.G>

And all I need is animate the y and height in Svg.Rect and y in Svg.Circle

Is it only possible by keeping the actual y in some component state and updating it using Animated ?

Best!

msand commented 6 years ago

Animated is probably the way to go. If you want more complex path transitions then can probably get some inspiration here: https://github.com/emonidi/react-native-svg-transforms https://www.youtube.com/watch?v=_9R2b9TpYMA

Otherwise you can just do something like this: https://snack.expo.io/@msand/simple-chart-animation

import React, { Component } from 'react';
import { View, StyleSheet, Dimensions, Animated } from 'react-native';
import ZoomableSvg from 'zoomable-svg'; // Version can be specified in package.json
import { Svg } from 'expo';

const { G, Circle, Path, Rect } = Svg;
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
const AnimatedRect = Animated.createAnimatedComponent(Rect);
const { width, height } = Dimensions.get('window');

class SvgRoot extends Component {
  state = {
    initAnim: new Animated.Value(0),
  };

  componentDidMount() {
    Animated.timing(
      // Animate over time
      this.state.initAnim,
      {
        toValue: 1,
        duration: 3000,
        useNativeDriver: false,
      }
    ).start();
  }

  render() {
    const { initAnim } = this.state;
    let translateRectY = initAnim.interpolate({
      inputRange: [0, 1],
      outputRange: ['50', '0'],
    });
    let heightAnim = initAnim.interpolate({
      inputRange: [0, 1],
      outputRange: ['0', '50'],
    });
    let rAnim = initAnim.interpolate({
      inputRange: [0, 1],
      outputRange: ['1', '4'],
    });
    let strokeAnim = initAnim.interpolate({
      inputRange: [0, 1],
      outputRange: ['0.1', '2'],
    });

    return (
      <Svg width={width} height={height}>
        <G transform={this.props.transform}>
          <AnimatedRect
            height={heightAnim}
            y={translateRectY}
            fill={'green'}
            width="2"
            x="20"
          />
          <AnimatedCircle
            stroke="rgba(85, 198, 103, 0.8)"
            strokeWidth={strokeAnim}
            cy={translateRectY}
            fill="white"
            r={rAnim}
            cx="21"
          />
        </G>
      </Svg>
    );
  }
}

export default class App extends Component {
  state = {
    constraints: {
      translateExtent: [[0, 0], [100, 100]],
      scaleExtent: [width / height, 5],
      combine: 'dynamic',
    },
  };

  render() {
    return (
      <View style={styles.container}>
        <ZoomableSvg
          constrain={this.state.constraints}
          meetOrSlice="meet"
          svgRoot={SvgRoot}
          height={height}
          vbHeight={100}
          vbWidth={100}
          width={width}
          align="mid"
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#ecf0f1',
  },
});
Abinash018 commented 10 months ago

Greate share