これとかでもグラフ描きたかったんだよねぇ。
kivyLauncher 環境には MatplotLib は入ってない。これがあればワタシには理想だったんだけどね。
kivy 公式ドキュメントをみると、もとから kivy-garden が同梱されてんのかしら、とも思ったんだけど、違った。違った、んだけれども、「アドオン」として kivy パッケージの一味にはなる。がこのシカケがワタシにはよーわからんかった。
具体的には kivyLauncher アプリケーションフォルダの構造をこんな:
1 /sdcard/kivy/kivy-garden-graphdemo/android.txt
2 /sdcard/kivy/kivy-garden-graphdemo/libs
3 /sdcard/kivy/kivy-garden-graphdemo/libs/garden
4 /sdcard/kivy/kivy-garden-graphdemo/libs/garden/garden.graph
5 /sdcard/kivy/kivy-garden-graphdemo/libs/garden/garden.graph/__init__.py
6 /sdcard/kivy/kivy-garden-graphdemo/main.py
とすれば、(たとえば今の場合の) main.py から:
1 from kivy.garden.graph import Graph, MeshLinePlot
2 kv = '''
3 #:import Graph kivy.garden.graph.Graph
4 #...
5 '''
6 Builder.load_string(kv)
と出来る、てことなのね…。やっぱ気持ち悪いわ。標準 Python みたいに kivyLauncher 用 site-packages があればいいのに…。(libs の下に置くと kivy の一味になるってルールも、かなり受け容れがたい、です。) ⇒ kivyLauncher でなく apk として作る際に「自アプリケーションにアドオンを同梱」てのが本来のノリなので、アプリケーションに同梱、てのはわかるんだけれども、このノリは今ひとつ kivyLauncher で使う際には気持ちが悪い。
さて。http://nullege.com/ にヒットするとガッカリしませんか? なぜって「Python だぁ」といえばほんとに .py しか置かれてなくて、サンプルとして不完全だからな。このサイト、いったいどんな価値があるんだ、と毎度思う。(多分元サイトからコピーして管理してるんだと思うが、リンクだけでいいよ、まったく。) ともあれ、kivy.garden.graph のサンプルがここにある。kv ファイルが置かれてないのでこのままでは動かぬ。ばっかじゃねーの。
ちぅわけで、仕方なく自分で kv を補って…:
1 # -*- coding: utf-8 -*-
2 import kivy
3 kivy.require('1.8.0')
4
5 from kivy.app import App
6 from kivy.lang import Builder
7 from kivy.properties import ObjectProperty
8 from kivy.uix.boxlayout import BoxLayout
9 from kivy.clock import Clock
10
11 from plyer import accelerometer
12
13 '''
14 This example uses Kivy Garden Graph addon to draw graphs plotting the
15 accelerometer values in X,Y and Z axes.
16 The package is installed in the directory: ./libs/garden/garden.graph
17 To read more about kivy garden, visit: http://kivy-garden.github.io/.
18 '''
19 from kivy.garden.graph import Graph, MeshLinePlot
20
21 kv = '''
22 #:import Graph kivy.garden.graph.Graph
23 <AccelerometerDemo>:
24 orientation: 'vertical'
25
26 Graph:
27 id: graph_plot
28 ToggleButton:
29 id: toggle_button
30 on_release: root.do_toggle()
31 text: 'Start Accelerometer'
32 size_hint: 1, None
33 height: self.texture_size[1] * 5
34 '''
35 Builder.load_string(kv)
36
37 class AccelerometerDemo(BoxLayout):
38 def __init__(self):
39 super(AccelerometerDemo, self).__init__()
40
41 self.sensorEnabled = False
42 self.graph = self.ids.graph_plot
43
44 # For all X, Y and Z axes
45 self.plot = []
46 self.plot.append(MeshLinePlot(color=[1, 0, 0, 1])) #X - Red
47 self.plot.append(MeshLinePlot(color=[0, 1, 0, 1])) #Y - Green
48 self.plot.append(MeshLinePlot(color=[0, 0, 1, 1])) #Z - Blue
49
50 self.reset_plots()
51
52 for plot in self.plot:
53 self.graph.add_plot(plot)
54
55 def reset_plots(self):
56 for plot in self.plot:
57 plot.points = [(0,0)]
58
59 self.counter = 1
60
61 def do_toggle(self):
62 if not self.sensorEnabled:
63 accelerometer.enable()
64 Clock.schedule_interval(self.get_acceleration, 1 / 20.)
65
66 self.sensorEnabled = True
67 self.ids.toggle_button.text = "Stop Accelerometer"
68 else:
69 accelerometer.disable()
70 self.reset_plots()
71 Clock.unschedule(self.get_acceleration)
72
73 self.sensorEnabled = False
74 self.ids.toggle_button.text = "Start Accelerometer"
75
76 def get_acceleration(self, dt):
77 val = accelerometer.acceleration
78 if val[0] is None:
79 return
80
81 if (self.counter == 100):
82 # We re-write our points list if number of values exceed 100.
83 # ie. Move each timestamp to the left.
84 for plot in self.plot:
85 del(plot.points[0])
86 plot.points[:] = [(i[0] - 1, i[1]) for i in plot.points[:]]
87 pass
88
89 self.counter = 99
90
91 self.plot[0].points.append((self.counter, val[0] * 5))
92 self.plot[1].points.append((self.counter, val[1] * 5))
93 self.plot[2].points.append((self.counter, val[2] * 5))
94
95 self.counter += 1
96
97 class AccelerometerDemoApp(App):
98 def build(self):
99 return AccelerometerDemo()
100
101 if __name__ == '__main__':
102 AccelerometerDemoApp().run()
libs/garden/garden.graph/__init__.py
はこれ。
動かしたらこんなだった:
どの程度のこと出来んのか、ドキュメントが整備されてるともいいがたい(docstring くらい)のでよくわからない。ticks を描いてる例はあったのでそれは出来るのはわかる。線を太くしたり背景色を変えたりとか簡単に出来んのかしら、とかまだ全然わかんない。けどひとまずは「気軽にグラフ」の一歩目にはなりそうだ。(ライブラリ管理は悩ましいけれど。)
さて。「グラフ描きたいよぉ」で辿り着いたのが kivy.garden であったが、kivy-gardenは色んなものが揃ってるらしい。Google Mapsの件について書いたが、garden.mapview で解決しそうだし、ひょっとすると (たぶんダメと思うが) garden.matplotlib が使えたりもするであろうか?
kivyLauncher での MatplotLib についてなんだけど、オリジナルには入ってないので、入れてみれるだろうか、とちょっとだけ検証したのね。MatplotLib って、最も依存性の小さな使い方は「backend = ‘Agg’ だけ使えればいい」(PIL だけで動く)のはずで、そこまでが pure Python だけで書かれてればひょっとして、と思ったんだけれど、_path とか色々 C で書かれててダメだった。やっぱしこれとかで kivyLauncher そのものに手を入れない限りムリね。
09:50追記:
「どの程度のこと出来んのか」について少しだけやってみた。まず Graph に渡せるパラメータは GitHub の README.md から概ねわかるが、
background_color
についてはソースみないとわからなかった。
それと、線の太さは…、たぶん MeshLinePlot にはこれの制御はなくて、LinePlot にはある。うーん、中途半端。てわけで:
1 # -*- coding: utf-8 -*-
2 import kivy
3 kivy.require('1.8.0')
4
5 from kivy.app import App
6 from kivy.lang import Builder
7 from kivy.properties import ObjectProperty, StringProperty
8 from kivy.uix.boxlayout import BoxLayout
9 from kivy.clock import Clock
10
11 from plyer import accelerometer
12
13 '''
14 This example uses Kivy Garden Graph addon to draw graphs plotting the
15 accelerometer values in X,Y and Z axes.
16 The package is installed in the directory: ./libs/garden/garden.graph
17 To read more about kivy garden, visit: http://kivy-garden.github.io/.
18 '''
19 from kivy.garden.graph import Graph, LinePlot
20
21 kv = '''
22 #:import Graph kivy.garden.graph.Graph
23 <AccelerometerDemo>:
24 orientation: 'vertical'
25
26 Graph:
27 id: graph_plot
28 background_color: (0.5, 0.5, 0.5, 1)
29 xlabel: 't'
30 ylabel: 'accelerometer'
31 x_grid: True
32 y_grid: True
33 x_grid_label: True
34 y_grid_label: True
35 x_ticks_minor: 5
36 x_ticks_major: 25
37 ymin: 0
38 ymax: 10
39 y_ticks_major: 1
40
41 ToggleButton:
42 id: toggle_button
43 on_release: root.do_toggle()
44 text: 'Start Accelerometer'
45 size_hint: 1, None
46 height: self.texture_size[1] * 5
47 '''
48 Builder.load_string(kv)
49
50 class AccelerometerDemo(BoxLayout):
51 def __init__(self):
52 super(AccelerometerDemo, self).__init__()
53
54 self.sensorEnabled = False
55 self.graph = self.ids.graph_plot
56
57 # For all X, Y and Z axes
58 self.plot = []
59 self.plot.append(LinePlot(color=[1, 0, 0, 1], line_width=3)) #X - Red
60 self.plot.append(LinePlot(color=[0, 1, 0, 1], line_width=3)) #Y - Green
61 self.plot.append(LinePlot(color=[0, 0, 1, 1], line_width=3)) #Z - Blue
62
63 self.reset_plots()
64
65 for plot in self.plot:
66 self.graph.add_plot(plot)
67
68 def reset_plots(self):
69 for plot in self.plot:
70 plot.points = [(0,0)]
71
72 self.counter = 1
73
74 def do_toggle(self):
75 if not self.sensorEnabled:
76 accelerometer.enable()
77 Clock.schedule_interval(self.get_acceleration, 1 / 20.)
78
79 self.sensorEnabled = True
80 self.ids.toggle_button.text = "Stop Accelerometer"
81 else:
82 accelerometer.disable()
83 self.reset_plots()
84 Clock.unschedule(self.get_acceleration)
85
86 self.sensorEnabled = False
87 self.ids.toggle_button.text = "Start Accelerometer"
88
89 def get_acceleration(self, dt):
90 val = accelerometer.acceleration
91 if val[0] is None:
92 return
93
94 if (self.counter == 100):
95 # We re-write our points list if number of values exceed 100.
96 # ie. Move each timestamp to the left.
97 for plot in self.plot:
98 del(plot.points[0])
99 plot.points[:] = [(i[0] - 1, i[1]) for i in plot.points[:]]
100 pass
101
102 self.counter = 99
103
104 self.plot[0].points.append((self.counter, val[0]))
105 self.plot[1].points.append((self.counter, val[1]))
106 self.plot[2].points.append((self.counter, val[2]))
107
108 self.counter += 1
109
110 class AccelerometerDemoApp(App):
111 def build(self):
112 return AccelerometerDemo()
113
114 if __name__ == '__main__':
115 AccelerometerDemoApp().run()
これでこんなグラフになった:
あとみててわかったんだけど、ContourPlot なんてのもあるのね。これはあとでやってみよう…。