prettyformat -> hooke.util.si, and 'plot SI format' option to GUI
[hooke.git] / hooke / ui / gui / panel / plot.py
1 # Copyright\r
2 \r
3 """Plot panel for Hooke.\r
4 \r
5 Notes\r
6 -----\r
7 Originally based on `this example`_.\r
8 \r
9 .. _this example:\r
10   http://matplotlib.sourceforge.net/examples/user_interfaces/embedding_in_wx2.html\r
11 """\r
12 \r
13 import matplotlib\r
14 matplotlib.use('WXAgg')  # use wxpython with antigrain (agg) rendering\r
15 from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas\r
16 from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavToolbar\r
17 from matplotlib.figure import Figure\r
18 from matplotlib.ticker import Formatter\r
19 import wx\r
20 \r
21 from ....util.callback import callback, in_callback\r
22 from ....util.si import ppSI, split_data_label\r
23 from . import Panel\r
24 \r
25 \r
26 class HookeFormatter (Formatter):\r
27     """:class:`matplotlib.ticker.Formatter` using SI prefixes.\r
28     """\r
29     def __init__(self, unit='', decimals=2):\r
30         self.decimals = decimals\r
31         self.unit = unit\r
32 \r
33     def __call__(self, x, pos=None):\r
34         """Return the format for tick val `x` at position `pos`.\r
35         """\r
36         if x == 0:\r
37             return '0'\r
38         return ppSI(value=x, unit=self.unit, decimals=self.decimals)\r
39 \r
40 \r
41 class PlotPanel (Panel, wx.Panel):\r
42     """UI for graphical curve display.\r
43     """\r
44     def __init__(self, callbacks=None, **kwargs):\r
45         self.display_coordinates = False\r
46         self.style = 'line'\r
47         self._curve = None\r
48         super(PlotPanel, self).__init__(\r
49             name='plot', callbacks=callbacks, **kwargs)\r
50         self._c = {}\r
51         self._c['figure'] = Figure()\r
52         self._c['canvas'] = FigureCanvas(\r
53             parent=self, id=wx.ID_ANY, figure=self._c['figure'])\r
54         self._c['toolbar'] = NavToolbar(self._c['canvas'])\r
55 \r
56         self._set_color(wx.NamedColor('WHITE'))\r
57         sizer = wx.BoxSizer(wx.VERTICAL)\r
58         sizer.Add(self._c['canvas'], 1, wx.LEFT | wx.TOP | wx.GROW)\r
59         self._setup_toolbar(toolbar=self._c['toolbar'], sizer=sizer)\r
60         self.SetSizer(sizer)\r
61         self.Fit()\r
62 \r
63         self.Bind(wx.EVT_SIZE, self._on_size) \r
64         self._c['figure'].canvas.mpl_connect(\r
65             'button_press_event', self._on_click)\r
66         self._c['figure'].canvas.mpl_connect(\r
67             'axes_enter_event', self._on_enter_axes)\r
68         self._c['figure'].canvas.mpl_connect(\r
69             'axes_leave_event', self._on_leave_axes)\r
70         self._c['figure'].canvas.mpl_connect(\r
71             'motion_notify_event', self._on_mouse_move)\r
72 \r
73     def _setup_toolbar(self, toolbar, sizer):\r
74         self._c['toolbar'].Realize()  # call after putting items in the toolbar\r
75         if wx.Platform == '__WXMAC__':\r
76             # Mac platform (OSX 10.3, MacPython) does not seem to cope with\r
77             # having a toolbar in a sizer. This work-around gets the buttons\r
78             # back, but at the expense of having the toolbar at the top\r
79             self.SetToolBar(toolbar)\r
80         elif wx.Platform == '__WXMSW__':\r
81             # On Windows platform, default window size is incorrect, so set\r
82             # toolbar width to figure width.\r
83             tw, th = toolbar.GetSizeTuple()\r
84             fw, fh = self._c['canvas'].GetSizeTuple()\r
85             # By adding toolbar in sizer, we are able to put it at the bottom\r
86             # of the frame - so appearance is closer to GTK version.\r
87             # As noted above, doesn't work for Mac.\r
88             toolbar.SetSize(wx.Size(fw, th))\r
89             sizer.Add(toolbar, 0 , wx.LEFT | wx.EXPAND)\r
90         else:\r
91             sizer.Add(toolbar, 0 , wx.LEFT | wx.EXPAND)\r
92         self._c['toolbar'].update()  # update the axes menu on the toolbar\r
93 \r
94     def _set_color(self, rgbtuple=None):\r
95         """Set both figure and canvas colors to `rgbtuple`.\r
96         """\r
97         if rgbtuple == None:\r
98             rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()\r
99         col = [c/255.0 for c in rgbtuple]\r
100         self._c['figure'].set_facecolor(col)\r
101         self._c['figure'].set_edgecolor(col)\r
102         self._c['canvas'].SetBackgroundColour(wx.Colour(*rgbtuple))\r
103 \r
104     #def SetStatusText(self, text, field=1):\r
105     #    self.Parent.Parent.statusbar.SetStatusText(text, field)\r
106 \r
107     def _on_size(self, event):\r
108         event.Skip()\r
109         wx.CallAfter(self._resize_canvas)\r
110 \r
111     def _on_click(self, event):\r
112         #self.SetStatusText(str(event.xdata))\r
113         #print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(event.button, event.x, event.y, event.xdata, event.ydata)\r
114         pass\r
115 \r
116     def _on_enter_axes(self, event):\r
117         self.display_coordinates = True\r
118 \r
119     def _on_leave_axes(self, event):\r
120         self.display_coordinates = False\r
121         #self.SetStatusText('')\r
122 \r
123     def _on_mouse_move(self, event):\r
124         if event.guiEvent.m_shiftDown:\r
125             self._c['toolbar'].set_cursor(wx.CURSOR_RIGHT_ARROW)\r
126         else:\r
127             self._c['toolbar'].set_cursor(wx.CURSOR_ARROW)\r
128         if self.display_coordinates:\r
129             coordinateString = ''.join(\r
130                 ['x: ', str(event.xdata), ' y: ', str(event.ydata)])\r
131             #TODO: pretty format\r
132             #self.SetStatusText(coordinateString)\r
133 \r
134     def _resize_canvas(self):\r
135         w,h = self.GetClientSize()\r
136         tw,th = self._c['toolbar'].GetSizeTuple()\r
137         dpi = float(self._c['figure'].get_dpi())\r
138         self._c['figure'].set_figwidth(w/dpi)\r
139         self._c['figure'].set_figheight((h-th)/dpi)\r
140         self._c['canvas'].draw()\r
141         self.Refresh()\r
142 \r
143     def OnPaint(self, event):\r
144         print 'painting'\r
145         super(PlotPanel, self).OnPaint(event)\r
146         self._c['canvas'].draw()\r
147 \r
148     def set_curve(self, curve, config={}):\r
149         self._curve = curve\r
150         self.update(config=config)\r
151 \r
152     def update(self, config={}):\r
153         x_name = 'z piezo (m)'\r
154         y_name = 'deflection (m)'\r
155 \r
156         self._c['figure'].clear()\r
157         self._c['figure'].suptitle(\r
158             self._hooke_frame._file_name(self._curve.name),\r
159             fontsize=12)\r
160         axes = self._c['figure'].add_subplot(1, 1, 1)\r
161 \r
162         if config['plot si format'] == 'True':  # TODO: config should convert\r
163             d = int(config['plot decimals'])  # TODO: config should convert\r
164             x_n, x_unit = split_data_label(x_name)\r
165             y_n, y_unit = split_data_label(y_name)\r
166             fx = HookeFormatter(decimals=d, unit=x_unit)\r
167             axes.xaxis.set_major_formatter(fx)\r
168             fy = HookeFormatter(decimals=d, unit=y_unit)\r
169             axes.yaxis.set_major_formatter(fy)\r
170             axes.set_xlabel(x_n)\r
171             axes.set_ylabel(y_n)\r
172         else:\r
173             axes.set_xlabel(x_name)\r
174             axes.set_ylabel(y_name)\r
175 \r
176         self._c['figure'].hold(True)\r
177         for i,data in enumerate(self._curve.data):\r
178             axes.plot(data[:,data.info['columns'].index(x_name)],\r
179                       data[:,data.info['columns'].index(y_name)],\r
180                       '.',\r
181                       label=data.info['name'])\r
182         if config['plot legend'] == 'True':  # HACK: config should convert\r
183             axes.legend(loc='best')\r
184         self._c['canvas'].draw()\r
185 \r
186 #  LocalWords:  matplotlib\r