3 A graph consist of several signals that share the same abscisse,
4 and plotted according a to mode, which is currently scalar.
6 Signals are managed as a dict, where the key is the signal name.
8 In a graph, signals with a different sampling, but with the same abscisse
9 can be plotted toget_her.
11 Handle a cursor dict, for convenience limited to two horizontal
12 and two vertical, limit can be removed.
17 Class Graph -- Handle the representation of a list of signals
21 Create a graph and fill it with the sigs
24 Return a string with the list of signals, and abscisse name
27 Add signal list to the graph, set_ the abscisse name
30 Delete signals from the graph
36 Return a list of the signal names
39 Return a string with the type of graph, to be overloaded.
45 Set plot axis scale (lin, logx, logy, loglog)
51 from matplotlib
.pyplot
import Axes
as mplAxes
52 from matplotlib
import rc
53 from cursor
import Cursor
55 factors_to_names
= {-18: ("a", 'atto'), -15:("f", 'femto'),
56 -12:("p", 'pico'), -9:("n", 'nano'),
57 -6:("u", 'micro'), -3:("m", 'milli'),
58 0:("",'(no scaling)'), 3:("k",'kilo'),
59 6:("M", 'mega'), 9:("G",'giga'),
60 12:("T", 'Tera'), 15:("P",'peta'),
61 18:("E", 'exa'), -31416:('auto', '(auto)')}
63 abbrevs_to_factors
= {'E':18, 'P':15, 'T':12, 'G':9, 'M':6, 'k':3, '':0,
64 'a':-18, 'f':-15, 'p':-12, 'n':-9, 'u':-6, 'm':-3,
67 names_to_factors
= {'exa':18, 'peta':15, 'tera':12, 'giga':9, 'mega':6,
68 'kilo':3, '(no scaling)':0, 'atto':-18, 'femto':-15,
69 'pico':-12, 'nano':-9, 'micro':-6, 'milli':-3,
74 def __init__(self
, fig
, rect
, sigs
={}, **kwargs
):
76 If signals are provided, fill in the graph otherwise the graph is empty
77 Signals are assumed to exist and to be valid
78 If first argument is a Graph, then copy things
80 mplAxes
.__init
__(self
, fig
, rect
, **kwargs
)
83 if isinstance(sigs
, Graph
):
85 mysigs
= sigs
.sigs
.copy()
86 self
._xrange
= sigs
.xrange
87 self
._yrange
= sigs
.yrange
88 self
._cursors
= {"horiz": [None, None], "vert": [None, None]}
90 self
._scale
_factors
= sigs
.scale_factors
91 self
._signals
2lines
= sigs
._signals
2lines
.copy()
100 # Cursors values, only two horiz and two vert but can be changed
101 self
._cursors
= {"horiz":[None, None], "vert":[None, None]}
103 self
._scale
_factors
= [None, None]
104 self
._signals
2lines
= {}
108 """ Return a string with the type and the signal list of the graph
110 a
= "(" + self
.type + ") "
111 for sn
in self
._sigs
.keys():
115 def insert(self
, sigs
={}):
116 """ Add a list of signals into the graph
117 The first signal to be added defines the abscisse.
118 The remaining signals to be added must have the same abscisse name,
119 otherwise they are ignored
122 for sn
, s
in sigs
.iteritems():
124 # First signal, set_ the abscisse name and add signal
125 self
._xaxis
= s
.ref
.name
126 self
._xunit
= s
.ref
.unit
127 self
._yaxis
= "Signals" # To change
130 self
.set_unit((self
._xunit
, self
._yunit
))
131 fx
, l
= self
._find
_scale
_factor
("X")
132 fy
, l
= self
._find
_scale
_factor
("Y")
133 x
= s
.ref
.data
* pow(10, fx
)
134 y
= s
.data
* pow(10, fy
)
135 line
, = self
.plot(x
, y
, label
=sn
)
136 self
._signals
2lines
[sn
] = line
138 self
._print
_cursors
()
141 if s
.ref
.name
== self
._xaxis
and s
.unit
== self
._yunit
and\
142 not self
._sigs
.has_key(s
.name
):
145 fx
, l
= self
._find
_scale
_factor
("X")
146 fy
, l
= self
._find
_scale
_factor
("Y")
147 x
= s
.ref
.data
* pow(10, fx
)
148 y
= s
.data
* pow(10, fy
)
149 line
, = self
.plot(x
, y
, label
=sn
)
150 self
._signals
2lines
[sn
] = line
157 def remove(self
, sigs
={}):
158 """ Delete signals from the graph
160 for sn
in sigs
.iterkeys():
161 if sn
in self
._sigs
.keys():
163 self
._signals
2lines
[sn
].remove()
166 del self
._signals
2lines
[sn
]
167 return len(self
._sigs
)
169 def update_signals(self
):
172 for sn
, s
in self
._sigs
.iteritems():
173 fx
, l
= self
._find
_scale
_factor
("X")
174 fy
, l
= self
._find
_scale
_factor
("Y")
175 x
= s
.ref
.data
* pow(10, fx
)
176 y
= s
.data
* pow(10, fy
)
177 self
._signals
2lines
[sn
].set_xdata(x
)
178 self
._signals
2lines
[sn
].set_ydata(y
)
180 def _find_scale_factor(self
, a
):
181 """ Choose the right scale for data on axis a
182 Return the scale factor (f) and a string with the label. (l)
183 E.g. for data from 0.001 to 0.01 return 3 and "m" for milli-
185 if a
== "X" and self
._scale
_factors
[0] is not None:
186 return self
._scale
_factors
[0], factors_to_names
[-self
._scale
_factors
[0]][0]
187 if a
== "Y" and self
._scale
_factors
[1] is not None:
188 return self
._scale
_factors
[1], factors_to_names
[-self
._scale
_factors
[1]][0]
189 # Find the absolute maximum of the data
193 for s
in self
._sigs
.itervalues():
195 mxs
.append(max(s
.ref
.data
))
196 mns
.append(min(s
.ref
.data
))
198 mxs
.append(max(s
.data
))
199 mns
.append(min(s
.data
))
204 # Find the scaling factor using the absolute maximum
210 while not (abs(mx
* pow(10.0, f
)) < 1000.0 \
211 and abs(mx
* pow(10.0, f
)) >= 1.0):
213 if factors_to_names
.has_key(-f
) and \
214 ((self
._xunit
!= "" and a
== "X") or \
215 (self
._yunit
!= "" and a
!= "X")):
216 l
= factors_to_names
[-f
][0]
221 l
= "10e" + str(-f
) + " "
225 """ Return the graph units """
226 return self
._xunit
, self
._yunit
228 def set_unit(self
, unit
):
229 """ Define the graph units. If only one argument is provided,
230 set y axis, if both are provided, set both.
232 if isinstance(unit
, tuple):
233 if len(unit
) == 1 or (len(unit
) == 2 and not unit
[1]):
234 self
._yunit
= unit
[0]
235 elif len(unit
) == 2 and unit
[1]:
236 self
._xunit
= unit
[0]
237 self
._yunit
= unit
[1]
239 assert 0, "Invalid argument"
241 assert 0, "Invalid argument"
248 fx
, l
= self
._find
_scale
_factor
("X")
249 xl
= xl
+ " (" + l
+ xu
+ ")"
255 fy
, l
= self
._find
_scale
_factor
("Y")
256 yl
= yl
+ " (" + l
+ yu
+ ")"
257 mplAxes
.set_xlabel(self
, xl
)
258 mplAxes
.set_ylabel(self
, yl
)
262 """ Return the axes scale
264 # return self._FUNC_TO_SCALES[self._plotf]
265 # Is there a better way?
266 x
= self
.get_xscale()
267 y
= self
.get_yscale()
268 if x
== "linear" and y
== "linear":
270 elif x
== "linear" and y
== "log":
277 def set_scale(self
, scale
):
278 """ Set axes scale, either lin, logx, logy or loglog
280 SCALES_TO_STR
= {"lin": ["linear", "linear"],\
281 "logx": ["log","linear"],\
282 "logy": ["linear", "log"],\
283 "loglog": ["log", "log"]}
284 mplAxes
.set_xscale(self
, SCALES_TO_STR
[scale
][0])
285 mplAxes
.set_yscale(self
, SCALES_TO_STR
[scale
][1])
288 """ Return the axes limits
290 self
._xrange
= mplAxes
.get_xlim(self
)
291 self
._yrange
= mplAxes
.get_ylim(self
)
292 return self
._xrange
, self
._yrange
294 def set_range(self
, arg
="reset"):
296 Form 1: set_range("reset") delete range specs
297 Form 2: set_range(("x", [xmin, xmax])) set range for x axis
298 set_range(("y", [ymin, ymax])) set range for y axis
299 Form 3: set_range([xmin, xmax, ymin, ymax]) set range for both axis
305 elif isinstance(arg
, list) and len(arg
) == 4:
306 self
._xrange
= [arg
[0], arg
[1]]
307 self
._yrange
= [arg
[2], arg
[3]]
308 elif isinstance(arg
, tuple):
309 if len(arg
) == 2 and isinstance(arg
[0], str):
310 if arg
[0] == "x" and isinstance(arg
[1], list) and\
312 self
._xrange
= arg
[1]
313 elif arg
[0] == "y" and isinstance(arg
[1], list) and\
315 self
._yrange
= arg
[1]
317 assert 0, "Unrecognized argument"
319 if len(self
._xrange
) == 2:
320 mplAxes
.set_xlim(self
, self
._xrange
[0], self
._xrange
[1])
321 if len(self
._yrange
) == 2:
322 mplAxes
.set_ylim(self
, self
._yrange
[0], self
._yrange
[1])
324 def toggle_cursors(self
, ctype
="", num
=None, val
=None):
325 """ Toggle the cursors in the graph
326 Call canvas.draw() shoud be called after to update the figure
329 if not ctype
or num
is None or val
is None:
332 if not ctype
in ["horiz", "vert"]:
334 if num
>= len(self
._cursors
[ctype
]):
338 if self
._cursors
[ctype
][num
] is None:
339 self
._set
_cursor
(ctype
, num
, val
)
341 self
._cursors
[ctype
][num
].value
= val
342 self
._cursors
[ctype
][num
].set_visible()
343 self
._cursors
[ctype
][num
].draw(self
)
344 self
._print
_cursors
()
345 fx
, lx
= self
._find
_scale
_factor
("X")
346 fy
, ly
= self
._find
_scale
_factor
("Y")
348 def _draw_cursors(self
):
349 """ Draw the cursor lines on the graph
350 Called at the end of plot()
352 fx
, lx
= self
._find
_scale
_factor
("X")
353 fy
, ly
= self
._find
_scale
_factor
("Y")
354 l
= {"horiz": ly
, "vert": lx
}
355 txt
= {"horiz": "", "vert": ""}
356 for t
, ct
in self
._cursors
.iteritems():
359 c
.draw(self
, ct
.index(c
))
361 def _set_cursor(self
, ctype
, num
, val
):
362 """ Add a cursor to the graph
364 if ctype
in ["horiz", "vert"]:
365 if num
>= 0 and num
< 2:
366 # Just handle two cursor
367 self
._cursors
[ctype
][num
] = Cursor(val
, ctype
)
368 self
._cursors
[ctype
][num
].draw(self
, num
)
370 assert 0, "Invalid cursor number"
372 assert 0, "Invalid cursor type"
374 def _print_cursors(self
):
375 """ Print cursors values on the graph
376 If both cursors are set_, print difference (delta)
378 fx
, lx
= self
._find
_scale
_factor
("X")
379 fy
, ly
= self
._find
_scale
_factor
("Y")
380 l
= {"horiz": ly
, "vert": lx
}
381 u
= {"horiz": self
._yunit
, "vert": self
._xunit
}
382 txt
= {"horiz": "", "vert": ""}
383 # Preapre string for each cursor type (i.e. "horiz" and "vert")
384 for t
, cl
in self
._cursors
.iteritems():
386 if c
is not None and c
.visible
:
387 # Add cursors value to text
388 txt
[t
] += " %d: %8.3f %2s%-3s" \
389 % (cl
.index(c
) + 1, float(c
.value
), l
[t
], u
[t
])
390 if not None in cl
and cl
[0].visible
and cl
[1].visible
:
391 # Add cursors difference (delta)
392 txt
[t
] += " d%s: %8.3f %2s%-3s"\
393 % (u
[t
], float(cl
[1].value
- cl
[0].value
),\
396 if self
._txt
is None or not self
._txt
.axes
== self
:
398 rc('font', family
='monospace')
399 self
._txt
= self
.text(0.02, 0.1, "",\
400 transform
=self
.transAxes
,\
402 self
._txt
.set_size(0.75 * self
._txt
.get_size())
404 self
._txt
.set_text("%s\n%s" % (txt
["horiz"], txt
["vert"]))
408 """ Return a list of the signal names
414 """ Return a string with the type of the graph
415 To be overloaded by derived classes.
420 def axis_names(self
):
421 """Return the axis name"""
422 return [self
._xaxis
, self
._yaxis
]
424 def get_scale_factors(self
):
425 """Return the scane factors names"""
426 fx
, lx
= self
._find
_scale
_factor
('X')
427 fy
, ly
= self
._find
_scale
_factor
('Y')
430 def set_scale_factors(self
, x_factor
, y_factor
):
431 old_fx
, lx
= self
._find
_scale
_factor
('X')
432 old_fy
, ly
= self
._find
_scale
_factor
('Y')
433 if x_factor
is not None:
434 self
._scale
_factors
[0] = -x_factor
436 self
._scale
_factors
[0] = x_factor
437 if y_factor
is not None:
438 self
._scale
_factors
[1] = -y_factor
440 self
._scale
_factors
[1] = y_factor
441 fx
, lx
= self
._find
_scale
_factor
('X')
442 fy
, ly
= self
._find
_scale
_factor
('Y')
443 xr
, yr
= self
.get_range()
444 self
.set_range([xr
[0] * pow(10, fx
- old_fx
),
445 xr
[1] * pow(10, fx
- old_fx
),
446 yr
[0] * pow(10, fy
- old_fy
),
447 yr
[1] * pow(10, fy
- old_fy
)])
448 self
.update_signals()
449 self
.set_unit((self
._xunit
, self
._yunit
))
451 unit
= property(get_unit
, set_unit
)
452 scale
= property(get_scale
, set_scale
)
453 range = property(get_range
, set_range
)
454 scale_factors
= property(get_scale_factors
, set_scale_factors
)