manimの作法 その27

#概要

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))



```

#生成した動画

https://www.youtube.com/watch?v=PT98_arbTz0


以上。