# Animating a Donut with Svg.animate (Chartist example)

> A plugin draws the donut slice by slice with SMIL, each one starting where the previous one ends.

`src/main.ts`:

```ts
import 'chartist/styles.css';
import {
  pieChart,
  easings,
  findAll,
  Svg,
  type AnimationDefinition,
  type ChartPlugin
} from 'chartist';

// Draws the slices one after the other, each one starting when the previous one ends.
const drawSlices: ChartPlugin = {
  name: 'draw-slices',
  install(chart) {
    let timerId: ReturnType<typeof setTimeout> | undefined;

    // `mount` runs after every render, once the SVG is in the page.
    chart.on('mount', ({ scene, elementOf }) => {
      for (const slice of findAll(scene, { role: 'slice' })) {
        const element = elementOf(slice);
        if (!element) {
          continue;
        }
        const index = slice.ctx.index;
        const svg = new Svg(element);

        // Get the total path length in order to use for dash array animation
        const pathLength = (element as SVGGeometryElement).getTotalLength();

        // Set a dasharray that matches the path length as prerequisite to animate dashoffset
        svg.attr({
          'stroke-dasharray': pathLength + 'px ' + pathLength + 'px'
        });

        // Create animation definition while also assigning an ID to the animation for later sync usage
        const animationDefinition: Record<string, AnimationDefinition> = {
          'stroke-dashoffset': {
            id: 'anim' + index,
            dur: 1000,
            from: -pathLength + 'px',
            to: '0px',
            easing: easings.easeOutQuint,
            // We need to use `fill: 'freeze'` otherwise our animation will fall back to initial (not visible)
            fill: 'freeze'
          }
        };

        // If this was not the first slice, we need to time the animation so that it uses the end sync event of the previous animation
        if (index !== 0) {
          animationDefinition['stroke-dashoffset'].begin =
            'anim' + (index - 1) + '.end';
        }

        // We need to set an initial value before the animation starts as we are not in guided mode which would do that for us
        svg.attr({
          'stroke-dashoffset': -pathLength + 'px'
        });

        // We can't use guided mode as the animations need to rely on setting begin manually
        svg.animate(animationDefinition, false);
      }

      // For the sake of the example we redraw the chart every time it's rendered with a delay of 10 seconds
      if (timerId) {
        clearTimeout(timerId);
      }
      timerId = setTimeout(() => chart.update({}), 10000);
    });
  }
};

pieChart(
  '#chart',
  {
    series: [10, 20, 50, 20, 5, 50, 15],
    labels: [1, 2, 3, 4, 5, 6, 7]
  },
  {
    plugins: [drawSlices],
    donut: true,
    showLabel: false
  }
);
```

`index.html`:

```html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script type="module" src="/src/main.ts"></script>
  </head>
  <body>
    <div id="chart" style="height: 50vh"></div>
  </body>
</html>
```

The same chart with the 1.x classes (`src/main.classic.ts`):

```ts
import 'chartist/styles.css';
import { PieChart, easings, type AnimationDefinition } from 'chartist';
import { compat } from 'chartist/compat';

const chart = new PieChart(
  '#chart',
  {
    series: [10, 20, 50, 20, 5, 50, 15],
    labels: [1, 2, 3, 4, 5, 6, 7]
  },
  {
    // 1.x draw events come from the compat plugin in v2.
    plugins: [compat],
    donut: true,
    showLabel: false
  }
);

chart.on('draw', data => {
  if (data.type === 'slice') {
    // Get the total path length in order to use for dash array animation
    const pathLength = data.element
      .getNode<SVGGeometryElement>()
      .getTotalLength();

    // Set a dasharray that matches the path length as prerequisite to animate dashoffset
    data.element.attr({
      'stroke-dasharray': pathLength + 'px ' + pathLength + 'px'
    });

    // Create animation definition while also assigning an ID to the animation for later sync usage
    const animationDefinition: Record<string, AnimationDefinition> = {
      'stroke-dashoffset': {
        id: 'anim' + data.index,
        dur: 1000,
        from: -pathLength + 'px',
        to: '0px',
        easing: easings.easeOutQuint,
        // We need to use `fill: 'freeze'` otherwise our animation will fall back to initial (not visible)
        fill: 'freeze'
      }
    };

    // If this was not the first slice, we need to time the animation so that it uses the end sync event of the previous animation
    if (data.index !== 0) {
      animationDefinition['stroke-dashoffset'].begin =
        'anim' + (data.index - 1) + '.end';
    }

    // We need to set an initial value before the animation starts as we are not in guided mode which would do that for us
    data.element.attr({
      'stroke-dashoffset': -pathLength + 'px'
    });

    // We can't use guided mode as the animations need to rely on setting begin manually
    data.element.animate(animationDefinition, false);
  }
});

let timerId: ReturnType<typeof setTimeout> | undefined;

// For the sake of the example we update the chart every time it's created with a delay of 8 seconds
chart.on('created', () => {
  if (timerId) {
    clearTimeout(timerId);
  }

  timerId = setTimeout(chart.update.bind(chart), 10000);
});
```
