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にかけて何も指摘はされませんでした。よしよし。
この記事が気に入ったらサポートをしてみませんか?