Help us understand the problem. What is going on with this article?

PySimpleGUIでグラフを描く

この記事を読んでできるもの

graph_example.jpg

PySimpleGUIで以下のことができるようになります。

  • 棒グラフ、折れ線グラフなどが描画できるようになります。
  • 定期的に表示を更新するグラフ

PySimpleGUIの基本的な操作についてはTkinterを使うのであればPySimpleGUIを使ってみたらという話を参考にしてください。

概要

Pythonの利用方法としてデータ解析を行う人は多いと思います。
また解析した結果を折れ線グラフや棒グラフなどで可視化したいという人もいるかと思います。

PySimpleGUIでグラフを利用する場合は大まかに言って2通りあります

  • graph-elementを使って描画する
  • matplotlibを埋め込んで描画する

今回はgraph-elementを使って作成する方法を説明します。

graph-elementについて

graph-elementは描画に使うクラスです。tkinterではcanvasを使用して描画しますがPySimpleGUIのgraph-elementはcanvsの座標系を工夫してより使いやすいものにしたものです。

基本的な使い方としては
Graphクラスを使用して描画エリアの大きさ、描画エリアの左下の座標(原点)、右上の座標を指定します。

graph = sg.Graph((500,500), (0,0), (500, 500))

あとはそのグラフに関して各描画のメソッドを使用していきます。

  • DrawCircle :円を描画
  • DrawLine :線を引く
  • DrawPoint :点を配置する
  • DrawRectangle :四角形を描画する
  • DrawOval :円弧を絵描画する
  • DrawImage :画像を表示する
  • DrawText :文字を描画する

このうち、棒グラフや円グラフを描画する際に使うのは以下になります。

  • DrawLine :折れ線グラフの線や、補助線、目盛りを引くのに使用します
  • DrawPoint :折れ線グラフのプロットされた点を引いたり、サイン波のような円弧を引くのに使用します
  • DrawText :グラフのラベルや、プロットごとの値を表示するのに使用します

また以下のメソッドも使用します。

  • Erase :描画領域をクリアします。グラフを切り替える際に、描画していたグラフを削除するnonのに仕様用します
  • Move :描画されているものをx方向、y方向に移動させます。リアルタイムでグラフを動かすのに使用します

コード

公式のサンプルコードをまとめたものを掲載します。
ボタンでグラフの内容を切り替えます

実行画面
graph_example.jpg

コード

import PySimpleGUI as sg
from random import randint
import math
"""
以下の公式サンプルを一つに統合しています
https://pysimplegui.trinket.io/demo-programs#/graph-element/graph-element-bar-chart
https://pysimplegui.trinket.io/demo-programs#/graph-element/graph-element-sine-wave
https://pysimplegui.trinket.io/demo-programs#/graph-element/graph-element-line-graph-with-labels
https://pysimplegui.trinket.io/demo-programs#/graph-element/animated-line-graph
"""

# 定数
GRAPH_SIZE = (500, 500)
DATA_SIZE = (500, 500)

# 
PLOTS_NUMBER = 30
RAND_MAX = 400

# 折れ線用
LINE_BAR_WIDTH = 10
LINE_BAR_SPACING = 16
LINE_EDGE_OFFSET = 3

# 棒グラフ
BAR_WIDTH = 50
BAR_SPACING = 75
EDGE_OFFSET = 3

# サイングラフ
SIZE_X = GRAPH_SIZE[0]//2
SIZE_Y = GRAPH_SIZE[1]//2
NUMBER_MARKER_FREQUENCY = 25

# アニメ―ション
GRAPH_STEP_SIZE = 5
DELAY = 15  # 時間間隔

# レイアウト
# グラフの描画領域の設定
graph = sg.Graph(GRAPH_SIZE, (0, 0), DATA_SIZE,
                 key='-GRAPH-', background_color='white',)

