diff --git a/.github/workflows/ets-from-source.yml b/.github/workflows/ets-from-source.yml index af80d7484..5cc91fe09 100644 --- a/.github/workflows/ets-from-source.yml +++ b/.github/workflows/ets-from-source.yml @@ -24,7 +24,11 @@ jobs: - name: Install Qt dependencies for Linux run: | sudo apt-get update - sudo apt-get install qt5-default + sudo apt-get install qtbase5-dev + sudo apt-get install qtchooser + sudo apt-get install qt5-qmake + sudo apt-get install qtbase5-dev-tools + sudo apt-get install libegl1 sudo apt-get install libxkbcommon-x11-0 sudo apt-get install libglu1-mesa-dev sudo apt-get install libxcb-icccm4 @@ -33,6 +37,7 @@ jobs: sudo apt-get install libxcb-randr0 sudo apt-get install libxcb-render-util0 sudo apt-get install libxcb-xinerama0 + sudo apt-get install libxcb-shape0 shell: bash if: runner.os == 'Linux' - name: Cache EDM packages diff --git a/.github/workflows/test-with-edm-3.8.yml b/.github/workflows/test-with-edm-3.8.yml new file mode 100644 index 000000000..19c5cfd61 --- /dev/null +++ b/.github/workflows/test-with-edm-3.8.yml @@ -0,0 +1,80 @@ +# This workflow targets stable released dependencies from EDM. +# Note that some packages may not actually be installed from EDM but from +# PyPI, see ci/edmtool.py implementations. + +name: Test with EDM on Python 3.8 + +on: pull_request + +env: + INSTALL_EDM_VERSION: 3.5.0 + +jobs: + + # Test against EDM packages on Linux + test-edm-linux-38: + strategy: + matrix: + toolkit: ['null', 'pyside6'] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Qt dependencies for Linux + run: | + sudo apt-get update + sudo apt-get install libegl1 + sudo apt-get install libxkbcommon-x11-0 + sudo apt-get install libglu1-mesa-dev + sudo apt-get install libxcb-icccm4 + sudo apt-get install libxcb-image0 + sudo apt-get install libxcb-keysyms1 + sudo apt-get install libxcb-randr0 + sudo apt-get install libxcb-render-util0 + sudo apt-get install libxcb-xinerama0 + sudo apt-get install libxcb-shape0 + # Needed to work around https://bugreports.qt.io/browse/PYSIDE-1547 + sudo apt-get install libopengl0 + if: matrix.toolkit != 'null' + - name: Cache EDM packages + uses: actions/cache@v2 + with: + path: ~/.cache + key: ${{ runner.os }}-3.8-${{ matrix.toolkit }}-${{ hashFiles('ci/edmtool.py') }} + - name: Setup EDM + uses: enthought/setup-edm-action@v1 + with: + edm-version: ${{ env.INSTALL_EDM_VERSION }} + - name: Install click to the default EDM environment + run: edm install -y wheel click coverage + - name: Install test environment + run: edm run -- python ci/edmtool.py install --runtime=3.8 --toolkit=${{ matrix.toolkit }} + - name: Flake8 + run: edm run -- python ci/edmtool.py flake8 --runtime=3.8 --toolkit=${{ matrix.toolkit }} + if: matrix.toolkit == 'null' + - name: Run tests + run: xvfb-run --server-args="-screen 0 1024x768x24" edm run -- python ci/edmtool.py test --runtime=3.8 --toolkit=${{ matrix.toolkit }} + + # Test against EDM packages on Windows and OSX + test-with-edm-38: + strategy: + matrix: + os: [macos-latest, windows-latest] + toolkit: ['null', 'pyside6'] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Cache EDM packages + uses: actions/cache@v2 + with: + path: ~/.cache + key: ${{ runner.os }}-3.8-${{ matrix.toolkit }}-${{ hashFiles('ci/edmtool.py') }} + - name: Setup EDM + uses: enthought/setup-edm-action@v1 + with: + edm-version: ${{ env.INSTALL_EDM_VERSION }} + - name: Install click to the default EDM environment + run: edm install -y wheel click coverage + - name: Install test environment + run: edm run -- python ci/edmtool.py install --runtime=3.8 --toolkit=${{ matrix.toolkit }} + - name: Run tests + run: edm run -- python ci/edmtool.py test --runtime=3.8 --toolkit=${{ matrix.toolkit }} diff --git a/.github/workflows/test-with-edm.yml b/.github/workflows/test-with-edm.yml index f3c7106ea..6ff7e9d98 100644 --- a/.github/workflows/test-with-edm.yml +++ b/.github/workflows/test-with-edm.yml @@ -22,7 +22,11 @@ jobs: - name: Install Qt dependencies for Linux run: | sudo apt-get update - sudo apt-get install qt5-default + sudo apt-get install qtbase5-dev + sudo apt-get install qtchooser + sudo apt-get install qt5-qmake + sudo apt-get install qtbase5-dev-tools + sudo apt-get install libegl1 sudo apt-get install libxkbcommon-x11-0 sudo apt-get install libglu1-mesa-dev sudo apt-get install libxcb-icccm4 @@ -31,6 +35,7 @@ jobs: sudo apt-get install libxcb-randr0 sudo apt-get install libxcb-render-util0 sudo apt-get install libxcb-xinerama0 + sudo apt-get install libxcb-shape0 if: matrix.toolkit != 'null' - name: Cache EDM packages uses: actions/cache@v2 diff --git a/chaco/axis.py b/chaco/axis.py index d2818e168..9635b8fe5 100644 --- a/chaco/axis.py +++ b/chaco/axis.py @@ -10,6 +10,8 @@ """ Defines the PlotAxis class, and associated validator and UI. """ +import logging + # Major library import from numpy import ( array, @@ -58,6 +60,8 @@ from .label import Label from .log_mapper import LogMapper +logger = logging.getLogger(__name__) + def DEFAULT_TICK_FORMATTER(val): return ("%f" % val).rstrip("0").rstrip(".") @@ -500,20 +504,21 @@ def _compute_tick_positions(self, gc, overlay_component=None): screenlow, screenhigh = screenhigh, screenlow if ( - (datalow == datahigh) + (datalow >= datahigh) or (screenlow == screenhigh) or (datalow in [inf, -inf]) or (datahigh in [inf, -inf]) ): + if datalow > datahigh: + logger.warning( + "{self.mapper} has an invalid data range with " + "low={datalow} > high={datahigh}; unable to compute axis " + "ticks." + ) self._reset_cache() self._cache_valid = True return - if datalow > datahigh: - raise RuntimeError( - "DataRange low is greater than high; unable to compute axis ticks." - ) - if not self.tick_generator: return diff --git a/chaco/multi_array_data_source.py b/chaco/multi_array_data_source.py index 1c2aa8347..9663bb90e 100644 --- a/chaco/multi_array_data_source.py +++ b/chaco/multi_array_data_source.py @@ -13,7 +13,7 @@ import warnings # Major package imports -from numpy import nanmax, nanmin, array, shape, ones, bool, newaxis, nan_to_num +from numpy import nanmax, nanmin, array, shape, ones, newaxis, nan_to_num # Enthought library imports from traits.api import Any, Int, Tuple diff --git a/chaco/plot_containers.py b/chaco/plot_containers.py index 7ee0046e6..f5eaa4df4 100644 --- a/chaco/plot_containers.py +++ b/chaco/plot_containers.py @@ -18,6 +18,7 @@ array, cumsum, hstack, + resize, sum, zeros, zeros_like, @@ -182,7 +183,7 @@ class GridPlotContainer(BasePlotContainer): # The internal component grid, in row-major order. This gets updated # when any of the following traits change: shape, components, grid_components - _grid = Array + _grid = Array() _cached_total_size = Any _h_size_prefs = Any @@ -510,7 +511,7 @@ def _reflow_layout(self): numrows, numcols = divmod(len(self.components), self.shape[0]) self.shape = (numrows, numcols) grid = array(self.components, dtype=object) - grid.resize(self.shape) + grid = resize(grid, self.shape) grid[grid == 0] = None self._grid = grid self._layout_needed = True diff --git a/chaco/plots/image_plot.py b/chaco/plots/image_plot.py index 30284542a..a76b8f44d 100644 --- a/chaco/plots/image_plot.py +++ b/chaco/plots/image_plot.py @@ -22,13 +22,12 @@ from traits.api import ( Bool, Enum, + Float, Instance, - List, Range, Tuple, Property, cached_property, - Union, ) from kiva.agg import GraphicsContextArray @@ -84,7 +83,7 @@ class ImagePlot(Base2DPlot): # Tuple-defined rectangle (x, y, dx, dy) in screen space in which the # **_cached_image** is to be drawn. - _cached_dest_rect = Union(Tuple, List, transient=True) + _cached_dest_rect = Tuple(Float, Float, Float, Float, transient=True) # Bool indicating whether the origin is top-left or bottom-right. # The name "principal diagonal" is borrowed from linear algebra. @@ -285,7 +284,7 @@ def _compute_cached_image(self, data=None, mapper=None): # Update cached image and rectangle. self._cached_image = self._kiva_array_from_numpy_array(data) - self._cached_dest_rect = screen_rect + self._cached_dest_rect = tuple(screen_rect) self._image_cache_valid = True def _kiva_array_from_numpy_array(self, data): diff --git a/chaco/plots/tests/test_image_plot.py b/chaco/plots/tests/test_image_plot.py index 448af7637..afed30367 100644 --- a/chaco/plots/tests/test_image_plot.py +++ b/chaco/plots/tests/test_image_plot.py @@ -37,7 +37,7 @@ # The Quartz backend rescales pixel values, so use a higher threshold. MAX_RMS_ERROR = 16 if ETSConfig.kiva_backend == "quartz" else 1 -IMAGE = np.random.random_integers(0, 255, size=(100, 200)).astype(np.uint8) +IMAGE = np.random.randint(0, 256, size=(100, 200)).astype(np.uint8) RGB = np.dstack([IMAGE] * 3) # Rendering adds rows and columns for some reason. TRIM_RENDERED = (slice(1, -1), slice(1, -1), 0) diff --git a/chaco/ticks.py b/chaco/ticks.py index 9eb160f81..284cb742b 100644 --- a/chaco/ticks.py +++ b/chaco/ticks.py @@ -305,6 +305,8 @@ def auto_ticks( if upper > end: end += tick_interval + if isnan([start, end]).any(): + return [] ticks = arange(start, end + (tick_interval / 2.0), tick_interval) if len(ticks) < 2: diff --git a/chaco/tools/lasso_selection.py b/chaco/tools/lasso_selection.py index cc17851f8..fa446189d 100644 --- a/chaco/tools/lasso_selection.py +++ b/chaco/tools/lasso_selection.py @@ -158,11 +158,11 @@ def normal_mouse_down(self, event): """ # We may want to generalize this for the n-dimensional case... - self._active_selection = empty((0, 2), dtype=numpy.bool) + self._active_selection = empty((0, 2), dtype=bool) if self.selection_datasource is not None: self.selection_datasource.metadata[self.metadata_name] = zeros( - len(self.selection_datasource.get_data()), dtype=numpy.bool + len(self.selection_datasource.get_data()), dtype=bool ) self.selection_mode = "include" self.event_state = "selecting" @@ -197,7 +197,7 @@ def selecting_mouse_up(self, event): self._update_selection() self._previous_selections.append(self._active_selection) - self._active_selection = empty((0, 2), dtype=numpy.bool) + self._active_selection = empty((0, 2), dtype=bool) def selecting_mouse_move(self, event): """Handles the mouse moving when the tool is in the 'selecting' state. @@ -246,12 +246,12 @@ def normal_key_pressed(self, event): # ---------------------------------------------------------------------- def _dataspace_points_default(self): - return empty((0, 2), dtype=numpy.bool) + return empty((0, 2), dtype=bool) def _reset(self): """Resets the selection""" self.event_state = "normal" - self._active_selection = empty((0, 2), dtype=numpy.bool) + self._active_selection = empty((0, 2), dtype=bool) self._previous_selections = [] self._update_selection() @@ -279,7 +279,7 @@ def _update_selection(self): return selected_mask = zeros( - self.selection_datasource._data.shape, dtype=numpy.bool + self.selection_datasource._data.shape, dtype=bool ) data = self._get_data() diff --git a/chaco/tools/pan_tool.py b/chaco/tools/pan_tool.py index 61f2e28bd..66a4e9ae4 100644 --- a/chaco/tools/pan_tool.py +++ b/chaco/tools/pan_tool.py @@ -231,8 +231,10 @@ def panning_mouse_move(self, event): newlow = mapper.map_data(screenlow + screen_delta) # Use .set_bounds() so that we don't generate two range_changed - # events on the DataRange - mapper.range.set_bounds(newlow, newhigh) + # events on the DataRange. + # Do an explicit conversion to float because raw values may not + # be floating-point types, which makes NumPy unhappy (#854). + mapper.range.set_bounds(float(newlow), float(newhigh)) event.handled = True diff --git a/chaco/tools/tests/test_data_label_tool.py b/chaco/tools/tests/test_data_label_tool.py index c4deabb85..1f40c55af 100644 --- a/chaco/tools/tests/test_data_label_tool.py +++ b/chaco/tools/tests/test_data_label_tool.py @@ -10,7 +10,7 @@ from chaco.api import ArrayPlotData, DataLabel, Plot from chaco.tools.api import DataLabelTool -IMAGE = np.random.random_integers(0, 255, size=(100, 200)).astype(np.uint8) +IMAGE = np.random.randint(0, 256, size=(100, 200)).astype(np.uint8) RGB = np.dstack([IMAGE] * 3) diff --git a/ci/edmtool.py b/ci/edmtool.py index 118ec605a..dbd8ddb1f 100644 --- a/ci/edmtool.py +++ b/ci/edmtool.py @@ -77,7 +77,8 @@ import click supported_combinations = { - '3.6': {'pyside2', 'pyqt', 'pyqt5', 'null'}, + '3.6': {'pyside2', 'pyqt5', 'null'}, + '3.8': {'pyside6', 'null'}, } dependencies = { @@ -95,7 +96,7 @@ "swig", } -pypi_dependencies = {"sphinx-copybutton"} +pypi_dependencies = {} # Dependencies we install from source for cron tests # Order from packages with the most dependencies to one with the least @@ -111,6 +112,7 @@ extra_dependencies = { 'pyside2': {'pyside2'}, + 'pyside6': {'pyside6'}, 'pyqt': {'pyqt'}, 'pyqt5': {'pyqt5'}, 'null': set() @@ -118,7 +120,8 @@ doc_dependencies = { "sphinx", - "enthought_sphinx_theme" + "enthought_sphinx_theme", + "sphinx-copybutton", } doc_ignore = { @@ -161,6 +164,7 @@ environment_vars = { 'pyside2': {'ETS_TOOLKIT': 'qt4', 'QT_API': 'pyside2'}, + 'pyside6': {'ETS_TOOLKIT': 'qt4', 'QT_API': 'pyside6'}, 'pyqt': {'ETS_TOOLKIT': 'qt4', 'QT_API': 'pyqt'}, 'pyqt5': {'ETS_TOOLKIT': 'qt4', 'QT_API': 'pyqt5'}, 'null': {'ETS_TOOLKIT': 'null.image'}, @@ -204,7 +208,7 @@ def install(runtime, toolkit, environment, editable, source): | ci_dependencies ) - if toolkit == "pyside2": + if toolkit.startswith("pyside"): addn_repositories = "--add-repository enthought/lgpl" else: addn_repositories = "" diff --git a/pyproject.toml b/pyproject.toml index b1256a319..26b1ea674 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["cython", "oldest-supported-numpy", "setuptools", "wheel"] +requires = ["cython", "oldest-supported-numpy", "setuptools<65.2", "wheel"] build-backend = "setuptools.build_meta" [tool.cibuildwheel]