Changeset 9290b1a in sasview


Ignore:
Timestamp:
Dec 16, 2016 12:43:18 PM (7 years ago)
Author:
Piotr Rozyczko <rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
d3ca363
Parents:
0ba0774
Message:

Added AddText? to plot, enabled legend drag - SASVIEW-378

Location:
src/sas/qtgui
Files:
3 added
11 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/DataExplorer.py

    r0ba0774 r9290b1a  
    401401        new_plot = Plotter(self) 
    402402 
    403         def addDataPlot(plot, plot_set): 
    404             plot.data = plot_set 
    405             plot.plot() 
    406  
    407403        def addDataPlot2D(plot_set): 
    408404            plot2D = Plotter2D(self) 
    409             addDataPlot(plot2D, plot_set) 
     405            plot2D.plot(plot_set) 
    410406            self.plotAdd(plot2D) 
    411407 
    412408        for plot_set in plots: 
    413409            if isinstance(plot_set, Data1D): 
    414                 addDataPlot(new_plot, plot_set) 
     410                new_plot.plot(plot_set) 
    415411            elif isinstance(plot_set, Data2D): 
    416412                addDataPlot2D(plot_set) 
     
    418414                msg = "Incorrect data type passed to Plotting" 
    419415                raise AttributeError, msg 
    420  
    421416 
    422417        if plots and \ 
  • src/sas/qtgui/GUITests.py

    r27313b7 r9290b1a  
    22 
    33from UnitTesting import AboutBoxTest 
     4from UnitTesting import AddTextTest 
    45from UnitTesting import DataExplorerTest 
    56from UnitTesting import WelcomePanelTest 
     
    2223    suites = ( 
    2324        unittest.makeSuite(AboutBoxTest.AboutBoxTest,          'test'), 
     25        unittest.makeSuite(AddTextTest.AddTextTest,          'test'), 
    2426        unittest.makeSuite(DataExplorerTest.DataExplorerTest,  'test'), 
    2527        unittest.makeSuite(WelcomePanelTest.WelcomePanelTest,  'test'), 
  • src/sas/qtgui/PlotUtilities.py

    rfecfe28 r9290b1a  
    175175 
    176176    return image 
     177 
     178def rescale(lo, hi, step, pt=None, bal=None, scale='linear'): 
     179    """ 
     180    Rescale (lo,hi) by step, returning the new (lo,hi) 
     181    The scaling is centered on pt, with positive values of step 
     182    driving lo/hi away from pt and negative values pulling them in. 
     183    If bal is given instead of point, it is already in [0,1] coordinates. 
     184 
     185    This is a helper function for step-based zooming. 
     186    """ 
     187    # Convert values into the correct scale for a linear transformation 
     188    # TODO: use proper scale transformers 
     189    loprev = lo 
     190    hiprev = hi 
     191    if scale == 'log': 
     192        assert lo > 0 
     193        if lo > 0: 
     194            lo = numpy.log10(lo) 
     195        if hi > 0: 
     196            hi = numpy.log10(hi) 
     197        if pt is not None: 
     198            pt = numpy.log10(pt) 
     199 
     200    # Compute delta from axis range * %, or 1-% if persent is negative 
     201    if step > 0: 
     202        delta = float(hi - lo) * step / 100 
     203    else: 
     204        delta = float(hi - lo) * step / (100 - step) 
     205 
     206    # Add scale factor proportionally to the lo and hi values, 
     207    # preserving the 
     208    # point under the mouse 
     209    if bal is None: 
     210        bal = float(pt - lo) / (hi - lo) 
     211    lo = lo - (bal * delta) 
     212    hi = hi + (1 - bal) * delta 
     213 
     214    # Convert transformed values back to the original scale 
     215    if scale == 'log': 
     216        if (lo <= -250) or (hi >= 250): 
     217            lo = loprev 
     218            hi = hiprev 
     219        else: 
     220            lo, hi = numpy.power(10., lo), numpy.power(10., hi) 
     221    return (lo, hi) 
     222 
  • src/sas/qtgui/Plotter.py

    r27313b7 r9290b1a  
    22 
    33import matplotlib.pyplot as plt 
    4  
     4from matplotlib.font_manager import FontProperties 
     5 
     6from sas.sasgui.guiframe.dataFitting import Data1D 
    57from sas.sasgui.plottools import transform 
    68from sas.sasgui.plottools.convert_units import convert_unit 
    79from sas.qtgui.PlotterBase import PlotterBase 
     10from sas.qtgui.AddText import AddText 
    811 
    912class PlotterWidget(PlotterBase): 
     
    2730        self.title(title=value.title) 
    2831 
    29     def plot(self, marker=None, linestyle=None, hide_error=False): 
     32    def plot(self, data=None, marker=None, linestyle=None, hide_error=False): 
    3033        """ 
    3134        Plot self._data 
    3235        """ 
     36        # Data1D 
     37        if isinstance(data, Data1D): 
     38            self.data = data 
     39        assert(self._data) 
     40 
    3341        # Shortcut for an axis 
    3442        ax = self.ax 
     
    4553                    marker=marker, 
    4654                    linestyle=linestyle, 
    47                     label=self._title) 
     55                    label=self._title, 
     56                    picker=True) 
    4857        else: 
    4958            ax.errorbar(self._data.view.x, self._data.view.y, 
     
    5463                        lolims=False, uplims=False, 
    5564                        xlolims=False, xuplims=False, 
    56                         label=self._title) 
     65                        label=self._title, 
     66                        picker=True) 
    5767 
    5868        # Now add the legend with some customizations. 
    59         legend = ax.legend(loc='upper right', shadow=True) 
     69        self.legend = ax.legend(loc='upper right', shadow=True) 
     70        #self.legend.get_frame().set_alpha(0.4) 
     71        self.legend.set_picker(True) 
    6072 
    6173        # Current labels for axes 
     
    142154        Show a dialog allowing adding custom text to the chart 
    143155        """ 
    144         print("onAddText") 
    145         pass 
     156        self.addText = AddText(self) 
     157        if self.addText.exec_() == QtGui.QDialog.Accepted: 
     158            # Retrieve the new text, its font and color 
     159            extra_text = self.addText.text() 
     160            extra_font = self.addText.font() 
     161            extra_color = self.addText.color() 
     162 
     163            # Place the text on the screen at (0,0) 
     164            pos_x = self.x_click 
     165            pos_y = self.y_click 
     166 
     167            # Map QFont onto MPL font 
     168            mpl_font = FontProperties() 
     169            mpl_font.set_size(int(extra_font.pointSize())) 
     170            mpl_font.set_family(str(extra_font.family())) 
     171            mpl_font.set_weight(int(extra_font.weight())) 
     172            # MPL style names 
     173            styles = ['normal', 'italic', 'oblique'] 
     174            # QFont::Style maps directly onto the above 
     175            try: 
     176                mpl_font.set_style(styles[extra_font.style()]) 
     177            except: 
     178                pass 
     179 
     180            if len(extra_text) > 0: 
     181                new_text = self.ax.text(str(pos_x), 
     182                                        str(pos_y), 
     183                                        extra_text, 
     184                                        color=extra_color, 
     185                                        fontproperties=mpl_font) 
     186                # Update the list of annotations 
     187                self.textList.append(new_text) 
     188                self.canvas.draw_idle() 
    146189 
    147190    def onRemoveText(self): 
    148191        """ 
    149         Remove the most recent added text 
    150         """ 
    151         print("onRemoveText") 
    152         pass 
     192        Remove the most recently added text 
     193        """ 
     194        num_text = len(self.textList) 
     195        if num_text < 1: 
     196            return 
     197        txt = self.textList[num_text - 1] 
     198        text_remove = txt.get_text() 
     199        txt.remove() 
     200        self.textList.remove(txt) 
     201 
     202        self.canvas.draw_idle() 
    153203 
    154204    def onSetGraphRange(self): 
     
    172222        # Clear the plot first 
    173223        plt.cla() 
     224        self.ax.cla() 
    174225 
    175226        # Changing the scale might be incompatible with 
  • src/sas/qtgui/Plotter2D.py

    rc4e5400 r9290b1a  
    66 
    77DEFAULT_CMAP = pylab.cm.jet 
     8from mpl_toolkits.mplot3d import Axes3D 
    89 
    910import sas.qtgui.PlotUtilities as PlotUtilities 
    1011from sas.qtgui.PlotterBase import PlotterBase 
    11 from mpl_toolkits.mplot3d import Axes3D 
     12from sas.sasgui.guiframe.dataFitting import Data2D 
    1213 
    1314# Minimum value of Z for which we will present data. 
     
    4344        self.title(title=data.title) 
    4445 
    45     def plot(self, marker=None, linestyle=None): 
     46    def plot(self, data=None, marker=None, linestyle=None): 
    4647        """ 
    4748        Plot 2D self._data 
    4849        """ 
     50        # Assing data 
     51        if isinstance(data, Data2D): 
     52            self.data = data 
     53 
     54        assert(self._data) 
     55 
    4956        # Toggle the scale 
    5057        zmin_2D_temp = self.zmin 
  • src/sas/qtgui/PlotterBase.py

    r27313b7 r9290b1a  
    11import pylab 
     2import numpy 
    23 
    34from PyQt4 import QtGui 
     
    1314 
    1415DEFAULT_CMAP = pylab.cm.jet 
     16from sas.sasgui.plottools.binder import BindArtist 
     17 
    1518from sas.qtgui.ScaleProperties import ScaleProperties 
    1619from sas.qtgui.WindowTitle import WindowTitle 
    1720import sas.qtgui.PlotHelper as PlotHelper 
     21import sas.qtgui.PlotUtilities as PlotUtilities 
    1822 
    1923class PlotterBase(QtGui.QWidget): 
     
    5357        self.y_label = "log10(y)" 
    5458 
     59        # Mouse click related 
     60        self.x_click = None 
     61        self.y_click = None 
     62        self.event_pos = None 
     63        self.leftdown = False 
     64        self.gotLegend = 0 
     65 
     66        # Annotations 
     67        self.selectedText = None 
     68        self.textList = [] 
     69 
    5570        # Pre-define the Scale properties dialog 
    5671        self.properties = ScaleProperties(self, 
     
    6681        self.ax = self.figure.add_subplot(self.current_plot) 
    6782 
     83        # Remove this, DAMMIT 
     84        self.axes = [self.ax] 
     85 
    6886        # Set the background color to white 
    6987        self.canvas.figure.set_facecolor('#FFFFFF') 
     88 
     89        # Canvas event handlers 
     90        self.canvas.mpl_connect('button_release_event', self.onMplMouseUp) 
     91        self.canvas.mpl_connect('button_press_event', self.onMplMouseDown) 
     92        self.canvas.mpl_connect('motion_notify_event', self.onMplMouseMotion) 
     93        self.canvas.mpl_connect('pick_event', self.onMplPick) 
     94        self.canvas.mpl_connect('scroll_event', self.onMplWheel) 
    7095 
    7196        if not quickplot: 
     
    166191        """ 
    167192        Define common context menu and associated actions for the MPL widget 
    168         TODO: move to plotter1d/plotter2d 
    169193        """ 
    170194        raise NotImplementedError("Context menu method must be implemented in derived class.") 
     
    180204        Display the context menu 
    181205        """ 
    182         self.contextMenu.exec_(self.canvas.mapToGlobal(event.pos())) 
     206        event_pos = event.pos() 
     207        self.contextMenu.exec_(self.canvas.mapToGlobal(event_pos)) 
     208 
     209    def onMplMouseDown(self, event): 
     210        """ 
     211        Left button down and ready to drag 
     212        """ 
     213        # Check that the LEFT button was pressed 
     214        if event.button == 1: 
     215            self.leftdown = True 
     216            ax = event.inaxes 
     217            for text in self.textList: 
     218                if text.contains(event)[0]: # If user has clicked on text 
     219                    self.selectedText = text 
     220                    return 
     221 
     222            if ax != None: 
     223                self.xInit, self.yInit = event.xdata, event.ydata 
     224                try: 
     225                    self.x_click = float(event.xdata)  # / size_x 
     226                    self.y_click = float(event.ydata)  # / size_y 
     227                except: 
     228                    self.position = None 
     229 
     230    def onMplMouseUp(self, event): 
     231        """ 
     232        Set the data coordinates of the click 
     233        """ 
     234        self.x_click = event.xdata 
     235        self.y_click = event.ydata 
     236 
     237        # Check that the LEFT button was released 
     238        if event.button == 1: 
     239            self.leftdown = False 
     240            #self.leftup = True 
     241            self.selectedText = None 
     242 
     243        #release the legend 
     244        if self.gotLegend == 1: 
     245            self.gotLegend = 0 
     246 
     247    def onMplMouseMotion(self, event): 
     248        """ 
     249        Check if the left button is press and the mouse in moving. 
     250        Compute delta for x and y coordinates and then perform the drag 
     251        """ 
     252        if self.gotLegend == 1 and self.leftdown: 
     253            self.onLegendMotion(event) 
     254            return 
     255 
     256        if self.leftdown and self.selectedText is not None: 
     257            # User has clicked on text and is dragging 
     258            ax = event.inaxes 
     259            if ax != None: 
     260                # Only move text if mouse is within axes 
     261                self.selectedText.set_position((event.xdata, event.ydata)) 
     262                self.canvas.draw_idle() 
     263            else: 
     264                # User has dragged outside of axes 
     265                self.selectedText = None 
     266            return 
     267 
     268    def onMplPick(self, event): 
     269        """ 
     270        On pick legend 
     271        """ 
     272        legend = self.legend 
     273        if event.artist == legend: 
     274            # Get the box of the legend. 
     275            bbox = self.legend.get_window_extent() 
     276            # Get mouse coordinates at time of pick. 
     277            self.mouse_x = event.mouseevent.x 
     278            self.mouse_y = event.mouseevent.y 
     279            # Get legend coordinates at time of pick. 
     280            self.legend_x = bbox.xmin 
     281            self.legend_y = bbox.ymin 
     282            # Indicate we picked up the legend. 
     283            self.gotLegend = 1 
     284 
     285            #self.legend.legendPatch.set_alpha(0.5) 
     286 
     287    def onLegendMotion(self, event): 
     288        """ 
     289        On legend in motion 
     290        """ 
     291        ax = event.inaxes 
     292        if ax == None: 
     293            return 
     294        # Event occurred inside a plotting area 
     295        lo_x, hi_x = ax.get_xlim() 
     296        lo_y, hi_y = ax.get_ylim() 
     297        # How much the mouse moved. 
     298        x = mouse_diff_x = self.mouse_x - event.x 
     299        y = mouse_diff_y = self.mouse_y - event.y 
     300        # Put back inside 
     301        if x < lo_x: 
     302            x = lo_x 
     303        if x > hi_x: 
     304            x = hi_x 
     305        if y < lo_y: 
     306            y = lo_y 
     307        if y > hi_y: 
     308            y = hi_y 
     309        # Move the legend from its previous location by that same amount 
     310        loc_in_canvas = self.legend_x - mouse_diff_x, \ 
     311                        self.legend_y - mouse_diff_y 
     312        # Transform into legend coordinate system 
     313        trans_axes = self.legend.parent.transAxes.inverted() 
     314        loc_in_norm_axes = trans_axes.transform_point(loc_in_canvas) 
     315        self.legend_pos_loc = tuple(loc_in_norm_axes) 
     316        self.legend._loc = self.legend_pos_loc 
     317        # self.canvas.draw() 
     318        self.canvas.draw_idle() 
     319 
     320    def onMplWheel(self, event): 
     321        """ 
     322        Process mouse wheel as zoom events 
     323        """ 
     324        ax = event.inaxes 
     325        step = event.step 
     326 
     327        if ax != None: 
     328            # Event occurred inside a plotting area 
     329            lo, hi = ax.get_xlim() 
     330            lo, hi = PlotUtilities.rescale(lo, hi, step, 
     331                              pt=event.xdata, scale=ax.get_xscale()) 
     332            if not self.xscale == 'log' or lo > 0: 
     333                self._scale_xlo = lo 
     334                self._scale_xhi = hi 
     335                ax.set_xlim((lo, hi)) 
     336 
     337            lo, hi = ax.get_ylim() 
     338            lo, hi = PlotUtilities.rescale(lo, hi, step, pt=event.ydata, 
     339                              scale=ax.get_yscale()) 
     340            if not self.yscale == 'log' or lo > 0: 
     341                self._scale_ylo = lo 
     342                self._scale_yhi = hi 
     343                ax.set_ylim((lo, hi)) 
     344        else: 
     345            # Check if zoom happens in the axes 
     346            xdata, ydata = None, None 
     347            x, y = event.x, event.y 
     348 
     349            for ax in self.axes: 
     350                insidex, _ = ax.xaxis.contains(event) 
     351                if insidex: 
     352                    xdata, _ = ax.transAxes.inverted().transform_point((x, y)) 
     353                insidey, _ = ax.yaxis.contains(event) 
     354                if insidey: 
     355                    _, ydata = ax.transAxes.inverted().transform_point((x, y)) 
     356            if xdata is not None: 
     357                lo, hi = ax.get_xlim() 
     358                lo, hi = PlotUtilities.rescale(lo, hi, step, 
     359                                  bal=xdata, scale=ax.get_xscale()) 
     360                if not self.xscale == 'log' or lo > 0: 
     361                    self._scale_xlo = lo 
     362                    self._scale_xhi = hi 
     363                    ax.set_xlim((lo, hi)) 
     364            if ydata is not None: 
     365                lo, hi = ax.get_ylim() 
     366                lo, hi = PlotUtilities.rescale(lo, hi, step, bal=ydata, 
     367                                  scale=ax.get_yscale()) 
     368                if not self.yscale == 'log' or lo > 0: 
     369                    self._scale_ylo = lo 
     370                    self._scale_yhi = hi 
     371                    ax.set_ylim((lo, hi)) 
     372        self.canvas.draw_idle() 
    183373 
    184374    def clean(self): 
  • src/sas/qtgui/UI/convert_all.sh

    r3968752 r9290b1a  
    11# UI -> PY 
    22for filename in *.ui; do 
    3   pyuic4 $filename > "`basename "$filename" .ui`.py" 
     3  pyuic.bat $filename > "`basename "$filename" .ui`.py" 
    44done 
    55 
  • src/sas/qtgui/UnitTesting/PlotterBaseTest.py

    r27313b7 r9290b1a  
    157157 
    158158    def testOnWindowsTitle(self): 
    159         ''' test changing the plot title''' 
     159        """ Test changing the plot title""" 
    160160        # Mock the modal dialog's response 
    161161        QtGui.QDialog.exec_ = MagicMock(return_value=QtGui.QDialog.Accepted) 
     
    171171        self.assertEqual(self.plotter.windowTitle(), "I am a new title") 
    172172 
     173    def testOnMplMouseDown(self): 
     174        """ Test what happens on mouse click down in chart """ 
     175        pass 
     176 
     177    def testOnMplMouseUp(self): 
     178        """ Test what happens on mouse release in chart """ 
     179        pass 
     180 
     181    def testOnMplMouseMotion(self): 
     182        """ Test what happens on mouse move in chart """ 
     183        pass 
     184 
     185    def testOnMplPick(self): 
     186        """ Test what happens on mouse pick in chart """ 
     187        pass 
     188 
     189    def testOnMplWheel(self): 
     190        """ Test what happens on mouse pick in chart """ 
     191        pass 
     192 
    173193if __name__ == "__main__": 
    174194    unittest.main() 
  • src/sas/qtgui/UnitTesting/PlotterTest.py

    r27313b7 r9290b1a  
    154154        self.assertEqual(self.plotter.ax.get_ylabel(), "$ \\ \\ ^{4}(()^{4})$") 
    155155 
     156    def testAddText(self): 
     157        """ Checks the functionality of adding text to graph """ 
     158        pass 
     159 
     160    def testOnRemoveText(self): 
     161        """ Cheks if annotations can be removed from the graph """ 
     162        pass 
     163 
     164 
    156165if __name__ == "__main__": 
    157166    unittest.main() 
  • src/sas/qtgui/run_tests.bat

    r27313b7 r9290b1a  
    1717python -m UnitTesting.ScalePropertiesTest 
    1818python -m UnitTesting.WindowTitleTest 
    19  
     19python -m UnitTesting.AddTextTest 
  • src/sas/qtgui/run_tests.sh

    r27313b7 r9290b1a  
    1616python -m UnitTesting.DensityCalculatorTest 
    1717python -m UnitTesting.WindowTitleTest 
     18python -m UnitTesting.AddTextTest 
Note: See TracChangeset for help on using the changeset viewer.