Repository: holoviz/holoviews
Pull request #6497 (2025-01-27 09:45:24)

enh(dev): Don't force holoviews to be installed for test-example and docs-build
Right now, if we want to `pixi run test-example` and `pixi run docs-build`, it will fail because 'HoloViews' is not installed, and for new developers give a weird error message like `holoviews is not installed`. It is stated in the docs that you need to run `pixi run install` before doing anything, but people forget or don't read the docs. 

I can't currently think of a reason why this should be done, as holoviews is doing anything weird as installing/downloading doing the build step.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/holoviz/holoviews/pull/6497?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 88.78%. Comparing base [(`d9861ae`)](https://app.codecov.io/gh/holoviz/holoviews/commit/d9861ae9279d4be1b762878e8af82c8a7d2709ce?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) to head [(`58ed503`)](https://app.codecov.io/gh/holoviz/holoviews/commit/58ed5034b6acb77fdec992d03bab5cbc58ca050b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #6497      +/-   ##
==========================================
- Coverage   88.79%   88.78%   -0.01%     
==========================================
  Files         323      323              
  Lines       68731    68731              
==========================================
- Hits        61028    61026       -2     
- Misses       7703     7705       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/holoviz/holoviews/pull/6497?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz).

hoxbro: > Wondering if prefixing the commands (pytest ..., nbsite ...) with python -m wouldn't do the trick?

I tested it locally, and it did not do the trick. 

> FWIW I've done it a bit differently in hvPlot with the following as I prefer to make sure the package is installed.

I like the idea behind the `install-dev` command; I would maybe call it something like `setup-dev`, as I don't think it only needs to install stuff, and some would argue that downloading data is already more than what the name suggests.  

I don't like `docs-build-dev` mainly because it can be confusing to have two so similar tasks. For example, should I use `docs-build` or `docs-build-dev`? Oh, `docs-build` did not work at first, and `docs-build-dev` did. I would rather see 100% commitment to ensure the package is installed, so there only is `docs-build`, which does what `docs-build-dev` currently does.  Though, this discussion is out of scope for this PR :upside_down_face: 
maximlt: > I tested it locally, and it did not do the trick.

Ah ok that's a little disappointing 🙃 

> I like the idea behind the `install-dev` command; I would maybe call it something like `setup-dev`, as I don't think it only needs to install stuff, and some would argue that downloading data is already more than what the name suggests.

Yes `setup-dev` is better.

> I don't like `docs-build-dev` mainly because it can be confusing to have two so similar tasks. For example, should I use `docs-build` or `docs-build-dev`? Oh, `docs-build` did not work at first, and `docs-build-dev` did. I would rather see 100% commitment to ensure the package is installed, so there only is `docs-build`, which does what `docs-build-dev` currently does. Though, this discussion is out of scope for this PR 🙃

Yes also fine with having `docs-build` always install the package before building the docs. In my opinion this is just a workaround for now until hopefully `pixi` gains better support for developing a Python package.

Patch:
diff --git a/.github/workflows/nightly_lock.yaml b/.github/workflows/nightly_lock.yaml
index 00e670c88..c701d67a2 100644
--- a/.github/workflows/nightly_lock.yaml
+++ b/.github/workflows/nightly_lock.yaml
@@ -9,13 +9,13 @@ env:
 
 jobs:
   pixi_lock:
+    if: ${{ !github.event.repository.fork }}
     name: Pixi lock
     runs-on: ubuntu-latest
     timeout-minutes: 5
     steps:
       - uses: holoviz-dev/holoviz_tasks/pixi_lock@v0
       - name: Upload lock-file to S3
-        if: "!github.event.pull_request.head.repo.fork"
         env:
           AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
           AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
diff --git a/doc/conf.py b/doc/conf.py
index 782087293..6391f8604 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -13,6 +13,11 @@ authors = 'HoloViz developers'
 copyright = '2005 ' + authors
 description = 'Stop plotting your data - annotate your data and let it visualize itself.'
 
+# Setting this to not error out if no install is done
+root_path = os.path.dirname(os.path.dirname(__file__))
+sys.path.insert(0, root_path)
+os.environ["PYTHONPATH"] = root_path
+
 import holoviews
 version = release = base_version(holoviews.__version__)
 
diff --git a/doc/developer_guide/index.md b/doc/developer_guide/index.md
index 50f0517e6..4fec2d9ab 100644
--- a/doc/developer_guide/index.md
+++ b/doc/developer_guide/index.md
@@ -95,6 +95,14 @@ pixi run download-data
 
 All available tasks can be found by running `pixi task list`, the following sections will give a brief introduction to the most common tasks.
 
+For setting up a complete development you can run:
+
+```bash
+pixi run setup-dev
+```
+
+This will run the `install` and `download-data` tasks, among other tasks deemed necessary for a development environment.
+
 ### Syncing Git tags with upstream repository
 
 If you are working from a forked repository of HoloViews, you will need to sync the tags with the upstream repo.
diff --git a/examples/user_guide/Deploying_Bokeh_Apps.ipynb b/examples/user_guide/Deploying_Bokeh_Apps.ipynb
index 6b8bdfd6c..dbf3acec3 100644
--- a/examples/user_guide/Deploying_Bokeh_Apps.ipynb
+++ b/examples/user_guide/Deploying_Bokeh_Apps.ipynb
@@ -428,16 +428,11 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "We can achieve the equivalent using the `.show` method on a Panel object:"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "server = pn.panel(dmap).show()"
+    "We can achieve the equivalent using the `.show` method on a Panel object:\n",
+    "\n",
+    "``` python\n",
+    "server = pn.panel(dmap).show()\n",
+    "```"
    ]
   },
   {
@@ -447,22 +442,6 @@
     "<img width='80%' src=\"https://assets.holoviews.org/gifs/guides/user_guide/Deploying_Bokeh_Apps/bokeh_server_new_window.png\"></img>"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "We will once again stop this Server before continuing:"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "server.stop()"
-   ]
-  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -514,9 +493,7 @@
     "    return hv.Curve((xs, np.sin(xs+phase))).opts(width=800)\n",
     "\n",
     "counter = hv.streams.Counter()\n",
-    "hv.DynamicMap(sine, streams=[counter])\n",
-    "\n",
-    "dmap"
+    "hv.DynamicMap(sine, streams=[counter])"
    ]
   },
   {
@@ -888,5 +865,5 @@
   }
  },
  "nbformat": 4,
- "nbformat_minor": 2
+ "nbformat_minor": 4
 }
diff --git a/holoviews/element/util.py b/holoviews/element/util.py
index 130be0c0c..bde652c71 100644
--- a/holoviews/element/util.py
+++ b/holoviews/element/util.py
@@ -212,7 +212,10 @@ class categorical_aggregate2d(Operation):
         groupby_kwargs = {"sort": False}
         if PANDAS_GE_2_1_0:
             groupby_kwargs["observed"] = False
