LoginSignup
0
0

More than 3 years have passed since last update.

manimの作法 その27

Posted at

概要

manimの作法、調べてみた。
Histogram使ってみた。

サンプルコード

from manimlib.imports import *

def text_range(start,stop,step):
    numbers = np.arange(start,stop,step)
    labels = []
    for x in numbers:
        labels.append(str(x))
    return labels

class Histogram(VMobject):
    CONFIG = {
        "start_color": RED,
        "end_color": BLUE,
        "x_scale": 1.0,
        "y_scale": 1.0,
        "x_labels": "auto",
        "y_labels": "auto",
        "y_label_position": "top",
        "x_min": 0,
        "bar_stroke_width": 5,
        "outline_stroke_width": 0,
        "stroke_color" : WHITE
    }
    def __init__(self, x_values, y_values, mode = "widths", **kwargs):
        digest_config(self, kwargs)
        if mode == "widths" and len(x_values) != len(y_values):
            raise Exception("Array lengths do not match up!")
        elif mode == "posts" and len(x_values) != len(y_values) + 1:
            raise Exception("Array lengths do not match up!")
        self.y_values = y_values
        self.x_values = x_values
        self.mode = mode
        self.process_values()
        VMobject.__init__(self, **kwargs)
    def process_values(self):
        self.y_values = np.array(self.y_values)
        if self.mode == "widths":
            self.widths = self.x_values
            self.posts = np.cumsum(self.widths)
            self.posts = np.insert(self.posts, 0, 0)
            self.posts += self.x_min
            self.x_max = self.posts[-1]
        elif self.mode == "posts":
            self.posts = self.x_values
            self.widths = self.x_values[1:] - self.x_values[:-1]
            self.x_min = self.posts[0]
            self.x_max = self.posts[-1]
        else:
            raise Exception("Invalid mode or no mode specified!")
        self.x_mids = 0.5 * (self.posts[:-1] + self.posts[1:])
        self.widths_scaled = self.x_scale * self.widths
        self.posts_scaled = self.x_scale * self.posts
        self.x_min_scaled = self.x_scale * self.x_min
        self.x_max_scaled = self.x_scale * self.x_max
        self.y_values_scaled = self.y_scale * self.y_values
    def generate_points(self):
        self.process_values()
        for submob in self.submobjects:
            self.remove(submob)
        def empty_string_array(n):
            arr = []
            for i in range(n):
                arr.append("")
            return arr
        def num_arr_to_string_arr(arr):
            ret_arr = []
            for x in arr:
                if x == np.floor(x):
                    new_x = int(np.floor(x))
                else:
                    new_x = x
                ret_arr.append(str(new_x))
            return ret_arr
        previous_bar = ORIGIN
        self.bars = VGroup()
        self.x_labels_group = VGroup()
        self.y_labels_group = VGroup()
        outline_points = []
        if self.x_labels == "widths":
            self.x_labels = num_arr_to_string_arr(self.widths)
        elif self.x_labels == "mids":
            self.x_labels = num_arr_to_string_arr(self.x_mids)
        elif self.x_labels == "auto":
            self.x_labels = num_arr_to_string_arr(self.x_mids)
        elif self.x_labels == "none":
            self.x_labels = empty_string_array(len(self.widths))
        if self.y_labels == "auto":
            self.y_labels = num_arr_to_string_arr(self.y_values)
        elif self.y_labels == "none":
            self.y_labels = empty_string_array(len(self.y_values))
        for (i,x) in enumerate(self.x_mids):
            bar = Rectangle(width = self.widths_scaled[i],height = self.y_values_scaled[i],stroke_width = self.bar_stroke_width,stroke_color = self.stroke_color,)
            if bar.height == 0:
                bar.height = 0.01
                bar.generate_points()
            t = float(x - self.x_min)/(self.x_max - self.x_min)
            bar_color = interpolate_color(self.start_color,self.end_color,t)
            bar.set_fill(color = bar_color, opacity = 1)
            bar.next_to(previous_bar,RIGHT,buff = 0, aligned_edge = DOWN)
            self.bars.add(bar)
            x_label = TextMobject(self.x_labels[i])
            x_label.next_to(bar,DOWN)
            self.x_labels_group.add(x_label)
            y_label = TextMobject(self.y_labels[i])
            if self.y_label_position == "top":
                y_label.next_to(bar, UP)
            elif self.y_label_position == "center":
                y_label.move_to(bar)
            else:
                raise Exception("y_label_position must be top or center")
            self.y_labels_group.add(y_label)
            if i == 0:
                outline_points.append(bar.get_anchors()[-2])
            outline_points.append(bar.get_anchors()[0])
            outline_points.append(bar.get_anchors()[1])
            previous_bar = bar
        outline_points.append(bar.get_anchors()[2])
        outline_points.append(outline_points[0])
        self.outline = Polygon(*outline_points,stroke_width = self.outline_stroke_width,stroke_color = self.stroke_color)
        self.add(self.bars, self.x_labels_group, self.y_labels_group, self.outline)
        self.move_to(ORIGIN)
    def get_lower_left_point(self):
        return self.bars[0].get_anchors()[-2]

