简体   繁体   中英

How to animate a google material bar chart

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;

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 .

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM