Repository: holoviz/holoviews
Pull request #6369 (2024-09-10 07:54:03)

General maintenance 


Patch:
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 4bfa6b7b8..bef067ee9 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -12,7 +12,7 @@ on:
 
 defaults:
   run:
-    shell: bash -el {0}
+    shell: bash -l {0}
 
 env:
   PYTHON_VERSION: "3.11"
@@ -33,14 +33,14 @@ jobs:
     name: Pixi lock
     runs-on: ubuntu-latest
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_lock@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
 
   conda_build:
     name: Build Conda
     needs: [pixi_lock]
     runs-on: "ubuntu-latest"
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_install@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
         with:
           environments: "build"
           download-data: false
@@ -88,7 +88,7 @@ jobs:
     needs: [pixi_lock]
     runs-on: "ubuntu-latest"
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_install@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
         with:
           environments: "build"
           download-data: false
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 660cbf350..5dedd6b75 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -22,14 +22,14 @@ on:
 
 defaults:
   run:
-    shell: bash -el {0}
+    shell: bash -l {0}
 
 jobs:
   pixi_lock:
     name: Pixi lock
     runs-on: ubuntu-latest
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_lock@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
 
   docs_build:
     name: Build Documentation
@@ -41,7 +41,7 @@ jobs:
     env:
       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_install@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
         with:
           environments: docs
       - name: Build documentation
diff --git a/.github/workflows/nightly_lock.yaml b/.github/workflows/nightly_lock.yaml
index 9b93b85a2..00e670c88 100644
--- a/.github/workflows/nightly_lock.yaml
+++ b/.github/workflows/nightly_lock.yaml
@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     timeout-minutes: 5
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_lock@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
       - name: Upload lock-file to S3
         if: "!github.event.pull_request.head.repo.fork"
         env:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 75de2eb85..6a3131a02 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,7 +20,7 @@ repos:
       - id: check-json
       - id: detect-private-key
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.6.2
+    rev: v0.6.7
     hooks:
       - id: ruff
         files: holoviews/|scripts/
