カテゴリー

発展系

【plotly&コッホ曲線】フラクタル図形の雪の結晶をスライダーで作成してみる

2021年10月3日

こんな人にオススメ

雪の結晶ってフラクタル図形の一種でキレイなのは知ってるけど、これって実際にグラフで描ける?

ということで、今回は雪の結晶として有名なフラクタル図形の一種、コッホ曲線をpltplotlyを用いてグラフ化してみる。雪の結晶はかなり興味深い構造をしていて、端の方を拡大すると拡大前と同じ構造が現れる。

だから、騙し絵のような形でループ動画で無限に近づき続ける動画を作成することもできる。構造が同じだから動画の切れ目がわかりにくくなるからだ。

今回は完全に執筆者自身の疑問を解決するための記事になるが、キレイな図形なので是非とも色んなフラクタル図形を描いてくれたら幸いだ。

python環境は以下。

  • Python 3.9.7
  • numpy 1.21.2
  • matplotlib 3.4.3
  • plotly 5.3.1
  • plotly-orca 3.4.2
スポンサーリンク
スポンサーリンク

運営者のメガネと申します。TwitterInstagramも運営中。

自己紹介はこちらから、お問い合わせはこちらからお願いいたします。

運営者メガネ

作成したコード全文

下準備

import sys
import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import plotly.io as pio

sys.path.append('../../')
import plt_layout_template
import plotly_layout_template as template
plt.ioff()

まずは下準備としてのimport関連。plt_layout_templatematplotlib.pyplotplt)の自作テンプレート。plotly_layout_templateplotlyの自作テンプレー ト。これらを使用することで簡単にキレイなグラフを描ける。

plt.rcParamsでデフォルトを変更した後のグラフ
【pltテンプレート】matplotlib.pyplotのグラフ作成テンプレート

続きを見る

【随時更新 備忘録】plotlyのグラフ即席作成コード

続きを見る

また、plt.ioff()はVisual Studio Codeの拡張機能「Code Runner」でコードを実行する際に設定する内容。デフォルトでplt.ioff()になっているが、自作テンプレートではplt.ion()なので設定している。

これを設定しないとCode Runnerでpltのグラフを表示することができない。グラフを保存するだけやエラーなく動作することを確認したいだけなら書く必要はない。

コッホ曲線のデータ作成

def _koch_snowflake_complex(order, scale):
    if order == 0:
        # 初期三角形
        angles = np.array([0, 120, 240]) + 90
        return scale / np.sqrt(3) * np.exp(np.deg2rad(angles) * 1j)
    else:
        ZR = 0.5 - 0.5j * np.sqrt(3) / 3

        p1 = _koch_snowflake_complex(order - 1, scale)  # 始点
        p2 = np.roll(p1, shift=-1)  # 終点
        dp = p2 - p1  # 視点と終点をつなぐ

        new_points = np.empty(len(p1) * 4, dtype=np.complex128)
        new_points[::4] = p1
        new_points[1::4] = p1 + dp / 3
        new_points[2::4] = p1 + dp * ZR
        new_points[3::4] = p1 + dp / 3 * 2
        return new_points

今回はフラクタル図形の1つであるコッホ曲線を使用するんだけど、これをグラフにするには少し骨が折れる。自分で計算してもよかったけど面倒だったのでpltの「Filled polygon」にあったコードを参考にした。

このサイトではコッホ曲線をグラフにするためのコードとグラフ化の一例が載ってある。上のコードが実数と虚数を計算しているっぽい。そして以下のコードでこれをx, yに落としている。

def koch_snowflake(order, scale=2):
    """
    フラクタル図形であるコッホ曲線を描くためのx, yを出力

    Parameters
    ----------
    order : int
        曲線の次元
    scale : float
        曲線全体のサイズ
    """

    points = _koch_snowflake_complex(order, scale)
    x, y = points.real, points.imag
    return x, y

このx, yをグラフにすることでコッホ曲線を描くことができる。

pltでグラフ化

まずはpltでグラフ化。pltのサイトの方ではオーダーごとに1つのグラフを作成していたが、ここではsubplotを使用して一気に複数グラフを描くことにした。こっちの方が全体を俯瞰しやすい。

【plt&subplot】pythonのpltで複数グラフを1つの画像に描く

続きを見る

画像だと伝わりにくいけど、orderが大きいグラフを実際に細部を拡大してみると同じ構造が続いているのがわかる。これがフラクタル図形。

