Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a correct pie chart with manim

Tags:

python

manim

Anyway, I'm kind of trying (this is actually my first manim program).

from manim import *
import copy
import numpy as np
import random

color_palette = [BLUE, GREEN, YELLOW, GREY_BROWN]

class TestPie(Scene):
    def construct(self):
        n_elements = 3
        radius = 1
        weights = np.random.rand(n_elements)
        weights /= weights.sum()
        angles = weights*np.pi*2
        angles_offset = [0]+np.cumsum(weights*np.pi*2)[:-1].tolist()
        arcs = [Arc(angles_offset[i], angles[i]) for i in range(n_elements)]
        arcs2 = copy.deepcopy(arcs)
        triangles = [Polygon(*[
            # first element
            (radius*np.cos(angles_offset[i]), radius*np.sin(angles_offset[i]),0),
            (0, 0, 0), # second element
            # third element
            (radius*np.cos(angles_offset[(i+1)%n_elements]),
             radius*np.sin(angles_offset[(i+1)%n_elements]), 0)], stroke_width=0)
                    for i in range(n_elements)]
        lines = [Line((0,0,0),
                      (radius*np.cos(angles_offset[i]), radius*np.sin(angles_offset[i]), 0))
                 for i in range(n_elements)]
        for i in range(n_elements):
            arcs2[i].set_fill(color_palette[i%len(color_palette)], opacity=0.5)
            triangles[i].set_fill(color_palette[i%len(color_palette)], opacity=0.5)

        self.play(
            *map(lambda obj: ShowCreation(obj, run_time=1), arcs),
            *map(lambda obj: ShowCreation(obj, run_time=1), lines),
        )
        self.play(
            *map(lambda i: Transform(arcs[i], arcs2[i], runtime=1), range(n_elements)),
            *map(lambda obj: FadeIn(obj, run_time=1), triangles),

        )

        self.wait()
        weights = np.random.rand(n_elements)
        weights /= weights.sum()
        angles = weights*np.pi*2
        angles_offset = [0]+np.cumsum(weights*np.pi*2)[:-1].tolist()
        arcs2 = [Arc(angles_offset[i], angles[i]) for i in range(n_elements)]
        lines2 = [Line((0,0,0),
                       (radius*np.cos(angles_offset[i]), radius*np.sin(angles_offset[i]), 0))
                  for i in range(n_elements)]
        triangles2 = [Polygon(*[
            # first element
            (radius*np.cos(angles_offset[i]), radius*np.sin(angles_offset[i]),0),
            (0, 0, 0), # second element
            # third element
            (radius*np.cos(angles_offset[(i+1)%n_elements]),
             radius*np.sin(angles_offset[(i+1)%n_elements]), 0)], stroke_width=0)
                    for i in range(n_elements)]

        for i in range(n_elements):
            arcs2[i].set_fill(color_palette[i%len(color_palette)], opacity=0.5)
            triangles2[i].set_fill(color_palette[i%len(color_palette)], opacity=0.5)

        self.play(
            *map(lambda i: Transform(lines[i], lines2[i],
                                     runtime=1), range(n_elements)),
            *map(lambda i: Transform(arcs[i], arcs2[i],
                                     runtime=1), range(n_elements)),
            *map(lambda i: Transform(triangles[i], triangles2[i],
                                     runtime=1), range(n_elements)),
        )
        self.wait(2)
        

The output:

out

Thus, I have two problems with my current program. And I would appreciate a little help.

1. Since I'm using triangles and arcs, I get an ugly gap as you can see in the following picture.

image

2. I'm getting ugly transformations with the Arc, Triange and Line classes, the transformations should follow the circumference which is not the case right now. You can appreciate more one of the intermediary ugly steps in the image below. (As you can see, it's not round any more)

image

like image 997
silgon Avatar asked Jan 19 '21 00:01

silgon


People also ask

What is the best program to make a pie chart?

Top Reasons SmartDraw is the Best Pie Chart Software You can easily import data to make your pie chart. Edit your chart to change colors, legend placement and more. You can even easily swap between chart types.