layout = [[sg.Text('chart demo')],
          [graph],
          [sg.Button('LINE'), sg.Button('chart'), sg.Button('両方'), sg.Button('円グラフ'), sg.Button('サイン波'),sg.Button('アニメーション') ]]

window = sg.Window('シンプルなグラフのサンプル', layout)

before_value = 0  # 折れ線グラフの初期化
delay = x = lastx = lasty = 0  # アニメーションの初期化

is_animated = False


def draw_axis():
    """ X軸とY軸に補助線を引く

    グラフエレメントの左下を原点(0,0)と設定しているのを
    グラフ領域の真ん中を原点として移動している
    """

    graph.draw_line((0, SIZE_Y), (SIZE_X*2, SIZE_Y))  # 原点
    graph.draw_line((SIZE_X, 0), (SIZE_X, SIZE_Y*2))

    for x in range(0, SIZE_X*2, NUMBER_MARKER_FREQUENCY):
        graph.draw_line((x, SIZE_Y-3), (x, SIZE_Y+3))  # 目盛りを引く
        if x != 0:
            graph.draw_text(str(x-SIZE_X), (x, SIZE_Y-10),
                            color='green')  # 目盛りの値を引く

    for y in range(0, SIZE_Y*2+1, NUMBER_MARKER_FREQUENCY):
        graph.draw_line((SIZE_X-3, y), (SIZE_X+3, y))
        if y != 0:
            graph.draw_text(str(y-SIZE_Y), (SIZE_X-10, y), color='blue')