-        df = obj.data.set_index(index_cols).groupby(index_cols, **groupby_kwargs).first()
+        df = obj.data
+        if not all(c in df.index.names for c in index_cols):
+            df = df.set_index(index_cols)
+        df = df.groupby(index_cols, **groupby_kwargs).first()
         label = 'unique' if len(df) == len(obj) else 'non-unique'
         levels = self._get_coords(obj)
         index = pd.MultiIndex.from_product(levels, names=df.index.names)
diff --git a/holoviews/plotting/plotly/annotation.py b/holoviews/plotting/plotly/annotation.py
index 0b330b7ad..9e579a00d 100644
--- a/holoviews/plotting/plotly/annotation.py
+++ b/holoviews/plotting/plotly/annotation.py
@@ -2,6 +2,7 @@ import param
 
 from ...element import Tiles
 from .chart import ScatterPlot
+from .util import PLOTLY_SCATTERMAP
 
 
 class LabelPlot(ScatterPlot):
@@ -21,7 +22,7 @@ class LabelPlot(ScatterPlot):
     @classmethod
     def trace_kwargs(cls, is_geo=False, **kwargs):
         if is_geo:
-            return {'type': 'scattermapbox', 'mode': 'text'}
+            return {'type': PLOTLY_SCATTERMAP, 'mode': 'text'}
         else:
             return {'type': 'scatter', 'mode': 'text'}
 
diff --git a/holoviews/plotting/plotly/callbacks.py b/holoviews/plotting/plotly/callbacks.py
index 56e0c0d67..5f38f24c4 100644
--- a/holoviews/plotting/plotly/callbacks.py
+++ b/holoviews/plotting/plotly/callbacks.py
@@ -12,7 +12,7 @@ from ...streams import (
     SelectionXY,
     Stream,
 )
-from .util import _trace_to_subplot
+from .util import PLOTLY_MAP, PLOTLY_SCATTERMAP, _trace_to_subplot
 
 
 class PlotlyCallbackMetaClass(type):
@@ -172,10 +172,10 @@ class BoundsCallback(PlotlyCallback):
             trace_type = trace.get('type', 'scatter')
             trace_uid = trace.get('uid', None)
 
-            if _trace_to_subplot.get(trace_type, None) != ['mapbox']:
+            if _trace_to_subplot.get(trace_type, None) != [PLOTLY_MAP]:
                 continue
 
-            mapbox_ref = trace.get('subplot', 'mapbox')
+            mapbox_ref = trace.get('subplot', PLOTLY_MAP)
             if mapbox_ref in range_data:
                 lon_bounds = [range_data[mapbox_ref][0][0], range_data[mapbox_ref][1][0]]
                 lat_bounds = [range_data[mapbox_ref][0][1], range_data[mapbox_ref][1][1]]
@@ -268,13 +268,13 @@ class RangeCallback(PlotlyCallback):
         # Process traces
         event_data = {}
         for trace in traces:
-            trace_type = trace.get('type', 'scattermapbox')
+            trace_type = trace.get('type', PLOTLY_SCATTERMAP)
             trace_uid = trace.get('uid', None)
 
-            if _trace_to_subplot.get(trace_type, None) != ['mapbox']:
+            if _trace_to_subplot.get(trace_type, None) != [PLOTLY_MAP]:
                 continue
 
-            subplot_id = trace.get("subplot", "mapbox")
+            subplot_id = trace.get("subplot", PLOTLY_MAP)
             derived_prop = subplot_id + "._derived"
 
             if not property_value:
diff --git a/holoviews/plotting/plotly/chart.py b/holoviews/plotting/plotly/chart.py
index 89493c7ff..28e536bc4 100644
--- a/holoviews/plotting/plotly/chart.py
+++ b/holoviews/plotting/plotly/chart.py
@@ -6,6 +6,7 @@ from ...operation import interpolate_curve
 from ..mixins import AreaMixin, BarsMixin
 from .element import ColorbarPlot, ElementPlot
 from .selection import PlotlyOverlaySelectionDisplay
+from .util import PLOTLY_SCATTERMAP
 
 
 class ChartPlot(ElementPlot):
@@ -59,7 +60,7 @@ class ScatterPlot(ChartPlot, ColorbarPlot):
     @classmethod
     def trace_kwargs(cls, is_geo=False, **kwargs):
         if is_geo:
-            return {'type': 'scattermapbox', 'mode': 'markers'}
+            return {'type': PLOTLY_SCATTERMAP, 'mode': 'markers'}
         else:
             return {'type': 'scatter', 'mode': 'markers'}
 
@@ -101,7 +102,7 @@ class CurvePlot(ChartPlot, ColorbarPlot):
     @classmethod
     def trace_kwargs(cls, is_geo=False, **kwargs):
         if is_geo:
-            return {'type': 'scattermapbox', 'mode': 'lines'}
+            return {'type': PLOTLY_SCATTERMAP, 'mode': 'lines'}
         else:
             return {'type': 'scatter', 'mode': 'lines'}
 
