LoginSignup
1
5

はじめに

こんにちは!今回は、Pythonとpygameを使って、シンプルで使いやすいフローチャート作成ツールを作ってみました。この記事では、ツールの要件、仕様、実装方法、そして使い方までを詳しく解説します。

📋 要件

  1. 複数の図形(開始/終了、処理、判断、入出力)を配置できること
  2. 図形間を線で接続できること
  3. 図形内にテキストを入力できること
  4. 直感的な操作で図形の配置と接続ができること
  5. シンプルで分かりやすいユーザーインターフェース

🛠️ 仕様

image.png

  1. 画面レイアウト

    • 左側にパレット(図形選択用サイドバー)
    • 右側にキャンバス(フローチャート作成エリア)
  2. 図形

    • 種類:楕円(開始/終了)、長方形(処理)、ひし形(判断)、平行四辺形(入出力)
    • 各図形に4つの接続端子(上下左右)
  3. 操作方法

    • 図形の追加:パレットからドラッグ&ドロップ
    • 図形の移動:ドラッグ
    • 線の接続:端子をクリックして別の端子をクリック
    • テキスト入力:図形をクリックして選択後、キーボード入力
  4. 視覚的フィードバック

    • 線を引いている途中で仮の線を表示

🚀 実装

それでは、実際のコードを見ていきましょう。

import pygame
import sys

# 色の定義
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
LIGHT_BLUE = (173, 216, 230)
LIGHT_GREEN = (144, 238, 144)
LIGHT_PINK = (255, 182, 193)
LIGHT_YELLOW = (255, 255, 224)
GRAY = (200, 200, 200)

# 画面サイズ
WIDTH = 1000
HEIGHT = 600
SIDEBAR_WIDTH = 200

