From 8485d80466004461fe9cfa3c9cc4e1a2b8c0549c Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Oct 2024 16:25:15 +0200 Subject: [PATCH 01/18] Fix issues with unwrapping longitudes in RangeXY stream --- geoviews/plotting/bokeh/callbacks.py | 2 +- geoviews/plotting/bokeh/plot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/geoviews/plotting/bokeh/callbacks.py b/geoviews/plotting/bokeh/callbacks.py index a1bdab56..78e37cd5 100644 --- a/geoviews/plotting/bokeh/callbacks.py +++ b/geoviews/plotting/bokeh/callbacks.py @@ -96,7 +96,7 @@ def project_ranges(cb, msg, attributes): extents = x0, y0, x1, y1 x0, y0, x1, y1 = project_extents(extents, plot.projection, plot.current_frame.crs) - if plot._unwrap_lons and -180 <= x0 < 0 or -180 <= x1 < 0: + if plot._unwrap_lons and (-180 <= x0 < 0 or -180 <= x1 < 0): x0, x1 = x0 + 360, x1 + 360 if x0 > x1: x0, x1 = x1, x0 diff --git a/geoviews/plotting/bokeh/plot.py b/geoviews/plotting/bokeh/plot.py index 20d1a118..80888282 100644 --- a/geoviews/plotting/bokeh/plot.py +++ b/geoviews/plotting/bokeh/plot.py @@ -107,7 +107,7 @@ def _update_ranges(self, element, ranges): def _set_unwrap_lons(self, element): if isinstance(self.geographic, _CylindricalProjection): x1, x2 = element.range(0) - self._unwrap_lons = 0 <= x1 <= 360 and 0 <= x2 <= 360 + self._unwrap_lons = 0 <= x1 <= 360 and 180 <= x2 <= 360 def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): opts = {} if isinstance(self, HvOverlayPlot) else {'source': source} From 77694827bcd2ddff9278b7c57069d7afc602c76b Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Oct 2024 16:36:11 +0200 Subject: [PATCH 02/18] Use pre-computed ranges --- geoviews/plotting/bokeh/plot.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/geoviews/plotting/bokeh/plot.py b/geoviews/plotting/bokeh/plot.py index 80888282..af63b92f 100644 --- a/geoviews/plotting/bokeh/plot.py +++ b/geoviews/plotting/bokeh/plot.py @@ -6,7 +6,7 @@ from bokeh.models.tools import BoxZoomTool, WheelZoomTool from cartopy.crs import GOOGLE_MERCATOR, Mercator, PlateCarree, _CylindricalProjection from holoviews.core.dimension import Dimension -from holoviews.core.util import dimension_sanitizer +from holoviews.core.util import dimension_sanitizer, match_spec from holoviews.plotting.bokeh.element import ElementPlot, OverlayPlot as HvOverlayPlot from ...element import Shape, _Element, is_geographic @@ -104,14 +104,21 @@ def _update_ranges(self, element, ranges): ax_range.end = mid + min_interval/2. ax_range.min_interval = min_interval - def _set_unwrap_lons(self, element): + def _set_unwrap_lons(self, element, ranges): if isinstance(self.geographic, _CylindricalProjection): - x1, x2 = element.range(0) - self._unwrap_lons = 0 <= x1 <= 360 and 180 <= x2 <= 360 + xdim = element.get_dimension(0) + x_range = ranges.get(xdim.name, {}).get('data') + if x_range: + x0, x1 = x_range + else: + x0, x1 = element.range(0) + self._unwrap_lons = 0 <= x0 <= 360 and 180 <= x1 <= 360 def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): opts = {} if isinstance(self, HvOverlayPlot) else {'source': source} fig = super().initialize_plot(ranges, plot, plots, **opts) + style_element = self.current_frame.last if self.batched else self.current_frame + el_ranges = match_spec(style_element, ranges) if self.geographic and self.show_bounds and not self.overlaid: from . import GeoShapePlot shape = Shape(self.projection.boundary, crs=self.projection).options(fill_alpha=0) @@ -119,12 +126,14 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): overlaid=True, renderer=self.renderer) shapeplot.geographic = False shapeplot.initialize_plot(plot=fig) - self._set_unwrap_lons(self.current_frame) + self._set_unwrap_lons(self.current_frame, el_ranges) return fig def update_frame(self, key, ranges=None, element=None): if element is not None: - self._set_unwrap_lons(element) + style_element = element.last if self.batched else element + el_ranges = match_spec(style_element, ranges) + self._set_unwrap_lons(element, el_ranges) super().update_frame(key, ranges=ranges, element=element) def _postprocess_hover(self, renderer, source): From 19c8d5f5ee9649baf67e3496703be4f68453cf0e Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Oct 2024 16:43:24 +0200 Subject: [PATCH 03/18] Small fixes --- geoviews/plotting/bokeh/plot.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/geoviews/plotting/bokeh/plot.py b/geoviews/plotting/bokeh/plot.py index af63b92f..a9df05f4 100644 --- a/geoviews/plotting/bokeh/plot.py +++ b/geoviews/plotting/bokeh/plot.py @@ -118,7 +118,7 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): opts = {} if isinstance(self, HvOverlayPlot) else {'source': source} fig = super().initialize_plot(ranges, plot, plots, **opts) style_element = self.current_frame.last if self.batched else self.current_frame - el_ranges = match_spec(style_element, ranges) + el_ranges = match_spec(style_element, self.current_ranges) if self.geographic and self.show_bounds and not self.overlaid: from . import GeoShapePlot shape = Shape(self.projection.boundary, crs=self.projection).options(fill_alpha=0) @@ -126,15 +126,14 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): overlaid=True, renderer=self.renderer) shapeplot.geographic = False shapeplot.initialize_plot(plot=fig) - self._set_unwrap_lons(self.current_frame, el_ranges) + self._set_unwrap_lons(style_element, el_ranges) return fig def update_frame(self, key, ranges=None, element=None): - if element is not None: - style_element = element.last if self.batched else element - el_ranges = match_spec(style_element, ranges) - self._set_unwrap_lons(element, el_ranges) super().update_frame(key, ranges=ranges, element=element) + style_element = self.current_frame.last if self.batched else self.current_frame + el_ranges = match_spec(style_element, self.current_ranges) + self._set_unwrap_lons(style_element, el_ranges) def _postprocess_hover(self, renderer, source): super()._postprocess_hover(renderer, source) From 410bd5f3fa66afdc1ebb922f0464f3d8f645cdf0 Mon Sep 17 00:00:00 2001 From: Andrew <15331990+ahuang11@users.noreply.github.com> Date: Wed, 16 Oct 2024 08:54:37 -0700 Subject: [PATCH 04/18] Check for current ranges --- geoviews/plotting/bokeh/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geoviews/plotting/bokeh/plot.py b/geoviews/plotting/bokeh/plot.py index a9df05f4..835cc53a 100644 --- a/geoviews/plotting/bokeh/plot.py +++ b/geoviews/plotting/bokeh/plot.py @@ -118,7 +118,7 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): opts = {} if isinstance(self, HvOverlayPlot) else {'source': source} fig = super().initialize_plot(ranges, plot, plots, **opts) style_element = self.current_frame.last if self.batched else self.current_frame - el_ranges = match_spec(style_element, self.current_ranges) + el_ranges = match_spec(style_element, self.current_ranges) if self.current_ranges else {} if self.geographic and self.show_bounds and not self.overlaid: from . import GeoShapePlot shape = Shape(self.projection.boundary, crs=self.projection).options(fill_alpha=0) From 6cea2589ad3e134395de2c8126dab256440fb1c6 Mon Sep 17 00:00:00 2001 From: Andrew <15331990+ahuang11@users.noreply.github.com> Date: Wed, 16 Oct 2024 09:10:49 -0700 Subject: [PATCH 05/18] Check in update frame too --- geoviews/plotting/bokeh/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geoviews/plotting/bokeh/plot.py b/geoviews/plotting/bokeh/plot.py index 835cc53a..0aae831b 100644 --- a/geoviews/plotting/bokeh/plot.py +++ b/geoviews/plotting/bokeh/plot.py @@ -132,7 +132,7 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): def update_frame(self, key, ranges=None, element=None): super().update_frame(key, ranges=ranges, element=element) style_element = self.current_frame.last if self.batched else self.current_frame - el_ranges = match_spec(style_element, self.current_ranges) + el_ranges = match_spec(style_element, self.current_ranges) if self.current_ranges else {} self._set_unwrap_lons(style_element, el_ranges) def _postprocess_hover(self, renderer, source): From 87019c0366f5da7d247fb1def62007c8d019b5ba Mon Sep 17 00:00:00 2001 From: Andrew <15331990+ahuang11@users.noreply.github.com> Date: Wed, 16 Oct 2024 09:11:09 -0700 Subject: [PATCH 06/18] Spacing --- geoviews/plotting/bokeh/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geoviews/plotting/bokeh/plot.py b/geoviews/plotting/bokeh/plot.py index 0aae831b..39cf3c9c 100644 --- a/geoviews/plotting/bokeh/plot.py +++ b/geoviews/plotting/bokeh/plot.py @@ -132,7 +132,7 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): def update_frame(self, key, ranges=None, element=None): super().update_frame(key, ranges=ranges, element=element) style_element = self.current_frame.last if self.batched else self.current_frame - el_ranges = match_spec(style_element, self.current_ranges) if self.current_ranges else {} + el_ranges = match_spec(style_element, self.current_ranges) if self.current_ranges else {} self._set_unwrap_lons(style_element, el_ranges) def _postprocess_hover(self, renderer, source): From e93823852c77ae3287d520511de63146c2b757d9 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Wed, 16 Oct 2024 10:42:07 -0700 Subject: [PATCH 07/18] extend ranges from -180 to 540 --- geoviews/plotting/bokeh/callbacks.py | 3 ++- geoviews/plotting/bokeh/plot.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/geoviews/plotting/bokeh/callbacks.py b/geoviews/plotting/bokeh/callbacks.py index 78e37cd5..b274bb8c 100644 --- a/geoviews/plotting/bokeh/callbacks.py +++ b/geoviews/plotting/bokeh/callbacks.py @@ -97,9 +97,10 @@ def project_ranges(cb, msg, attributes): x0, y0, x1, y1 = project_extents(extents, plot.projection, plot.current_frame.crs) if plot._unwrap_lons and (-180 <= x0 < 0 or -180 <= x1 < 0): - x0, x1 = x0 + 360, x1 + 360 + x1 += 360 if x0 > x1: x0, x1 = x1, x0 + print(x0, x1) coords = {'x_range': (x0, x1), 'y_range': (y0, y1)} return {k: v for k, v in coords.items() if k in attributes} diff --git a/geoviews/plotting/bokeh/plot.py b/geoviews/plotting/bokeh/plot.py index 39cf3c9c..26c4ddee 100644 --- a/geoviews/plotting/bokeh/plot.py +++ b/geoviews/plotting/bokeh/plot.py @@ -112,7 +112,7 @@ def _set_unwrap_lons(self, element, ranges): x0, x1 = x_range else: x0, x1 = element.range(0) - self._unwrap_lons = 0 <= x0 <= 360 and 180 <= x1 <= 360 + self._unwrap_lons = -1.25 <= x0 <= 360 and 180 <= x1 <= 360 def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): opts = {} if isinstance(self, HvOverlayPlot) else {'source': source} From 02e47faabf78d9f68a4ddf63396951709187d2cb Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Wed, 16 Oct 2024 10:43:44 -0700 Subject: [PATCH 08/18] rm print --- geoviews/plotting/bokeh/callbacks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/geoviews/plotting/bokeh/callbacks.py b/geoviews/plotting/bokeh/callbacks.py index b274bb8c..1ec24844 100644 --- a/geoviews/plotting/bokeh/callbacks.py +++ b/geoviews/plotting/bokeh/callbacks.py @@ -100,7 +100,6 @@ def project_ranges(cb, msg, attributes): x1 += 360 if x0 > x1: x0, x1 = x1, x0 - print(x0, x1) coords = {'x_range': (x0, x1), 'y_range': (y0, y1)} return {k: v for k, v in coords.items() if k in attributes} From eea3ee9c6377716c2ea51ebe4788f3d628af5ef3 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Wed, 16 Oct 2024 11:51:26 -0700 Subject: [PATCH 09/18] adjust range and add comment --- geoviews/plotting/bokeh/plot.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/geoviews/plotting/bokeh/plot.py b/geoviews/plotting/bokeh/plot.py index 26c4ddee..d44017d0 100644 --- a/geoviews/plotting/bokeh/plot.py +++ b/geoviews/plotting/bokeh/plot.py @@ -105,6 +105,9 @@ def _update_ranges(self, element, ranges): ax_range.min_interval = min_interval def _set_unwrap_lons(self, element, ranges): + """ + Check whether the lons should be transformed from 0, 360 to -180, 180 + """ if isinstance(self.geographic, _CylindricalProjection): xdim = element.get_dimension(0) x_range = ranges.get(xdim.name, {}).get('data') @@ -112,7 +115,10 @@ def _set_unwrap_lons(self, element, ranges): x0, x1 = x_range else: x0, x1 = element.range(0) - self._unwrap_lons = -1.25 <= x0 <= 360 and 180 <= x1 <= 360 + # x0, depending on the step/interval, can be slightly less than 0, + # e.g. lon=np.arange(0, 360, 10) -> x0 = -5 from (step 10 / 2) + # other projections likely will not fall within this range + self._unwrap_lons = -90 <= x0 <= 360 and 180 <= x1 <= 540 def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): opts = {} if isinstance(self, HvOverlayPlot) else {'source': source} From 9ee4e3080d4d4a0eadf3e28ae925e99b5a9c586f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Mon, 28 Oct 2024 17:03:42 +0100 Subject: [PATCH 10/18] ci: Add UI tests (#760) --- .github/workflows/test.yaml | 34 +++++++++++++++++++++++++++++ geoviews/tests/conftest.py | 36 +++++++++++++++++++++++++++++++ geoviews/tests/ui/test_example.py | 7 ++++++ pixi.toml | 26 +++++++++++++++++----- 4 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 geoviews/tests/ui/test_example.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2fe7fa20..f20c8617 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -134,6 +134,40 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} + ui_test_suite: + name: ui:${{ matrix.environment }}:${{ matrix.os }} + needs: [pre_commit, setup, pixi_lock] + runs-on: ${{ matrix.os }} + if: needs.setup.outputs.code_change == 'true' + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest"] + environment: ["test-ui"] + timeout-minutes: 60 + env: + PANEL_LOG_LEVEL: info + steps: + - uses: holoviz-dev/holoviz_tasks/pixi_install@v0 + with: + environments: ${{ matrix.environment }} + - name: Test UI + run: | + # Create a .uicoveragerc file to set the concurrency library to greenlet + # https://github.com/microsoft/playwright-python/issues/313 + echo "[run]\nconcurrency = greenlet" > .uicoveragerc + FAIL="--screenshot only-on-failure --full-page-screenshot --output ui_screenshots --tracing retain-on-failure" + pixi run -e ${{ matrix.environment }} test-ui $COV --cov-config=.uicoveragerc $FAIL + - uses: actions/upload-artifact@v4 + if: always() + with: + name: ui_screenshots_${{ runner.os }} + path: ./ui_screenshots + if-no-files-found: ignore + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + core_test_suite: name: core:${{ matrix.environment }}:${{ matrix.os }} needs: [pre_commit, setup, pixi_lock] diff --git a/geoviews/tests/conftest.py b/geoviews/tests/conftest.py index 0e0bbaa9..8d29ff23 100644 --- a/geoviews/tests/conftest.py +++ b/geoviews/tests/conftest.py @@ -2,6 +2,42 @@ import geoviews as gv +CUSTOM_MARKS = ("ui",) + + +def pytest_addoption(parser): + for marker in CUSTOM_MARKS: + parser.addoption( + f"--{marker}", + action="store_true", + default=False, + help=f"Run {marker} related tests", + ) + + +def pytest_configure(config): + for marker in CUSTOM_MARKS: + config.addinivalue_line("markers", f"{marker}: {marker} test marker") + + +def pytest_collection_modifyitems(config, items): + skipped, selected = [], [] + markers = [m for m in CUSTOM_MARKS if config.getoption(f"--{m}")] + empty = not markers + for item in items: + if empty and any(m in item.keywords for m in CUSTOM_MARKS): + skipped.append(item) + elif empty: + selected.append(item) + elif not empty and any(m in item.keywords for m in markers): + selected.append(item) + else: + skipped.append(item) + + config.hook.pytest_deselected(items=skipped) + items[:] = selected + + with suppress(Exception): gv.extension("bokeh") diff --git a/geoviews/tests/ui/test_example.py b/geoviews/tests/ui/test_example.py new file mode 100644 index 00000000..e7d4254b --- /dev/null +++ b/geoviews/tests/ui/test_example.py @@ -0,0 +1,7 @@ +import pytest + +pytestmark = pytest.mark.ui + + +def test_ui_example(page): + assert True diff --git a/pixi.toml b/pixi.toml index f0c4e05d..2c75f545 100644 --- a/pixi.toml +++ b/pixi.toml @@ -13,10 +13,11 @@ USE_PYGEOS = "0" DASK_DATAFRAME__QUERY_PLANNING = "False" [environments] -test-310 = ["py310", "test-core", "test", "example", "test-example", "download-data"] -test-311 = ["py311", "test-core", "test", "example", "test-example", "download-data"] -test-312 = ["py312", "test-core", "test", "example", "test-example", "download-data"] -test-core = ["py312", "test-core"] +test-310 = ["py310", "test-core", "test-unit-task", "test", "example", "test-example", "download-data"] +test-311 = ["py311", "test-core", "test-unit-task", "test", "example", "test-example", "download-data"] +test-312 = ["py312", "test-core", "test-unit-task", "test", "example", "test-example", "download-data"] +test-core = ["py312", "test-unit-task", "test-core"] +test-ui = ["py312", "test-core", "test", "test-ui"] docs = ["py311", "example", "doc", "download-data"] build = ["py311", "build"] lint = ["py311", "lint"] @@ -82,7 +83,7 @@ pytest-cov = "*" pytest-github-actions-annotate-failures = "*" pytest-xdist = "*" -[feature.test-core.tasks] +[feature.test-unit-task.tasks] # So it is not showing up in the test-ui environment test-unit = 'pytest geoviews/tests -n logical --dist loadgroup' [feature.test.dependencies] @@ -90,6 +91,7 @@ cftime = "*" datashader = "*" filelock = "*" fiona = "*" +gdal = "!=3.9.3" # Crashes CI geopandas-base = "*" iris = ">=3.5" matplotlib-base = ">2.2" @@ -108,6 +110,20 @@ test-example = 'pytest -n logical --dist loadscope --nbval-lax examples' [feature.test-example.dependencies] nbval = "*" +[feature.test-ui] +channels = ["microsoft"] + +[feature.test-ui.dependencies] +playwright = { version = "*", channel = "microsoft" } +pytest-playwright = { version = "*", channel = "microsoft" } + +[feature.test-ui.tasks] +_install-ui = 'playwright install chromium' + +[feature.test-ui.tasks.test-ui] +cmd = 'pytest geoviews/tests/ui --ui --browser chromium' +depends_on = ["_install-ui"] + # ============================================= # =================== DOCS ==================== # ============================================= From 4536e7aea6acfd866dbabf6cea96681b1f796f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Tue, 29 Oct 2024 17:12:03 +0100 Subject: [PATCH 11/18] ci: Remove gdal pin (#761) --- .github/workflows/test.yaml | 2 +- pixi.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f20c8617..0c4e3832 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: code: - 'geoviews/**' - 'examples/**' - - 'setup.py' + - 'pixi.toml' - 'pyproject.toml' - '.github/workflows/test.yaml' - name: Set matrix option diff --git a/pixi.toml b/pixi.toml index 2c75f545..d6d2234c 100644 --- a/pixi.toml +++ b/pixi.toml @@ -91,7 +91,6 @@ cftime = "*" datashader = "*" filelock = "*" fiona = "*" -gdal = "!=3.9.3" # Crashes CI geopandas-base = "*" iris = ">=3.5" matplotlib-base = ">2.2" From f7b1e7bd19983066b90b4af1596f12cbb1ac071e Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Sat, 9 Nov 2024 06:11:14 -0800 Subject: [PATCH 12/18] add ui tests --- geoviews/tests/conftest.py | 2 ++ geoviews/tests/ui/test_example.py | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 geoviews/tests/ui/test_example.py diff --git a/geoviews/tests/conftest.py b/geoviews/tests/conftest.py index 8d29ff23..53925ffb 100644 --- a/geoviews/tests/conftest.py +++ b/geoviews/tests/conftest.py @@ -1,5 +1,7 @@ from contextlib import suppress +from holoviews.tests.conftest import bokeh_backend, port, serve_hv # noqa: F401 + import geoviews as gv CUSTOM_MARKS = ("ui",) diff --git a/geoviews/tests/ui/test_example.py b/geoviews/tests/ui/test_example.py deleted file mode 100644 index e7d4254b..00000000 --- a/geoviews/tests/ui/test_example.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - -pytestmark = pytest.mark.ui - - -def test_ui_example(page): - assert True From 1a2d9b8d4356148d0afb65a2d33a0a3bdb5a58c1 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 21 Nov 2024 17:16:47 -0800 Subject: [PATCH 13/18] actually add the file --- geoviews/tests/ui/test_plot.py | 84 ++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 geoviews/tests/ui/test_plot.py diff --git a/geoviews/tests/ui/test_plot.py b/geoviews/tests/ui/test_plot.py new file mode 100644 index 00000000..0cd671f7 --- /dev/null +++ b/geoviews/tests/ui/test_plot.py @@ -0,0 +1,84 @@ +import cartopy.crs as ccrs +import holoviews as hv +import numpy as np +import pytest +from holoviews.operation.datashader import rasterize +from holoviews.tests.ui import expect, wait_until + +import geoviews as gv + +xr = pytest.importorskip("xarray") +pytestmark = pytest.mark.ui + + +@pytest.mark.usefixtures("bokeh_backend") +def test_range_correct_longitude(serve_hv): + """ + Regression test for https://github.com/holoviz/geoviews/issues/753 + """ + coastline = gv.feature.coastline().opts(active_tools=["box_zoom"]) + xy_range = hv.streams.RangeXY(source=coastline) + + page = serve_hv(coastline) + hv_plot = page.locator(".bk-events") + + expect(hv_plot).to_have_count(1) + + bbox = hv_plot.bounding_box() + hv_plot.click() + + page.mouse.move(bbox["x"] + 100, bbox["y"] + 100) + page.mouse.down() + page.mouse.move(bbox["x"] + 150, bbox["y"] + 150, steps=5) + page.mouse.up() + + wait_until(lambda: np.isclose(xy_range.x_range[0], -105.68691588784145), page) + wait_until(lambda: np.isclose(xy_range.x_range[1], -21.80841121496224), page) + wait_until(lambda: np.isclose(xy_range.y_range[0], -15.90389795822735), page) + wait_until(lambda: np.isclose(xy_range.y_range[1], 56.056770531698916), page) + + +@pytest.mark.usefixtures("bokeh_backend") +@pytest.mark.parametrize("lon_start,lon_end", [(-180, 180), (0, 360)]) +@pytest.mark.parametrize("bbox_x", [100, 250]) +def test_rasterize_with_coastline_not_blank_on_zoom(serve_hv, lon_start, lon_end, bbox_x): + """ + Regression test for https://github.com/holoviz/geoviews/issues/726 + """ + + gv.extension("bokeh") + + lon = np.linspace(lon_start, lon_end, 360) + lat = np.linspace(-90, 90, 180) + data = np.random.rand(180, 360) + ds = xr.Dataset({"data": (["lat", "lon"], data)}, coords={"lon": lon, "lat": lat}) + + overlay = rasterize( + gv.Image(ds, ["lon", "lat"], ["data"], crs=ccrs.PlateCarree()).opts( + tools=["hover"], active_tools=["box_zoom"] + ) + ) * gv.feature.coastline() + + page = serve_hv(overlay) + + hv_plot = page.locator(".bk-events") + + expect(hv_plot).to_have_count(1) + + bbox = hv_plot.bounding_box() + hv_plot.click() + + page.mouse.move(bbox["x"] + bbox_x, bbox["y"] + 100) + page.mouse.down() + page.mouse.move(bbox["x"] + bbox_x + 50, bbox["y"] + 150, steps=5) + page.mouse.up() + + # get hover tooltip + page.mouse.move(bbox["x"] + 100, bbox["y"] + 150) + + wait_until(lambda: expect(page.locator(".bk-Tooltip")).to_have_count(1), page=page) + + expect(page.locator(".bk-Tooltip")).to_contain_text("lon:") + expect(page.locator(".bk-Tooltip")).to_contain_text("lat:") + expect(page.locator(".bk-Tooltip")).to_contain_text("data:") + expect(page.locator(".bk-Tooltip")).not_to_contain_text("?") From b1c252a44ed2980d5bdfef37775c04fd000a7670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 22 Nov 2024 14:56:59 +0100 Subject: [PATCH 14/18] fix infra --- geoviews/tests/ui/__init__.py | 0 geoviews/tests/ui/test_example.py | 7 ------- geoviews/tests/ui/test_plot.py | 7 +++---- 3 files changed, 3 insertions(+), 11 deletions(-) create mode 100644 geoviews/tests/ui/__init__.py delete mode 100644 geoviews/tests/ui/test_example.py diff --git a/geoviews/tests/ui/__init__.py b/geoviews/tests/ui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/geoviews/tests/ui/test_example.py b/geoviews/tests/ui/test_example.py deleted file mode 100644 index e7d4254b..00000000 --- a/geoviews/tests/ui/test_example.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - -pytestmark = pytest.mark.ui - - -def test_ui_example(page): - assert True diff --git a/geoviews/tests/ui/test_plot.py b/geoviews/tests/ui/test_plot.py index 0cd671f7..c15b2451 100644 --- a/geoviews/tests/ui/test_plot.py +++ b/geoviews/tests/ui/test_plot.py @@ -2,14 +2,14 @@ import holoviews as hv import numpy as np import pytest -from holoviews.operation.datashader import rasterize from holoviews.tests.ui import expect, wait_until import geoviews as gv -xr = pytest.importorskip("xarray") pytestmark = pytest.mark.ui +xr = pytest.importorskip("xarray") + @pytest.mark.usefixtures("bokeh_backend") def test_range_correct_longitude(serve_hv): @@ -45,8 +45,7 @@ def test_rasterize_with_coastline_not_blank_on_zoom(serve_hv, lon_start, lon_end """ Regression test for https://github.com/holoviz/geoviews/issues/726 """ - - gv.extension("bokeh") + from holoviews.operation.datashader import rasterize lon = np.linspace(lon_start, lon_end, 360) lat = np.linspace(-90, 90, 180) From 81ba4db7ae5cd4672462ab31d124299844530512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 22 Nov 2024 15:13:39 +0100 Subject: [PATCH 15/18] Add cartopy download --- pixi.toml | 2 +- scripts/download_data.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pixi.toml b/pixi.toml index 744fb6e9..a8e497ea 100644 --- a/pixi.toml +++ b/pixi.toml @@ -16,7 +16,7 @@ test-310 = ["py310", "test-core", "test-unit-task", "test", "example", "test-exa test-311 = ["py311", "test-core", "test-unit-task", "test", "example", "test-example", "download-data"] test-312 = ["py312", "test-core", "test-unit-task", "test", "example", "test-example", "download-data"] test-core = ["py312", "test-unit-task", "test-core"] -test-ui = ["py312", "test-core", "test", "test-ui"] +test-ui = ["py312", "test-core", "test", "test-ui", "download-data"] docs = ["py311", "example", "doc", "download-data"] build = ["py311", "build"] lint = ["py311", "lint"] diff --git a/scripts/download_data.py b/scripts/download_data.py index 77511056..c758fb16 100644 --- a/scripts/download_data.py +++ b/scripts/download_data.py @@ -28,3 +28,8 @@ xr.tutorial.open_dataset("air_temperature") xr.tutorial.open_dataset("rasm") + +with suppress(ImportError): + from cartopy.feature import shapereader + + shapereader.natural_earth() From 0165d7884cc89a6d5843f5b1f88ca2d56718f72a Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 22 Nov 2024 12:13:27 -0800 Subject: [PATCH 16/18] add atol --- geoviews/tests/ui/test_plot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geoviews/tests/ui/test_plot.py b/geoviews/tests/ui/test_plot.py index c15b2451..4ac748bc 100644 --- a/geoviews/tests/ui/test_plot.py +++ b/geoviews/tests/ui/test_plot.py @@ -34,8 +34,8 @@ def test_range_correct_longitude(serve_hv): wait_until(lambda: np.isclose(xy_range.x_range[0], -105.68691588784145), page) wait_until(lambda: np.isclose(xy_range.x_range[1], -21.80841121496224), page) - wait_until(lambda: np.isclose(xy_range.y_range[0], -15.90389795822735), page) - wait_until(lambda: np.isclose(xy_range.y_range[1], 56.056770531698916), page) + wait_until(lambda: np.isclose(xy_range.y_range[0], -15, atol=1), page) + wait_until(lambda: np.isclose(xy_range.y_range[1], 56, atol=1), page) @pytest.mark.usefixtures("bokeh_backend") From 348ea2a91757d7dd443ed9218a0198e559098d35 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 22 Nov 2024 12:22:28 -0800 Subject: [PATCH 17/18] remove irrelevant asserts --- geoviews/tests/ui/test_plot.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/geoviews/tests/ui/test_plot.py b/geoviews/tests/ui/test_plot.py index 4ac748bc..df9b64ad 100644 --- a/geoviews/tests/ui/test_plot.py +++ b/geoviews/tests/ui/test_plot.py @@ -34,8 +34,6 @@ def test_range_correct_longitude(serve_hv): wait_until(lambda: np.isclose(xy_range.x_range[0], -105.68691588784145), page) wait_until(lambda: np.isclose(xy_range.x_range[1], -21.80841121496224), page) - wait_until(lambda: np.isclose(xy_range.y_range[0], -15, atol=1), page) - wait_until(lambda: np.isclose(xy_range.y_range[1], 56, atol=1), page) @pytest.mark.usefixtures("bokeh_backend") From 238513a9a69e632c3c44c82d315615782268a8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 22 Nov 2024 21:53:27 +0100 Subject: [PATCH 18/18] Add server cleanup --- geoviews/tests/conftest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/geoviews/tests/conftest.py b/geoviews/tests/conftest.py index 9155d0b7..34e20174 100644 --- a/geoviews/tests/conftest.py +++ b/geoviews/tests/conftest.py @@ -1,6 +1,11 @@ from contextlib import suppress -from holoviews.tests.conftest import bokeh_backend, port, serve_hv # noqa: F401 +from holoviews.tests.conftest import ( # noqa: F401 + bokeh_backend, + port, + serve_hv, + server_cleanup, +) import geoviews as gv