分治法進階篇 | 利用matplotlib畫出凸包問題分治遞歸策略實現過程動態圖

話不多說,先上結果:

過程演示動態圖:
在這裏插入圖片描述
結果演示動態圖:
在這裏插入圖片描述
最終結果靜態圖:
在這裏插入圖片描述
然後說過程:


注意,這篇文章是在基於我的上一篇凸包問題分治法寫的,這些動態圖也是基於我的那篇文章代碼


先說說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")  # 繪製動態結果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章