風で飛んでいくチラ裏

気ままに雑記メモ書き殴り。古い記事は別blogからの引っ越しなのでレイアウトがアレです。

python + matplotlib + Tkinter でリアルタイムグラフ描画

wxPythonで同じようなことをやっている方がいらっしゃいましたので、Tkinter + matplotlib でリアルタイムグラフを描画するサンプルを書いてみました。

参考にしたのはこちらです。ありがとうございます。

SerialDataクラスは適当なデータを作って渡しているだけなので、あとは適当に実データを作るクラスを作ってあげてください。

Tkinterのafterは指定時間後に登録関数を呼び出すので、描画処理時間を考慮しないと少しずつ遅れてしまいます。今は描画にかかる時間を測ってafterにセットする時間を調節しているのですが、必ず同じ時間ごとにイベントを発行するような機能ってTkinterにあるのでしょうか...?

# -*- coding:utf-8 -*-
# python2.7で動作確認

# matplotlib, Tkを使用したリアルタイムグラフ描画のサンプル
#
# 参考:
# http://d.hatena.ne.jp/saket/20111004/1317714358
# http://matplotlib.org/examples/user_interfaces/embedding_in_tk2.html

import matplotlib
matplotlib.use('TkAgg')

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# implement the default mpl key bindings
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import numpy as np

import sys
if sys.version_info[0] < 3:
    import Tkinter as Tk
else:
    import tkinter as Tk

import datetime

REDRAW_CYCLE = 100          # msec 再描画間隔
DRAW_SEC = 10.0             # sec 何秒分表示するか
DRAW_SAMPLING_RATE = 1000   # 点  秒間あたりの表示点数

class SerialData:
    """ グラフに表示するデータを用意するクラスの雛形
        
        グラフに表示するデータを返す get_next(),
        グラフのy軸範囲を返す get_data_range(),
        そのデータが秒間何サンプリングかを返す get_sampling_rate()
        が必要。
    """
    def __init__(self):
        self.sampling_rate = 3000
        self.data_range = [0, 330]
    
    def get_sampling_rate(self):
        return self.sampling_rate
    
    def get_data_range(self):
        """ 返すデータの取りうる範囲を返す。
            グラフの値域に相当するため、余裕を持った値を返してもOK。
            @return [min, max]
        """
        return self.data_range
    
    def get_next(self):
        """ グラフ表示用のデータを返す。
            リアルタイムにデータを用意する場合は、呼び出された時点までに
            用意できたデータをそのまま返せば良い。
        """
        # テスト時は固定長のデータなので、REDRAW間隔とデータサンプリングレートに
        # 注意すること。今はREDRAW100ms, rate3000なので300個のデータを
        # サンプリングしたことにする
        return range(0, 300)

