如何使用 manim 创建正确的饼图

问题描述

无论如何,我正在尝试(这实际上是我的第一个 manim 程序)。

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,# 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,(radius*np.cos(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,lines),)
        self.play(
            *map(lambda i: Transform(arcs[i],arcs2[i],runtime=1),range(n_elements)),*map(lambda obj: FadeIn(obj,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))
                  for i in range(n_elements)]
        triangles2 = [polygon(*[
            # first element
            (radius*np.cos(angles_offset[i]),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],*map(lambda i: Transform(arcs[i],*map(lambda i: Transform(triangles[i],triangles2[i],)
        self.wait(2)
        

输出

out

因此,我当前的程序有两个问题。我会很感激的一点帮助。

1.由于我使用的是三角形和圆弧,因此您可以在下图中看到一个难看的间隙。

image

2. 我对 ArcTriangeLine 类进行了丑陋的转换,转换应该遵循圆周,但情况并非如此现在。您可以在下图中欣赏更多中间丑陋步骤之一。 (如你所见,它不再是圆的了)

image

解决方法

对于第一个问题,避免通过精确排列不同的形状来创建形状。事实上,完全避免完全排列单独的形状:图形渲染引擎通常have trouble rendering such situations。不是从圆形线段和三角形中创建一个圆形扇区,而是创建一个单一的形状来表示将作为单个单元绘制的整个扇区。在这种情况下,使用 Sector 类来表示扇区而不是单独的 ArcPolygon

对于第二个问题,问题是默认情况下 manim 逐点计算中间形状。插值行为由形状的 interpolate 方法控制。通过对形状进行子类化,您可以覆盖 interpolate 方法,而是根据定义形状的更自然的高级参数计算中间形状:在本例中为中心、角度和半径。

这两个修复都包含在下面的示例中。

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,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,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,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,2.0])
        weights /= weights.sum()

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

        sectors2 = [
            MySector(start_angle=ao,angles))
        ]

        self.play(
            *(Transform(a1,sectors2))
        )
        self.wait(2)

这是生成的动画:

animation

以上不保留构成饼图扇区的线条的初始绘制动画。您可以覆盖 pointwise_become_partial 方法以将其恢复,或者您可以简单地从原始代码中恢复 Line 形状。

,

我知道这是一个月前制作的,但对于任何寻找的人,我有一个更好的解决方案。我创建了一个 manim 图形插件 (manim-graphing),它在非常高的水平上支持饼图。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...