Exemple #1
0
class DataSourcePanel(wx.Panel):
    '''
    A panel with controls for selecting the source data for a histogramplot 
    '''
    def __init__(self, parent, figpanel, **kwargs):
        wx.Panel.__init__(self, parent, **kwargs)

        # the panel to draw charts on
        self.SetBackgroundColour('white')  # color for the background of panel
        self.figpanel = figpanel

        sizer = wx.BoxSizer(wx.VERTICAL)

        self.table_choice = ui.TableComboBox(self, -1, style=wx.CB_READONLY)
        self.x_choice = ComboBox(self,
                                 -1,
                                 size=(200, -1),
                                 choices=[''],
                                 style=wx.CB_READONLY)
        self.x_choice.Select(0)
        self.bins_input = wx.SpinCtrl(self, -1, '100')
        self.bins_input.SetRange(1, 400)
        self.x_scale_choice = ComboBox(
            self,
            -1,
            choices=[LINEAR_SCALE, LOG_SCALE, LOG2_SCALE],
            style=wx.CB_READONLY)
        self.x_scale_choice.Select(0)
        self.y_scale_choice = ComboBox(self,
                                       -1,
                                       choices=[LINEAR_SCALE, LOG_SCALE],
                                       style=wx.CB_READONLY)
        self.y_scale_choice.Select(0)
        self.filter_choice = ui.FilterComboBox(self, style=wx.CB_READONLY)
        self.filter_choice.Select(0)
        self.gate_choice = ui.GateComboBox(self, style=wx.CB_READONLY)
        self.gate_choice.set_gatable_columns([self.x_column])
        self.update_chart_btn = wx.Button(self, -1, "Update Chart")

        self.update_column_fields()

        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "x-axis:"), 0, wx.TOP, 4)
        sz.AddSpacer(2)
        sz.Add(self.table_choice, 1, wx.EXPAND)
        sz.AddSpacer(3)
        sz.Add(self.x_choice, 2, wx.EXPAND)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 2, 0)

        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "x-scale:"), 0, wx.TOP, 4)
        sz.AddSpacer(2)
        sz.Add(self.x_scale_choice, 1, wx.EXPAND)
        sz.AddSpacer(5)
        sz.Add(wx.StaticText(self, -1, "y-scale:"), 0, wx.TOP, 4)
        sz.AddSpacer(2)
        sz.Add(self.y_scale_choice, 1, wx.EXPAND)
        sz.AddSpacer(5)
        sz.Add(wx.StaticText(self, -1, "bins:"), 0, wx.TOP, 4)
        sz.AddSpacer(2)
        sz.Add(self.bins_input)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 2, 0)

        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "filter:"), 0, wx.TOP, 4)
        sz.AddSpacer(2)
        sz.Add(self.filter_choice, 1, wx.EXPAND)
        sz.AddSpacer(5)
        sz.Add(wx.StaticText(self, -1, "gate:"), 0, wx.TOP, 4)
        sz.AddSpacer(2)
        sz.Add(self.gate_choice, 1, wx.EXPAND)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 2, 0)

        sizer.Add(self.update_chart_btn)

        self.table_choice.Bind(wx.EVT_COMBOBOX, self.on_table_selected)
        self.update_chart_btn.Bind(wx.EVT_BUTTON, self.update_figpanel)
        self.gate_choice.addobserver(self.on_gate_selected)

        self.SetSizer(sizer)
        self.Show(1)

    @property
    def x_column(self):
        return sql.Column(
            self.table_choice.GetString(self.table_choice.GetSelection()),
            self.x_choice.GetString(self.x_choice.GetSelection()))

    @property
    def filter(self):
        return self.filter_choice.get_filter_or_none()

    def on_table_selected(self, evt):
        table = self.table_choice.Value
        if table == ui.TableComboBox.OTHER_TABLE:
            t = ui.get_other_table_from_user(self)
            if t is not None:
                self.table_choice.Items = self.table_choice.Items[:-1] + [
                    t
                ] + self.table_choice.Items[-1:]
                self.table_choice.Select(self.table_choice.Items.index(t))
            else:
                self.table_choice.Select(0)
                return
        self.update_column_fields()

    def on_gate_selected(self, gate_name):
        self.update_gate_helper()

    def update_gate_helper(self):
        gate_name = self.gate_choice.get_gatename_or_none()
        if gate_name:
            self.figpanel.gate_helper.set_displayed_gate(
                p.gates[gate_name], self.x_column, None)
        else:
            self.figpanel.gate_helper.disable()

    def update_column_fields(self):
        tablename = self.table_choice.GetString(
            self.table_choice.GetSelection())
        fieldnames = self.get_numeric_columns_from_table(tablename)
        self.x_choice.Clear()
        self.x_choice.AppendItems(fieldnames)
        self.x_choice.SetSelection(0)

    def get_numeric_columns_from_table(self, table):
        ''' Fetches names of numeric columns for the given table. '''
        measurements = db.GetColumnNames(table)
        types = db.GetColumnTypes(table)
        return [m for m, t in zip(measurements, types) if t in (float, int)]

    def _plotting_per_object_data(self):
        return (p.object_table and p.object_table
                in [self.x_column.table, self.x_column.table]
                or (self.x_column.table != p.image_table
                    and db.adjacent(p.object_table, self.x_column.table)))

    def update_figpanel(self, evt=None):
        self.gate_choice.set_gatable_columns([self.x_column])
        points = self._load_points()
        bins = int(self.bins_input.GetValue())
        self.figpanel.set_x_label(self.x_column.col)
        self.figpanel.set_x_scale(
            self.x_scale_choice.GetString(self.x_scale_choice.GetSelection()))
        self.figpanel.set_y_scale(
            self.y_scale_choice.GetString(self.y_scale_choice.GetSelection()))
        self.figpanel.setpoints(points, bins)
        self.update_gate_helper()
        self.figpanel.draw()

    def _load_points(self):
        q = sql.QueryBuilder()
        select = [self.x_column]
        q.set_select_clause(select)
        if self.filter is not None:
            q.add_filter(self.filter)

        return np.array(db.execute(str(q))).T[0]

    def save_settings(self):
        '''save_settings is called when saving a workspace to file.
        returns a dictionary mapping setting names to values encoded as strings
        '''
        d = {
            'table':
            self.table_choice.GetString(self.table_choice.GetSelection()),
            'x-axis':
            self.x_choice.GetString(self.x_choice.GetSelection()),
            'bins':
            self.bins_input.GetValue(),
            'x-scale':
            self.x_scale_choice.GetString(self.x_scale_choice.GetSelection()),
            'y-scale':
            self.y_scale_choice.GetString(self.y_scale_choice.GetSelection()),
            'filter':
            self.filter_choice.GetString(self.filter_choice.GetSelection()),
            'x-lim':
            self.figpanel.subplot.get_xlim(),
            'y-lim':
            self.figpanel.subplot.get_ylim(),
        }
        if self.gate_choice.get_gatename_or_none():
            d['gate'] = self.gate_choice.GetString(
                self.gate_choice.GetSelection())
        return d

    def load_settings(self, settings):
        '''load_settings is called when loading a workspace from file.
        settings - a dictionary mapping setting names to values encoded as
                   strings.
        '''
        if 'table' in settings:
            self.table_choice.SetStringSelection(settings['table'])
            self.update_column_fields()
        if 'x-axis' in settings:
            self.x_choice.SetStringSelection(settings['x-axis'])
        if 'bins' in settings:
            self.bins_input.SetValue(int(settings['bins']))
        if 'x-scale' in settings:
            self.x_scale_choice.SetStringSelection(settings['x-scale'])
        if 'y-scale' in settings:
            self.y_scale_choice.SetStringSelection(settings['y-scale'])
        if 'filter' in settings:
            self.filter_choice.SetStringSelection(settings['filter'])
        self.update_figpanel()
        if 'x-lim' in settings:
            self.figpanel.subplot.set_xlim(eval(settings['x-lim']))
        if 'y-lim' in settings:
            self.figpanel.subplot.set_ylim(eval(settings['y-lim']))
        if 'gate' in settings:
            self.gate_choice.SetStringSelection(settings['gate'])
            self.figpanel.gate_helper.set_displayed_gate(
                p.gates[settings['gate']], self.x_column, None)
        self.figpanel.draw()
