#概要
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))
#生成した動画
以上。