見出し画像

PythonでCanvas使ってみる(1)

 customtkinterでのレイアウト方法など、基礎的なところは把握できたつもりなので、「前からやりたかったこと」に手を出そうと思います。Canvasです。
 ここで心機一転、これから作るソースコードを入れる新しいフォルダを用意しました。まずは下準備。


下準備

setup.cfgの配置

[isort]
profile = black

[flake8]
max-line-length = 88
ignore = E203, W503

.gitignoreの配置

*
!.gitignore
!setup.cfg
!*.py
!*/

Gitのリポジトリの作成

⇒Gitリポジトリを初期化(すべてはここからスタート)

コード書いてみる

まず公式のCTkWidgetドキュメントサイト

で、CTkCanvasを探したんですが見つからない。
でも「無い」わけじゃないようです。下のように、tk.Canvasと同じように、CTkCanvasに対してcreate_line関数を書いてみたらエラー無く動く。

どうやら「中の人」ならぬ、中の「ウィジェット」はtk.Canvasそのままなのかも?

import customtkinter as ctk

class App(ctk.CTk):
    def __init__(self, title):
        #main window
        super().__init__()
        self.title(title)
        #widget配置
        self.canvas = ctk.CTkCanvas(self,width=800, height=600, bg="black")
        self.canvas.pack()
        self.button1 = ctk.CTkButton(self,text = "コマンド1",command=self.mycommand1)
        self.button1.pack()
        #mainloop
        self.mainloop()
    def mycommand1(self):
        self.canvas.create_line(100,100,700,100, fill="white", width=5)
        self.canvas.create_line(700,100,700,500, fill="green", width=5)
        self.canvas.create_line(700,500,100,500, fill="red", width=5)
        self.canvas.create_line(100,500,100,100, fill="blue", width=5)
App("My First Canvas")

起動して

コマンド1をクリック

動いた!

 ソースコードをFormatterで整形しておきます。
・Blackにかけて

import customtkinter as ctk


class App(ctk.CTk):
    def __init__(self, title):
        # main window
        super().__init__()
        self.title(title)
        # widget配置
        self.canvas = ctk.CTkCanvas(self, width=800, height=600, bg="black")
        self.canvas.pack()
        self.button1 = ctk.CTkButton(self, text="コマンド1", command=self.mycommand1)
        self.button1.pack()
        # mainloop
        self.mainloop()

    def mycommand1(self):
        self.canvas.create_line(100, 100, 700, 100, fill="white", width=5)
        self.canvas.create_line(700, 100, 700, 500, fill="green", width=5)
        self.canvas.create_line(700, 500, 100, 500, fill="red", width=5)
        self.canvas.create_line(100, 500, 100, 100, fill="blue", width=5)


App("My First Canvas")

・これをLinterであるFlake8にかけて・・・何も指摘事項ありませんでした。

座標変換

 いま、「canvas表示の座標系」は左上が(0,0)右下が(800,600)です。これをふつうのxy座標では、x=-400から400,y=-300から300の範囲で扱いたい。この場合xy座標(x,y)を「canvas表示の座標系」(wx,wy)に変換するには
wx = x+400 ,  wy=-y+300
これで
原点(0,0)が(400,300)へ、
左下(-400,-300)が(0,600)へ
右上(400,300)が(800,0)へ
と変換されます。この変換をしてcanvasに出力すればいいのだけど、汎用的な座標変換を用意しておきたいと思います。縮尺変更があることも見込んで、
※XY座標上で、X座標及びY座標が共に異なる二つの点(x1,y1)を(wx1,wy1)へ、(x2,y2)を(wx2,wy2)へ変換する
・・という初期条件を与えて、任意の座標(x,y)を与えると、表示用の座標(wx,wy)を返してくれる処理をクラスで用意したいと思います。

 これくらいは自分の頭で考えないと、脳が退化しそう(^_^;)ですが、誘惑に負けてCopilot先生に頼っちゃいます。タイパですタイパ。

import tkinter as tk

class CoordinateTransformer:
    def __init__(self, x1, y1, wx1, wy1, x2, y2, wx2, wy2):
        # 設定された2点を基に変換行列を計算
        self.scale_x = (wx2 - wx1) / (x2 - x1)
        self.scale_y = (wy2 - wy1) / (y2 - y1)
        self.translate_x = wx1 - self.scale_x * x1
        self.translate_y = wy1 - self.scale_y * y1

    def transform(self, x, y):
        # 任意の座標を変換
        wx = self.scale_x * x + self.translate_x
        wy = self.scale_y * y + self.translate_y
        return wx, wy

# 例: (-400, -300) -> (0, 600), (400, 300) -> (800, 0) の変換
transformer = CoordinateTransformer(-400, -300, 0, 600, 400, 300, 800, 0)

# テスト
x, y = 0, 0
wx, wy = transformer.transform(x, y)
print(f"({x}, {y}) -> ({wx}, {wy})")  # 出力: (0, 0) -> (400.0, 300.0)

# tkinter Canvasの例
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg="white")
canvas.pack()

# 例として、中心に円を描画
cx, cy = transformer.transform(0, 0)
canvas.create_oval(cx-10, cy-10, cx+10, cy+10, fill="blue")

