💎

Diagramsでいらすとやの画像を使ってアーキテクチャ図を書いてみた

2023/10/27に公開

はじめに

アーキテクチャ図は、システムの全体像を視覚的に理解するための強力なツールです。
Pythonのモジュールで楽にアーキテクチャ図を作れないかという邪な考えで
Diagramsというモジュールを知り、あわよくばChatGPTに8割コードを書いてもらえないか
そんな感じでやっていきます。

今回はせっかくなので、通常のアーキテクチャ図だと面白みがないので
マスクド・アナライズさんのバズった絵を再現したいなと思います。

DiagramsとGraphvizのインストール及び環境変数設定

  1. Diagramsのインストール:

    pip install diagrams
    

    DiagramsはPythonライブラリなので、pipコマンドで簡単にインストールできます。

  2. Graphvizのインストール: Diagramsは内部でGraphvizを使用しているため、インストールが必要です。公式サイトからダウンロードし、環境変数PATHに追加してください。

いらすとやから画像を検索

  1. いらすとやの公式サイトで目的のイラストを探します。
  2. 気合で元の画像を検索しまくって探します。※なかなか時間がかかりました。

GPT4-Vで仮の配置の仮コードを生成

  1. GPT4-Vに上記の画像を入力し、初期のDiagramsコードスニペットを生成します。
  2. 生成されたコード
from diagrams import Diagram, Cluster
from diagrams.custom import Custom

with Diagram("図の説明", show=False):
    # カスタムアイコンの定義
    old_man = Custom("社長の意向", "path_to_icon_1.png")
    non_technical_ceo = Custom("経営者のないIT部門", "path_to_icon_2.png")
    ceo = Custom("命令だけの指導", "path_to_icon_3.png")
    it_worker = Custom("非効率なIT部署", "path_to_icon_4.png")
    non_ai_manager = Custom("AIはわからない代理店ler", "path_to_icon_5.png")
    first_worker = Custom("実施が遅い協力会社1号", "path_to_icon_6.png")
    second_worker = Custom("仕事が遅い協力会社2号", "path_to_icon_7.png")
    ai_system = Custom("複雑のAIベンチャー", "path_to_icon_8.png")
    vacationer = Custom("フルーツスムージー", "path_to_icon_9.png")
    presenter = Custom("コンサルの無駄な提案", "path_to_icon_10.png")
    factory = Custom("理解の無い現場", "path_to_icon_11.png")

    old_man >> non_technical_ceo >> ceo >> it_worker
    ceo >> presenter >> factory
    it_worker >> non_ai_manager >> first_worker >> second_worker >> ai_system >> vacationer
  1. ローカル環境で実行します。
    実行結果

    画像が入っていませんが、それぞれのノードのラベルがOCRで抽出されたり、大まかな関係性が反映されています。

ChatGPT4のAdvanced Data Analysisで仮コード生成

  1. GPT4のAdvanced Data Analysisを使用して、いらすとやでダウンロードしてきた画像を添付し、先程のコードに引用するようコードの改変をお願いしてみます。
以下のコードで画像は添付ファイルから割り当てるようなコードに修正してください。
なお実際の環境では画像ファイルはC:\Users\****\diagrams\resource\に同ファイル名で格納されています。
#自分のPCの画像の格納先を指定
====
#先程のコード

何度か修正やリファクタリングした結果のコード

from diagrams import Diagram, Cluster
from diagrams.custom import Custom  # Customクラスを正しくインポート
import os

def create_diagram(base_path, image_names):
    image_paths = {key: os.path.join(base_path, val) for key, val in image_names.items()}
    missing_keys = [key for key, val in image_paths.items() if not os.path.exists(val)]
    
    if missing_keys:
        print(f"Warning: Missing images for keys {missing_keys}")

    with Diagram("図の説明", show=False):
        with Cluster("Cluster 1"):
            elderly = Custom("社長の思いつき", image_paths.get("elderly"))
            non_tech_boss = Custom("命令だけの部長", image_paths.get("non_tech_boss"))
            tech_boss = Custom("開発力のないIT部門", image_paths.get("tech_boss"))
            non_tech_boss >> tech_boss

        with Cluster("Cluster 2"):
            non_ai_engineer = Custom("AIはわからない大手SIer", image_paths.get("non_ai_engineer"))
            coder_1 = Custom("実績がない協力会社1", image_paths.get("coder_1"))
            coder_2 = Custom("仕事が遅い協力会社2", image_paths.get("coder_2"))
            non_ai_engineer >> coder_1 >> coder_2

        factory_worker = Custom("現場の無い現場監督", image_paths.get("factory_worker"))
        presenter = Custom("コンサルの無駄な提案", image_paths.get("presenter"))
        ai_product = Custom("独自のAIベンチャー", image_paths.get("ai_product"))
        catastrophe = Custom("フルーツランドジョージ", image_paths.get("catastrophe"))

        elderly >> non_tech_boss
        tech_boss >> non_ai_engineer
        coder_2 >> ai_product >> catastrophe

