1 """Base classes and registry for notebook figure export.
2
3 Copyright 2008-2012 Research Foundation State University of New York
4 This file is part of QUB Express.
5
6 QUB Express is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 QUB Express is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License,
17 named LICENSE.txt, in the QUB Express program directory. If not, see
18 <http://www.gnu.org/licenses/>.
19
20 """
21
22 from cStringIO import StringIO
23 import os
24 import sys
25 import traceback
26 import qubx.accept
27 import qubx.pyenv
28 import qubx.settings
29 import qubx.tree
30 from qubx.util_types import Anon, WeakEvent, Reffer
31
33 """The simplest type of notebook item; this class has the minimal properties and methods to implement the notebook protocol:
34
35 mnu_caption: text for the notebook menu, e.g. "Chart"
36 global_name: How to find this item from the global namespace, e.g. QubX.AmpHist.plots[0].nbChart
37 (for script recording)
38 to_txt: function() -> string, returns plain-text representation of this item
39
40 Subclasses may present additional information, e.g. pictures or data series, but should
41 also provide to_txt for compatibility with console output.
42 """
43 - def __init__(self, mnu_caption, global_name, to_txt, *args, **kw):
47
49 """A bundle of related notebook items; e.g. "All Charts". Each item in items is an instance of NbItem or a subclass.
50
51 >>> if isinstance(myNbItem, NbItems):
52 ... for item in myNbItem:
53 ... print item
54
55 """
56 - def __init__(self, mnu_caption, global_name, *items):
65 buf = StringIO()
66 for item in self:
67 buf.write(item.to_txt())
68 return buf.getvalue()
69
70 -class NbText(NbItem):
71 """A simple notebook item with static text."""
72 - def __init__(self, msg):
73 NbItem.__init__(self, "Text", "", lambda: msg)
74
75 -class NbDynText(NbItem):
76 """A simple notebook item with text that's recomputed by the get_msg function whenever it's needed."""
77 - def __init__(self, mnu_caption, global_name, get_msg):
78 NbItem.__init__(self, mnu_caption, global_name, get_msg)
79
81 """A notebook item with tabular data. Subclasses NbChart and NbTrace define series using this underlying table of data.
82
83 get_caption: function() -> str, returns caption for the table
84 get_shape: function() -> (rows, columns), returns dimensions
85 get_headers: function() -> list of str, returns column headers
86 get_row: function(r) -> list, returns a row of data
87 get_col: function(c) -> list, returns a column of data
88 (When creating an NbTable, provide either get_row or get_col and the other will be auto-generated;
89 when using an NbTable, both get_row and get_col are available.)
90 get_col_format: function(c) -> (function(val) -> str), returns a function which formats values from column c as strings (by default, the str function)
91 get_type: function() -> type; returns e.g. float or str
92 """
93 - def __init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, get_row=None, get_col=None, get_col_format=None, get_type=None,
94 **kw):
95 the_get_row = get_row or self.__get_row
96 the_get_col = get_col or self.__get_col
97 the_get_col_format = get_col_format or self.__get_col_format
98 the_get_type = get_type or self.__get_type
99 NbItem.__init__(self, mnu_caption=mnu_caption, global_name=global_name,
100 get_caption=get_caption, to_txt=self.__to_txt, get_shape=get_shape, get_headers=get_headers,
101 get_row=the_get_row, get_col=the_get_col, get_col_format=the_get_col_format,
102 get_type=the_get_type, **kw)
108 Nr, Nc = self.get_shape()
109 return [self.get_row(r)[c] for r in xrange(Nr)]
111 Nr, Nc = self.get_shape()
112 return [(len(col) > r) and col[r] or 0.0 for col in [self.get_col(c) for c in xrange(Nc)]]
114 buf = StringIO()
115 buf.write('\t'.join(self.get_headers()))
116 buf.write('\n')
117 Nr, Nc = self.get_shape()
118 col_format = [self.get_col_format(c) for c in xrange(Nc)]
119 if self.__get_row != self.get_row:
120 for r in xrange(Nr):
121 buf.write('\t'.join(col_format[c](x) for c,x in enumerate(self.get_row(r))))
122 buf.write('\n')
123 else:
124 cols = [self.get_col(c) for c in xrange(Nc)]
125 for r in xrange(Nr):
126 buf.write('\t'.join((len(cols[c]) > r) and col_format[c](cols[c][r]) or "0.0" for c in xrange(Nc)))
127 buf.write('\n')
128 return buf.getvalue()
129
131 "Returns a function(qubx.table.Table), which returns an NbTable with only the specified columns."
132 def make_nb(table):
133 return qubx.notebook.NbTable(name, table.global_name and ('%s.notebook[%s]'%(table.global_name, repr(name))) or '',
134 get_caption=table.nb_get_caption,
135 get_shape=lambda: (table.nb_get_shape()[0], len([f for f in fields if f in table.fields])),
136 get_headers=lambda: [f for f in fields if f in table.fields],
137 get_row=lambda r: [table[r,f] for f in fields if f in table.fields],
138 get_col_format=lambda c: table.nb_get_col_format(table.fields.index([f for f in fields if f in table.fields][c])),
139 get_type=lambda: table.nb_get_type([f for f in fields if f in table.fields]))
140 make_nb.name = name
141 return make_nb
142
144 """A notebook item representing a specific array."""
145 - def __init__(self, arr, caption="", header="", format=str, as_column=True):
146 kw = {}
147 if as_column:
148 rows, cols = len(arr), 1
149 kw["get_col"] = lambda i: arr
150 else:
151 rows, cols = 1, len(arr)
152 kw["get_row"] = lambda i: arr
153 fmt = qubx.accept.acceptFormat(format)
154 kw["get_col_format"] = lambda i: fmt
155 if len(arr) and hasattr(arr[0], "replace"):
156 kw["get_type"] = lambda: str
157 else:
158 kw["get_type"] = lambda: float
159 NbTable.__init__(self, "Table", "", lambda: caption, lambda: (rows, cols), lambda: [header]*cols, **kw)
160
162 """A notebook item representing a specific 1d or 2d numpy.ndarray."""
163 - def __init__(self, arr, caption="", headers=[], format=str):
164 kw = {}
165 if len(arr.shape) == 2:
166 rows, cols = arr.shape
167 kw["get_col"] = lambda i: arr[:,i].flatten()
168 kw["get_row"] = lambda i: arr[i,:].flatten()
169 else:
170 rows, cols = 1, len(arr)
171 kw["get_col"] = lambda i: arr
172 fmt = qubx.accept.acceptFormat(format)
173 kw["get_col_format"] = lambda i: fmt
174 kw["get_type"] = lambda: float
175 hdrs = headers + (cols - len(headers)) * [""]
176 NbTable.__init__(self, "Table", "", lambda: caption, lambda: (rows, cols), lambda: hdrs, **kw)
177
178
179
180 DOTS = 0
181 LINES = 1
182 HISTOGRAM = 2
183
185 h = '%x'%c
186 if len(h) == 1:
187 return '0%s'%h
188 return h
190 return '#%s' % ''.join(c2h(255*x) for x in rgb[:3])
191
193 """Properties of one series in a chart item:
194
195 xcol: index of the X column
196 ycol: index of the Y column
197 first_row: this series may start on a row other than 0
198 nrow: and consist of fewer rows than the entire table
199 ser_type: one of DOTS, LINES, or HISTOGRAM = (0, 1, 2)
200 color_rgb: (r, g, b) with each coordinate between 0.0 and 1.0
201 weight: thickness of line, between 0.0 and 1.0
202 """
203 - def __init__(self, xcol, ycol, first_row=0, nrow=-1, ser_type=DOTS, color_rgb=(0,0,0), weight=1.0):
204 Anon.__init__(self, xcol=xcol, ycol=ycol, first_row=first_row, nrow=nrow,
205 ser_type=ser_type, color_rgb=color_rgb, color_hex=rgb_to_hex(color_rgb), weight=weight)
206
208 """Notebook item representing a 2D plot. In addition to the data defined by its L{NbTable} properties, it defines
209 L{NbChartSeries} to lay out dots, lines and histograms using the table data.
210
211 get_xlabel: function() -> string, caption for the x axis
212 get_ylabel: function() -> string, caption for the y axis
213 get_series: function() -> list of L{NbChartSeries}
214 get_colors: function() -> list of (r,g,b) (or None), optionally defining a palette
215 get_color_indices: function -> list of int (or None), optional colors for the first series
216 """
217 - def __init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, get_xlabel=lambda: "", get_ylabel=lambda: "",
218 get_series=lambda: [], get_colors=lambda:None, get_color_indices=lambda:None, *args, **kw):
219 kw['get_xlabel'] = get_xlabel
220 kw['get_ylabel'] = get_ylabel
221 kw['get_series'] = get_series
222 kw['get_colors'] = get_colors
223 kw['get_color_indices'] = get_color_indices
224 NbTable.__init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, *args, **kw)
225
227 """Notebook item representing sampled data from one or more signals.
228
229 get_trace_series: function() -> list of L{NbChartSeries}
230 """
231
232 - def __init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, get_trace_series=lambda: [], *args, **kw):
233 kw['get_trace_series'] = get_trace_series
234 NbChart.__init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, *args, **kw)
235
236
237
238
239
241 """Base class for objects which receive notebook items. Multiple NbTargets can be registered by name with the NbController qubx.notebook.Notebook.
242 Targets need not inherit from this class, but must implement these properties and methods:
243
244 supports: list of subclasses of NbItem which this target can handle, e.g. [NbText, NbDynText, NbTable]
245 nb_init(): called when the target is (re-) activated
246 nb_send(item): acts on the L{NbItem}
247 """
254
256 """Info about one registered notebook target:
257
258 target_id: short string to identify target in code
259 caption: user-visible name of target
260 menu_caption: shortened user-visible name, for menus
261 controls: gtk.Widget or None; additional gui elements
262 target: L{NbTarget}
263 """
264 pass
265
267 """Info about one automatic notebook item:
268
269 auto_id: string to programmatically identify this item and the occasion for automatically generating it
270 mnu_caption: user-visible description
271 active: False if this item is disabled (un-checked by user)
272 """
273 pass
274
276 """The global instance of this type, qubx.notebook.Notebook, is created by qubx.notebook.Init(),
277 and keeps registries of the various output methods (L{NbTarget}s) and automatically-triggered L{NbItem}s.
278
279 @ivar auto: dict(auto_id -> L{NbAutoRec})
280 @ivar targets: list of L{NbTargetRec}
281 @ivar target_ix: index of user's choice in targets
282 @ivar target_id: chosen target's target_id
283 @ivar target: chosen L{NbTarget}
284 @ivar OnAddAuto: L{WeakEvent}(index, L{NbAutoRec})
285 @ivar OnChangeAuto: L{WeakEvent}(index, L{NbAutoRec}) e.g. the user has toggled "active"
286 @ivar OnAddTarget: L{WeakEvent}(index)
287 @ivar OnRemovingTarget: L{WeakEvent}(index)
288 @ivar OnChangeTargetIx: L{WeakEvent}(index)
289 """
323 target_ix = property(lambda self: self.__target_ix, lambda self, x: self.set_target_ix(x))
325 if self.__target_ix < 0:
326 return ""
327 return self.targets[self.__target_ix].target_id
329 if (self.__target_ix >= 0) and (x == self.targets[self.__target_ix].target_id):
330 return
331 for i, t in enumerate(self.targets):
332 if t.target_id == x:
333 self.set_target_ix(i)
334 break
335 target_id = property(lambda self: self.get_target_id(), lambda self, x: self.set_target_id(x))
336 target = property(lambda self: self.targets[self.__target_ix])
338 """Returns all registered target_id values."""
339 return [target.target_id for target in self.targets]
353 - def register_target(self, target, target_id, caption, menu_caption=None, controls=None):
354 the_menu_caption = menu_caption
355 if the_menu_caption is None:
356 the_menu_caption = caption
357 ix = -1
358 for i,t in enumerate(self.targets):
359 if t.target_id == target_id:
360 ix = i
361 break
362 if ix >= 0:
363 self.OnRemovingTarget(ix)
364 del self.targets[ix]
365 ix = len(self.targets)
366 self.targets.append(NbTargetRec(target=target, target_id=target_id, caption=caption,
367 menu_caption=the_menu_caption, controls=controls))
368 self.OnAddTarget(ix)
369 if ix == 0:
370 self.target_ix = ix
372 """Declares that automatic items will be generated with this auto_id and caption. With default=False, the user will have to enable it in the Notebook tab."""
373 preexist = auto_id in self.auto
374 prop = self.props_auto[auto_id]
375 if prop.data:
376 active = bool(prop.data[0])
377 else:
378 active = default
379 prop.data = active
380 self.auto[auto_id] = NbAutoRec(auto_id=auto_id, mnu_caption=mnu_caption, active=active)
381 ix = sorted(self.auto.keys()).index(auto_id)
382 if preexist:
383 self.OnChangeAuto(ix, self.auto[auto_id])
384 else:
385 self.OnAddAuto(ix, self.auto[auto_id])
390 - def send(self, item, target_ix=None, auto_id=None, target_id=None):
391 """Writes an L{NbItem} to the chosen L{NbTarget}.
392
393 @param target_ix: index of target in targets, or None to use the user's choice
394 @param target_id: if (target_ix is None), picks a target by its target_id
395 @param auto_id: for items generated automatically rather than by explicit user or script request; will check if auto item is active before emitting
396 """
397 if auto_id and (auto_id in self.auto) and not self.auto[auto_id].active:
398 return
399 if not qubx.pyenv.env.globals['QUBX_READY']:
400 if qubx.pyenv.env.globals['DEBUG']:
401 print 'dropped early notebook item:'
402 print item.to_txt()
403 return
404 ix = None
405 if not (target_ix is None):
406 ix = target_ix
407 elif not (target_id is None):
408 for i, t in enumerate(self.targets):
409 if t.target_id == target_id:
410 ix = i
411 break
412 if ix is None:
413 ix = self.__target_ix
414
415 try:
416 if auto_id is None:
417 targeted = not ((target_ix is None) and (target_id is None))
418 if isinstance(item, NbText):
419 qubx.pyenv.env.OnScriptable('Notebook.send(qubx.notebook.NbText("""%s"""%s))' %
420 (item.to_txt(),
421 targeted and (", target_id='%s'" % self.targets[ix].target_id) or ""))
422 elif item.global_name:
423 qubx.pyenv.env.scriptable_if_matching("Notebook.send(%s%s)" %
424 (item.global_name,
425 targeted and (", target_id='%s'" % self.targets[ix].target_id) or ""),
426 [(item.global_name, item)])
427
428 self.targets[ix].target.nb_send(item)
429 except:
430 traceback.print_exc()
431
433 """Basic type of notebook output which prints everything to the stdout console."""
440
441
443 """Sets up qubx.notebook.Notebook as the global L{NbController} object."""
444 global Notebook
445 Notebook = NbController()
446
451