# subplotの作成
fig, ax = plt.subplots(nrows=3, ncols=3, figsize=(10, 10))
# グラフレイアウトの調節
plt.subplots_adjust(
    left=0,  # グラフ左を0とした時の左の余白
    bottom=0.1,  # グラフ左を0とした時の下の余白
    right=1,  # グラフ左を0とした時の右の余白
    top=0.95,  # グラフ左を0とした時の上の余白
    wspace=0,  # グラフの横の間隔
    hspace=0.5,  # グラフの縦の間隔
)

# axを展開して2次元から1次元に(3 x 3 = 9の配列)
ax = ax.ravel()
for order in range(9):
    x, y = koch_snowflake(order=order)

    ax[order].set_title(f"order: {order}")
    # x, yの最小値と最大値を設定
    ax[order].set_xlim(-1.5, 1.5)
    ax[order].set_ylim(-1.5, 1.5)

    ax[order].fill(x, y)

    # 縦横比を一定に
    ax[order].set_aspect('equal')
# グラフの保存と表示
fig.savefig('plt_koch_snowflake.png')
plt.show()

plotlyでグラフ化するための関数たち

続いてはplotlyを使用してグラフ化してみるんだけど、plotlyでもsubplotするのは面白くない。ということでplotlyではスライダーを使用してグラフ化してみる。

【plotly&スライダー】plotlyのslidersにスライダーを追加

続きを見る

plotlyでグラフを描くとなると結構コードが長くなるので、関数に小分けしてグラフを作成することにする。関数を使用すると繰り返し使用したり修正が楽だったりする。

【plotly&工夫】楽にグラフを描くためのplotlyの関数化

続きを見る

プロットデータ作成用の関数

# プロットデータ作成用関数
def scatter(x, y, name):
    d = go.Scatter(
        x=x, y=y, name=name,
        mode='lines',
        fill='toself',
        visible=False,
    )
    return d

まずはプロットデータ作成用の関数。今回は塗りつぶしを行うということでfillを自分自身であるfill='toself'に設定。また、スライダーで順次プロットを表示したいのでvisibleFalseにして初めは非表示にしている。

【plotly&fill】goで領域を塗りつぶし

続きを見る

スライダー設定用の関数

# スライダーの設定用関数
def slider(plot):
    # スライダーの各項目の設定
    steps = []
    for num, _ in enumerate(plot):
        visible = [False] * len(plot)  # 全プロットを非表示
        visible[num] = True  # 該当プロットのみ表示

        name = f"order: {num}"
        step = dict(
            method='update',  # データとレイアウトを変更する
            label=num,  # スライダー部分の文字
            args=[
                dict(visible=visible),  # 表示・非表示の設定
                dict(title=f"Koch snowflake ({name})")  # グラフタイトル
            ]
        )
        steps.append(step)

    # スライダーをグラフに設置するための設定
    sliders = [
        dict(
            active=0,  # 初期表示
            currentvalue_prefix="order: ",  # スライダー上の表示の接頭辞
            steps=steps,
        )
    ]
    return sliders

スライダーの設定手順は以下。

  1. 全プロットを非表示に
  2. 各スライダー項目に該当するプロットを表示に変更
  3. 各スライダー項目のタイトルなどの設定をする

for文の「_」は使用しないという慣例的な表現。for num in range(len(plot))としてもいいけどなんか癪だったのでenumrateを使った。どっちでもいい。

今回はプロットを表示・非表示にしたいのでデータをいじっているし、グラフタイトルも変更するのでレイアウトもいじっている。よってmethodはデータとレイアウトを司どるupdateにした。

また、stepsでスライダーの設定はできたが、これをグラフに設置しないといけない。その設定が変数slidersになる。このslidersをレイアウトに入れることで、グラフにスライダーを設定できる。

レイアウト作成用の関数

# レイアウト設定用の関数
def set_layout(sliders):
    layout = go.Layout(
        template=template.plotly_layout(),
        title="Koch snowflake (order: 0)",
        xaxis=dict(range=(-1.5, 1.5)),
        yaxis=dict(
            range=(-1.5, 1.5),
            scaleanchor="x", scaleratio=1,  # グラフ比率をxと合わせる
        ),
        sliders=sliders,
    )
    return layout

