Add autogen.sh and some other files
[oscopy/ivan.git] / oscopy / figure.py
blob216f64d3a4d163544e8959ec37acbf67a457c17e
1 """ Figure handler
3 A figure consist of a list of up to 4 graphs, with a layout.
4 Signal list are passed directly to each graph.
5 Layout can be either all graphs stacked verticaly, horizontaly or in quad
7 curgraph: alias to the current graph
8 gn: graph number
9 g: graph
10 sn: signal name
12 class:
14 Figure -- Handle a list of graphs
16 methods:
17 __init__(sigs)
18 Create a figure and eventually initialize a graphics with a signal list
20 add(sigs)
21 Add a grapth into the figure
23 delete(num)
24 Delete a graph
26 update(toupdate, todelete)
27 Update the signals of the figure
29 [get|set]_mode(mode)
30 Set the mode of the current graph
32 [get|set]_layout(layout)
33 Set the layout, either horiz, vert or quad
35 draw()
36 Overload of matplotlib.pyplot.figure.draw()
38 signals()
39 Return a list of the signals in all graphs
41 _key()
42 Handle keystrokes during plot
43 """
45 import matplotlib.pyplot as plt
46 from matplotlib.pyplot import Figure as MplFig
47 from graphs import Graph, LinGraph
49 class Figure(MplFig):
51 def __init__(self, sigs={}, fig=None):
52 """ Create a Figure.
53 If a signal list is provided, add a graph with the signal list
54 By default, create an empty list of graph and set_ the layout to horiz
55 """
56 MplFig.__init__(self)
58 self._layout = "horiz"
59 self._MODES_NAMES_TO_OBJ = {"lin":LinGraph}
60 self._kid = None
61 # Slow way... Surely there exist something faster
62 self._OBJ_TO_MODES_NAMES = {}
63 for k, v in self._MODES_NAMES_TO_OBJ.iteritems():
64 self._OBJ_TO_MODES_NAMES[v] = k
66 if not sigs:
67 return
68 elif isinstance(sigs, dict):
69 self.add(sigs)
70 else:
71 assert 0, "Bad type"
73 def add(self, sigs={}):
74 """ Add a graph into the figure and set it as current graph.
75 Up to four graphs can be plotted on the same figure.
76 Additionnal attemps are ignored.
77 By default, do nothing.
78 """
79 if len(self.axes) > 3:
80 assert 0, "Maximum graph number reached"
82 # _graph_position use the current length of self.axes to compute
83 # the graph coordinates. So we add a fake element into the list
84 # and we remove it once the size is computed
85 self.axes.append(None)
86 gr = LinGraph(self, self._graph_position(len(self.axes) - 1),\
87 sigs, label=str(len(self.axes)))
88 del self.axes[len(self.axes) - 1]
89 ax = self.add_axes(gr)
91 # Force layout refresh
92 self.set_layout(self._layout)
94 def delete(self, num=1):
95 """ Delete a graph from the figure
96 By default, delete the first graph.
97 Act as a "pop" with curgraph variable.
98 """
99 if not isinstance(num, int):
100 assert 0, "Bad graph number"
101 if len(self.axes) < 1 or num < 1 or num > len(self.axes):
102 assert 0, "Bad graph number"
103 del self.axes[num - 1]
105 def update(self, u, d):
106 """ Update the graphs
108 if not isinstance(u, dict):
109 assert 0, "Bad type"
110 if not isinstance(d, dict):
111 assert 0, "Bad type"
112 for g in self.axes:
113 ug = {}
114 dg = {}
115 for sn in g.signals():
116 if sn in u:
117 ug[sn] = u[sn]
118 elif sn in d:
119 dg[sn] = d[sn]
120 g.insert(ug)
121 g.remove(dg)
123 # def get_mode(self):
124 # """ Return the mode of the current graph"""
125 # return self._OBJ_TO_MODES_NAMES(self._current)
127 def set_mode(self, args):
128 """ Set the mode of the current graph
130 # Currently this cannot be tested (only one mode available)
131 old_graph = args[0]
132 gmode = args[1]
133 if not self._graphs:
134 assert 0, "No graph defined"
135 g = (self._MODES_NAMES_TO_OBJ(gmode))(old_graph)
136 self._graphs[self._graphs.index(old_graph)] = g
138 def get_layout(self):
139 """ Return the figure layout"""
140 return self._layout
142 def set_layout(self, layout="quad"):
143 """ Set the layout of the figure, default is quad
144 horiz : graphs are horizontaly aligned
145 vert : graphs are verticaly aligned
146 quad : graphs are 2 x 2 at maximum
147 Other values are ignored
149 # To change the layout: use ax.set_position
151 if layout == "horiz" or layout == "vert" or layout == "quad":
152 self._layout = layout
153 else:
154 assert 0, "Bad layout"
156 for gn, g in enumerate(self.axes):
157 g.set_position(self._graph_position(gn))
159 def draw(self, canvas):
160 tmp = MplFig.draw(self, canvas)
161 if not self._kid:
162 self._kid = self.canvas.mpl_connect('key_press_event', self._key)
164 return tmp
166 def _key(self, event):
167 """ Handle key press event
168 1, 2: toggle vertical cursors #0 and #1
169 3, 4: toggle horizontal cursors #0 and #1
171 if event.inaxes is None:
172 return
173 # Find graph
174 g = None
175 for g in self.axes:
176 if g == event.inaxes:
177 break
178 else:
179 g = None
180 if g is None:
181 return
182 # Set cursor for graph
183 if event.key == "1":
184 g.toggle_cursors("vert", 0, event.xdata)
185 elif event.key == "2":
186 g.toggle_cursors("vert", 1, event.xdata)
187 elif event.key == "3":
188 g.toggle_cursors("horiz", 0, event.ydata)
189 elif event.key == "4":
190 g.toggle_cursors("horiz", 1, event.ydata)
191 else:
192 return
193 event.canvas.draw()
195 def _graph_position(self, num):
196 """ Compute the position of the graph upon its number
198 # 1 graph: x:0->1 y:0->1 dx=1 dy=1
199 # 3 graphs horiz: x:0->.333 y:0->1 dx=.333 dy=1
200 # 3 graphs quads: x:0->.5 y:0->.5 dx=.5 dy=.5
202 if self._layout == "horiz":
203 dx = 1
204 dy = 1.0 / len(self.axes)
205 num_to_xy = [[0, y] for y in xrange(len(self.axes))]
206 elif self._layout == "vert":
207 dx = 1.0 / len(self.axes)
208 dy = 1
209 num_to_xy = [[x, 0] for x in xrange(len(self.axes))]
210 elif self._layout == "quad":
211 dx = 0.5
212 dy = 0.5
213 num_to_xy = [[x, y] for y in xrange(2) for x in xrange(2)]
214 else:
215 assert 0, "Bad layout"
217 pos_x = num_to_xy[num][0]
218 pos_y = num_to_xy[len(self.axes) - num - 1][1]
219 x1 = pos_x * dx + 0.15 * dx
220 y1 = pos_y * dy + 0.15 * dy
221 return [x1, y1, dx * 0.75, dy * 0.75]
223 @property
224 def signals(self):
225 """ Return the list of signals in all graphs
227 for g in self.axes:
228 for sn in g.get_signals():
229 yield sn
231 @property
232 def graphs(self):
233 """ Return the graph list """
234 return self.axes
236 layout = property(get_layout, set_layout)
237 # mode = property(get_mode, set_mode)