root.mainloop()

サンプルコードそのまま実行してみる。

うん、できています。じゃあ、この「クラス」部分だけmytool_lib.pyという別ファイル(ライブラリファイル)に放り込んでおきます。

#mytool_lib.py

class CoordinateTransformer:
    def __init__(self, x1, y1, wx1, wy1, x2, y2, wx2, wy2):
        # 設定された2点を基に変換行列を計算
        self.scale_x = (wx2 - wx1) / (x2 - x1)
        self.scale_y = (wy2 - wy1) / (y2 - y1)
        self.translate_x = wx1 - self.scale_x * x1
        self.translate_y = wy1 - self.scale_y * y1

    def transform(self, x, y):
        # 任意の座標を変換
        wx = self.scale_x * x + self.translate_x
        wy = self.scale_y * y + self.translate_y
        return wx, wy

座標変換のクラスを使って図形を描いてみる

縮尺も変えてみます。座標変換クラス (-4, -3) -> (0, 600), (4, 3) -> (800, 0) の変換を設定してみます。

from mytool_lib import CoordinateTransformer
import customtkinter as ctk

class App(ctk.CTk):
    def __init__(self, title):
        
        # main window
        super().__init__()
        self.title(title)
        
        # widget配置
        self.canvas = ctk.CTkCanvas(self, width=800, height=600, bg="black")
        self.canvas.pack()
        self.button1 = ctk.CTkButton(self, text="コマンド1", command=self.mycommand1)
        self.button1.pack()

        #座標変換クラス (-4, -3) -> (0, 600), (4, 3) -> (800, 0) の変換
        self.tfm = CoordinateTransformer(-4,-3,0,600,4,3,800,0)

        # mainloop
        self.mainloop()

    def mycommand1(self):
        self.canvas.create_line(self.tfm.transform(1,0),self.tfm.transform(0,1), fill="white", width=5)
        self.canvas.create_line(self.tfm.transform(0,1),self.tfm.transform(-1,0), fill="green", width=5)
        self.canvas.create_line(self.tfm.transform(-1,0),self.tfm.transform(0,-1), fill="red", width=5)
        self.canvas.create_line(self.tfm.transform(0,-1),self.tfm.transform(1,0), fill="blue", width=5)

App("My First Square")

いい感じじゃないですか?
行長すぎ問題など、コードが汚くなってきたのでFormatter Linterかけてみます。

black適用後

from mytool_lib import CoordinateTransformer
import customtkinter as ctk


class App(ctk.CTk):
    def __init__(self, title):

        # main window
        super().__init__()
        self.title(title)

        # widget配置
        self.canvas = ctk.CTkCanvas(self, width=800, height=600, bg="black")
        self.canvas.pack()
        self.button1 = ctk.CTkButton(self, text="コマンド1", command=self.mycommand1)
        self.button1.pack()

        # 座標変換クラス (-4, -3) -> (0, 600), (4, 3) -> (800, 0) の変換
        self.tfm = CoordinateTransformer(-4, -3, 0, 600, 4, 3, 800, 0)

        # mainloop
        self.mainloop()

    def mycommand1(self):
        self.canvas.create_line(
            self.tfm.transform(1, 0), self.tfm.transform(0, 1), fill="white", width=5
        )
        self.canvas.create_line(
            self.tfm.transform(0, 1), self.tfm.transform(-1, 0), fill="green", width=5
        )
        self.canvas.create_line(
            self.tfm.transform(-1, 0), self.tfm.transform(0, -1), fill="red", width=5
        )
        self.canvas.create_line(
            self.tfm.transform(0, -1), self.tfm.transform(1, 0), fill="blue", width=5
        )


App("My First Square")

isort適用後

import customtkinter as ctk

from mytool_lib import CoordinateTransformer


class App(ctk.CTk):
    def __init__(self, title):

        # main window
        super().__init__()
        self.title(title)

        # widget配置
        self.canvas = ctk.CTkCanvas(self, width=800, height=600, bg="black")
        self.canvas.pack()
        self.button1 = ctk.CTkButton(self, text="コマンド1", command=self.mycommand1)
        self.button1.pack()

        # 座標変換クラス (-4, -3) -> (0, 600), (4, 3) -> (800, 0) の変換
        self.tfm = CoordinateTransformer(-4, -3, 0, 600, 4, 3, 800, 0)

        # mainloop
        self.mainloop()

    def mycommand1(self):
        self.canvas.create_line(
            self.tfm.transform(1, 0), self.tfm.transform(0, 1), fill="white", width=5
        )
        self.canvas.create_line(
            self.tfm.transform(0, 1), self.tfm.transform(-1, 0), fill="green", width=5
        )
        self.canvas.create_line(
            self.tfm.transform(-1, 0), self.tfm.transform(0, -1), fill="red", width=5
        )
        self.canvas.create_line(
            self.tfm.transform(0, -1), self.tfm.transform(1, 0), fill="blue", width=5
        )


App("My First Square")

flake8適用

このあとflake8にかけて何も指摘はされませんでした。よしよし。

この記事が気に入ったらサポートをしてみませんか?