class View:
    """ グラフ表示用のGUI管理クラス
    """
    def __init__(self):
        self.t1 = datetime.datetime.now()
        
        self.root = Tk.Tk()
        self.root.wm_title("dynamic graph")
        
        # データ出力オブジェクトの準備
        self.serial = SerialData()
        self.data = []
        self.data_start_time = 0.0
        
        # 何秒分表示するか
        self.draw_sec = DRAW_SEC
        # 描画する点のレート(秒間何点使うか)
        self.draw_sampling_rate = DRAW_SAMPLING_RATE
        
        # インチ指定...
        self.f = Figure(figsize=(6,4), dpi=100)
        self.a = self.f.add_subplot(111)
        self.a.set_title('hogehoge data', size=12)

        self.plot_data = self.a.plot(
                self.data,
                linewidth=0.5,
                color=(1, 0, 0),
                )[0]
        
        self.canvas = FigureCanvasTkAgg(self.f, master=self.root)
        self.canvas.show()
        self.canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
        
        self.canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
        
        self.button = Tk.Button(master=self.root, text='Quit', command=self._quit)
        self.button.pack(side=Tk.BOTTOM)
        
        self.on_redraw_timer()

        self.root.mainloop()
    
    def on_redraw_timer(self):
        self.t1 = datetime.datetime.now()
        self.add_draw_data(self.serial.get_next())
        self.draw_plot()
        
        self.t2 = datetime.datetime.now()
        tdelta = self.t2 - self.t1
        tofs = datetime.timedelta(microseconds = (REDRAW_CYCLE * 1000)) - tdelta
        
        # 描画処理にかかった時間だけ、afterにセットする時間を調節する。
        # wxPythonだとwx.Timer()が必ず定期的に起こしてくれる?
        # もっとスマートな方法があればいいなぁ...
        self.root.after(tofs.microseconds / 1000, self.on_redraw_timer)
    
    def add_draw_data(self, data):
        """ 描画対象のデータを追加する
            @param data データ配列。前回のデータから時系列的に連続しているのが前提。
            描画サンプリングレートとデータのサンプリングレートのずれ
            で描画単位ごとに境界がずれるが、微々たるものなので気にしない。
        """
        sampling_rate = self.serial.get_sampling_rate()
        sampling_sec = 1. / sampling_rate
        
        draw_sampling_rate = self.draw_sampling_rate
        draw_sampling_sec = 1. / draw_sampling_rate
        
        newdata = []
        time = 0.0
        
        # データサンプリングレートが描画サンプリングレートより充分大きくないと
        # 正しい位置にデータをプロットできない
        for s in data:
            time += sampling_sec
            
            # データサンプリング周期が描画サンプリング周期を超えたところで
            # データをnewdataに追加する
            if time >= draw_sampling_sec:
                time -= draw_sampling_sec
                newdata.append(s)
        
        # 1つ前のデータに今回のデータを付け加える
        self.data += newdata
        # 画面に表示する全ての描画点数
        remain_frame_length = int(self.draw_sec * draw_sampling_rate)
        
        # 描画点数が画面全体の点数より少ない場合の処理
        self.data_start_time += max((len(self.data) - remain_frame_length), 0) / float(draw_sampling_rate)
        
        self.data = self.data[-remain_frame_length:]
        
    def draw_plot(self):
        num_draw_frame = int(self.draw_sec * self.draw_sampling_rate)
        draw_sampling_rate = self.draw_sampling_rate
        
        xmin = self.data_start_time
        xmax = xmin + self.draw_sec
        
        data_range = self.serial.get_data_range()
        ymin = data_range[0]
        ymax = data_range[1]
        
        self.a.set_xbound(lower=xmin, upper=xmax)
        self.a.set_ybound(lower=ymin, upper=ymax)
        
        self.a.grid(True, color='gray')
        
        xaxis = [float(con) / self.draw_sampling_rate + self.data_start_time for con in range(len(self.data))]
        self.plot_data.set_xdata(xaxis)
        self.plot_data.set_ydata(np.array(self.data))
        self.canvas.draw()
        
    def _quit(self):
        self.root.quit()     # stops mainloop
        self.root.destroy()  # this is necessary on Windows to prevent

if __name__ == '__main__':
    v = View()

Logicool Setpoint 6.50以上でThunderbirdのスクロールがおかしい(未完)

他のソフトだと3回スクロールするくらいのホイールの回し量でThunderbirdだと1回スクロールする。
つまりThunderbirdだけホイールを3倍スクロールさせないと同じスクロール量にならないという現象に見舞われた。

色々探ってみると、Logicoolマウスのユーティリティである、SetpointのVer.6.50以上でどうも問題があるようで。

Setpointをアンインストールするか、Ver.6.32あたりにすると具合が良くなるということなので、インストールしてみた。

が、今使っているマウスM545の設定項目がVer.6.32には無い。どうもSetpoint Ver.6.5X代で発売されたマウスのようで、
Ver.6.3X代だとM545向けの設定ができない。


\(^o^)/


サイドスイッチもそんなにバリバリ使ってる感じでもないので潔くSetpointとはおさらばしてやりましたとさ。
Setpoint Ver.6.6Xでも直ってなかったようなので、しばらくダメなんじゃなかろうか(未完)。


いやぁ、前世代のM310がすこぶる使い勝手が良かったから今回はM545にしてみたけど、このマウスは個人的にはダメでした。

  • 親指と薬指、小指の当たる側面に材質の違うラインが入っていて触り心地が均一じゃない
  • ソールの滑りが悪いのか、M310と比べて重いのか、微妙なカーソル移動がM310と比較してとてもやりづらい、滑らない
    • 伝家の宝刀カグスベールに張り替えてやっとなんとか使えるレベル
  • おまけにカーソルをちょっとだけ移動しようと低速で動かすとカーソルが移動しない
    • M310の時には感じなかったので、単純にM545の性能が悪いのか、マウスパッドとの相性が悪いのか...

OpenOfficeの設定を移行する

プレゼン用にカスタマイズ色を追加していたりすると、他のPCで編集したい、と思った時に作りなおすのがとても面倒なので、設定を移行する方法を調べました。

%AppData%\Roaming\OpenOffice\

にバージョン番号のフォルダがあります(Windows7)。複数のPC間で同じ設定を持ちたい場合はこれを移し替えればいけます。設定を共有するOpenOfficeのバージョンは同じにしておきましょう。

PC上でOpenOfficeのドキュメントを開いている場合、バージョン番号のフォルダのしたに.lockというファイルができますが、文字通りロック用のファイルですのでコピー不要です。できればOpenOfficeは起動していない状態で移し替えをしましょう。


似たようなバージョンであれば、LibreOffice<-->OpenOffice間の設定の移し替えもできることがありますが、もちろん自己責任で。

debian Wheezy に nginx を入れる

ハマりどころはなかったはずなんですが、情弱の私のための備忘録。

nginx のダウンロード

$ cd ~/src
$ wget http://nginx.org/download/nginx-1.7.2.tar.gz
$ tar zxvf nginx-1.7.2.tar.gz

ビルド

$ cd nginx-1.7.2
$ ./configure
...
./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.

PCREライブラリが無いと怒られた。

落とす。

$ cd ..
$ wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.33.tar.gz
$ tar zxvf pcre-8.33.tar.gz

リトライ。

$ cd nginx-1.7.2
$ ./configure --with_pcre=/home/XXXX/src/pcre-8.33
$ make
$ sudo make install

でけた。

後はこちらのサイト様のように /usr/local/nginx/conf/nginx.conf をいじって起動。
http://d.hatena.ne.jp/koujirou6218/20101019/1287479177

$ sudo /usr/local/nginx/sbin/nginx

サーバにHTTPでアクセスできれば成功。

windowsにRuby 1.8.7 + mechanize + hpricot を入れる

はじめに

Ruby 1.8.7 は既にサポート終了していますので、これから何か作りたい人はRubyの最新バージョンを入れてください。

昔作った1.8.7のスクリプトを別のPCでちょっとだけ動かしたいんじゃよ…。

Rubyのインストール

http://www.geocities.co.jp/SiliconValley-PaloAlto/9251/ruby/

こちらのサイト様から1.8.7をダウンロードしてインストール。
インストールパスはProgram Files下を避ける。

mechanize と hpricot のインストール

動かしたいスクリプトで必要なモジュールの2つをインストール。

$ gem install hpricot -v 0.8.6
$ gem install mechanize -v 1.0.0

nokogiri と mime-types が必要だけど、Rubyのバージョンが低いとかで弾かれるので
以下2つを実行。

$ gem install nokogiri -v 1.5.10
$ gem install mime-types -v 1.25.1
($ gem install mime-types -v "~>1.0")

もう一度 mechanize をインストールしてOK。

(function(document){ var pres = document.getElementsByTagName("pre") for(var i=pres.length; i--; ){  var el = makeOl(pres[i]) pres[i].appendChild(el) } function makeOl(pre){ var ol = document.createElement("ol") , li = document.createElement("li") , df = document.createDocumentFragment() , br = pre.innerHTML.match(/\n/g) ol.className = "preLine" ol.setAttribute("role", "presentation") for(var i=br.length; i--; ){ var li2 = li.cloneNode(true) df.appendChild(li2) } ol.appendChild(df) return ol } })(document)