話不多說,先上結果:
過程演示動態圖:
結果演示動態圖:
最終結果靜態圖:
然後說過程:
注意,這篇文章是在基於我的上一篇凸包問題分治法寫的,這些動態圖也是基於我的那篇文章代碼
先說說matplotlib怎麼實現動態圖:
在這裏我用的是 matplotlib 裏的animation.FuncAnimation
FuncAnimation類的說明(在這裏我用的就是這個):https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html
與之類似的還有一個ArtistAnimation(我在這裏踩過坑,後面說):https://matplotlib.org/api/_as_gen/matplotlib.animation.ArtistAnimation.html
官網的一些其他動態圖的例子:
https://matplotlib.org/gallery/index.html#animation
🆗,先說說我是怎麼踩的坑吧:
因爲我覺得ArtistAnimation和FuncAnimation比起來,參數更少,更易用,但是真的是一分錢一分貨(不知道這樣比喻…可能有點不恰當…😑,也可能是我太菜用不好…),因爲我用ArtistAnimation畫出來後,每次顯示出新的邊界線時,上一幀的邊界線就會消失。就像這樣:
後來在我深入瞭解(去stackoverflow逛了一圈)後,我明白了:
在調用ArtistAnimation之前,應已進行所有繪製並保存了相關Artist(ArtistAnimation使用固定的Artist對象)。ArtistAnimation只會記錄你當前的信息並畫在當前幀中,並不會保存上一幀的東西。所以纔會出現上面的情況。
(沒有捧一踩一的意思,也可能是我太菜還不會用…)
(本來我也不想用FuncAnimation啊,因爲它麻煩啊,但是麼的辦法…)
話說回來,其實FuncAnimation也挺好用的,雖然麻煩了點,但它是真的強大。
FuncAnimation 通過重複調用func函數,並在farg中傳入(可選)參數來製作動畫。
也就是說你沒必要先存儲結果在畫畫,這樣在數據較大時可以節約內存。
我是這麼用的:
def draws(list_points, list_frames, gif_name="save.gif"):
"""
生成動態圖並保存
:param list_points: 所有點集
:param list_frames: 幀 列表
:param gif_name: 保存動圖名稱
:return: .gif
"""
list_all_x = []
list_all_y = []
for item in list_points:
a, b = item
list_all_x.append(a)
list_all_y.append(b)
fig, ax = plt.subplots() # 生成軸和fig, 可迭代的對象
x, y = [], [] # 用於接受後更新的數據
line, = plt.plot([], [], color="red") # 繪製線對象,plot返回值類型,要加逗號
def init():
# 初始化函數用於繪製一塊乾淨的畫布,爲後續繪圖做準備
ax.set_xlim(min_value - abs(min_value * 0.1), max_value + abs(max_value * 0.1)) # 初始函數,設置繪圖範圍
ax.set_ylim(min_value - abs(min_value * 0.1), max_value + abs(max_value * 0.1))
return line
def update(lists):
a, b = lists
x.append(a)
y.append(b)
line.set_data(x, y)
return line
plt.scatter(list_all_x, list_all_y) # 繪製所有散點
ani = animation.FuncAnimation(fig, update, frames=list_frames, init_func=init, interval=500)
ani.save(gif_name, writer='pillow')
然後上源碼(複製可用的那種):
import random
import matplotlib.pyplot as plt
import matplotlib.animation as animation
min_value = 0
max_value = 100
draw_line_lists = []
def calc_area(a, b, c):
"""
判斷三角形面積
"""
x1, y1 = a
x2, y2 = b
x3, y3 = c
return x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3
def rand_point_set(n, range_min=0, range_max=101):
"""
隨機生成具有 n 個點的點集
:param range_max: 生成隨機點最小值,默認 0
:param range_min: 生成隨機點最大值,默認 100
:param n: int
:return: list [(x1,y1)...(xn,yn)]
"""
try:
return list(zip([random.uniform(range_min, range_max) for _ in range(n)],
[random.uniform(range_min, range_max) for _ in range(n)]))
except IndexError as e:
print("\033[31m" + ''.join(e.args) + "\n輸入範圍有誤!" + '\033[0m')
def border_point_up(left_point, right_point, lists, border_points):
"""
尋找上半部分邊界點
:param left_point: tuple, 最左邊的點
:param right_point: tuple, 最右邊的點
:param lists: 所有的點集
:param border_points: 邊界點集
:return:
"""
draw_line_lists.append(left_point)
draw_line_lists.append(right_point)
area_max = 0
max_point = ()
for item in lists:
if item == left_point or item == right_point:
continue
else:
max_point = item if calc_area(left_point, right_point, item) > area_max else max_point
area_max = calc_area(left_point, right_point, item) if calc_area(left_point, right_point,
item) > area_max else area_max
if area_max != 0:
border_points.append(max_point)
border_point_up(left_point, max_point, lists, border_points)
border_point_up(max_point, right_point, lists, border_points)
def border_point_down(left_point, right_point, lists, border_points):
"""
尋找下半部分邊界點
:param left_point: tuple, 最左邊的點
:param right_point: tuple, 最右邊的點
:param lists: 所有的點集
:param border_points: 邊界點集
:return:
"""
draw_line_lists.append(left_point)
draw_line_lists.append(right_point)
area_max = 0
max_point = ()
for item in lists:
if item == left_point or item == right_point:
continue
else:
max_point = item if calc_area(left_point, right_point, item) < area_max else max_point
area_max = calc_area(left_point, right_point, item) if calc_area(left_point, right_point,
item) < area_max else area_max
if area_max != 0:
border_points.append(max_point)
border_point_down(left_point, max_point, lists, border_points)
border_point_down(max_point, right_point, lists, border_points)
def order_border(lists):
"""
返回順時針的邊界點集
:param lists: 無序邊界點集
:return: list [( , )...( , )]
"""
lists.sort()
first_x, first_y = lists[0] # 最左邊的點
last_x, last_y = lists[-1] # 最右邊的點
list_border_up = [] # 上半邊界
for item in lists:
x, y = item
if y > max(first_y, last_y):
list_border_up.append(item)
if min(first_y, last_y) < y < max(first_y, last_y):
if calc_area(lists[0], lists[-1], item) > 0:
list_border_up.append(item)
else:
continue
list_border_down = [_ for _ in lists if _ not in list_border_up] # 下半邊界
list_end = list_border_up + list_border_down[::-1] # 最終順時針輸出的邊界點
return list_end
def draws(list_points, list_frames, gif_name="save.gif"):
"""
生成動態圖並保存
:param list_points: 所有點集
:param list_frames: 幀 列表
:param gif_name: 保存動圖名稱
:return: .gif
"""
list_all_x = []
list_all_y = []
for item in list_points:
a, b = item
list_all_x.append(a)
list_all_y.append(b)
fig, ax = plt.subplots() # 生成軸和fig, 可迭代的對象
x, y = [], [] # 用於接受後更新的數據
line, = plt.plot([], [], color="red") # 繪製線對象,plot返回值類型,要加逗號
def init():
# 初始化函數用於繪製一塊乾淨的畫布,爲後續繪圖做準備
ax.set_xlim(min_value - abs(min_value * 0.1), max_value + abs(max_value * 0.1)) # 初始函數,設置繪圖範圍
ax.set_ylim(min_value - abs(min_value * 0.1), max_value + abs(max_value * 0.1))
return line
def update(lists):
a, b = lists
x.append(a)
y.append(b)
line.set_data(x, y)
return line
plt.scatter(list_all_x, list_all_y) # 繪製所有散點
ani = animation.FuncAnimation(fig, update, frames=list_frames, init_func=init, interval=500)
ani.save(gif_name, writer='pillow')
def show_result(list_points, list_borders):
"""
畫圖
:param list_points: 所有點集
:param list_borders: 所有邊集
:return: picture
"""
list_all_x = []
list_all_y = []
for item in list_points:
a, b = item
list_all_x.append(a)
list_all_y.append(b)
for i in range(len(list_borders)-1):
item_1=list_borders[i]
item_2 = list_borders[i+1]
# 橫座標,縱座標
one_, oneI = item_1
two_, twoI = item_2
plt.plot([one_, two_], [oneI, twoI])
plt.scatter(list_all_x, list_all_y)
plt.show()
def main():
"""
:return: 所有點
"""
global min_value, max_value
inputs = list(map(int, input().split()))
if len(inputs) == 1:
n = inputs[0]
return rand_point_set(n)
elif len(inputs) == 2:
n, min_value = inputs[0], inputs[1]
return rand_point_set(n, min_value)
elif len(inputs) == 3:
n, min_value, max_value = inputs[0], inputs[1], inputs[2]
return rand_point_set(n, min_value, max_value)
else:
print("\033[31m輸入數據太多,請重新輸入!\033[0m")
main()
if __name__ == "__main__":
print("""輸入規則:
最少一個最多三個
後面可以跟數字用來指定生成區間(默認[0,100]),中間用空格隔開
例如:
輸入 10 ---即爲在默認區間[0,100]生成10個隨機點
輸入 10 50 ---即爲在區間[50,100]生成10個隨機點
輸入 10 50 200 ---即爲在區間[50,200]生成10個隨機點
請輸入:\t""")
list_points = main() # 所有點
list_points.sort()
border_points = [] # 邊界點集
border_point_up(list_points[0], list_points[-1], list_points, border_points) # 上邊界點集
border_point_down(list_points[0], list_points[-1], list_points, border_points) # 下邊界點集
border_points.append(list_points[0])
border_points.append(list_points[-1]) # 將首尾兩個點添加到邊界點集中
list_borders = order_border(border_points) # 順時針邊界點
# print(order_border(border_points)) # 順時針輸出邊界點
list_borders.append(list_borders[0]) # 順時針邊界點閉合
show_result(list_points,list_borders) # 顯示靜態結果
draws(list_points, draw_line_lists, "process.gif") # 繪製動態過程
draws(list_points, list_borders, "result.gif") # 繪製動態結果