diff --git a/holoviews/plotting/plotly/element.py b/holoviews/plotting/plotly/element.py
index b2aca8dac..d70191f36 100644
--- a/holoviews/plotting/plotly/element.py
+++ b/holoviews/plotting/plotly/element.py
@@ -14,6 +14,7 @@ from ..plot import GenericElementPlot, GenericOverlayPlot
 from ..util import dim_range_key
 from .plot import PlotlyPlot
 from .util import (
+    PLOTLY_MAP,
     STYLE_ALIASES,
     get_colorscale,
     legend_trace_types,
@@ -208,8 +209,8 @@ class ElementPlot(PlotlyPlot, GenericElementPlot):
                 components[k].extend(datum_components.get(k, []))
 
             # Handle mapbox
-            if "mapbox" in datum_components:
-                components["mapbox"] = datum_components["mapbox"]
+            if PLOTLY_MAP in datum_components:
+                components[PLOTLY_MAP] = datum_components[PLOTLY_MAP]
 
         self.handles['components'] = components
 
@@ -219,8 +220,8 @@ class ElementPlot(PlotlyPlot, GenericElementPlot):
             layout.setdefault(k, [])
             layout[k].extend(components.get(k, []))
 
-        if "mapbox" in components:
-            merge_layout(layout.setdefault("mapbox", {}), components["mapbox"])
+        if PLOTLY_MAP in components:
+            merge_layout(layout.setdefault(PLOTLY_MAP, {}), components[PLOTLY_MAP])
 
         self.handles['layout'] = layout
 
@@ -534,7 +535,7 @@ class ElementPlot(PlotlyPlot, GenericElementPlot):
                     max_y_zoom = (np.log2(max_delta / y_delta) -
                                 np.log2(mapbox_tile_size / viewport_height))
                 mapbox["zoom"] = min(max_x_zoom, max_y_zoom)
-            layout["mapbox"] = mapbox
+            layout[PLOTLY_MAP] = mapbox
 
         if isinstance(self.projection, str) and self.projection == '3d':
             scene = dict(xaxis=xaxis, yaxis=yaxis)
diff --git a/holoviews/plotting/plotly/images.py b/holoviews/plotting/plotly/images.py
index e0eaf3808..27cb575da 100644
--- a/holoviews/plotting/plotly/images.py
+++ b/holoviews/plotting/plotly/images.py
@@ -5,6 +5,7 @@ from ...core.util import VersionError
 from ...element import Tiles
 from .element import ElementPlot
 from .selection import PlotlyOverlaySelectionDisplay
+from .util import PLOTLY_MAP, PLOTLY_SCATTERMAP
 
 
 class RGBPlot(ElementPlot):
@@ -21,14 +22,13 @@ class RGBPlot(ElementPlot):
         if is_geo:
             layer = dict(datum, **options)
             dummy_trace = {
-                'type': 'scattermapbox',
+                'type': PLOTLY_SCATTERMAP,
                 'lat': [None],
                 'lon': [None],
                 'mode': 'markers',
                 'showlegend': False
             }
-            return dict(mapbox=dict(layers=[layer]),
-                        traces=[dummy_trace])
+            return {PLOTLY_MAP: dict(layers=[layer]), 'traces': [dummy_trace]}
         else:
             image = dict(datum, **options)
             # Create a dummy invisible scatter trace for this image.
diff --git a/holoviews/plotting/plotly/renderer.py b/holoviews/plotting/plotly/renderer.py
index 605af7273..3f4630041 100644
--- a/holoviews/plotting/plotly/renderer.py
+++ b/holoviews/plotting/plotly/renderer.py
@@ -9,7 +9,11 @@ from ...core import HoloMap
 from ...core.options import Store
 from ..renderer import HTML_TAGS, MIME_TYPES, Renderer
 from .callbacks import callbacks
-from .util import clean_internal_figure_properties
+from .util import (
+    PLOTLY_GE_6_0_0,
+    _convert_numpy_in_fig_dict,
+    clean_internal_figure_properties,
+)
 
 with param.logging_level('CRITICAL'):
     import plotly.graph_objs as go
@@ -67,7 +71,7 @@ class PlotlyRenderer(Renderer):
     _render_with_panel = True
 
     @bothmethod
-    def get_plot_state(self_or_cls, obj, doc=None, renderer=None, **kwargs):
+    def get_plot_state(self_or_cls, obj, doc=None, renderer=None, numpy_convert=False, **kwargs):
         """
         Given a HoloViews Viewable return a corresponding figure dictionary.
         Allows cleaning the dictionary of any internal properties that were added
@@ -85,6 +89,10 @@ class PlotlyRenderer(Renderer):
 
         # Remove template
         fig_dict.get('layout', {}).pop('template', None)
+
+        if numpy_convert and PLOTLY_GE_6_0_0:
+            return _convert_numpy_in_fig_dict(fig_dict)
+
         return fig_dict
 
     def _figure_data(self, plot, fmt, as_script=False, **kwargs):
diff --git a/holoviews/plotting/plotly/shapes.py b/holoviews/plotting/plotly/shapes.py
index f4e7a5370..91c1837c3 100644
--- a/holoviews/plotting/plotly/shapes.py
+++ b/holoviews/plotting/plotly/shapes.py
@@ -4,6 +4,7 @@ import param
 from ...element import HLine, HSpan, Tiles, VLine, VSpan
 from ..mixins import GeomMixin
 from .element import ElementPlot
+from .util import PLOTLY_SCATTERMAP
 
 
 class ShapePlot(ElementPlot):
@@ -17,7 +18,7 @@ class ShapePlot(ElementPlot):
     def init_graph(self, datum, options, index=0, is_geo=False, **kwargs):
         if is_geo:
             trace = {
-                'type': 'scattermapbox',
+                'type': PLOTLY_SCATTERMAP,
                 'mode': 'lines',
                 'showlegend': False,
                 'hoverinfo': 'skip',
diff --git a/holoviews/plotting/plotly/tiles.py b/holoviews/plotting/plotly/tiles.py
index 88d0cebbb..b5abefd76 100644
--- a/holoviews/plotting/plotly/tiles.py
+++ b/holoviews/plotting/plotly/tiles.py
@@ -2,28 +2,48 @@ import numpy as np
 
 from holoviews.element.tiles import _ATTRIBUTIONS
 from holoviews.plotting.plotly import ElementPlot
-from holoviews.plotting.plotly.util import STYLE_ALIASES
+from holoviews.plotting.plotly.util import (
+    PLOTLY_GE_6_0_0,
+    PLOTLY_MAP,
+    PLOTLY_SCATTERMAP,
+    STYLE_ALIASES,
+)
+from holoviews.util.warnings import warn
 
 
 class TilePlot(ElementPlot):
-    style_opts = ['min_zoom', 'max_zoom', "alpha", "accesstoken", "mapboxstyle"]
+    style_opts = [
+        "min_zoom",
+        "max_zoom",
+        "alpha",
+        "mapstyle",
+        # Only needed for Plotly <6
+        "accesstoken",
+        "mapboxstyle",
+    ]
 
     _supports_geo = True
 
     @classmethod
     def trace_kwargs(cls, **kwargs):
-        return {'type': 'scattermapbox'}
+        return {'type': PLOTLY_SCATTERMAP}
 
     def get_data(self, element, ranges, style, **kwargs):
         return [{
-            "type": "scattermapbox", "lat": [], "lon": [], "subplot": "mapbox",
+            "type": PLOTLY_SCATTERMAP, "lat": [], "lon": [], "subplot": PLOTLY_MAP,
             "showlegend": False,
         }]
 
     def graph_options(self, element, ranges, style, **kwargs):
         style = dict(style)
+        if PLOTLY_GE_6_0_0 and (mapboxstyle := style.pop("mapboxstyle", None)):
+            warn("'mapboxstyle' no longer supported with Plotly 6.0 use 'mapstyle' instead", category=UserWarning)
+            style["mapstyle"] = mapboxstyle
+        if PLOTLY_GE_6_0_0 and style.pop("accesstoken", None):
+            warn("'accesstoken' no longer needed with Plotly 6.0", category=UserWarning)
+
         opts = dict(
-            style=style.pop("mapboxstyle", "white-bg"),
+            style=style.pop("mapstyle", "white-bg"),
             accesstoken=style.pop("accesstoken", None),
         )
         # Extract URL and lower case wildcard characters for mapbox
@@ -65,7 +85,7 @@ class TilePlot(ElementPlot):
         return extents
 
     def init_graph(self, datum, options, index=0, **kwargs):
-        return {'traces': [datum], "mapbox": options}
+        return {'traces': [datum], PLOTLY_MAP: options}
 
     def generate_plot(self, key, ranges, element=None, is_geo=False):
         """
diff --git a/holoviews/plotting/plotly/util.py b/holoviews/plotting/plotly/util.py
index 96b19050c..20538647c 100644
--- a/holoviews/plotting/plotly/util.py
+++ b/holoviews/plotting/plotly/util.py
@@ -1,12 +1,19 @@
+import base64
 import copy
 import re
 
 import numpy as np
-from plotly import colors
+from packaging.version import Version
+from plotly import __version__, colors
 
 from ...core.util import isfinite, max_range
 from ..util import color_intervals, process_cmap
 
+PLOTLY_VERSION = Version(__version__).release
+PLOTLY_GE_6_0_0 = PLOTLY_VERSION >= (6, 0, 0)
+PLOTLY_SCATTERMAP = "scattermap" if PLOTLY_GE_6_0_0 else "scattermapbox"
+PLOTLY_MAP = "map" if PLOTLY_GE_6_0_0 else "mapbox"
+
 # Constants
 # ---------
 
@@ -23,7 +30,7 @@ _domain_trace_types = {'parcoords', 'pie', 'table', 'sankey', 'parcats'}
 # Each of these subplot types has a `domain` property with `x`/`y` properties.
 # Note that this set does not contain `xaxis`/`yaxis` because these behave a
 # little differently.
-_subplot_types = {'scene', 'geo', 'polar', 'ternary', 'mapbox'}
+_subplot_types = {'scene', 'geo', 'polar', 'ternary', PLOTLY_MAP}
 
 # For most subplot types, a trace is associated with a particular subplot
 # using a trace property with a name that matches the subplot type. For
@@ -34,7 +41,7 @@ _subplot_types = {'scene', 'geo', 'polar', 'ternary', 'mapbox'}
 # the trace property is just named `subplot`.  For example setting
 # the `scatterpolar.subplot` property to `polar3` associates the scatterpolar
 # trace with the third polar subplot in the figure
-_subplot_prop_named_subplot = {'polar', 'ternary', 'mapbox'}
+_subplot_prop_named_subplot = {'polar', 'ternary', PLOTLY_MAP}
 
 # Mapping from trace type to subplot type(s).
 _trace_to_subplot = {
@@ -77,7 +84,7 @@ _trace_to_subplot = {
     'scatterternary': ['ternary'],
 
     # mapbox
-    'scattermapbox': ['mapbox']
+    PLOTLY_SCATTERMAP: [PLOTLY_MAP],
 }
 
 # trace types that support legends
@@ -97,7 +104,7 @@ legend_trace_types = {
     'scattergl',
     'splom',
     'pointcloud',
-    'scattermapbox',
+    PLOTLY_SCATTERMAP,
     'scattercarpet',
     'contourcarpet',
     'ohlc',
@@ -911,3 +918,17 @@ def clean_internal_figure_properties(fig):
         elif isinstance(val, (list, tuple)) and val and isinstance(val[0], dict):
             for el in val:
                 clean_internal_figure_properties(el)
+
+
+def _convert_numpy_in_fig_dict(fig_dict):
+    if isinstance(fig_dict, dict):
+        if fig_dict.keys() == {"dtype", "bdata"}:
+            return np.frombuffer(base64.b64decode(fig_dict["bdata"]), dtype=fig_dict["dtype"])
+        elif fig_dict.keys() == {"dtype", "bdata", "shape"}:
+            shape = list(map(int, fig_dict["shape"].split(",")))
+            return np.frombuffer(base64.b64decode(fig_dict["bdata"]), dtype=fig_dict["dtype"]).reshape(shape)
+        return {key: _convert_numpy_in_fig_dict(value) for key, value in fig_dict.items()}
+    elif isinstance(fig_dict, list):
+        return [_convert_numpy_in_fig_dict(item) for item in fig_dict]
+    else:
+        return fig_dict
diff --git a/pixi.toml b/pixi.toml
index 11100ac9f..0e39cb589 100644
--- a/pixi.toml
+++ b/pixi.toml
@@ -32,6 +32,10 @@ no-default-feature = true
 features = ["required", "py312", "optional", "test-core", "test-example", "test-unit-task"]
 no-default-feature = true
 
+[environments.test-313]
+features = ["required", "py313", "optional", "test-core", "test-example", "test-unit-task"]
+no-default-feature = true
+
 [environments.test-ui]
 features = ["required", "py312", "optional", "test-core", "test-ui"]
 no-default-feature = true
@@ -141,6 +145,7 @@ setuptools_scm = "*"
 
 [feature.dev.tasks]
 lab = 'jupyter lab'
+setup-dev = { depends-on = ["install", "download-data", "lint-install"] }
 
 # =============================================
 # =================== TESTS ===================


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 9faaa2ac4..c92632047 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -83,7 +83,7 @@ jobs:
         run: |
           MATRIX=$(jq -nsc '{
               "os": ["ubuntu-latest", "macos-latest", "windows-latest"],
-              "environment": ["test-39", "test-312"]
+              "environment": ["test-39", "test-313"]
           }')
           echo "MATRIX=$MATRIX" >> $GITHUB_ENV
       - name: Set test matrix with 'full' option
@@ -91,7 +91,7 @@ jobs:
         run: |
           MATRIX=$(jq -nsc '{
               "os": ["ubuntu-latest", "macos-latest", "windows-latest"],
-              "environment": ["test-39", "test-310", "test-311", "test-312"]
+              "environment": ["test-39", "test-310", "test-311", "test-312", "test-313"]
           }')
           echo "MATRIX=$MATRIX" >> $GITHUB_ENV
       - name: Set test matrix with 'downstream' option
diff --git a/examples/conftest.py b/examples/conftest.py
index 9341fbe42..3546847c8 100644
--- a/examples/conftest.py
+++ b/examples/conftest.py
@@ -7,15 +7,13 @@ import bokeh
 import pandas as pd
 from packaging.version import Version
 
+# Setting this to not error out if no install is done.
+os.environ["PYTHONPATH"] = os.path.dirname(os.path.dirname(__file__))
+
 system = platform.system()
 py_version = sys.version_info[:2]
 PANDAS_GE_2_0_0 = Version(pd.__version__).release >= (2, 0, 0)
 
-# Having "OMP_NUM_THREADS"=1, set as an environment variable, can be needed
-# to avoid crashing when running tests with pytest-xdist on Windows.
-# This is set in the .github/workflows/test.yaml file.
-# https://github.com/holoviz/holoviews/pull/5720
-
 collect_ignore_glob = [
     # Needs selenium, phantomjs, firefox, and geckodriver to save a png picture
     "user_guide/Plotting_with_Bokeh.ipynb",
diff --git a/holoviews/tests/core/data/test_ibisinterface.py b/holoviews/tests/core/data/test_ibisinterface.py
index 65f66648a..67ec2d3ce 100644
--- a/holoviews/tests/core/data/test_ibisinterface.py
+++ b/holoviews/tests/core/data/test_ibisinterface.py
@@ -1,10 +1,16 @@
 import sqlite3
+import warnings
 from tempfile import NamedTemporaryFile
 from unittest import SkipTest
 
 try:
     import ibis
-    from ibis import sqlite
+    # Getting this Warnings on Python 3.13 and Ibis 9.5
+    # DeprecationWarning: Attribute.__init__ missing 1 required positional argument: 'value'.
+    # This will become an error in Python 3.15.
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore")
+        from ibis import sqlite
 except ImportError:
     raise SkipTest("Could not import ibis, skipping IbisInterface tests.")
 
diff --git a/holoviews/tests/plotting/bokeh/test_heatmapplot.py b/holoviews/tests/plotting/bokeh/test_heatmapplot.py
index 67f315692..bbd820fb8 100644
--- a/holoviews/tests/plotting/bokeh/test_heatmapplot.py
+++ b/holoviews/tests/plotting/bokeh/test_heatmapplot.py
@@ -156,3 +156,15 @@ class TestHeatMapPlot(TestBokehPlot):
         np.testing.assert_array_equal(data["X"], df["X"])
         np.testing.assert_array_equal(data["Y"], df["Y"])
         np.testing.assert_array_equal(data["zvalues"], df["Z"])
+
+    def test_heatmap_pandas_multiindex(self):
+        df = pd.DataFrame(
+            data={'C': [5, 2, -1, 5]},
+            index=pd.MultiIndex.from_product([(0, 1), (0, 1)], names=['A', 'B']),
+        )
+        hm = HeatMap(df, ['A', 'B'], 'C')
+        plot = bokeh_renderer.get_plot(hm)
+        data = plot.handles["cds"].data
+        np.testing.assert_array_equal(data['A'], df.index.get_level_values('A'))
+        np.testing.assert_array_equal(data['B'], df.index.get_level_values('B'))
+        np.testing.assert_array_equal(data['zvalues'], df['C'])
diff --git a/holoviews/tests/plotting/plotly/test_callbacks.py b/holoviews/tests/plotting/plotly/test_callbacks.py
index f760dca65..96b4833ca 100644
--- a/holoviews/tests/plotting/plotly/test_callbacks.py
+++ b/holoviews/tests/plotting/plotly/test_callbacks.py
@@ -14,6 +14,7 @@ from holoviews.plotting.plotly.callbacks import (
     RangeYCallback,
     Selection1DCallback,
 )
+from holoviews.plotting.plotly.util import PLOTLY_MAP, PLOTLY_SCATTERMAP
 from holoviews.streams import (
     BoundsX,
     BoundsXY,
@@ -95,9 +96,9 @@ class TestCallbacks(TestCase):
 
         self.mapbox_fig_dict = go.Figure({
             'data': [
-                {'type': 'scattermapbox', 'uid': 'first', 'subplot': 'mapbox'},
-                {'type': 'scattermapbox', 'uid': 'second', 'subplot': 'mapbox2'},
-                {'type': 'scattermapbox', 'uid': 'third', 'subplot': 'mapbox3'}
+                {'type': PLOTLY_SCATTERMAP, 'uid': 'first', 'subplot': PLOTLY_MAP},
+                {'type': PLOTLY_SCATTERMAP, 'uid': 'second', 'subplot': PLOTLY_MAP + '2'},
+                {'type': PLOTLY_SCATTERMAP, 'uid': 'third', 'subplot': PLOTLY_MAP + '3'}
             ],
             'layout': {
                 'title': {'text': 'Figure Title'},
@@ -211,8 +212,8 @@ class TestCallbacks(TestCase):
 
     def testMapboxRangeXYCallbackEventData(self):
         relayout_data = {
-            'mapbox._derived': {"coordinates": self.mapbox_coords1},
-            'mapbox3._derived': {"coordinates": self.mapbox_coords2}
+            f'{PLOTLY_MAP}._derived': {"coordinates": self.mapbox_coords1},
+            f'{PLOTLY_MAP}3._derived': {"coordinates": self.mapbox_coords2}
         }
 
         event_data = RangeXYCallback.get_event_data_from_property_update(
@@ -226,8 +227,8 @@ class TestCallbacks(TestCase):
 
     def testMapboxRangeXCallbackEventData(self):
         relayout_data = {
-            'mapbox._derived': {"coordinates": self.mapbox_coords1},
-            'mapbox3._derived': {"coordinates": self.mapbox_coords2}
+            f'{PLOTLY_MAP}._derived': {"coordinates": self.mapbox_coords1},
+            f'{PLOTLY_MAP}3._derived': {"coordinates": self.mapbox_coords2}
         }
 
         event_data = RangeXCallback.get_event_data_from_property_update(
@@ -241,8 +242,8 @@ class TestCallbacks(TestCase):
 
     def testMapboxRangeYCallbackEventData(self):
         relayout_data = {
-            'mapbox._derived': {"coordinates": self.mapbox_coords1},
-            'mapbox3._derived': {"coordinates": self.mapbox_coords2}
+            f'{PLOTLY_MAP}._derived': {"coordinates": self.mapbox_coords1},
+            f'{PLOTLY_MAP}3._derived': {"coordinates": self.mapbox_coords2}
         }
 
         event_data = RangeYCallback.get_event_data_from_property_update(
@@ -387,7 +388,7 @@ class TestCallbacks(TestCase):
         })
 
     def testMapboxBoundsXYCallbackEventData(self):
-        selected_data = {"range": {'mapbox2': [
+        selected_data = {"range": {f'{PLOTLY_MAP}2': [
             [self.lon_range1[0], self.lat_range1[0]],
             [self.lon_range1[1], self.lat_range1[1]]
         ]}}
@@ -406,7 +407,7 @@ class TestCallbacks(TestCase):
         })
 
     def testMapboxBoundsXCallbackEventData(self):
-        selected_data = {"range": {'mapbox': [
+        selected_data = {"range": {f'{PLOTLY_MAP}': [
             [self.lon_range1[0], self.lat_range1[0]],
             [self.lon_range1[1], self.lat_range1[1]]
         ]}}
@@ -424,7 +425,7 @@ class TestCallbacks(TestCase):
         })
 
     def testMapboxBoundsYCallbackEventData(self):
-        selected_data = {"range": {'mapbox3': [
+        selected_data = {"range": {f'{PLOTLY_MAP}3': [
             [self.lon_range1[0], self.lat_range1[0]],
             [self.lon_range1[1], self.lat_range1[1]]
         ]}}
diff --git a/holoviews/tests/plotting/plotly/test_curveplot.py b/holoviews/tests/plotting/plotly/test_curveplot.py
index d0a7c2cc0..1020fc904 100644
--- a/holoviews/tests/plotting/plotly/test_curveplot.py
+++ b/holoviews/tests/plotting/plotly/test_curveplot.py
@@ -1,6 +1,7 @@
 import numpy as np
 
 from holoviews.element import Curve, Tiles
+from holoviews.plotting.plotly.util import PLOTLY_MAP
 
 from .test_plot import TestPlotlyPlot
 
@@ -86,7 +87,7 @@ class TestMapboxCurvePlot(TestPlotlyPlot):
         self.assertEqual(state['data'][1]['lat'], self.lats)
         self.assertEqual(state['data'][1]['mode'], 'lines')
         self.assertEqual(
-            state['layout']['mapbox']['center'], {
+            state['layout'][PLOTLY_MAP]['center'], {
                 'lat': self.lat_center, 'lon': self.lon_center
             }
         )
diff --git a/holoviews/tests/plotting/plotly/test_dynamic.py b/holoviews/tests/plotting/plotly/test_dynamic.py
index 636349d4e..1aa6ad93b 100644
--- a/holoviews/tests/plotting/plotly/test_dynamic.py
+++ b/holoviews/tests/plotting/plotly/test_dynamic.py
@@ -6,6 +6,7 @@ from bokeh.document import Document
 from pyviz_comms import Comm
 
 import holoviews as hv
+from holoviews.plotting.plotly.util import _convert_numpy_in_fig_dict
 from holoviews.streams import (
     BoundsXY,
     RangeXY,
@@ -44,7 +45,7 @@ class TestDynamicMap(TestPlotlyPlot):
         _, plotly_pane = next(iter(dmap_pane._plots.values()))
 
         # Check initial data
-        data = plotly_pane.object['data']
+        data = _convert_numpy_in_fig_dict(plotly_pane.object['data'])
         self.assertEqual(len(data), 1)
         self.assertEqual(data[0]['type'], 'scatter')
         np.testing.assert_equal(data[0]['y'], ys)
@@ -57,7 +58,7 @@ class TestDynamicMap(TestPlotlyPlot):
         scale_stream.event(scale=2.0)
 
         # Check that figure object was updated
-        data = plotly_pane.object['data']
+        data = _convert_numpy_in_fig_dict(plotly_pane.object['data'])
         np.testing.assert_equal(data[0]['y'], ys * 2.0)
 
         # Check that object callback was triggered
diff --git a/holoviews/tests/plotting/plotly/test_labelplot.py b/holoviews/tests/plotting/plotly/test_labelplot.py
index ed3adff9a..1baad8398 100644
--- a/holoviews/tests/plotting/plotly/test_labelplot.py
+++ b/holoviews/tests/plotting/plotly/test_labelplot.py
@@ -3,6 +3,7 @@ import numpy as np
 from holoviews.core.options import Cycle
 from holoviews.core.spaces import HoloMap
 from holoviews.element import Labels, Tiles
+from holoviews.plotting.plotly.util import PLOTLY_MAP
 
 from .test_plot import TestPlotlyPlot
 
@@ -86,7 +87,7 @@ class TestMapboxLabelsPlot(TestPlotlyPlot):
         self.assertEqual(state['data'][1]['text'], ['A', 'B', 'C'])
         self.assertEqual(state['data'][1]['mode'], 'text')
         self.assertEqual(
-            state['layout']['mapbox']['center'], {
+            state['layout'][PLOTLY_MAP]['center'], {
                 'lat': self.lat_center, 'lon': self.lon_center
             }
         )
diff --git a/holoviews/tests/plotting/plotly/test_plot.py b/holoviews/tests/plotting/plotly/test_plot.py
index 0f71a9952..225176db3 100644
--- a/holoviews/tests/plotting/plotly/test_plot.py
+++ b/holoviews/tests/plotting/plotly/test_plot.py
@@ -83,7 +83,7 @@ class TestPlotlyPlot(ComparisonTestCase):
             plot.padding = padding
 
     def _get_plot_state(self, element):
-        fig_dict = plotly_renderer.get_plot_state(element)
+        fig_dict = plotly_renderer.get_plot_state(element, numpy_convert=True)
         return fig_dict
 
     def assert_property_values(self, obj, props):
diff --git a/holoviews/tests/plotting/plotly/test_rgb.py b/holoviews/tests/plotting/plotly/test_rgb.py
index e6737e9bc..ac75e8d2d 100644
--- a/holoviews/tests/plotting/plotly/test_rgb.py
+++ b/holoviews/tests/plotting/plotly/test_rgb.py
@@ -3,6 +3,10 @@ import PIL.Image
 import plotly.graph_objs as go
 
 from holoviews.element import RGB, Tiles
+from holoviews.plotting.plotly.util import (
+    PLOTLY_MAP,
+    PLOTLY_SCATTERMAP,
+)
 
 from .test_plot import TestPlotlyPlot, plotly_renderer
 
@@ -276,20 +280,20 @@ class TestMapboxRGBPlot(TestPlotlyPlot):
 
         fig_dict = plotly_renderer.get_plot_state(rgb)
         # Check dummy trace
-        self.assertEqual(fig_dict["data"][1]["type"], "scattermapbox")
+        self.assertEqual(fig_dict["data"][1]["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(fig_dict["data"][1]["lon"], [None])
         self.assertEqual(fig_dict["data"][1]["lat"], [None])
         self.assertEqual(fig_dict["data"][1]["showlegend"], False)
 
         # Check mapbox subplot
-        subplot = fig_dict["layout"]["mapbox"]
+        subplot = fig_dict["layout"][PLOTLY_MAP]
         self.assertEqual(subplot["style"], "white-bg")
         self.assertEqual(
             subplot['center'], {'lat': self.lat_center, 'lon': self.lon_center}
         )
 
         # Check rgb layer
-        layers = fig_dict["layout"]["mapbox"]["layers"]
+        layers = fig_dict["layout"][PLOTLY_MAP]["layers"]
         self.assertEqual(len(layers), 1)
         rgb_layer = layers[0]
         self.assertEqual(rgb_layer["below"], "traces")
diff --git a/holoviews/tests/plotting/plotly/test_scatterplot.py b/holoviews/tests/plotting/plotly/test_scatterplot.py
index ae4be3ac0..92760c816 100644
--- a/holoviews/tests/plotting/plotly/test_scatterplot.py
+++ b/holoviews/tests/plotting/plotly/test_scatterplot.py
@@ -1,6 +1,7 @@
 import numpy as np
 
 from holoviews.element import Scatter, Tiles
+from holoviews.plotting.plotly.util import PLOTLY_MAP, PLOTLY_SCATTERMAP
 
 from .test_plot import TestPlotlyPlot
 
@@ -82,12 +83,12 @@ class TestMapboxScatterPlot(TestPlotlyPlot):
 
         scatter = Tiles('') * Scatter((xs, ys)).redim.range(x=x_range, y=y_range)
         state = self._get_plot_state(scatter)
-        self.assertEqual(state['data'][1]['type'], 'scattermapbox')
+        self.assertEqual(state['data'][1]['type'], PLOTLY_SCATTERMAP)
         self.assertEqual(state['data'][1]['lon'], lons)
         self.assertEqual(state['data'][1]['lat'], lats)
         self.assertEqual(state['data'][1]['mode'], 'markers')
         self.assertEqual(
-            state['layout']['mapbox']['center'], {'lat': lat_center, 'lon': lon_center}
+            state['layout'][PLOTLY_MAP]['center'], {'lat': lat_center, 'lon': lon_center}
         )
 
         # There xaxis and yaxis should not be in the layout
diff --git a/holoviews/tests/plotting/plotly/test_shapeplots.py b/holoviews/tests/plotting/plotly/test_shapeplots.py
index f7d1b67ca..0e2e97f12 100644
--- a/holoviews/tests/plotting/plotly/test_shapeplots.py
+++ b/holoviews/tests/plotting/plotly/test_shapeplots.py
@@ -10,6 +10,7 @@ from holoviews.element import (
     Tiles,
     VLine,
 )
+from holoviews.plotting.plotly.util import PLOTLY_MAP, PLOTLY_SCATTERMAP
 
 from .test_plot import TestPlotlyPlot
 
@@ -160,7 +161,7 @@ class TestMapboxPathShape(TestMapboxShape):
         )
 
         state = self._get_plot_state(path)
-        self.assertEqual(state["data"][1]["type"], "scattermapbox")
+        self.assertEqual(state["data"][1]["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(state["data"][1]["mode"], "lines")
         self.assertEqual(state["data"][1]["lon"], np.array([
             self.lon_range[i] for i in (0, 1, 0, 0)
@@ -171,7 +172,7 @@ class TestMapboxPathShape(TestMapboxShape):
         self.assertEqual(state["data"][1]["showlegend"], False)
         self.assertEqual(state["data"][1]["line"]["color"], default_shape_color)
         self.assertEqual(
-            state['layout']['mapbox']['center'], {
+            state['layout'][PLOTLY_MAP]['center'], {
                 'lat': self.lat_center, 'lon': self.lon_center
             }
         )
@@ -221,7 +222,7 @@ class TestMapboxBounds(TestMapboxShape):
         )
 
         state = self._get_plot_state(bounds)
-        self.assertEqual(state["data"][1]["type"], "scattermapbox")
+        self.assertEqual(state["data"][1]["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(state["data"][1]["mode"], "lines")
         self.assertEqual(state["data"][1]["lon"], np.array([
             self.lon_range[i] for i in (0, 0, 1, 1, 0)
@@ -232,7 +233,7 @@ class TestMapboxBounds(TestMapboxShape):
         self.assertEqual(state["data"][1]["showlegend"], False)
         self.assertEqual(state["data"][1]["line"]["color"], default_shape_color)
         self.assertEqual(
-            state['layout']['mapbox']['center'], {
+            state['layout'][PLOTLY_MAP]['center'], {
                 'lat': self.lat_center, 'lon': self.lon_center
             }
         )
@@ -247,10 +248,10 @@ class TestMapboxBounds(TestMapboxShape):
                   Tiles("") * bounds3 + Tiles("") * bounds4).cols(2)
 
         state = self._get_plot_state(layout)
-        self.assertEqual(state['data'][1]["subplot"], "mapbox")
-        self.assertEqual(state['data'][3]["subplot"], "mapbox2")
-        self.assertEqual(state['data'][5]["subplot"], "mapbox3")
-        self.assertEqual(state['data'][7]["subplot"], "mapbox4")
+        self.assertEqual(state['data'][1]["subplot"], PLOTLY_MAP)
+        self.assertEqual(state['data'][3]["subplot"], PLOTLY_MAP + "2")
+        self.assertEqual(state['data'][5]["subplot"], PLOTLY_MAP + "3")
+        self.assertEqual(state['data'][7]["subplot"], PLOTLY_MAP + "4")
         self.assertNotIn("xaxis", state['layout'])
         self.assertNotIn("yaxis", state['layout'])
 
@@ -299,7 +300,7 @@ class TestMapboxBox(TestMapboxShape):
         lon_box_range, lat_box_range = Tiles.easting_northing_to_lon_lat(x_box_range, y_box_range)
 
         state = self._get_plot_state(box)
-        self.assertEqual(state["data"][1]["type"], "scattermapbox")
+        self.assertEqual(state["data"][1]["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(state["data"][1]["mode"], "lines")
         self.assertEqual(state["data"][1]["showlegend"], False)
         self.assertEqual(state["data"][1]["line"]["color"], default_shape_color)
@@ -311,7 +312,7 @@ class TestMapboxBox(TestMapboxShape):
         ]))
 
         self.assertEqual(
-            state['layout']['mapbox']['center'], {
+            state['layout'][PLOTLY_MAP]['center'], {
                 'lat': self.lat_center, 'lon': self.lon_center
             }
         )
@@ -344,7 +345,7 @@ class TestMapboxRectangles(TestMapboxShape):
         )
 
         state = self._get_plot_state(rectangles)
-        self.assertEqual(state["data"][1]["type"], "scattermapbox")
+        self.assertEqual(state["data"][1]["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(state["data"][1]["mode"], "lines")
         self.assertEqual(state["data"][1]["showlegend"], False)
         self.assertEqual(state["data"][1]["line"]["color"], default_shape_color)
@@ -358,7 +359,7 @@ class TestMapboxRectangles(TestMapboxShape):
         ]))
 
         self.assertEqual(
-            state['layout']['mapbox']['center'], {
+            state['layout'][PLOTLY_MAP]['center'], {
                 'lat': self.lat_center, 'lon': self.lon_center
             }
         )
@@ -391,7 +392,7 @@ class TestMapboxSegments(TestMapboxShape):
         )
 
         state = self._get_plot_state(rectangles)
-        self.assertEqual(state["data"][1]["type"], "scattermapbox")
+        self.assertEqual(state["data"][1]["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(state["data"][1]["mode"], "lines")
         self.assertEqual(state["data"][1]["showlegend"], False)
         self.assertEqual(state["data"][1]["line"]["color"], default_shape_color)
@@ -405,7 +406,7 @@ class TestMapboxSegments(TestMapboxShape):
         ]))
 
         self.assertEqual(
-            state['layout']['mapbox']['center'], {
+            state['layout'][PLOTLY_MAP]['center'], {
                 'lat': self.lat_center, 'lon': self.lon_center
             }
         )
diff --git a/holoviews/tests/plotting/plotly/test_tiles.py b/holoviews/tests/plotting/plotly/test_tiles.py
index 8155fc8c7..302de77c2 100644
--- a/holoviews/tests/plotting/plotly/test_tiles.py
+++ b/holoviews/tests/plotting/plotly/test_tiles.py
@@ -3,6 +3,11 @@ import pytest
 
 from holoviews.element import RGB, Bounds, Points, Tiles
 from holoviews.element.tiles import _ATTRIBUTIONS, StamenTerrain
+from holoviews.plotting.plotly.util import (
+    PLOTLY_GE_6_0_0,
+    PLOTLY_MAP,
+    PLOTLY_SCATTERMAP,
+)
 
 from .test_plot import TestPlotlyPlot, plotly_renderer
 
@@ -35,13 +40,13 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
         # Check dummy trace
         self.assertEqual(len(fig_dict["data"]), 1)
         dummy_trace = fig_dict["data"][0]
-        self.assertEqual(dummy_trace["type"], "scattermapbox")
+        self.assertEqual(dummy_trace["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(dummy_trace["lon"], [])
         self.assertEqual(dummy_trace["lat"], [])
         self.assertEqual(dummy_trace["showlegend"], False)
 
         # Check mapbox subplot
-        subplot = fig_dict["layout"]["mapbox"]
+        subplot = fig_dict["layout"][PLOTLY_MAP]
         self.assertEqual(subplot["style"], "white-bg")
         self.assertEqual(
             subplot['center'], {'lat': self.lat_center, 'lon': self.lon_center}
@@ -53,20 +58,20 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
 
         # Check no layers are introduced when an empty tile server string is
         # passed
-        layers = fig_dict["layout"]["mapbox"].get("layers", [])
+        layers = fig_dict["layout"][PLOTLY_MAP].get("layers", [])
         self.assertEqual(len(layers), 0)
 
     def test_styled_mapbox_tiles(self):
-        tiles = Tiles().opts(mapboxstyle="dark", accesstoken="token-str").redim.range(
-            x=self.x_range, y=self.y_range
-        )
+        opts = dict(mapstyle="dark") if PLOTLY_GE_6_0_0 else dict(mapboxstyle="dark", accesstoken="token-str")
+        tiles = Tiles().opts(**opts).redim.range(x=self.x_range, y=self.y_range)
 
         fig_dict = plotly_renderer.get_plot_state(tiles)
 
         # Check mapbox subplot
-        subplot = fig_dict["layout"]["mapbox"]
+        subplot = fig_dict["layout"][PLOTLY_MAP]
         self.assertEqual(subplot["style"], "dark")
-        self.assertEqual(subplot["accesstoken"], "token-str")
+        if not PLOTLY_GE_6_0_0:
+            self.assertEqual(subplot["accesstoken"], "token-str")
         self.assertEqual(
             subplot['center'], {'lat': self.lat_center, 'lon': self.lon_center}
         )
@@ -81,20 +86,20 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
         # Check dummy trace
         self.assertEqual(len(fig_dict["data"]), 1)
         dummy_trace = fig_dict["data"][0]
-        self.assertEqual(dummy_trace["type"], "scattermapbox")
+        self.assertEqual(dummy_trace["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(dummy_trace["lon"], [])
         self.assertEqual(dummy_trace["lat"], [])
         self.assertEqual(dummy_trace["showlegend"], False)
 
         # Check mapbox subplot
-        subplot = fig_dict["layout"]["mapbox"]
+        subplot = fig_dict["layout"][PLOTLY_MAP]
         self.assertEqual(subplot["style"], "white-bg")
         self.assertEqual(
             subplot['center'], {'lat': self.lat_center, 'lon': self.lon_center}
         )
 
         # Check for raster layer
-        layers = fig_dict["layout"]["mapbox"].get("layers", [])
+        layers = fig_dict["layout"][PLOTLY_MAP].get("layers", [])
         self.assertEqual(len(layers), 1)
         layer = layers[0]
         self.assertEqual(layer["source"][0].lower(), tiles.data.lower())
@@ -114,7 +119,7 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
 
         fig_dict = plotly_renderer.get_plot_state(tiles)
         # Check mapbox subplot
-        layers = fig_dict["layout"]["mapbox"].get("layers", [])
+        layers = fig_dict["layout"][PLOTLY_MAP].get("layers", [])
         self.assertEqual(len(layers), 1)
         layer = layers[0]
         self.assertEqual(layer["source"][0].lower(), osm.build_url(scale_factor="@2x"))
@@ -123,7 +128,8 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
 
     def test_overlay(self):
         # Base layer is mapbox vector layer
-        tiles = Tiles("").opts(mapboxstyle="dark", accesstoken="token-str")
+        opts = dict(mapstyle="dark") if PLOTLY_GE_6_0_0 else dict(mapboxstyle="dark", accesstoken="token-str")
+        tiles = Tiles("").opts(**opts)
 
         # Raster tile layer
         stamen_raster = StamenTerrain().opts(alpha=0.7)
@@ -151,11 +157,11 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
         )
 
         # Render to plotly figure dictionary
-        fig_dict = plotly_renderer.get_plot_state(overlay)
+        fig_dict = plotly_renderer.get_plot_state(overlay, numpy_convert=True)
 
         # Check number of traces and layers
         traces = fig_dict["data"]
-        subplot = fig_dict["layout"]["mapbox"]
+        subplot = fig_dict["layout"][PLOTLY_MAP]
         layers = subplot["layers"]
 
         self.assertEqual(len(traces), 5)
@@ -163,13 +169,14 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
 
         # Check vector layer
         dummy_trace = traces[0]
-        self.assertEqual(dummy_trace["type"], "scattermapbox")
+        self.assertEqual(dummy_trace["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(dummy_trace["lon"], [])
         self.assertEqual(dummy_trace["lat"], [])
         self.assertFalse(dummy_trace["showlegend"])
 
         self.assertEqual(subplot["style"], "dark")
-        self.assertEqual(subplot["accesstoken"], "token-str")
+        if not PLOTLY_GE_6_0_0:
+            self.assertEqual(subplot["accesstoken"], "token-str")
         self.assertEqual(
             subplot['center'], {'lat': self.lat_center, 'lon': self.lon_center}
         )
@@ -177,7 +184,7 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
         # Check raster layer
         dummy_trace = traces[1]
         raster_layer = layers[0]
-        self.assertEqual(dummy_trace["type"], "scattermapbox")
+        self.assertEqual(dummy_trace["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(dummy_trace["lon"], [])
         self.assertEqual(dummy_trace["lat"], [])
         self.assertFalse(dummy_trace["showlegend"])
@@ -191,7 +198,7 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
         # Check RGB layer
         dummy_trace = traces[2]
         rgb_layer = layers[1]
-        self.assertEqual(dummy_trace["type"], "scattermapbox")
+        self.assertEqual(dummy_trace["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(dummy_trace["lon"], [None])
         self.assertEqual(dummy_trace["lat"], [None])
         self.assertFalse(dummy_trace["showlegend"])
@@ -210,7 +217,7 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
 
         # Check Points layer
         points_trace = traces[3]
-        self.assertEqual(points_trace["type"], "scattermapbox")
+        self.assertEqual(points_trace["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(points_trace["lon"], np.array([0, self.lon_range[1]]))
         self.assertEqual(points_trace["lat"], np.array([0, self.lat_range[1]]))
         self.assertEqual(points_trace["mode"], "markers")
@@ -218,7 +225,7 @@ class TestMapboxTilesPlot(TestPlotlyPlot):
 
         # Check Bounds layer
         bounds_trace = traces[4]
-        self.assertEqual(bounds_trace["type"], "scattermapbox")
+        self.assertEqual(bounds_trace["type"], PLOTLY_SCATTERMAP)
         self.assertEqual(bounds_trace["lon"], np.array([
             self.lon_range[0], self.lon_range[0], 0, 0, self.lon_range[0]
         ]))