diff --git a/.travis.yml b/.travis.yml index 2512c9a..e088560 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,52 @@ language: python sudo: required -dist: trusty -python: - - "2.7" +jobs: + include: + - name: "python 2.7 qt4 trusty pgmpl" + dist: trusty + python: 2.7 +# virtualenv: +# system_site_packages: true + before_install: + - sudo apt-get update + - sudo apt-get -y install python-qt4 + before_script: + # From pyqtgraph's .travis.yml: https://github.com/pyqtgraph/pyqtgraph/blob/develop/.travis.yml + # We need to create a (fake) display on Travis, let's use a funny resolution + - export DISPLAY=:99.0 + - "sh -e /etc/init.d/xvfb start" + - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render + - export PGMPL_TEST_VERBOSE=1 + - name: "python 2.7 qt5 bionic pgmpl" + dist: bionic + python: 2.7 + before_install: + - sudo apt-get update + - sudo apt-get -y install python-pyqt5 + services: + - xvfb + - name: "python 3.6 qt5 bionic pgmpl" + dist: bionic + python: 3.6 + before_install: + - sudo apt-get update + - sudo apt-get -y install python3-pyqt5 + services: + - xvfb # https://stackoverflow.com/a/35029430/6605826 - +# system_site_packages limits py versions https://travis-ci.community/t/python-3-6-and-3-7-with-system-site-packages-enabled-fails-to-activate-on-xenial/1697 virtualenv: system_site_packages: true +# apt-get commands depend on py version: https://stackoverflow.com/a/20621143/6605826 install: - - sudo apt-get update - - sudo apt-get -y install python-qt4 +# - sudo apt-get update +# - if [[ $TRAVIS_PYTHON_VERSION < 3 ]]; then sudo apt-get -y install python-qt4; fi +# - if [[ $TRAVIS_PYTHON_VERSION > 3 ]]; then sudo apt-get -y install python3-pyqt5; fi - pip install -r requirements.txt - pip install codecov coverage -before_script: - # From pyqtgraph's .travis.yml: https://github.com/pyqtgraph/pyqtgraph/blob/develop/.travis.yml - # We need to create a (fake) display on Travis, let's use a funny resolution - - export DISPLAY=:99.0 - - "sh -e /etc/init.d/xvfb start" - - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render - - export PGMPL_TEST_VERBOSE=1 - script: - coverage run -m unittest discover --pattern=*.py -s tests diff --git a/pgmpl/axes.py b/pgmpl/axes.py index 6895559..e54884a 100644 --- a/pgmpl/axes.py +++ b/pgmpl/axes.py @@ -110,9 +110,12 @@ def _make_custom_verts(verts): :param verts: sequence of (x, y), optional :return: a pyqt path suitable for use with Axes.plot()'s symbol keyword. """ + from pyqtgraph.graphicsItems.ScatterPlotItem import Symbols verts_x = np.array([vert[0] for vert in verts]) verts_y = np.array([vert[1] for vert in verts]) - return pg.arrayToQPath(verts_x, verts_y, connect='all') + key = 'custom_pgmpl_symbol_1' + Symbols[key] = pg.arrayToQPath(verts_x, verts_y, connect='all') + return key def scatter(self, x=None, y=None, **kwargs): """ @@ -171,7 +174,6 @@ def scatter(self, x=None, y=None, **kwargs): plotkw['symbolPen'] = [pg.mkPen(**spkw) for spkw in sympen_kw] if plotkw.get('symbol', None) is None: # Shouldn't happen unless user sets None explicitly b/c default is 'o' plotkw['symbol'] = self._make_custom_verts(kwargs.pop('verts', None)) - return super(Axes, self).plot(x=x, y=y, **plotkw) def imshow(self, x=None, aspect=None, **kwargs): @@ -190,8 +192,7 @@ def contour(self, *args, **kwargs): def contourf(self, *args, **kwargs): printd(' pgmpl.axes.Axes.contourf()...') kwargs['filled'] = True - contours = QuadContourSet(self, *args, **kwargs) - return contours + return QuadContourSet(self, *args, **kwargs) def set_xlabel(self, label): """Imitates basic use of matplotlib.axes.Axes.set_xlabel()""" diff --git a/pgmpl/figure.py b/pgmpl/figure.py index 2cc8450..c8c7cf2 100644 --- a/pgmpl/figure.py +++ b/pgmpl/figure.py @@ -135,6 +135,11 @@ def add_subplot(self, nrows, ncols, index, **kwargs): col = (index-1) % ncols ax = Axes(nrows=nrows, ncols=ncols, index=index, **kwargs) self.layout.addItem(ax, row+1, col) + try: + tolist(self.axes) + except RuntimeError: + print('Warning: Qt has deleted the axes; figure had a residual reference to a bad Qt object. (add_subplot)') + self.axes = None self.axes = ax if self.axes is None else tolist(self.axes) + [ax] self.fig_colspan = max([ncols, self.fig_colspan]) self.refresh_suptitle() @@ -196,10 +201,14 @@ def gca(self): Imitation of matplotlib gca() :return: Current axes for this figure, creating them if necessary """ + if self.axes is not None: + try: + ax = list(flatten(np.atleast_1d(self.axes)))[-1] + except RuntimeError: # Happens if Qt has deleted the Axes + print('Warning: Qt has deleted the axes; figure had a residual reference to a bad Qt object. (gca)') + self.axes = None if self.axes is None: ax = self.add_subplot(1, 1, 1) - else: - ax = list(flatten(np.atleast_1d(self.axes)))[-1] return ax def close(self): diff --git a/pgmpl/translate.py b/pgmpl/translate.py index bb5eae5..5cf4fd2 100644 --- a/pgmpl/translate.py +++ b/pgmpl/translate.py @@ -28,6 +28,17 @@ # pgmpl imports from pgmpl.util import set_debug, printd, tolist +from pyqtgraph.graphicsItems.ScatterPlotItem import Symbols + +# Install custom symbols +theta = np.linspace(0, 2 * np.pi, 36) +Symbols['.'] = pg.arrayToQPath(np.cos(theta) * 0.125, np.sin(theta) * 0.125, connect='all') +Symbols[','] = pg.arrayToQPath(np.array([-0.01, 0, 0.01, 0, -0.01]), np.array([0, 0.01, 0, -0.01, 0]), connect='all') +Symbols['_'] = pg.arrayToQPath(np.array([-0.5, 0.5]), np.array([0, 0]), connect='all') +Symbols['|'] = pg.arrayToQPath(np.array([0, 0]), np.array([-0.5, 0.5]), connect='all') +Symbols['x'] = pg.arrayToQPath( + np.array([-0.5, 0.5, 0, 0.5, -0.5, 0]), np.array([-0.5, 0.5, 0, -0.5, 0.5, 0]), connect='all' +) def dealias(**kws): @@ -171,19 +182,12 @@ def symbol_translator(**kw): :return: string Code for the relevant pyqtgraph symbol. """ - theta = np.linspace(0, 2 * np.pi, 36) - return { # mpl symbol : pyqt4 symbol - '.': pg.arrayToQPath(np.cos(theta) * 0.125, np.sin(theta) * 0.125, connect='all'), - ',': pg.arrayToQPath(np.array([-0.01, 0, 0.01, 0, -0.01]), - np.array([0, 0.01, 0, -0.01, 0]), connect='all'), - 'x': pg.arrayToQPath(np.array([-0.5, 0.5, 0, 0.5, -0.5, 0]), - np.array([-0.5, 0.5, 0, -0.5, 0.5, 0]), connect='all'), - '+': '+', '*': 'star', 'o': 'o', 'v': 't', '^': 't1', '>': 't2', '<': 't3', - 'd': 'd', 's': 's', 'p': 'p', 'h': 'h', - '_': pg.arrayToQPath(np.array([-0.5, 0.5]), np.array([0, 0]), connect='all'), - '|': pg.arrayToQPath(np.array([0, 0]), np.array([-0.5, 0.5]), connect='all'), - 'None': None, 'none': None, None: None, - }.get(kw['marker'], 'o') if 'marker' in kw else None + # mpl symbol : pyqt4 symbol + pyqt_symbol = { + '.': '.', ',': ',', 'x': 'x', '+': '+', '*': 'star', 'o': 'o', 'v': 't', '^': 't1', '>': 't2', '<': 't3', + 'd': 'd', 's': 's', 'p': 'p', 'h': 'h', '_': '_', '|': '|', 'None': None, 'none': None, None: None, + }.get(kw.get('marker', None), 'o') + return pyqt_symbol def symbol_edge_setup(pgkw, plotkw): diff --git a/test.sh b/test.sh index 195ad3d..310df8c 100755 --- a/test.sh +++ b/test.sh @@ -1,2 +1,2 @@ #!/bin/bash -python2.7 -m unittest discover --pattern=*.py -s tests +python3.7 -m unittest discover --pattern=*.py -s tests diff --git a/tests/test_translate.py b/tests/test_translate.py index 762a47d..a81fbaa 100755 --- a/tests/test_translate.py +++ b/tests/test_translate.py @@ -86,6 +86,7 @@ def test_style_translator(self): assert style_translator(linestyle="-") == QtCore.Qt.SolidLine def test_symbol_translator(self): + from pyqtgraph.graphicsItems.ScatterPlotItem import Symbols news = [None] * self.nt for i in range(self.nt): news[i] = symbol_translator(**self.plot_kw_tests[i]) @@ -95,7 +96,8 @@ def test_symbol_translator(self): assert symbol_translator(marker='^') == 't1' custom_markers = '_x|,.' for custom in custom_markers: - assert isinstance(symbol_translator(marker=custom), QtGui.QPainterPath) + assert isinstance(symbol_translator(marker=custom), QtGui.QPainterPath) \ + or isinstance(Symbols.get(custom, None), QtGui.QPainterPath) def test_setup_pen_kw(self): newp = [None] * self.nt