What is the rule for constructing pie chart?

If creating the pie chart by hand, you will have to determine how many degrees of the circle each slice is. Since the circle has 360 degrees, multiply the percentage for each category by 360 to determine how big to make each slice.


Video Answer


2 Answers

For the first problem, avoid creating shapes by exactly lining up separate shapes. In fact, avoid exactly lining up separate shapes at all: graphics rendering engines often have trouble rendering such situations. Instead of creating a circular sector out of a circular segment and a triangle, create a single shape that will represent the whole sector that will be drawn as a single unit. In this case, use the Sector class to represent the sector instead of separate Arc and Polygon.

For the second problem, the issue is that by default manim computes intermediate shapes pointwise. The interpolation behaviour is controlled by the shape’s interpolate method. By subclassing the shape, you can override the interpolate method and instead compute intermediate shapes from more natural high-level parameters that define the shape: in this case, the centre, angles and radius.

Both of those fixes are incorporated in the sample below.

from manim import *
import numpy as np

class MySector(Sector):
    """ Circular sector shape with a custom interpolation method. """

    def interpolate(self, mobject1, mobject2, alpha, path_func=straight_path):
        if not (isinstance(mobject1, MySector) and isinstance(mobject2, MySector)):
            return super().interpolate(mobject1, mobject2, alpha, path_func=path_func)

        for attr in (
            'start_angle', 'angle',
            'inner_radius', 'outer_radius',
        ):
            v1 = getattr(mobject1, attr)
            v2 = getattr(mobject2, attr)
            setattr(self, attr, path_func(v1, v2, alpha))

        self.arc_center = path_func(
            mobject1.get_arc_center(),
            mobject2.get_arc_center(),
            alpha
        )
        self.interpolate_color(mobject1, mobject2, alpha)
        self.clear_points()
        self.generate_points()
        return self


color_palette = [BLUE, GREEN, YELLOW, GREY_BROWN]

class TestPie(Scene):
    def construct(self):
        weights = np.array([2.0, 3.0, 4.0])
        weights /= weights.sum()

        angles = weights * TAU
        angles_offset = np.cumsum((0, *angles[:-1]))

        sectors1 = [
            MySector(start_angle=ao, angle=a,
                stroke_width=DEFAULT_STROKE_WIDTH,
                fill_opacity=0)
            for ao, a in zip(angles_offset, angles)
        ]

        sectors2 = [
            MySector(start_angle=ao, angle=a,
                stroke_width=DEFAULT_STROKE_WIDTH,
                fill_color=color_palette[i % len(color_palette)], fill_opacity=0.5)
            for i, (ao, a) in enumerate(zip(angles_offset, angles))
        ]

        self.play(
            *(ShowCreation(a1, run_time=1) for a1 in sectors1)
        )

        self.play(
            *(Transform(a1, a2, runtime=1) for (a1, a2) in zip(sectors1, sectors2))
        )

        self.wait()

        weights = np.array([4.0, 3.0, 2.0])
        weights /= weights.sum()

        angles = weights * TAU
        angles_offset = np.cumsum((0, *angles[:-1]))

        sectors2 = [
            MySector(start_angle=ao, angle=a,
                stroke_width=DEFAULT_STROKE_WIDTH,
                fill_color=color_palette[i % len(color_palette)], fill_opacity=0.5)
            for i, (ao, a) in enumerate(zip(angles_offset, angles))
        ]

        self.play(
            *(Transform(a1, a2, runtime=1) for (a1, a2) in zip(sectors1, sectors2))
        )
        self.wait(2)

And here’s the resulting animation:

animation

The above doesn’t preserve the initial drawing animation for the lines that make up the pie chart sectors. You can override the pointwise_become_partial method to bring it back, or you may simply restore the Line shapes from your original code.

like image 114
user3840170 Avatar answered Sep 21 '22 12:09

user3840170


I know this was made a month ago, but for anyone looking, I have a better solution. I have created a manim graphing plugin (manim-graphing), that supports pie charts at a really high level.

like image 26
GameDungeon Avatar answered Sep 17 '22 12:09

GameDungeon