レイアウト作成で自作テンプレートを適用する。x, y軸の描画範囲を固定することで変に軸範囲のズレが起きないようにしている。また、scaleanchor, scaleratioを設定することでx, y軸の長さ比率を等しくしている。

先ほど作成したslidersはここに入れてグラフに適用している。なお、ボタンを設置する際にもレイアウトでupdatemenusという引数で設定可能。

【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加

続きを見る

グラフ保存用の関数

# グラフ保存用の関数
def save(fig, config, save_name):
    pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca'
    pio.write_html(fig, f"{save_name}.html", config=config,)
    pio.write_image(fig, f"{save_name}.png")

最後はグラフ保存用の関数。保存形式はhtmlとpng。configをhtmlで設定することで、保存後のグラフでもツールバーを表示することができる。ツールバーでは図形の追加などが行える。

【plotly&config】グラフのツールバーを編集する

続きを見る

plotlyでグラフを静止画として保存する際に出るポップアップ
【plotly&orca】plotlyで静止画保存(orca)

続きを見る

plotlyのスライダーでグラフ化


ということで、plotlyのスライダーを使用してグラフを作成する。基本的には上で作成した関数を使用すればいいのでスッキリした内容で作成可能。

オーダーが大きいと記事に載せられないくらい重くなるので、オーダーは少し下げておいた。

スライダーを使用しているので、下のバーを左右に移動させることでコッホ曲線の次数を変更することができる。subplotとは異なり一覧性はないけど、同じ位置で次数が増えていく様子がわかりやすい。

def graph(orders):
    plot = []
    for order in range(orders):
        # コッホ曲線のx, yを計算
        x, y = koch_snowflake(order=order)
        name = f"order: {order}"

        # プロットデータを作成、格納
        d = scatter(x=x, y=y, name=name)
        plot.append(d)
    # 初期表示として初めのプロットのvisibleをTrueに変更
    plot[0]['visible'] = True

    # スライダーの設定
    sliders = slider(plot)

    # レイアウトの設定
    layout = set_layout(sliders=sliders)

    # グラフの表示と保存
    config = template.plotly_config()
    fig = go.Figure(data=plot, layout=layout)
    fig.show(config=config)

    save(fig=fig, config=config, save_name=f"plotly_koch_snowflake_{orders}")

graph(orders=8)

自然現象をグラフ化するのはおもしろい

今回はフラクタル図形の1つであるコッホ曲線をpltplotlyを使用してグラフ化してみた。コッホ曲線のような不思議な性質を持つ図形を自分で描けるとどんどん改良したくなる。

コッホ曲線で言えば勝手にズームして無限ループできるようなアニメーションを加えてみても面白いかもしれない。世の中には不思議な図形が山のようにあるので、また気が向いたら作成したい。

関連記事

【plt&円グラフ】plt.pieでmatplotlibの円グラフを作成

続きを見る

【plotly&Scattergeo】世界地図に各国の首都の位置をプロット

続きを見る

【plotly&レーダーチャート】plotlyのRadar Chartの使い方とか設定とか

続きを見る

関連コンテンツ

スポンサーリンク

Amazonのお買い物で損したない人へ

1回のチャージ金額通常会員プライム会員
¥90,000〜2.0%2.5%
¥40,000〜1.5%2.0%
¥20,000〜1.0%1.5%
¥5,000〜0.5%1.0%

Amazonギフト券にチャージすることでお得にお買い物できる。通常のAmazon会員なら最大2.0%、プライム会員なら2.5%還元なのでバカにならない。

ゲットしたポイントは通常のAmazonでのお買い物に使えるからお得だ。一度チャージしてしまえば、好きなタイミングでお買いものできる。

なお、有効期限は10年だから安心だ。いつでも気軽にAmazonでお買い物できる。

Amazonチャージはここから出来るで

もっとお得なAmazon Prime会員はこちらから

30日間無料登録

執筆者も便利に使わせてもらってる

スポンサーリンク

  • この記事を書いた人

メガネ

独学でpythonを学び天文学系の大学院を修了。 ガジェット好きでMac×Android使い。色んなスマホやイヤホンを購入したいけどお金がなさすぎて困窮中。 元々、人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂ったために今も人生めちゃくちゃ楽しい。 pythonとガジェットをメインにブログを書いていますので、興味を持たれましたらちょこちょこ訪問してくだされば幸いです🥰。 自己紹介→変わって楽しいの繰り返し

-発展系
-, , ,