# 画像ファイル名とノード名の対応
image_names = {
    "elderly": "ojisan4.png",
    "non_tech_boss": "walking_businessman2.png",
    "tech_boss": "sensu_salaryman.png",
    "non_ai_engineer": "building_koujou_entotsu.png",
    "coder_1": "job_programmer.png",
    "coder_2": "job_sagyouin_computer_man.png",
    "factory_worker": "ai_shigoto.png",
    "presenter": "presentation_man.png",
    "ai_product": "nomad_surfing_nangoku.png",
    "catastrophe": "nomad_surfing_nangoku.png"
}

# 基本パス
# 自分の環境の画像が格納されたフォルダパスを指定してください
base_path = "C:\\Users\\****\\****\\diagrams\\resource"


# ダイアグラムの作成
create_diagram(base_path, image_names)

結果

ちょっとそれっぽくなってきましたね!

自分で画像選択や配置を調整

  1. 生成されたコードを基に、いらすとやの画像を適切な位置に選択されたファイルを修正します。
  2. ノード間の関係や依存関係を>><<演算子を使用して明示的に作成します。

node_attrとgraph_attrで見栄えなど設定

  1. node_attrを使用して、各ノードの属性、例えば色やサイズを調整します。
  2. graph_attrでグラフ全体の属性、例えば背景色や向きなどを設定します。

<コード>

from diagrams import Diagram, Cluster
from diagrams.custom import Custom
import os

def create_image_paths(base_path, image_names):
    return {key: os.path.join(base_path, val) for key, val in image_names.items()}

def check_missing_images(image_paths):
    missing_keys = [key for key, val in image_paths.items() if not os.path.exists(val)]
    if missing_keys:
        print(f"Warning: Missing images for keys {missing_keys}")

def create_diagram(base_path, image_names):
    image_paths = create_image_paths(base_path, image_names)
    check_missing_images(image_paths)

    common_graph_attrs = {"label": "", "color": "none", "penwidth": "0", "bgcolor": "none"}
    diagram_attrs = {
        "node_attr": {"fontname": "Meiryo UI", "fontsize": "12", "fontweight": "bold", "width": "1.2", "height": "1.2"},
        "graph_attr": {"labelloc": "t", "fontweight": "bold", "fontsize": "28", "fontname": "Meiryo UI"}
    }

    with Diagram("絶対に失敗するChatGPT導入プロジェクト in JTC", show=False, **diagram_attrs):
        with Cluster("Cluster 1", graph_attr=common_graph_attrs):
            elderly = Custom("社長の思いつき", image_paths.get("elderly"))
            non_tech_boss = Custom("命令だけの部長", image_paths.get("non_tech_boss"))
            presenter = Custom("プロの驚き屋", image_paths.get("presenter"))
            elderly >> non_tech_boss << presenter

        with Cluster("Cluster 2", graph_attr=common_graph_attrs):
            tech_boss = Custom("外注するだけのIT部門", image_paths.get("tech_boss"))
            factory_worker = Custom("理解の無い現場", image_paths.get("factory_worker"))
            tech_boss << factory_worker
        
        with Cluster("Cluster 3", graph_attr=common_graph_attrs):
            non_ai_engineer = Custom("ノウハウがない大手SIer", image_paths.get("non_ai_engineer"))
            coder_1 = Custom("実績がほしい下請け1", image_paths.get("coder_1"))
            coder_2 = Custom("仕事がほしい下請け2", image_paths.get("coder_1"))
            ai_product = Custom("謎のChatGPTスタートアップ", image_paths.get("ai_product"))
            catastrophe = Custom("フリーランスエンジニア", image_paths.get("catastrophe"))
            non_ai_engineer >> coder_1 >> coder_2 >> ai_product >> catastrophe

        non_tech_boss >> tech_boss
        tech_boss >> non_ai_engineer

image_names = {
    "elderly": "sensu_salaryman.png",
    "non_tech_boss": "ojisan4.png",
    "tech_boss": "job_sagyouin_computer_man.png",
    "non_ai_engineer": "walking_businessman2.png",
    "coder_1": "job_programmer.png",
    "coder_2": "job_sagyouin_computer_man.png",
    "factory_worker": "building_koujou_entotsu.png",
    "presenter": "presentation_man.png",
    "ai_product": "ai_shigoto.png",
    "catastrophe": "nomad_surfing_nangoku.png"
}

# 自分の環境の画像が格納されたフォルダパスを指定してください
base_path = "C:\\Users\\****\\****\\diagrams\\resource"
create_diagram(base_path, image_names)

<出力>

以下の部分でClusterのラベルだったり囲いの表示をオフにします。

common_graph_attrs = {"label": "", "color": "none", "penwidth": "0", "bgcolor": "none"}

各Clusterでcommon_graph_attrsを参照します。

with Cluster("Cluster 1", graph_attr=common_graph_attrs):

まとめ

次に書こうと思っていた記事のLangChainのload_summarized_chainのRefinmeについてのアーキテクチャを書くために勉強していたが、絶対に手書きの方が速いのと
あと配置を調整できないところがなかなかもどかしい
とはいえChatGPTに明確な指示を与えたらわりと的確なアーキテクチャ図を生成してくれるので知見が深まったことは喜ばしい

Discussion