class BuildUpHistogram(Animation):
    def __init__(self, hist, **kwargs):
        self.histogram = hist

class FlashThroughHistogram(Animation):
    CONFIG = {
        "cell_color" : WHITE,
        "cell_opacity" : 0.8,
        "hist_opacity" : 0.2
    }
    def __init__(self, mobject, direction = "horizontal", mode = "random", **kwargs):
        digest_config(self, kwargs)
        self.cell_height = mobject.y_scale
        self.prototype_cell = Rectangle(width = 1,height = self.cell_height,fill_color = self.cell_color,fill_opacity = self.cell_opacity,stroke_width = 0,)
        x_values = mobject.x_values
        y_values = mobject.y_values
        self.mode = mode
        self.direction = direction
        self.generate_cell_indices(x_values,y_values)
        Animation.__init__(self,mobject,**kwargs)
    def generate_cell_indices(self,x_values,y_values):
        self.cell_indices = []
        for (i,x) in enumerate(x_values):
            nb_cells = int(np.floor(y_values[i]))
            for j in range(nb_cells):
                self.cell_indices.append((i, j))
        self.reordered_cell_indices = self.cell_indices
        if self.mode == "random":
            shuffle(self.reordered_cell_indices)
    def cell_for_index(self,i,j):
        if self.direction == "vertical":
            width = self.mobject.x_scale
            height = self.mobject.y_scale
            x = (i + 0.5) * self.mobject.x_scale
            y = (j + 0.5) * self.mobject.y_scale
            center = self.mobject.get_lower_left_point() + x * RIGHT + y * UP
        elif self.direction == "horizontal":
            width = self.mobject.x_scale / self.mobject.y_values[i]
            height = self.mobject.y_scale * self.mobject.y_values[i]
            x = i * self.mobject.x_scale + (j + 0.5) * width
            y = height / 2
            center = self.mobject.get_lower_left_point() + x * RIGHT + y * UP
        cell = Rectangle(width = width, height = height)
        cell.move_to(center)
        return cell
    def interpolate_mobject(self,t):
        if t == 0:
            self.mobject.add(self.prototype_cell)
        flash_nb = int(t * (len(self.cell_indices))) - 1
        (i,j) = self.reordered_cell_indices[flash_nb]
        cell = self.cell_for_index(i,j)
        self.prototype_cell.width = cell.get_width()
        self.prototype_cell.height = cell.get_height()
        self.prototype_cell.generate_points()
        self.prototype_cell.move_to(cell.get_center())
        if t == 1:
           self.mobject.remove(self.prototype_cell)
    def clean_up_from_scene(self, scene = None):
        Animation.clean_up_from_scene(self, scene)
        self.update(1)
        if scene is not None:
            if self.is_remover():
                scene.remove(self.prototype_cell)
            else:
                scene.add(self.prototype_cell)
        return self

class OutlineableBars(VGroup):
    CONFIG = {
        "outline_stroke_width": 3,
        "stroke_color": WHITE
    }
    def create_outline(self, animated = False, **kwargs):
        outline_points = []
        for (i, bar) in enumerate(self.submobjects):
            if i == 0:
                outline_points.append(bar.get_corner(DOWN + LEFT))
            outline_points.append(bar.get_corner(UP + LEFT))
            outline_points.append(bar.get_corner(UP + RIGHT))
            previous_bar = bar
        outline_points.append(previous_bar.get_corner(DOWN + RIGHT))
        outline_points.append(outline_points[0])
        self.outline = Polygon(*outline_points,stroke_width = self.outline_stroke_width,stroke_color = self.stroke_color)
        if animated:
            self.play(FadeIn(self.outline, **kwargs))
        return self.outline

class test(Scene):
    def construct(self):
        self.probs = 1.0 / 6 * np.ones(6)
        x_scale = 1.3
        y_labels = ["${1\over 6}$"] * 6
        hist = Histogram(np.ones(6), self.probs, mode = "widths", x_labels = "none",y_labels = y_labels,y_label_position = "center",y_scale = 20,x_scale = x_scale,)
        hist.rotate(-TAU / 4)
        for label in hist.y_labels_group:
            label.rotate(TAU / 4)
        hist.remove(hist.y_labels_group)
        self.play(FadeIn(hist))
        self.play(LaggedStartMap(FadeIn, hist.y_labels_group))



生成した動画

以上。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0