class Shape:
    def __init__(self, x, y, width, height, color, shape_type):
        self.rect = pygame.Rect(x, y, width, height)
        self.color = color
        self.text = ""
        self.shape_type = shape_type
        self.connectors = [
            pygame.Rect(x, y + height // 2 - 5, 10, 10),
            pygame.Rect(x + width - 10, y + height // 2 - 5, 10, 10),
            pygame.Rect(x + width // 2 - 5, y, 10, 10),
            pygame.Rect(x + width // 2 - 5, y + height - 10, 10, 10)
        ]

    def draw(self, screen):
        if self.shape_type == "rectangle":
            pygame.draw.rect(screen, self.color, self.rect)
        elif self.shape_type == "oval":
            pygame.draw.ellipse(screen, self.color, self.rect)
        elif self.shape_type == "diamond":
            points = [
                (self.rect.centerx, self.rect.top),
                (self.rect.right, self.rect.centery),
                (self.rect.centerx, self.rect.bottom),
                (self.rect.left, self.rect.centery)
            ]
            pygame.draw.polygon(screen, self.color, points)
        elif self.shape_type == "parallelogram":
            offset = self.rect.width // 4
            points = [
                (self.rect.left + offset, self.rect.top),
                (self.rect.right, self.rect.top),
                (self.rect.right - offset, self.rect.bottom),
                (self.rect.left, self.rect.bottom)
            ]
            pygame.draw.polygon(screen, self.color, points)

        for connector in self.connectors:
            pygame.draw.rect(screen, BLACK, connector)

        font = pygame.font.Font(None, 24)
        text_surface = font.render(self.text, True, BLACK)
        text_rect = text_surface.get_rect(center=self.rect.center)
        screen.blit(text_surface, text_rect)

    def contains(self, pos):
        return self.rect.collidepoint(pos)

    def get_connector(self, pos):
        for i, connector in enumerate(self.connectors):
            if connector.collidepoint(pos):
                return i
        return None

    def move(self, dx, dy):
        self.rect.move_ip(dx, dy)
        for connector in self.connectors:
            connector.move_ip(dx, dy)

class FlowchartTool:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        pygame.display.set_caption("フローチャートメーカー")
        self.clock = pygame.time.Clock()
        self.shapes = []
        self.lines = []
        self.dragging_shape = None
        self.selected_shape = None
        self.line_start = None
        self.font = pygame.font.Font(None, 24)
        self.palette = [
            Shape(20, 20, 160, 80, LIGHT_BLUE, "oval"),
            Shape(20, 120, 160, 80, LIGHT_GREEN, "rectangle"),
            Shape(20, 220, 160, 80, LIGHT_PINK, "diamond"),
            Shape(20, 320, 160, 80, LIGHT_YELLOW, "parallelogram")
        ]
        self.dragging_new = False

    def run(self):
        while True:
            self.handle_events()
            self.draw()
            self.clock.tick(60)

    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:  # 左クリック
                    self.handle_left_click(event.pos)
            elif event.type == pygame.MOUSEBUTTONUP:
                if event.button == 1:
                    self.handle_left_release(event.pos)
            elif event.type == pygame.MOUSEMOTION:
                self.handle_mouse_motion(event.pos, event.rel)
            elif event.type == pygame.KEYDOWN:
                if self.selected_shape:
                    if event.key == pygame.K_BACKSPACE:
                        self.selected_shape.text = self.selected_shape.text[:-1]
                    elif event.key == pygame.K_RETURN:
                        self.selected_shape = None
                    else:
                        self.selected_shape.text += event.unicode

    def handle_left_click(self, pos):
        for shape in self.palette:
            if shape.contains(pos):
                self.dragging_new = True
                new_shape = Shape(pos[0], pos[1], 160, 80, shape.color, shape.shape_type)
                self.shapes.append(new_shape)
                self.dragging_shape = new_shape
                return

        for shape in self.shapes:
            connector = shape.get_connector(pos)
            if connector is not None:
                if self.line_start is None:
                    self.line_start = (shape, connector)
                else:
                    start_shape, start_connector = self.line_start
                    if start_shape != shape:
                        self.lines.append((self.line_start, (shape, connector)))
                    self.line_start = None
                return

            if shape.contains(pos):
                self.dragging_shape = shape
                self.selected_shape = shape
                return

        self.selected_shape = None
        self.line_start = None

    def handle_left_release(self, pos):
        if self.dragging_new:
            self.dragging_new = False
            if pos[0] < SIDEBAR_WIDTH:
                self.shapes.pop()
            self.dragging_shape = None
            return

        self.dragging_shape = None

    def handle_mouse_motion(self, pos, rel):
        if self.dragging_shape:
            self.dragging_shape.move(rel[0], rel[1])

    def draw(self):
        self.screen.fill(WHITE)
        pygame.draw.rect(self.screen, GRAY, (0, 0, SIDEBAR_WIDTH, HEIGHT))
        
        for shape in self.shapes:
            shape.draw(self.screen)
        
        for line in self.lines:
            start_shape, start_connector = line[0]
            end_shape, end_connector = line[1]
            start_pos = start_shape.connectors[start_connector].center
            end_pos = end_shape.connectors[end_connector].center
            pygame.draw.line(self.screen, BLACK, start_pos, end_pos, 2)
        
        if self.line_start:
            start_shape, start_connector = self.line_start
            start_pos = start_shape.connectors[start_connector].center
            pygame.draw.line(self.screen, BLACK, start_pos, pygame.mouse.get_pos(), 2)
        
        for shape in self.palette:
            shape.draw(self.screen)
        
        pygame.display.flip()

if __name__ == "__main__":
    tool = FlowchartTool()
    tool.run()

🎨 主要な機能の解説

  1. Shape クラス

    • 各図形(楕円、長方形、ひし形、平行四辺形)を表現
    • 図形の描画、移動、テキスト表示を管理
    • 4つの接続端子を持つ
  2. FlowchartTool クラス

    • メインのアプリケーションロジックを管理
    • イベント処理(マウス操作、キーボード入力)
    • 図形の配置、線の接続、画面の描画を制御
  3. 図形の追加と移動

    • パレットからドラッグ&ドロップで新しい図形を追加
    • 既存の図形をドラッグして移動
  4. 線の接続

    • 2つの端子を順にクリックして線を引く
    • 接続中は仮の線を表示してフィードバックを提供
  5. テキスト入力

    • 図形をクリックして選択し、キーボードで入力

🖱️ 使い方

  1. 左側のパレットから必要な図形を選び、ドラッグしてキャンバスに配置します。
  2. 図形をクリック&ドラッグで移動できます。
  3. 線を引くには、以下の手順で行います:
    • 最初の図形の端子(小さな黒い四角)をクリックします。
    • マウスを動かすと、仮の線が表示されます。
    • 別の図形の端子をクリックすると、線が確定します。
  4. 図形をクリックして選択し、キーボードでテキストを入力できます。

🌈 今後の改善案

  1. 線の種類(直線、曲線、矢印)を選択できる機能
  2. 図形のリサイズ機能
  3. フローチャートの保存と読み込み機能
  4. テキストのフォントやサイズの変更機能
  5. 取り消し(Undo)と再実行(Redo)機能

🎉 さいごに

このツールを使えば、簡単にフローチャートを作成できます。プログラムの流れやビジネスプロセスの可視化に役立つでしょう。ぜひ、このコードを基に自分だけのカスタマイズを加えて、さらに素晴らしいツールに進化させてください!

Happy Flowcharting! 🚀✨

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5