diff --git a/holoviews/__init__.py b/holoviews/__init__.py
index 1ca017cdf..a8b3f5868 100644
--- a/holoviews/__init__.py
+++ b/holoviews/__init__.py
@@ -73,9 +73,9 @@ In a notebook or ipython environment the usual
 To ask the community go to https://discourse.holoviz.org/.
 To report issues go to https://github.com/holoviz/holoviews.
 """
+import builtins
 import os
 import sys
-import warnings
 
 import param
 
@@ -114,12 +114,7 @@ from .util.warnings import (  # noqa: F401
     HoloviewsUserWarning,
 )
 
-# Suppress warnings generated by NumPy in matplotlib
-# Expected to be fixed in next matplotlib release
-warnings.filterwarnings("ignore",
-                        message="elementwise comparison failed; returning scalar instead")
-
-if "IPython" in sys.modules:
+if hasattr(builtins, "__IPYTHON__"):
     from .ipython import notebook_extension
     extension = notebook_extension
 else:
@@ -181,7 +176,7 @@ def help(obj, visualization=True, ansi=True, backend=None,
         pydoc.help(obj)
 
 
-del os, sys, rcfile, warnings
+del builtins, os, rcfile, sys
 
 def __getattr__(name):
     if name == "annotate":
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index 17ef69983..dd76fcc68 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -880,7 +880,7 @@ class BarPlot(BarsMixin, ColorbarPlot, LegendPlot):
 
     def get_data(self, element, ranges, style):
         # Get x, y, group, stack and color dimensions
-        group_dim, stack_dim = None, None
+        group_dim, stack_dim, stack_order = None, None, None
         if element.ndims == 1:
             grouping = None
         elif self.stacked:
@@ -893,6 +893,8 @@ class BarPlot(BarsMixin, ColorbarPlot, LegendPlot):
             else:
                 stack_order = element.dimension_values(1, False)
             stack_order = list(stack_order)
+            stack_data = element.dimension_values(stack_dim)
+            stack_idx = stack_data == stack_data[0]
         else:
             grouping = 'grouped'
             group_dim = element.get_dimension(1)
@@ -921,7 +923,8 @@ class BarPlot(BarsMixin, ColorbarPlot, LegendPlot):
             grouped = {0: element}
             is_dt = isdatetime(xvals)
             if is_dt or xvals.dtype.kind not in 'OU':
-                xdiff = np.abs(np.diff(xvals))
+                xslice = stack_idx if stack_order else slice(None)
+                xdiff = np.abs(np.diff(xvals[xslice]))
                 diff_size = len(np.unique(xdiff))
                 if diff_size == 0 or (diff_size == 1 and xdiff[0] == 0):
                     xdiff = 1
diff --git a/holoviews/plotting/mixins.py b/holoviews/plotting/mixins.py
index 524308a1c..8da7c7bf7 100644
--- a/holoviews/plotting/mixins.py
+++ b/holoviews/plotting/mixins.py
@@ -174,7 +174,7 @@ class BarsMixin:
         else:
             y0, y1 = ranges[vdim]['combined']
 
-        x0, x1 = (l, r) if util.isnumeric(l) and len(element.kdims) == 1 else ('', '')
+        x0, x1 = (l, r) if (util.isnumeric(l) or isinstance(l, util.datetime_types)) else ('', '')
         if range_type == 'data':
             return (x0, y0, x1, y1)
 
diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py
index 5da620b85..917b4a631 100644
--- a/holoviews/plotting/mpl/chart.py
+++ b/holoviews/plotting/mpl/chart.py
@@ -936,9 +936,12 @@ class BarPlot(BarsMixin, ColorbarPlot, LegendPlot):
 
         cats = None
         style_dim = None
+        xslice = slice(None)
         if sdim:
             cats = values['stack']
             style_dim = sdim
+            stack_data = element.dimension_values(sdim)
+            xslice = stack_data == stack_data[0]
         elif cdim:
             cats = values['category']
             style_dim = cdim
@@ -954,14 +957,15 @@ class BarPlot(BarsMixin, ColorbarPlot, LegendPlot):
         is_dt = isdatetime(xvals)
         continuous = True
         if is_dt or xvals.dtype.kind not in 'OU' and not (cdim or len(element.kdims) > 1):
-            xdiff_vals = date2num(xvals) if is_dt else xvals
-            xdiff = np.abs(np.diff(xdiff_vals))
-            if len(np.unique(xdiff)) == 1:
-                # if all are same
+            xvals = xvals[xslice]
+            xdiff = np.abs(np.diff(xvals))
+            diff_size = len(np.unique(xdiff))
+            if diff_size == 0 or (diff_size == 1 and xdiff[0] == 0):
                 xdiff = 1
             else:
                 xdiff = np.min(xdiff)
-            width = (1 - self.bar_padding) * xdiff
+            width = (1 - self.bar_padding) * (date2num(xdiff) / 1000 if is_dt else xdiff)
+            data_width = (1 - self.bar_padding) * xdiff
         else:
             xdiff = len(values.get('category', [None]))
             width = (1 - self.bar_padding) / xdiff
@@ -1050,16 +1054,23 @@ class BarPlot(BarsMixin, ColorbarPlot, LegendPlot):
             axis.legend(title=title, **legend_opts)
 
         x_range = ranges[gdim.label]["data"]
-        if continuous and not is_dt:
-            if style.get('align', 'center') == 'center':
-                left_multiplier = 0.5
-                right_multiplier = 0.5
-            else:
-                left_multiplier = 0
-                right_multiplier = 1
+        if style.get('align', 'center') == 'center':
+            left_multiplier = 0.5
+            right_multiplier = 0.5
+        else:
+            left_multiplier = 0
+            right_multiplier = 1
+        if continuous:
+            ranges[gdim.label]["data"] = (
+                x_range[0] - data_width * left_multiplier,
+                x_range[1] + data_width * right_multiplier,
+            )
+        else:
+            locs = [item[0] for item in xticks]
+            xmin, xmax = min(locs), max(locs)
             ranges[gdim.label]["data"] = (
-                x_range[0] - width * left_multiplier,
-                x_range[1] + width * right_multiplier
+                - width * left_multiplier + xmin,
+                width * right_multiplier + xmax
             )
         return bars, xticks if not continuous else None, ax_dims
 


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 2f5e4f208..6f2db511b 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -30,7 +30,7 @@ concurrency:
 
 defaults:
   run:
-    shell: bash -el {0}
+    shell: bash -l {0}
 
 env:
   DISPLAY: ":99.0"
@@ -107,7 +107,7 @@ jobs:
     name: Pixi lock
     runs-on: ubuntu-latest
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_lock@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
         with:
           cache: ${{ github.event.inputs.cache == 'true' || github.event.inputs.cache == '' }}
 
@@ -120,7 +120,7 @@ jobs:
       matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
     timeout-minutes: 120
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_install@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
         if: needs.setup.outputs.code_change == 'true'
         with:
           environments: ${{ matrix.environment }}
@@ -154,7 +154,7 @@ jobs:
     env:
       PANEL_LOG_LEVEL: info
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_install@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
         if: needs.setup.outputs.code_change == 'true'
         with:
           environments: ${{ matrix.environment }}
@@ -188,7 +188,7 @@ jobs:
         environment: ["test-core"]
     timeout-minutes: 120
     steps:
-      - uses: holoviz-dev/holoviz_tasks/pixi_install@pixi
+      - uses: holoviz-dev/holoviz_tasks/pixi_install@v0
         if: needs.setup.outputs.code_change == 'true'
         with:
           environments: ${{ matrix.environment }}
diff --git a/holoviews/tests/plotting/bokeh/test_barplot.py b/holoviews/tests/plotting/bokeh/test_barplot.py
index 272b32103..777d81826 100644
--- a/holoviews/tests/plotting/bokeh/test_barplot.py
+++ b/holoviews/tests/plotting/bokeh/test_barplot.py
@@ -1,6 +1,11 @@
 import numpy as np
 import pandas as pd
-from bokeh.models import CategoricalColorMapper, LinearAxis, LinearColorMapper
+from bokeh.models import (
+    CategoricalColorMapper,
+    DatetimeAxis,
+    LinearAxis,
+    LinearColorMapper,
+)
 
 from holoviews.core.overlay import NdOverlay, Overlay
 from holoviews.element import Bars
@@ -313,6 +318,29 @@ class TestBarPlot(TestBokehPlot):
         plot = bokeh_renderer.get_plot(bars)
         np.testing.assert_almost_equal(plot.handles["glyph"].width, 69120000.0)
 
+    def test_bars_continuous_datetime_timezone_in_overlay(self):
+        # See: https://github.com/holoviz/holoviews/issues/6364
+        bars = Bars((pd.date_range("1/1/2000", periods=10, tz="UTC"), np.random.rand(10)))
+        overlay = Overlay([bars])
+        plot = bokeh_renderer.get_plot(overlay)
+        assert isinstance(plot.handles["xaxis"], DatetimeAxis)
+
+    def test_bars_continuous_datetime_stacked(self):
+        # See: https://github.com/holoviz/holoviews/issues/6288
+        data = pd.DataFrame({
+            "x": pd.to_datetime([
+                    "2017-01-01T00:00:00",
+                    "2017-01-01T00:00:00",
+                    "2017-01-01T01:00:00",
+                    "2017-01-01T01:00:00",
+                ]),
+            "cat": ["A", "B", "A", "B"],
+            "y": [1, 2, 3, 4],
+        })
+        bars = Bars(data, ["x", "cat"], ["y"]).opts(stacked=True)
+        plot = bokeh_renderer.get_plot(bars)
+        assert isinstance(plot.handles["xaxis"], DatetimeAxis)
+
     def test_bars_not_continuous_data_list(self):
         bars = Bars([("A", 1), ("B", 2), ("C", 3)])
         plot = bokeh_renderer.get_plot(bars)
@@ -356,3 +384,10 @@ class TestBarPlot(TestBokehPlot):
         )
         plot = bokeh_renderer.get_plot(bars)
         assert plot.handles["glyph"].width == 0.8
+
+    def test_bar_stacked_stack_variable_sorted(self):
+        # Check that if the stack dim is ordered
+        df = pd.DataFrame({"a": [*range(50), *range(50)], "b": sorted("ab" * 50), "c": range(100)})
+        bars = Bars(df, kdims=["a", "b"], vdims=["c"]).opts(stacked=True)
+        plot = bokeh_renderer.get_plot(bars)
+        assert plot.handles["glyph"].width == 0.8
diff --git a/holoviews/tests/plotting/matplotlib/test_barplot.py b/holoviews/tests/plotting/matplotlib/test_barplot.py
index 5e4f85cd1..7b9d66f50 100644
--- a/holoviews/tests/plotting/matplotlib/test_barplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_barplot.py
@@ -45,7 +45,7 @@ class TestBarPlot(LoggingComparisonTestCase, TestMPLPlot):
         bars = Bars([("A", 1), ("B", 2), ("C", 3)])
         plot = mpl_renderer.get_plot(bars)
         ax = plot.handles["axis"]
-        np.testing.assert_almost_equal(ax.get_xlim(), (-0.54, 2.54))
+        np.testing.assert_almost_equal(ax.get_xlim(), (-0.4, 2.4))
         assert ax.patches[0].get_width() == 0.8
         np.testing.assert_equal(ax.get_xticks(), [0, 1, 2])
         np.testing.assert_equal(
@@ -69,7 +69,7 @@ class TestBarPlot(LoggingComparisonTestCase, TestMPLPlot):
         plot = mpl_renderer.get_plot(bars)
         ax = plot.handles["axis"]
 
-        np.testing.assert_almost_equal(ax.get_xlim(), (-0.3233333, 3.8566667))
+        np.testing.assert_almost_equal(ax.get_xlim(), (-0.1333333,  3.6666667))
         assert ax.patches[0].get_width() == 0.26666666666666666
         ticklabels = ax.get_xticklabels()
         expected = [
@@ -113,7 +113,7 @@ class TestBarPlot(LoggingComparisonTestCase, TestMPLPlot):
         plot = mpl_renderer.get_plot(bars)
         ax = plot.handles["axis"]
 
-        np.testing.assert_almost_equal(ax.get_xlim(), (-0.59, 3.59))
+        np.testing.assert_almost_equal(ax.get_xlim(), (-0.4, 3.4))
         assert ax.patches[0].get_width() == 0.8
         ticklabels = ax.get_xticklabels()
         expected = [
@@ -136,7 +136,7 @@ class TestBarPlot(LoggingComparisonTestCase, TestMPLPlot):
         plot = mpl_renderer.get_plot(bars)
         ax = plot.handles["axis"]
 
-        np.testing.assert_almost_equal(ax.get_xlim(), (-0.34,  2.74))
+        np.testing.assert_almost_equal(ax.get_xlim(), (-0.2,  2.6))
         assert ax.patches[0].get_width() == 0.4
         assert len(ax.get_xticks()) > 3