while True:

    if is_animated:
        # 定期的に '__TIMEOUT__' イベントが発行される
        event, values = window.Read(timeout=DELAY)
    else:
        event, values = window.Read()

    if event is None:
        break

    if event == 'LINE':
        is_animated = False
        # ラベル付き折れ線グラフを表示
        graph.Erase()  # グラフの表示両機を削除

        for i in range(PLOTS_NUMBER):
            graph_value = randint(0, 400)
            if i > 0:
                graph.DrawLine(((i-1) * LINE_BAR_SPACING + LINE_EDGE_OFFSET + LINE_BAR_WIDTH/2,  before_value),
                               (i * LINE_BAR_SPACING + LINE_EDGE_OFFSET + LINE_BAR_WIDTH/2, graph_value), color='green', width=1)

            # 折れ線のラベル(yの値)を表示
            graph.DrawText(text=graph_value, location=(
                i * LINE_BAR_SPACING+EDGE_OFFSET+2, graph_value+10))

            graph.DrawPoint((i * LINE_BAR_SPACING + LINE_EDGE_OFFSET +
                             LINE_BAR_WIDTH/2, graph_value), size=3, color='green',)

            before_value = graph_value

    if event == 'chart':
        is_animated = False

        # 棒グラフを削除
        graph.Erase()
        for i in range(PLOTS_NUMBER):
            graph_value = randint(0, 400)
            graph.DrawRectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, graph_value),
                                bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0), fill_color='blue')
            graph.DrawText(text=graph_value, location=(
                i*BAR_SPACING+EDGE_OFFSET+25, graph_value+10))

    if event == '両方':
        is_animated = False

        # 折れ線グラフと棒グラフの両方を表示
        graph.Erase()
        for i in range(PLOTS_NUMBER):
            graph_value = randint(0, 400)
            graph.DrawRectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, graph_value),
                                bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0), fill_color='blue')
            graph.DrawText(text=graph_value, location=(
                i*BAR_SPACING+EDGE_OFFSET+25, graph_value+10))

            if i > 0:
                graph.DrawLine(((i-1) * LINE_BAR_SPACING + LINE_EDGE_OFFSET + LINE_BAR_WIDTH/2,  before_value),
                               (i * LINE_BAR_SPACING + LINE_EDGE_OFFSET + LINE_BAR_WIDTH/2, graph_value), color='green', width=1)

            graph.DrawText(text=graph_value, location=(
                i * LINE_BAR_SPACING+EDGE_OFFSET+2, graph_value+10))

            graph.DrawPoint((i * LINE_BAR_SPACING + LINE_EDGE_OFFSET +
                             LINE_BAR_WIDTH/2, graph_value), size=3, color='green',)
            before_value = graph_value

    if event == '円グラフ':
        is_animated = False
        graph.erase()

        # create_arc()のfillはないので塗りつぶしはできない
        graph.DrawArc( (50,50), (DATA_SIZE[0]-50, DATA_SIZE[1]-50), extent=-200, start_angle=90)
        graph.DrawArc( (50,50), (DATA_SIZE[0]-50, DATA_SIZE[1]-50), extent=-400, start_angle=-110 ,arc_color="yellow")
        graph.DrawArc( (50,50), (DATA_SIZE[0]-50, DATA_SIZE[1]-50), extent=-50,  start_angle=-510 , arc_color="blue")
        graph.DrawArc( (50,50), (DATA_SIZE[0]-50, DATA_SIZE[1]-50), extent=-50,  start_angle=-560 , arc_color="red")
        graph.DrawArc( (50,50), (DATA_SIZE[0]-50, DATA_SIZE[1]-50), extent=-20,  start_angle=-610 , arc_color="green")

    if event == 'サイン波':
        is_animated = False
        graph.erase()

        draw_axis()
        prev_x = prev_y = None
        for x in range(0, SIZE_X*2):
            y = math.sin(x/75)*100 + SIZE_Y
            if prev_x is not None:
                graph.draw_line((prev_x, prev_y), (x, y), color='red')
            prev_x, prev_y = x, y

    if event == 'アニメーション' or is_animated:

        if not is_animated:
            graph.Erase()  # グラフを一回削除

        is_animated = True

        # 時系列にそって動くグラフを表示
        step_size, delay = GRAPH_STEP_SIZE, DELAY
        y = randint(0, GRAPH_SIZE[1])
        if x < GRAPH_SIZE[0]:  #  初回
            # window['-GRAPH-'].DrawLine((lastx, lasty), (x, y), width=1)
            graph.DrawLine((lastx, lasty), (x, y), width=1)
        else:
            # window['-GRAPH-'].Move(-step_size, 0)  # グラフ全体を左にずらす
            # window['-GRAPH-'].DrawLine((lastx, lasty), (x, y), width=1)
            graph.Move(-step_size, 0)  # グラフ全体を左にずらす
            graph.DrawLine((lastx, lasty), (x, y), width=1)
            x -= step_size
            lastx, lasty = x, y
        lastx, lasty = x, y
        x += step_size

window.Close()

公式のグラフと違うのはサイン波の座標は今回のキャンバス全体の原点が(0,0)開始だったので、(250,250)の座標をずらして原点にしています。公式のサンプルですと、左端の座標を(250,-250)としています。
また円グラフは公式ではサンプルがなくDrawArcメソッドを使用して独自に実装してみましたが、円の中を塗りつぶすfillパラメータがないのと、アングルの指定が独特で手間がかかるので円グラフを作るのはやめた方がいいかと思います

PySimpleGUIのgraph-elementを使用することで、例えばエクセルのグラフ機能で作るような、棒グラフや折れ線グラフといった簡単なグラフは作成可能になります。

公式のグラフ関係のサンプルについて

PySimpleGUIはサンプルファイルが多くて、実装の参考になるもので役に立つサンプルがたくさんあります。

この中から個人的に役に立つ面白いと感じたサンプルを紹介します

matplotlibとの連携

Demo_Matplotlib.png

ソートするアニメーション

visualizing-sorts.png

CPUの使用率を表示するリアルタイムのグラフ

PySimpleGUI_Rainmeter_CPU_Cores.png

まとめ

PySimpleGUIを使うとグラフも簡単に作れることが分かったかと思います。
PySimpleGUIの公式サンプルではほかにもグラフを使用したサンプルがいくつかあるので興味があったら覗いてみてください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした