python curses でグラフ…

glances で windows-curses を入れたついでというか。まあ相当残念なネタであることは覚悟しといたらいいよ。

「コンソールにまつわるもの」としてもう一つ ANSI シーケンスそのもの、と colorama のネタも持っているんだけど、今回はcurses のほうだけにしとく。

一連の COVID19 ネタは「良い見える化とそれの共有」の話で、「ほらね今やこういうのって簡単なのさぁ」みたいなことで pygal にも触れたわけなんだけれど、その、「かつてはそんなに簡単なことではなかった」の一例だと思うのよね、「テキストオンリーベースのターミナルだけを使ってグラフを表現する」というタスクは。

その「かつてはそんなに簡単なことではなかった」の中身は、もちろん「数学的なサポート」などの欠如なんかも多分にあるけれど、それ以前の問題として「可視化表現に使えるインフラの欠如による自由度のなさ」なわけね。決して「そのプアな表現をするためのプログラミングも困難」だったわけじゃない。たとえばこういうことな:

ほかのとこで説明した通り、Windows では windows-curses が必要。
 1 # -*- coding: utf-8 -*-
 2 # pygal を紹介する際に「pygal は簡単なんだけど numpy 部分がこ難しくみえちゃうのが歯痒い」
 3 # かったので、あえて numpy を使わずに書いてみた。
 4 import io
 5 import os
 6 import sys
 7 import datetime
 8 import csv
 9 import curses
10 
11 
12 def _main(stdscr, tkoprefdat):
13     _COLSNM = (
14         "testedPositive",
15         "peopleTested",
16         "hospitalized",
17         "serious",
18         "discharged",
19         "deaths",
20         "effectiveReproductionNumber",
21     )
22     reader = csv.reader(io.open(tkoprefdat, encoding="utf-8-sig"))
23     next(reader)
24     def _f(d):
25         if not d or d == "-":
26             return 0
27         return float(d)
28     trgprefecdat = {"testedPositive": [], "deaths": [], "serious": []}
29     tpn = "大阪府"
30     for line in reader:
31         pn = line[3]
32         data = list(map(_f, line[5:]))
33         if pn == tpn:
34             for k in trgprefecdat.keys():
35                 trgprefecdat[k].append(data[_COLSNM.index(k)])
36     #
37     stdscr.clear()
38     curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLACK)
39     curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
40     curses.init_pair(4, curses.COLOR_RED, curses.COLOR_BLACK)
41     height, width = stdscr.getmaxyx()
42     height -= 4
43     width -= 2
44     for k in trgprefecdat.keys():
45         trgprefecdat[k] = trgprefecdat[k][-width:]
46         maxv = max(trgprefecdat[k])
47         trgprefecdat[k] = [(height - int((v / maxv) * height)) for v in trgprefecdat[k]]
48     for i in range(len(trgprefecdat["testedPositive"])):
49         # addstr の x, y が矩形 [width, height] 範囲外を指すと即(なんの説明もない
50         # 不親切な)エラーが返るので注意。
51         stdscr.addstr(trgprefecdat["testedPositive"][i], i, "#", curses.color_pair(2))
52         stdscr.addstr(trgprefecdat["serious"][i], i, "#", curses.color_pair(3))
53         stdscr.addstr(trgprefecdat["deaths"][i], i, "#", curses.color_pair(4))
54     tit = "(" + tpn + "の感染流行波の形 ) "
55     for i, c in enumerate(tit):
56         stdscr.addstr(height + 1, i * 2, c)
57     tit = "「東洋経済オンライン「新型コロナウイルス 国内感染の状況」」を加工 "
58     for i, c in enumerate(tit):
59         stdscr.addstr(height + 2, i * 2, c)
60     stdscr.getch()
61 
62 
63 if __name__ == '__main__':
64     import urllib.request
65     import shutil
66     if os.path.exists("prefectures.csv"):
67         ctm = datetime.datetime.utcfromtimestamp(os.stat("prefectures.csv").st_mtime)
68         now = datetime.datetime.utcnow()
69         if (now - ctm).total_seconds() > 60 * 60:
70             os.remove("prefectures.csv")
71     if not os.path.exists("prefectures.csv"):
72         cont, msg = urllib.request.urlretrieve(
73             "https://toyokeizai.net/sp/visual/tko/covid19/csv/prefectures.csv")
74         shutil.move(cont, "prefectures.csv")
75     curses.wrapper(_main, "prefectures.csv")

curses であることのメリットは、色付けのこともあるけどカーソル位置を自由にコントロール出来て好きな場所に文字印字出来ること、ね。実際一切 curses 使わずに全く同じグラフは書ける、でしょ、わかるよね?

結果はワタシのコンソールだとこんな感じ:

直近の「トレンド」の雰囲気をつかむだけなら、こんなんでも結構十分なんではないかという気はするよね。例えばこれを毎日監視してたりすれば、「上がりバナ」にはすぐに気付くであろうよ。

さて。

こういうやたらにレガシーなスキルを身につけることが何か役に立つのか、と言えば、「わざわざ身につけるつもりなら見合わない」とは思う。そうではなくて、「いざとなったらこういうやり方もある」という事実を「頭の片隅に入れておく」ことが、柔軟性の源となる、かもしれない、てハナシね。以前 curses の話をした際には「いまでも curses (や termcap/terminfo)の需要は結構ある」と書いたが、これはおそらく主として「サーバメンテナンスをリモートで行う」などの用事がある際にのみ頻出となりうるわけね、それ以外のシチュエーションはさすがにこれだけに固執する理由はほとんどなくて、ほかのもっとリッチなものを選ぶべき、となるだろうと思う。

ただ、「手っ取り早くコストをかけずに」の一つのやり方として、ということであれば、このアプローチは実は技術的には「vt100 的コンソール」にしかほとんど依存していない点が割と優れていて、特に Unix 系ならほぼ 100% 通用するという点は、強いといえば強い。ので、やはり「こういう考え方/やり方もある」と知っとくのは悪くないと思う。


2021-04-03追記:
こちらもどぞ:



Related Posts