class DataSourcePanel(wx.Panel):
    '''
    A panel with controls for selecting the source data for a boxplot 
    '''
    def __init__(self, parent, figpanel, **kwargs):
        wx.Panel.__init__(self, parent, **kwargs)
        
        # the panel to draw charts on
        self.SetBackgroundColour('white') # color for the background of panel
        self.figpanel = figpanel
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        
        self.x_columns = [] # column names to plot if selecting multiple columns

        self.table_choice = ui.TableComboBox(self, -1, style=wx.CB_READONLY)
        self.x_choice = ComboBox(self, -1, size=(200,-1), style=wx.CB_READONLY)
        self.x_multiple = wx.Button(self, -1, 'select multiple')
        self.group_choice = ComboBox(self, -1, choices=[NO_GROUP]+p._groups_ordered, style=wx.CB_READONLY)
        self.group_choice.Select(0)
        self.filter_choice = ui.FilterComboBox(self, style=wx.CB_READONLY)
        self.filter_choice.Select(0)
        self.update_chart_btn = wx.Button(self, -1, "Update Chart")
        
        self.update_column_fields()
        
        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "table:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.table_choice, 1, wx.EXPAND)
        sz.AddSpacer(3)
        sz.Add(wx.StaticText(self, -1, "measurement:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.x_choice, 2, wx.EXPAND)
        sz.AddSpacer(3)
        sz.Add(self.x_multiple, 0, wx.EXPAND|wx.TOP, 2)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 3, 0)
        
        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "group x-axis by:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.group_choice, 1, wx.EXPAND)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 3, 0)

        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "filter:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.filter_choice, 1, wx.EXPAND)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 3, 0)
        
        sizer.Add(self.update_chart_btn)    

        self.x_multiple.Bind(wx.EVT_BUTTON, self.on_select_multiple)
        self.table_choice.Bind(wx.EVT_COMBOBOX, self.on_table_selected)
        self.x_choice.Bind(wx.EVT_COMBOBOX, self.on_column_selected)
        self.update_chart_btn.Bind(wx.EVT_BUTTON, self.update_figpanel)

        self.SetSizer(sizer)
        self.Show(1)

    def on_select_multiple(self, evt):
        tablename = self.table_choice.GetString(self.table_choice.GetSelection())
        column_names = self.get_numeric_columns_from_table(tablename)
        dlg = wx.MultiChoiceDialog(self, 
                                   'Select the columns you would like to plot',
                                   'Select Columns', column_names)
        dlg.SetSelections([column_names.index(v) for v in self.x_columns])
        if (dlg.ShowModal() == wx.ID_OK):
            self.x_choice.SetValue(SELECT_MULTIPLE)
            self.x_columns = [column_names[i] for i in dlg.GetSelections()]
            self.group_choice.Disable()
            self.group_choice.SetStringSelection(NO_GROUP)
        
    def on_table_selected(self, evt):
        table = self.table_choice.Value
        if table == ui.TableComboBox.OTHER_TABLE:
            t = ui.get_other_table_from_user(self)
            if t is not None:
                self.table_choice.Items = self.table_choice.Items[:-1] + [t] + self.table_choice.Items[-1:]
                self.table_choice.Select(self.table_choice.Items.index(t))
            else:
                self.table_choice.Select(0)
                return
        self.group_choice.Enable()
        self.x_columns = []
        self.update_column_fields()
        
    def on_column_selected(self, evt):
        self.group_choice.Enable()        
    
    def update_column_fields(self):
        tablename = self.table_choice.GetString(self.table_choice.GetSelection())
        fieldnames = self.get_numeric_columns_from_table(tablename)
        self.x_choice.Clear()
        self.x_choice.AppendItems(fieldnames)
        self.x_choice.Append(SELECT_MULTIPLE)
        self.x_choice.SetSelection(0)

    def get_numeric_columns_from_table(self, table):
        ''' Fetches names of numeric columns for the given table. '''
        measurements = db.GetColumnNames(table)
        types = db.GetColumnTypes(table)
        return [m for m,t in zip(measurements, types) if t in (float, int)]
        
    def update_figpanel(self, evt=None):
        table = self.table_choice.Value
        fltr = self.filter_choice.get_filter_or_none()
        grouping = self.group_choice.Value
        if self.x_choice.Value == SELECT_MULTIPLE:
            points_dict = {}
            for col in self.x_columns:
                pts = self.loadpoints(table, col, fltr, NO_GROUP)
                for k in list(pts.keys()): assert k not in points_dict
                points_dict.update(pts)
        else:
            col = self.x_choice.Value
            points_dict = self.loadpoints(table, col, fltr, grouping)
        
        # Check if the user is creating a plethora of plots by accident
        if 100 >= len(points_dict) > 25:
            res = wx.MessageDialog(self, 'Are you sure you want to show %s box '
                                   'plots on one axis?'%(len(points_dict)), 
                                   'Warning', style=wx.YES_NO|wx.NO_DEFAULT
                                   ).ShowModal()
            if res != wx.ID_YES:
                return
        elif len(points_dict) > 100:
            wx.MessageBox('Sorry, boxplot can not show more than 100 plots on\n'
                          'a single axis. Your current settings would plot %d.\n'
                          'Try using a filter to narrow your query.'
                          %(len(points_dict)), 'Too many groups to plot')
            return

        self.figpanel.setpoints(points_dict)
        if self.group_choice.Value != NO_GROUP:
            self.figpanel.set_x_axis_label(grouping)
            self.figpanel.set_y_axis_label(self.x_choice.Value)
        self.figpanel.draw()
        
    def loadpoints(self, tablename, col, fltr=None, grouping=NO_GROUP):
        '''
        Returns a dict mapping x label values to lists of values from col
        '''
        q = sql.QueryBuilder()
        select = [sql.Column(tablename, col)]
        if grouping != NO_GROUP:
            dm = datamodel.DataModel()
            group_cols = dm.GetGroupColumnNames(grouping, include_table_name=True)
            select += [sql.Column(*col.split('.')) for col in group_cols]
        q.set_select_clause(select)
        if fltr is not None:
            q.add_filter(fltr)

        res = db.execute(str(q))
        res = np.array(res, dtype=object)
        # replaces Nones with NaNs
        for row in res:
            if row[0] is None:
                row[0] = np.nan
        
        points_dict = {}
        if self.group_choice.Value != NO_GROUP:
            for row in res:
                groupkey = tuple(row[1:])
                points_dict[groupkey] = points_dict.get(groupkey, []) + [row[0]]
        else:
            points_dict = {col : [r[0] for r in res]}
        return points_dict

    def save_settings(self):
        '''
        Called when saving a workspace to file.
        returns a dictionary mapping setting names to values encoded as strings
        '''
        if self.x_choice.Value == SELECT_MULTIPLE:
            cols = self.x_columns
        else:
            cols = [self.x_choice.GetString(self.x_choice.GetSelection())]
        return {'table'  : self.table_choice.Value,
                'x-axis' : ','.join(cols),
                'filter' : self.filter_choice.Value,
                'x-lim'  : self.figpanel.subplot.get_xlim(),
                'y-lim'  : self.figpanel.subplot.get_ylim(),
##                'grouping': self.group_choice.Value,
##                'version': '1',
                }
    
    def load_settings(self, settings):
        '''load_settings is called when loading a workspace from file.
        
        settings - a dictionary mapping setting names to values encoded as
                   strings.
        '''
##        if 'version' not in settings:
##            settings['grouping'] = NO_GROUP
##            settings['version'] = '1'
        if 'table' in settings:
            self.table_choice.SetStringSelection(settings['table'])
            self.update_column_fields()
        if 'x-axis' in settings:
            cols = list(map(str.strip, settings['x-axis'].split(',')))
            if len(cols) == 1:
                self.x_choice.SetStringSelection(cols[0])
            else:
                self.x_choice.SetValue(SELECT_MULTIPLE)
                self.x_columns = cols
        if 'filter' in settings:
            self.filter_choice.SetStringSelection(settings['filter'])
        self.update_figpanel()
        if 'x-lim' in settings:
            self.figpanel.subplot.set_xlim(eval(settings['x-lim']))
        if 'y-lim' in settings:
            self.figpanel.subplot.set_ylim(eval(settings['y-lim']))
        self.figpanel.draw()
Exemple #3
0
class ScatterControlPanel(wx.Panel):
    '''
    A panel with controls for selecting the source data for a scatterplot 
    '''
    def __init__(self, parent, figpanel, **kwargs):
        wx.Panel.__init__(self, parent, **kwargs)
        
        # the panel to draw charts on
        self.figpanel = figpanel
        self.SetBackgroundColour('white') # color for the background of panel

        sizer = wx.BoxSizer(wx.VERTICAL)
        self.x_table_choice = ui.TableComboBox(self, -1, style=wx.CB_READONLY)
        self.y_table_choice = ui.TableComboBox(self, -1, style=wx.CB_READONLY)
        self.x_choice = ComboBox(self, -1, choices=[''], size=(200,-1), style=wx.CB_READONLY)
        self.x_choice.Select(0)
        self.y_choice = ComboBox(self, -1, choices=[''], size=(200,-1), style=wx.CB_READONLY)
        self.y_choice.Select(0)
        self.x_scale_choice = ComboBox(self, -1, choices=[LINEAR_SCALE, LOG_SCALE], size=(90,-1), style=wx.CB_READONLY)
        self.x_scale_choice.Select(0)
        self.y_scale_choice = ComboBox(self, -1, choices=[LINEAR_SCALE, LOG_SCALE], size=(90,-1), style=wx.CB_READONLY)
        self.y_scale_choice.Select(0)
        self.filter_choice = ui.FilterComboBox(self, style=wx.CB_READONLY)
        self.filter_choice.Select(0)
        self.gate_choice = ui.GateComboBox(self, style=wx.CB_READONLY)
        self.gate_choice.set_gatable_columns([self.x_column, self.y_column])
        self.update_chart_btn = wx.Button(self, -1, "Update Chart")
        
        self.update_x_choices()
        self.update_y_choices()
                
        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "x-axis:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.x_table_choice, 1, wx.EXPAND) 
        sz.AddSpacer(3)
        sz.Add(self.x_choice, 2, wx.EXPAND)
        sz.AddSpacer(3)
        sz.Add(wx.StaticText(self, -1, "scale:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.x_scale_choice)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 2, 0)
        
        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "y-axis:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.y_table_choice, 1, wx.EXPAND)
        sz.AddSpacer(3)
        sz.Add(self.y_choice, 2, wx.EXPAND)
        sz.AddSpacer(3)
        sz.Add(wx.StaticText(self, -1, "scale:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.y_scale_choice)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 2, 0)
        
        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(wx.StaticText(self, -1, "filter:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.filter_choice, 1, wx.EXPAND)
        sz.AddSpacer(3)
        sz.Add(wx.StaticText(self, -1, "gate:"), 0, wx.TOP, 4)
        sz.AddSpacer(3)
        sz.Add(self.gate_choice, 1, wx.EXPAND)
        sizer.Add(sz, 1, wx.EXPAND)
        sizer.Add(-1, 2, 0)
        
        sizer.Add(self.update_chart_btn)

        self.x_table_choice.Bind(wx.EVT_COMBOBOX, self.on_x_table_selected)
        self.y_table_choice.Bind(wx.EVT_COMBOBOX, self.on_y_table_selected)
        self.gate_choice.addobserver(self.on_gate_selected)
        self.update_chart_btn.Bind(wx.EVT_BUTTON, self.update_figpanel)

        self.SetSizer(sizer)
        self.Show(1)
        
    @property
    def x_column(self):
        x_table_choice_id = self.x_table_choice.GetSelection()
        x_choice_id = self.x_choice.GetSelection()
        return sql.Column(self.x_table_choice.GetString(x_table_choice_id), 
                          self.x_choice.GetString(x_choice_id))
    @property
    def y_column(self):
        y_table_choice_id = self.y_table_choice.GetSelection()
        y_choice_id = self.y_choice.GetSelection()
        return sql.Column(self.y_table_choice.GetString(y_table_choice_id), 
                          self.y_choice.GetString(y_choice_id))
    @property
    def filter(self):
        return self.filter_choice.get_filter_or_none()
        
    def on_x_table_selected(self, evt):
        table = self.x_table_choice.Value
        if table == ui.TableComboBox.OTHER_TABLE:
            t = ui.get_other_table_from_user(self)
            if t is not None:
                self.x_table_choice.Items = self.x_table_choice.Items[:-1] + [t] + self.x_table_choice.Items[-1:]
                self.x_table_choice.Select(self.x_table_choice.Items.index(t))
                sel = self.y_table_choice.GetSelection()
                self.y_table_choice.Items = self.y_table_choice.Items[:-1] + [t] + self.y_table_choice.Items[-1:]
                self.y_table_choice.SetSelection(sel)
            else:
                self.x_table_choice.Select(0)
                return
        self.update_x_choices()
        
    def on_y_table_selected(self, evt):
        table = self.y_table_choice.Value
        if table == ui.TableComboBox.OTHER_TABLE:
            t = ui.get_other_table_from_user(self)
            if t is not None:
                self.y_table_choice.Items = self.y_table_choice.Items[:-1] + [t] + self.y_table_choice.Items[-1:]
                self.y_table_choice.Select(self.y_table_choice.Items.index(t))
                sel = self.x_table_choice.GetSelection()
                self.x_table_choice.Items = self.x_table_choice.Items[:-1] + [t] + self.x_table_choice.Items[-1:]
                self.x_table_choice.SetSelection(sel)
            else:
                self.y_table_choice.Select(0)
                return
        self.update_y_choices()
        
    def on_gate_selected(self, gate_name):
        self.update_gate_helper()
            
    def update_gate_helper(self):
        gate_name = self.gate_choice.get_gatename_or_none()
        if gate_name:
            # Deactivate the lasso tool
            self.figpanel.get_toolbar().toggle_user_tool('lasso', False)
            self.figpanel.gate_helper.set_displayed_gate(p.gates[gate_name], self.x_column, self.y_column)
        else:
            self.figpanel.gate_helper.disable()        

    def update_x_choices(self):
        tablename = self.x_table_choice.Value
        fieldnames = db.GetColumnNames(tablename)
        self.x_choice.Clear()
        self.x_choice.AppendItems(fieldnames)
        self.x_choice.SetSelection(0)
            
    def update_y_choices(self):
        tablename = self.y_table_choice.Value
        fieldnames = db.GetColumnNames(tablename)
        self.y_choice.Clear()
        self.y_choice.AppendItems(fieldnames)
        self.y_choice.SetSelection(0)
        
    def _plotting_per_object_data(self):
        return (p.object_table is not None and
                p.object_table in [self.x_column.table, self.y_column.table]
                or (self.x_column.table != p.image_table and db.adjacent(p.object_table, self.x_column.table))
                or (self.y_column.table != p.image_table and db.adjacent(p.object_table, self.y_column.table))
                )
        
    def update_figpanel(self, evt=None):
        self.gate_choice.set_gatable_columns([self.x_column, self.y_column])
        self.gate_choice.update_info()
        keys_and_points = self._load_points()
        col_types = self.get_selected_column_types()
                
        # Convert keys and points into a np array
        # NOTE: We must set dtype "object" on creation or values like 0.34567e-9
        #       may be truncated to 0.34567e (error) or 0.345 (no error) when
        #       the array contains strings.
        kps = np.array(keys_and_points, dtype='object')
        # Strip out keys
        if self._plotting_per_object_data():
            key_indices = list(range(len(object_key_columns())))
        else:
            key_indices = list(range(len(image_key_columns())))

        keys = kps[:,key_indices].astype(int) 
        # Strip out x coords
        if col_types[0] in (float, int):
            xpoints = kps[:,-2].astype('float32')
        else:
            xpoints = kps[:,-2]
        # Strip out y coords
        if col_types[1] in (float, int):
            ypoints = kps[:,-1].astype('float32')
        else:
            ypoints = kps[:,-1]

        # plot the points
        self.figpanel.set_points(xpoints, ypoints)
        self.figpanel.set_keys(keys)
        self.figpanel.set_x_scale(self.x_scale_choice.Value)
        self.figpanel.set_y_scale(self.y_scale_choice.Value)
        self.figpanel.set_x_label(self.x_column.col)
        self.figpanel.set_y_label(self.y_column.col)
        self.update_gate_helper()
        self.figpanel.redraw()
        self.figpanel.draw()
        
    def _load_points(self):
        q = sql.QueryBuilder()
        select = []
        #
        # If there's an object table fetch object keys. Else fetch image keys.
        #
        # TODO: linking per-well data doesn't work if we fetch keys this way
        #
        if self._plotting_per_object_data():
            select += [sql.Column(p.object_table, col) for col in object_key_columns()]
        else:
            select += [sql.Column(p.image_table, col) for col in image_key_columns()]
        select += [self.x_column, self.y_column]
        q.set_select_clause(select)
        if self.filter != None:
            q.add_filter(self.filter)
        q.add_where(sql.Expression(self.x_column, 'IS NOT NULL'))
        q.add_where(sql.Expression(self.y_column, 'IS NOT NULL'))
        return db.execute(str(q))
    
    def get_selected_column_types(self):
        ''' Returns a tuple containing the x and y column types. '''
        return (db.GetColumnType(self.x_table_choice.Value, self.x_choice.Value),
                db.GetColumnType(self.y_table_choice.Value, self.y_choice.Value))

    def save_settings(self):
        '''save_settings is called when saving a workspace to file.
        returns a dictionary mapping setting names to values encoded as strings
        '''
        d = {'x-table' : self.x_table_choice.Value,
             'y-table' : self.y_table_choice.Value,
             'x-axis'  : self.x_choice.Value,
             'y-axis'  : self.y_choice.Value,
             'x-scale' : self.x_scale_choice.Value,
             'y-scale' : self.y_scale_choice.Value,
             'filter'  : self.filter_choice.Value,
             'x-lim'   : self.figpanel.subplot.get_xlim(),
             'y-lim'   : self.figpanel.subplot.get_ylim(),
             'version' : '1',
             }
        if self.gate_choice.get_gatename_or_none():
            gate_choice_id = self.gate_choice.GetSelection() 
            d['gate'] = self.gate_choice.GetString(gate_choice_id)
        return d
    
    def load_settings(self, settings):
        '''load_settings is called when loading a workspace from file.
        settings - a dictionary mapping setting names to values encoded as
                   strings.
        '''
        if 'version' not in settings:
            if 'table' in settings:
                settings['x-table'] = settings['table']
                settings['y-table'] = settings['table']
            settings['version'] = '1'
        if 'x-table' in settings:
            self.x_table_choice.SetStringSelection(settings['x-table'])
            self.update_x_choices()
        if 'y-table' in settings:
            self.y_table_choice.SetStringSelection(settings['y-table'])
            self.update_y_choices()
        if 'x-axis' in settings:
            self.x_choice.SetStringSelection(settings['x-axis'])
        if 'y-axis' in settings:
            self.y_choice.SetStringSelection(settings['y-axis'])
        if 'x-scale' in settings:
            self.x_scale_choice.SetStringSelection(settings['x-scale'])
        if 'y-scale' in settings:
            self.y_scale_choice.SetStringSelection(settings['y-scale'])
        if 'filter' in settings:
            self.filter_choice.SetStringSelection(settings['filter'])
        self.update_figpanel()
        if 'x-lim' in settings:
            self.figpanel.subplot.set_xlim(eval(settings['x-lim']))
        if 'y-lim' in settings:
            self.figpanel.subplot.set_ylim(eval(settings['y-lim']))
        if 'gate' in settings:
            self.gate_choice.SetStringSelection(settings['gate'])
            self.figpanel.gate_helper.set_displayed_gate(
                p.gates[settings['gate']], self.x_column, self.y_column)
        self.figpanel.draw()