From: <jd...@us...> - 2008-04-30 19:53:25
|
Revision: 5100 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=5100&view=rev Author: jdh2358 Date: 2008-04-30 12:53:10 -0700 (Wed, 30 Apr 2008) Log Message: ----------- refinements to the rec editors plus examples Modified Paths: -------------- trunk/matplotlib/CHANGELOG trunk/matplotlib/lib/matplotlib/mlab.py trunk/matplotlib/lib/mpl_toolkits/gtktools.py Added Paths: ----------- trunk/matplotlib/examples/data/demodata.csv trunk/matplotlib/examples/date_demo.py trunk/matplotlib/examples/rec_edit_gtk_custom.py trunk/matplotlib/examples/rec_edit_gtk_simple.py Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2008-04-29 21:10:44 UTC (rev 5099) +++ trunk/matplotlib/CHANGELOG 2008-04-30 19:53:10 UTC (rev 5100) @@ -1,3 +1,6 @@ +2008-04-30 Added some record array editing widgets for gtk -- see + examples/rec_edit*.py - JDH + 2008-04-29 Fix bug in mlab.sqrtm - MM 2008-04-28 Fix bug in SVG text with Mozilla-based viewers (the symbol Added: trunk/matplotlib/examples/data/demodata.csv =================================================================== --- trunk/matplotlib/examples/data/demodata.csv (rev 0) +++ trunk/matplotlib/examples/data/demodata.csv 2008-04-30 19:53:10 UTC (rev 5100) @@ -0,0 +1,11 @@ +clientid,date,weekdays,gains,prices,up +0,2008-04-30,Wed,-0.52458192906686452,7791404.0091921333,False +1,2008-05-01,Thu,0.076191536201738269,3167180.7366340165,True +2,2008-05-02,Fri,-0.86850970062880861,9589766.9613829032,False +3,2008-05-03,Sat,-0.42701083852713395,8949415.1867596991,False +4,2008-05-04,Sun,0.2532553652693274,937163.44375252665,True +5,2008-05-05,Mon,-0.68151636911081892,949579.88022264629,False +6,2008-05-06,Tue,0.0071911579626532168,7268426.906552773,True +7,2008-05-07,Wed,0.67449747200412147,7517014.782897247,True +8,2008-05-08,Thu,-1.1841008656818983,1920959.5423492221,False +9,2008-05-09,Fri,-1.5803692595811152,8456240.6198725495,False Added: trunk/matplotlib/examples/date_demo.py =================================================================== --- trunk/matplotlib/examples/date_demo.py (rev 0) +++ trunk/matplotlib/examples/date_demo.py 2008-04-30 19:53:10 UTC (rev 5100) @@ -0,0 +1,14 @@ +""" +Simple example showing how to plot a time series with datetime objects +""" +import datetime +import matplotlib.pyplot as plt + +today = datetime.date.today() +dates = [today+datetime.timedelta(days=i) for i in range(10)] + +fig = plt.figure() +ax = fig.add_subplot(111) +ax.plot(dates, range(10)) +fig.autofmt_xdate() +plt.show() Added: trunk/matplotlib/examples/rec_edit_gtk_custom.py =================================================================== --- trunk/matplotlib/examples/rec_edit_gtk_custom.py (rev 0) +++ trunk/matplotlib/examples/rec_edit_gtk_custom.py 2008-04-30 19:53:10 UTC (rev 5100) @@ -0,0 +1,37 @@ +""" +generate an editable gtk treeview widget for record arrays with custom +formatting of the cells and show how to limit string entries to a list +of strings +""" +import gtk +import numpy as np +import matplotlib.mlab as mlab +import mpl_toolkits.gtktools as gtktools + +r = mlab.csv2rec('data/demodata.csv', converterd={'weekdays':str}) + + +formatd = mlab.get_formatd(r) +formatd['date'] = mlab.FormatDate('%Y-%m-%d') +formatd['prices'] = mlab.FormatMillions(precision=1) +formatd['gain'] = mlab.FormatPercent(precision=2) + +# use a drop down combo for weekdays +stringd = dict(weekdays=['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']) +constant = ['clientid'] # block editing of this field + + +liststore = gtktools.RecListStore(r, formatd=formatd, stringd=stringd) +treeview = gtktools.RecTreeView(liststore, constant=constant) + +def mycallback(liststore, rownum, colname, oldval, newval): + print 'verify: old=%s, new=%s, rec=%s'%(oldval, newval, liststore.r[rownum][colname]) + +liststore.callbacks.connect('cell_changed', mycallback) + +win = gtk.Window() +win.set_title('click to edit') +win.add(treeview) +win.show_all() +win.connect('delete-event', lambda *args: gtk.main_quit()) +gtk.main() Added: trunk/matplotlib/examples/rec_edit_gtk_simple.py =================================================================== --- trunk/matplotlib/examples/rec_edit_gtk_simple.py (rev 0) +++ trunk/matplotlib/examples/rec_edit_gtk_simple.py 2008-04-30 19:53:10 UTC (rev 5100) @@ -0,0 +1,15 @@ +""" +Load a CSV file into a record array and edit it in a gtk treeview +""" + +import gtk +import numpy as np +import matplotlib.mlab as mlab +import mpl_toolkits.gtktools as gtktools + +r = mlab.csv2rec('data/demodata.csv', converterd={'weekdays':str}) + +liststore, treeview, win = gtktools.edit_recarray(r) +win.set_title('click to edit') +win.connect('delete-event', lambda *args: gtk.main_quit()) +gtk.main() Modified: trunk/matplotlib/lib/matplotlib/mlab.py =================================================================== --- trunk/matplotlib/lib/matplotlib/mlab.py 2008-04-29 21:10:44 UTC (rev 5099) +++ trunk/matplotlib/lib/matplotlib/mlab.py 2008-04-30 19:53:10 UTC (rev 5100) @@ -2170,7 +2170,7 @@ data type. When set to zero all rows are validated. converterd, if not None, is a dictionary mapping column number or - munged column name to a converter function + munged column name to a converter function. names, if not None, is a list of header names. In this case, no header will be read from the file @@ -2256,11 +2256,18 @@ return func(val) return newfunc + + def mybool(x): + if x=='True': return True + elif x=='False': return False + else: raise ValueError('invalid bool') + dateparser = dateutil.parser.parse mydateparser = with_default_value(dateparser, datetime.date(1,1,1)) myfloat = with_default_value(float, np.nan) myint = with_default_value(int, -1) mystr = with_default_value(str, '') + mybool = with_default_value(mybool, None) def mydate(x): # try and return a date object @@ -2273,7 +2280,7 @@ def get_func(name, item, func): # promote functions in this order - funcmap = {myint:myfloat, myfloat:mydate, mydate:mydateparser, mydateparser:mystr} + funcmap = {mybool:myint,myint:myfloat, myfloat:mydate, mydate:mydateparser, mydateparser:mystr} try: func(name, item) except: if func==mystr: @@ -2294,7 +2301,7 @@ converters = None for i, row in enumerate(reader): if i==0: - converters = [myint]*len(row) + converters = [mybool]*len(row) if checkrows and i>checkrows: break #print i, len(names), len(row) @@ -2308,6 +2315,9 @@ func = converters[j] if len(item.strip()): func = get_func(name, item, func) + else: + # how should we handle custom converters and defaults? + func = with_default_value(func, None) converters[j] = func return converters @@ -2427,6 +2437,13 @@ def fromstr(self, s): return int(s) +class FormatBool(FormatObj): + def toval(self, x): + return x + + def fromstr(self, s): + return bool(s) + class FormatPercent(FormatFloat): def __init__(self, precision=4): FormatFloat.__init__(self, precision, scale=100.) @@ -2465,6 +2482,7 @@ defaultformatd = { + np.bool_ : FormatBool(), np.int16 : FormatInt(), np.int32 : FormatInt(), np.int64 : FormatInt(), Modified: trunk/matplotlib/lib/mpl_toolkits/gtktools.py =================================================================== --- trunk/matplotlib/lib/mpl_toolkits/gtktools.py 2008-04-29 21:10:44 UTC (rev 5099) +++ trunk/matplotlib/lib/mpl_toolkits/gtktools.py 2008-04-30 19:53:10 UTC (rev 5100) @@ -37,6 +37,49 @@ import matplotlib.cbook as cbook import matplotlib.mlab as mlab + +def error_message(msg, parent=None, title=None): + """ + create an error message dialog with string msg. Optionally set + the parent widget and dialog title + """ + + dialog = gtk.MessageDialog( + parent = None, + type = gtk.MESSAGE_ERROR, + buttons = gtk.BUTTONS_OK, + message_format = msg) + if parent is not None: + dialog.set_transient_for(parent) + if title is not None: + dialog.set_title(title) + else: + dialog.set_title('Error!') + dialog.show() + dialog.run() + dialog.destroy() + return None + +def simple_message(msg, parent=None, title=None): + """ + create a simple message dialog with string msg. Optionally set + the parent widget and dialog title + """ + dialog = gtk.MessageDialog( + parent = None, + type = gtk.MESSAGE_INFO, + buttons = gtk.BUTTONS_OK, + message_format = msg) + if parent is not None: + dialog.set_transient_for(parent) + if title is not None: + dialog.set_title(title) + dialog.show() + dialog.run() + dialog.destroy() + return None + + def gtkformat_factory(format, colnum): """ copy the format, perform any overrides, and attach an gtk style attrs @@ -313,6 +356,10 @@ * r - the record array with the edited values + * formatd - the list of mlab.FormatObj instances, with gtk attachments + + * stringd - a dict mapping dtype names to a list of valid strings for the combo drop downs + * callbacks - a matplotlib.cbook.CallbackRegistry. Connect to the cell_changed with def mycallback(liststore, rownum, colname, oldval, newval): @@ -320,85 +367,265 @@ cid = liststore.callbacks.connect('cell_changed', mycallback) - """ - def __init__(self, r, formatd=None): + """ + def __init__(self, r, formatd=None, stringd=None): + """ + r is a numpy record array + + formatd is a dict mapping dtype name to mlab.FormatObj instances + + stringd, if not None, is a dict mapping dtype names to a list of + valid strings for a combo drop down editor + """ + + if stringd is None: + stringd = dict() + if formatd is None: formatd = mlab.get_formatd(r) + self.stringd = stringd self.callbacks = cbook.CallbackRegistry(['cell_changed']) self.r = r - gtk.ListStore.__init__(self, *([gobject.TYPE_STRING]*len(r.dtype))) + self.headers = r.dtype.names self.formats = [gtkformat_factory(formatd.get(name, mlab.FormatObj()),i) for i,name in enumerate(self.headers)] + + # use the gtk attached versions + self.formatd = formatd = dict(zip(self.headers, self.formats)) + types = [] + for format in self.formats: + if isinstance(format, mlab.FormatBool): + types.append(gobject.TYPE_BOOLEAN) + else: + types.append(gobject.TYPE_STRING) + + self.combod = dict() + if len(stringd): + types.extend([gobject.TYPE_INT]*len(stringd)) + + keys = stringd.keys() + keys.sort() + + valid = set(r.dtype.names) + for ikey, key in enumerate(keys): + assert(key in valid) + combostore = gtk.ListStore(gobject.TYPE_STRING) + for s in stringd[key]: + combostore.append([s]) + self.combod[key] = combostore, len(self.headers)+ikey + + + gtk.ListStore.__init__(self, *types) + for row in r: - self.append([func.tostr(val) for func, val in zip(self.formats, row)]) + vals = [] + for formatter, val in zip(self.formats, row): + if isinstance(formatter, mlab.FormatBool): + vals.append(val) + else: + vals.append(formatter.tostr(val)) + if len(stringd): + # todo, get correct index here? + vals.extend([0]*len(stringd)) + self.append(vals) def position_edited(self, renderer, path, newtext, position): - self[path][position] = newtext - format = self.formats[int(position)] + position = int(position) + format = self.formats[position] + rownum = int(path) - colname = self.headers[int(position)] + colname = self.headers[position] oldval = self.r[rownum][colname] - newval = format.fromstr(newtext) + try: newval = format.fromstr(newtext) + except ValueError: + msg = cbook.exception_to_str('Error converting "%s"'%newtext) + error_message(msg, title='Error') + return self.r[rownum][colname] = newval + + self[path][position] = format.tostr(newval) + + self.callbacks.process('cell_changed', self, rownum, colname, oldval, newval) -def editable_recarray(r, formatd=None): + def position_toggled(self, cellrenderer, path, position): + position = int(position) + format = self.formats[position] + + newval = not cellrenderer.get_active() + + rownum = int(path) + colname = self.headers[position] + oldval = self.r[rownum][colname] + self.r[rownum][colname] = newval + + self[path][position] = newval + + self.callbacks.process('cell_changed', self, rownum, colname, oldval, newval) + + + + + +class RecTreeView(gtk.TreeView): """ - return a (gtk.TreeView, RecListStore) from record array t and - format dictionary formatd where the keys are record array dtype - names and the values are matplotlib.mlab.FormatObj instances + An editable tree view widget for record arrays + """ + def __init__(self, recliststore, constant=None): + """ + build a gtk.TreeView to edit a RecListStore - Example: + constant, if not None, is a list of dtype names which are not editable + """ + self.recliststore = recliststore + + gtk.TreeView.__init__(self, recliststore) - formatd = mlab.get_formatd(r) - formatd['date'] = mlab.FormatDate('%Y-%m-%d') - formatd['volume'] = mlab.FormatMillions(precision=1) + combostrings = set(recliststore.stringd.keys()) - treeview, liststore = gtktools.editable_recarray(r, formatd=formatd) + + if constant is None: + constant = [] - def mycallback(liststore, rownum, colname, oldval, newval): - print 'verify: old=%s, new=%s, rec=%s'%(oldval, newval, liststore.r[rownum][colname]) + constant = set(constant) - liststore.callbacks.connect('cell_changed', mycallback) + for i, header in enumerate(recliststore.headers): + formatter = recliststore.formatd[header] + coltype = recliststore.get_column_type(i) + if coltype==gobject.TYPE_BOOLEAN: + renderer = gtk.CellRendererToggle() + if header not in constant: + renderer.connect("toggled", recliststore.position_toggled, i) + renderer.set_property('activatable', True) + elif header in combostrings: + renderer = gtk.CellRendererCombo() + renderer.connect("edited", recliststore.position_edited, i) + combostore, listind = recliststore.combod[header] + renderer.set_property("model", combostore) + renderer.set_property('editable', True) + else: + renderer = gtk.CellRendererText() + if header not in constant: + renderer.connect("edited", recliststore.position_edited, i) + renderer.set_property('editable', True) + + + if formatter is not None: + renderer.set_property('xalign', formatter.xalign) + + + + tvcol = gtk.TreeViewColumn(header) + self.append_column(tvcol) + tvcol.pack_start(renderer, True) + + if coltype == gobject.TYPE_STRING: + tvcol.add_attribute(renderer, 'text', i) + if header in combostrings: + combostore, listind = recliststore.combod[header] + tvcol.add_attribute(renderer, 'text-column', listind) + elif coltype == gobject.TYPE_BOOLEAN: + tvcol.add_attribute(renderer, 'active', i) + + + if formatter is not None and formatter.cell is not None: + tvcol.set_cell_data_func(renderer, formatter.cell) + + + + + self.connect("button-release-event", self.on_selection_changed) + self.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH) + self.get_selection().set_mode(gtk.SELECTION_BROWSE) + self.get_selection().set_select_function(self.on_select) + + + def on_select(self, *args): + return False + + def on_selection_changed(self, *args): + (path, col) = self.get_cursor() + ren = col.get_cell_renderers()[0] + if isinstance(ren, gtk.CellRendererText): + self.set_cursor_on_cell(path, col, ren, start_editing=True) + +def edit_recarray(r, formatd=None, stringd=None, constant=None, autowin=True): + """ + create a RecListStore and RecTreeView and return them. + + If autowin is True, create a gtk.Window, insert the treeview into + it, and return it (return value will be (liststore, treeview, win) + + See RecListStore and RecTreeView for a description of the keyword args + """ + + liststore = RecListStore(r, formatd=formatd, stringd=stringd) + treeview = RecTreeView(liststore, constant=constant) + + if autowin: win = gtk.Window() - win.show() - win.connect('destroy', lambda x: gtk.main_quit()) win.add(treeview) - gtk.main() + win.show_all() + return liststore, treeview, win + else: + return liststore, treeview + + - """ - liststore = RecListStore(r, formatd=formatd) - treeview = gtk.TreeView() - if formatd is None: - formatd = mlab.get_formatd(r) - for i, header in enumerate(liststore.headers): - renderer = gtk.CellRendererText() - renderer.connect("edited", liststore.position_edited, i) - renderer.set_property('editable', True) - formatter = gtkformat_factory(formatd.get(header), i) +if __name__=='__main__': - if formatter is not None: - renderer.set_property('xalign', formatter.xalign) + import datetime + import gtk + import numpy as np + import matplotlib.mlab as mlab + N = 10 + today = datetime.date.today() + dates = [today+datetime.timedelta(days=i) for i in range(N)] # datetimes + weekdays = [d.strftime('%a') for d in dates] # strings + gains = np.random.randn(N) # floats + prices = np.random.rand(N)*1e7 # big numbers + up = gains>0 # bools + clientid = range(N) # ints - tvcol = gtk.TreeViewColumn(header) - treeview.append_column(tvcol) - tvcol.pack_start(renderer, True) - tvcol.add_attribute(renderer, 'text', i) - if formatter is not None and formatter.cell is not None: - tvcol.set_cell_data_func(renderer, formatter.cell) + r = np.rec.fromarrays([clientid, dates, weekdays, gains, prices, up], + names='clientid,date,weekdays,gains,prices,up') + # some custom formatters + formatd = mlab.get_formatd(r) + formatd['date'] = mlab.FormatDate('%Y-%m-%d') + formatd['prices'] = mlab.FormatMillions(precision=1) + formatd['gain'] = mlab.FormatPercent(precision=2) - treeview.set_model(liststore) - treeview.show() - treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH) + # use a drop down combo for weekdays + stringd = dict(weekdays=['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']) + constant = ['clientid'] # block editing of this field - return treeview, liststore + liststore = RecListStore(r, formatd=formatd, stringd=stringd) + treeview = RecTreeView(liststore, constant=constant) + + def mycallback(liststore, rownum, colname, oldval, newval): + print 'verify: old=%s, new=%s, rec=%s'%(oldval, newval, liststore.r[rownum][colname]) + + liststore.callbacks.connect('cell_changed', mycallback) + + win = gtk.Window() + win.set_title('with full customization') + win.add(treeview) + win.show_all() + + # or you just use the defaults + r2 = r.copy() + ls, tv, win2 = edit_recarray(r2) + win2.set_title('with all defaults') + + gtk.main() + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |