4

I'm drawing a react google bar chart (the material one) in a react project and I'm trying to make an animation. I've read that this kind of chart doesn't support animation but I need to do it, there has to be any way to do it. It's hard for me to think that a newer thing is worse than the old one. Anyone knows how can I do it? I've tried many different ways but nothing worked. This is my code:

import React from 'react';
import './App.css';
import Chart from 'react-google-charts'

function App() {

  return (
    <div className="App">      
      <Chart
        width={'500px'}
        height={'300px'}
        // Note here we use Bar instead of BarChart to load the material design version
        chartType="Bar"
        loader={<div>Loading Chart</div>}
        data={[
          ['City', '2010 Population', '2000 Population'],
          ['New York City, NY', 8175000, 8008000],
          ['Los Angeles, CA', 3792000, 3694000],
          ['Chicago, IL', 2695000, 2896000],
          ['Houston, TX', 2099000, 1953000],
          ['Philadelphia, PA', 1526000, 1517000],
        ]}
        options={{
          // Material chart options
          chart: {
            title: 'Population of Largest U.S. Cities',
            subtitle: 'Based on most recent and previous census data',
          },
          hAxis: {
            title: 'Total Population',
            minValue: 0,
          },
          animation: {
            duration: 1000,
            easing: 'out',
            startup: true,
          },
          vAxis: {
            title: 'City',
          },
          bars: 'horizontal',
          axes: {
            y: {
              0: { side: 'right' },
            },
          },
        }}
      />
    </div>
  );
}

export default App;
Xim123
  • 145
  • 3
  • 21

3 Answers3

4

Demo using react | Demo Using vanilla javascript

Animation is not supported on google material charts.

If you want to add animation to material google charts, you can do it manually with css animations. let's do it (Demo):

First we should get a selector for actual bars. it seems the third svg group (g tag) is the actual bars in chart (other groups are for labels / titles / etc.):

.animated-chart g:nth-of-type(3) {...}

Then we should add a css transition to it:

.animated-chart g:nth-of-type(3) {
  transition: 1s;
}

Then we can create a class (.animated-chart-start) for toggling between transform: scaleX(1); and transform: scaleX(0); , like this:

.animated-chart g:nth-of-type(3) {
  transition: 1s;
  transform: scaleX(1);
}
.animated-chart.animated-chart-start g:nth-of-type(3) {
  transform: scaleX(0);
}

So far we added css, and now we should add these classes to our chart and also toggle the .animated-chart-start class after a short delay. we can do it on componentDidMount, but it's more clean to do it on chart ready:

<Chart
    ...
    className={`animated-chart animated-chart-start`}
    chartEvents={[
      {
        eventName: "ready",
        callback: ({ chartWrapper, google }) => {
          const chartEl = chartWrapper.getChart().container;
          setTimeout(() => {
            chartEl.classList.remove('animated-chart-start')
          }, 100)
        },
      }
    ]}
  />

It adds the .animated-chart-start class to the chart, and it removes it after 100 ms. (100ms is optional, you can toggle it instantly also) .

Also note that google charts doesn't seem to support binding a data to the className (like className={this.state.dynamicClass}), that's why we can't use a state variable for toggling the animation class.

At the end, we can wrap this animated chart to a separate component like AnimatedChart to make it more reusable. (you can see it on stackblitz code).

Run it live

Known Limitations:

  • Setting the state during the chart animation will cause a re-render and it ruins the css transition.
  • We supposed that the third svg group is the chart. but it may vary based on the chart type or even chart properties.

Update: for vertical charts, you can use scaleY for animation, and also you may want to set transform origin like: transform-origin: 0 calc(100% - 50px); to make it look better. (Run vertical version On Stackblitz)

Update 2: For vanilla javascript version (without any framework), see here.

yaya
  • 7,675
  • 1
  • 39
  • 38
  • if I want to make the animation from the bottom to the top of the y-axis how the CSS file has to be changed? @yaya – Xim123 Sep 02 '20 at 14:54
  • .animated-chart g:nth-of-type(3) { transition: 0.7s; transform: scaleY(1); } .animated-chart.animated-chart-start g:nth-of-type(3) { transform: scaleY(8); } body{ font-family: Roboto; color: #333; } – Xim123 Sep 02 '20 at 14:59
  • 1
    @Xim123 you can use transform origin as you can see here: https://stackblitz.com/edit/react-material-ui-tabs-xaw4jj?file=style.css updated the answer. – yaya Sep 02 '20 at 16:21
  • The last thing I want to ask if I want to do that in javascript how can I do it? I've seen there is a ready event but I don't know the correct way to write it. @yaya – Xim123 Sep 04 '20 at 09:21
  • 1
    @Xim123 Added js version. also note that i added `transform-origin: 70px 0;` because of labels in the demo. – yaya Sep 04 '20 at 10:33
  • 1
    @Xim123 No problem. Glad it helped. Also consider using apexcharts or chart.js in your future projects. They're more modern and well documented with more features. – yaya Sep 04 '20 at 14:32
0

You can try to simulate animation just by swap the chart data after some short amount of time. Here is my proposition in 3 steps.

  1. Initially load chart with chart values as "0".
  2. Then load the partial values of data.
  3. In the end set the real data values.
function ChartBox() {

  let initialData = [
    ['City', '2010 Population', '2000 Population'],
    ['New York City, NY', 0, 0],
    ['Los Angeles, CA', 0, 0],
    ['Chicago, IL', 0, 0],
    ['Houston, TX', 0, 0],
    ['Philadelphia, PA', 0, 0],
  ];

  let n = 250; // divider
  let dataLoading = [
    ['City', '2010 Population', '2000 Population'],
    ['New York City, NY', 8175000/n, 8008000/n],
    ['Los Angeles, CA', 3792000/n, 3694000/n],
    ['Chicago, IL', 2695000/n, 2896000/n],
    ['Houston, TX', 2099000/n, 1953000/n],
    ['Philadelphia, PA', 1526000/n, 1517000/n],
  ];

  let finalData = [
    ['City', '2010 Population', '2000 Population'],
    ['New York City, NY', 8175000, 8008000],
    ['Los Angeles, CA', 3792000, 3694000],
    ['Chicago, IL', 2695000, 2896000],
    ['Houston, TX', 2099000, 1953000],
    ['Philadelphia, PA', 1526000, 1517000],
  ];

  const [chartData, setChartData] = useState(initialData);

  useEffect(() => {
    const timer = setTimeout(() => {
      setChartData(dataLoading)
    }, 100);
    const timer2 = setTimeout(() => {
      setChartData(finalData)
    }, 300);
    return () => {clearTimeout(timer); clearTimeout(timer2)}
  }, []);

  return (
    <div className="App">
      <Chart
        {...}
        data={chartData}
        {...}

Using the State Hook along with useEffect help to manipulate data which we want to present. In <Chart/> component I pass the chartData, which value will change after 100ms and 300ms. Of course you can add more steps with fraction of values (like dataLoading), so your "animation" will look more smoothly.

Aga
  • 1,019
  • 1
  • 11
  • 16
0

Just updated the code and tried to re-implement it in a better way but Can't find a better solution to it.

You need to paly along with CSS a bit

For Y-axis animation

g:nth-of-type(3) transition: 2s; transform: scaleX(1);

OR

For X-axis animation

g:nth-of-type(3) transform: scaleX(0);

https://codesandbox.io/s/google-react-chart-do602?file=/src/styles.css

champion-runner
  • 1,489
  • 1
  • 13
  • 26