Repository: holoviz/holoviews
Pull request #5640 (2023-03-06 11:00:34)

Merge tools in grid and layout plots
In bokeh3 the tool merging is a lot more flexible but by default it is also a lot more conservative. The basic idea behind the new approach is:

> If the properties of the tools match then merge them otherwise don't.

There is a list of properties that are excluded from this logic which covers the 'renderer' and 'overlay' properties by default. Looking at a specific example we see this:

<img width="1571" alt="Screen_Shot_2023-03-06_at_11 34 41" src="https://user-images.githubusercontent.com/1550771/223091715-789f0e31-9594-40a6-b6bf-c3b1d9f8cc05.png">

- `BoxZoomTool` does not merge because the geo plot has set `match_aspect`
- `WheelZoomTool` does not merge because the geo plot has disabled  `zoom_on_axis`  (because the axes aren't rendered)
- `HoverTool` does not merge because the `tooltips` don't match
- The box select and lasso select tools don't merge because they have different `js_property_callbacks`

With this PR applied we avoid also skip `match_aspect`, `js_property_callbacks` and `zoom_axis` and I'm also considering adding `tooltips` to the list:

<img width="1570" alt="Screen Shot 2023-03-06 at 11 59 54" src="https://user-images.githubusercontent.com/1550771/223092089-cedf54c2-d1ae-470e-a666-d1ba7020da67.png">



philippjfr: After some internal discussion I've decided the logic that makes most sense for us is to always merge tools of the same type  **unless** they define what I've called `disambiguation_properties`, which by default include `name`, `icon`, `tags`, and `description`.
codecov-commenter: # [Codecov](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) Report
> Merging [#5640](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) (5baafa1) into [main](https://codecov.io/gh/holoviz/holoviews/commit/245d5cf07117a36480256cfbdcf553b733a21aa4?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) (245d5cf) will **decrease** coverage by `0.23%`.
> The diff coverage is `25.80%`.

```diff
@@            Coverage Diff             @@
##             main    #5640      +/-   ##
==========================================
- Coverage   88.26%   88.04%   -0.23%     
==========================================
  Files         302      302              
  Lines       62517    62540      +23     
==========================================
- Hits        55181    55063     -118     
- Misses       7336     7477     +141     
```


| [Impacted Files](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) | Coverage Δ | |
|---|---|---|
| [holoviews/plotting/bokeh/util.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL3Bsb3R0aW5nL2Jva2VoL3V0aWwucHk=) | `60.28% <4.76%> (-2.84%)` | :arrow_down: |
| [holoviews/plotting/bokeh/plot.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL3Bsb3R0aW5nL2Jva2VoL3Bsb3QucHk=) | `91.33% <70.00%> (-0.66%)` | :arrow_down: |
| [holoviews/tests/plotting/bokeh/test\_tiles.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL3Rlc3RzL3Bsb3R0aW5nL2Jva2VoL3Rlc3RfdGlsZXMucHk=) | `46.15% <0.00%> (-53.85%)` | :arrow_down: |
| [holoviews/plotting/bokeh/tiles.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL3Bsb3R0aW5nL2Jva2VoL3RpbGVzLnB5) | `24.00% <0.00%> (-42.00%)` | :arrow_down: |
| [holoviews/plotting/plotly/tiles.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL3Bsb3R0aW5nL3Bsb3RseS90aWxlcy5weQ==) | `80.43% <0.00%> (-8.70%)` | :arrow_down: |
| [holoviews/tests/plotting/bokeh/test\_layoutplot.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL3Rlc3RzL3Bsb3R0aW5nL2Jva2VoL3Rlc3RfbGF5b3V0cGxvdC5weQ==) | `91.94% <0.00%> (-8.06%)` | :arrow_down: |
| [holoviews/tests/plotting/plotly/test\_tiles.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL3Rlc3RzL3Bsb3R0aW5nL3Bsb3RseS90ZXN0X3RpbGVzLnB5) | `93.07% <0.00%> (-6.93%)` | :arrow_down: |
| [holoviews/core/data/ibis.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2NvcmUvZGF0YS9pYmlzLnB5) | `76.02% <0.00%> (-4.74%)` | :arrow_down: |
| [holoviews/tests/plotting/bokeh/test\_gridplot.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL3Rlc3RzL3Bsb3R0aW5nL2Jva2VoL3Rlc3RfZ3JpZHBsb3QucHk=) | `97.80% <0.00%> (-2.20%)` | :arrow_down: |
| [holoviews/element/tiles.py](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz#diff-aG9sb3ZpZXdzL2VsZW1lbnQvdGlsZXMucHk=) | `83.07% <0.00%> (-1.54%)` | :arrow_down: |
| ... and [9 more](https://codecov.io/gh/holoviz/holoviews/pull/5640?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz) | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=holoviz)


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a3468608a..c417d9416 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,7 @@
 # This is the configuration for pre-commit, a local framework for managing pre-commit hooks
 #   Check out the docs at: https://pre-commit.com/
 
+exclude: (\.min\.js$|\.svg$|\.html$)
 default_stages: [commit]
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
@@ -25,3 +26,9 @@ repos:
     rev: v0.1.6
     hooks:
       - id: clean-notebook
+  - repo: https://github.com/codespell-project/codespell
+    rev: v2.2.4
+    hooks:
+    - id: codespell
+      additional_dependencies:
+        - tomli
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c40155bc7..7c77507c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+Version 1.16.0
+==============
+** 2023 **
+
+
+Removed:
+
+- Raise DataError for non-string column names in DataFrame ([#5653](https://github.com/holoviz/holoviews/pull/5653))
+- Remove deprecated tilesources ([#5653](https://github.com/holoviz/holoviews/pull/5653))
+- Removed support for apply_groups for old option groups signature ([#5497](https://github.com/holoviz/holoviews/pull/5497))
+
+
 Version 1.15.4
 ==============
 **January 16, 2023**
@@ -403,7 +415,7 @@ Bug fixes:
 Documentation:
 
 - Updated Large data guide to reflect changes in Datashader and
-  antialising support
+  antialiasing support
   ([#5267](https://github.com/holoviz/holoviews/pull/5267),
   [#5290](https://github.com/holoviz/holoviews/pull/5290))
 
@@ -566,7 +578,7 @@ and @philippjfr for contributing.
 
 Enhancements:
 
-- Reenable `SaveTool` for plots with `Tiles`
+- Re-enable `SaveTool` for plots with `Tiles`
   ([#4922](https://github.com/holoviz/holoviews/pull/4922))
 - Enable dask `TriMesh` rasterization using datashader
   ([#4935](https://github.com/holoviz/holoviews/pull/4935))
@@ -732,7 +744,7 @@ Bug fixes:
   ([#4757](https://github.com/holoviz/holoviews/pull/4757))
 - Do not drop tools in linked selections
   ([#4756](https://github.com/holoviz/holoviews/pull/4756))
-- Fixed access to possibly non-existant key
+- Fixed access to possibly non-existent key
   ([#4742](https://github.com/holoviz/holoviews/pull/4742))
 
 Documentation:
@@ -867,7 +879,7 @@ Bug fixes:
 
 - Improvements to iteration over Series in CuDF data backend
   ([#4624](https://github.com/holoviz/holoviews/pull/4624))
-- Added .values_host calls needed for iteraction in CuDF backend
+- Added .values_host calls needed for iteration in CuDF backend
   ([#4646](https://github.com/holoviz/holoviews/pull/4646))
 - Fixed bug resetting ranges
   ([#4654](https://github.com/holoviz/holoviews/pull/4654))
@@ -1051,7 +1063,7 @@ Bug fixes:
   ([#4337](https://github.com/holoviz/holoviews/pull/4337))
 - Fix color and alpha option on bokeh Arrow plot
   ([#4338](https://github.com/holoviz/holoviews/pull/4338))
-- Fix undefined JS varaibles in various bokeh links
+- Fix undefined JS variables in various bokeh links
   ([#4341](https://github.com/holoviz/holoviews/pull/4341))
 - Fix matplotlib >=3.2.1 deprecation warnings
   ([#4335](https://github.com/holoviz/holoviews/pull/4335))
@@ -1469,7 +1481,7 @@ This is a minor release with a number of important bug fixes and a
 small number of enhancements. Many thanks to our users for reporting
 these issues, and special thanks to our internal developers
 @philippjfr, @jlstevens and @jonmease and external contributors
-incluing @ahuang11 and @arabidopsis for their contributions to the
+including @ahuang11 and @arabidopsis for their contributions to the
 code and the documentation.
 
 Enhancements:
@@ -2381,7 +2393,7 @@ improvements and bug fixes, including streaming and graph support,
 binary transfer of Bokeh data, fast Image/RGB regridding, first-class
 statistics elements and a complete overhaul of the geometry elements.
 
-Particular thanks to all users and contributers who have reported
+Particular thanks to all users and contributors who have reported
 issues and submitted pull requests.
 
 Features:
@@ -2463,7 +2475,7 @@ Version 1.8.4
 =============
 **September 13, 2017**
 
-This bugfix release includes a number of critical fixes for compatiblity
+This bugfix release includes a number of critical fixes for compatibility
 with Bokeh 0.12.9 along with various other bug fixes. Many thanks to our
 users for various detailed bug reports, feedback and contributions.
 
diff --git a/doc/releases.rst b/doc/releases.rst
index 9afadf376..193b14ede 100644
--- a/doc/releases.rst
+++ b/doc/releases.rst
@@ -455,7 +455,7 @@ Bug fixes:
 Documentation:
 
 -  Updated Large data guide to reflect changes in Datashader and
-   antialising support
+   antialiasing support
    (`#5267 <https://github.com/holoviz/holoviews/pull/5267>`__,
    `#5290 <https://github.com/holoviz/holoviews/pull/5290>`__)
 
@@ -622,7 +622,7 @@ and @philippjfr for contributing.
 
 Enhancements:
 
-- Reenable ``SaveTool`` for plots with ``Tiles``
+- Re-enable ``SaveTool`` for plots with ``Tiles``
   (`#4922 <https://github.com/holoviz/holoviews/pull/4922>`_)
 - Enable dask ``TriMesh`` rasterization using datashader
   (`#4935 <https://github.com/holoviz/holoviews/pull/4935>`_)
@@ -793,7 +793,7 @@ Bug fixes:
   (`#4757 <https://github.com/holoviz/holoviews/pull/4757>`__)
 - Do not drop tools in linked selections
   (`#4756 <https://github.com/holoviz/holoviews/pull/4756>`__)
-- Fixed access to possibly non-existant key
+- Fixed access to possibly non-existent key
   (`#4742 <https://github.com/holoviz/holoviews/pull/4742>`__)
 
 Documentation:
@@ -929,7 +929,7 @@ Bug fixes:
 
 - Improvements to iteration over Series in CuDF data backend
   (`#4624 <https://github.com/holoviz/holoviews/pull/4624>`_)
-- Added .values_host calls needed for iteraction in CuDF backend
+- Added .values_host calls needed for iteration in CuDF backend
   (`#4646 <https://github.com/holoviz/holoviews/pull/4646>`_)
 - Fixed bug resetting ranges
   (`#4654 <https://github.com/holoviz/holoviews/pull/4654>`_)
@@ -1130,7 +1130,7 @@ Bug fixes:
   (`#4337 <https://github.com/holoviz/holoviews/pull/4337>`_)
 - Fix color and alpha option on bokeh Arrow plot
   (`#4338 <https://github.com/holoviz/holoviews/pull/4338>`_)
-- Fix undefined JS varaibles in various bokeh links
+- Fix undefined JS variables in various bokeh links
   (`#4341 <https://github.com/holoviz/holoviews/pull/4341>`_)
 - Fix matplotlib >=3.2.1 deprecation warnings
   (`#4335 <https://github.com/holoviz/holoviews/pull/4335>`_)
@@ -1568,7 +1568,7 @@ This is a minor release with a number of important bug fixes and a
 small number of enhancements. Many thanks to our users for reporting
 these issues, and special thanks to our internal developers
 @philippjfr, @jlstevens and @jonmease and external contributors
-incluing @ahuang11 and @arabidopsis for their contributions to the
+including @ahuang11 and @arabidopsis for their contributions to the
 code and the documentation.
 
 Enhancements:
@@ -2499,7 +2499,7 @@ improvements and bug fixes, including streaming and graph support,
 binary transfer of Bokeh data, fast Image/RGB regridding, first-class
 statistics elements and a complete overhaul of the geometry elements.
 
-Particular thanks to all users and contributers who have reported issues
+Particular thanks to all users and contributors who have reported issues
 and submitted pull requests.
 
 Features:
@@ -2582,7 +2582,7 @@ Version 1.8.4
 
 **September 13, 2017**
 
-This bugfix release includes a number of critical fixes for compatiblity
+This bugfix release includes a number of critical fixes for compatibility
 with Bokeh 0.12.9 along with various other bug fixes. Many thanks to our
 users for various detailed bug reports, feedback and contributions.
 
diff --git a/examples/gallery/demos/bokeh/nyc_radial_heatmap.ipynb b/examples/gallery/demos/bokeh/nyc_radial_heatmap.ipynb
index 8ab9cd71a..74306b25f 100644
--- a/examples/gallery/demos/bokeh/nyc_radial_heatmap.ipynb
+++ b/examples/gallery/demos/bokeh/nyc_radial_heatmap.ipynb
@@ -32,9 +32,9 @@
     "\n",
     "Let's dive into a concrete example, namely the New York - Taxi Data ([For-Hire Vehicle (“FHV”) records](http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml)). The following data contains hourly pickup counts for the entire year of 2016. \n",
     "\n",
-    "**Considerations**: Thinking about taxi pickup counts, we might expect higher taxi usage during business hours. In addition, public holidays should be clearly distinguishable from regular business days. Furthermore, we might expect high taxi pickup counts during Friday and Saterday nights.\n",
+    "**Considerations**: Thinking about taxi pickup counts, we might expect higher taxi usage during business hours. In addition, public holidays should be clearly distinguishable from regular business days. Furthermore, we might expect high taxi pickup counts during Friday and Saturday nights.\n",
     "\n",
-    "**Design**: In order model the above ideas, we decide to assign days with hourly split to the *radial segments* and week of year to the *annulars*. This will allow to detect daily/hourly periodicity and weekly trends. To get you more familiar with the mapping of segemnts and annulars, take a look at the following radial heatmap:"
+    "**Design**: In order model the above ideas, we decide to assign days with hourly split to the *radial segments* and week of year to the *annulars*. This will allow to detect daily/hourly periodicity and weekly trends. To get you more familiar with the mapping of segments and annulars, take a look at the following radial heatmap:"
    ]
   },
   {
@@ -71,7 +71,7 @@
     "\n",
     "- **Remove annular ticks**: The information about week of year is not very important. Therefore, we hide it via `yticks=None`.\n",
     "- **Custom segment ticks**: Right now, segment labels are given via day and hour. We don't need hourly information and we want every day to be labeled. We can use a tuple here which will be passed to `xticks=(\"Friday\", ..., \"Thursday\")`\n",
-    "- **Add segment markers**: Moreover, we want to aid the viewer in distingushing each day more clearly. Hence, we can provide marker lines via `xmarks=7`.\n",
+    "- **Add segment markers**: Moreover, we want to aid the viewer in distinguishing each day more clearly. Hence, we can provide marker lines via `xmarks=7`.\n",
     "- **Rotate heatmap**: The week starts with Monday and ends with Sunday. Accordingly, we want to rotate the plot to have Sunday and Monday be at the top. This can be done via `start_angle=np.pi*19/14`. The default order is defined by the global sort order which is present in the data. The default starting angle is at 12 o'clock.\n",
     "\n",
     "Let's see the result of these modifications:"
@@ -98,9 +98,9 @@
     "**There are many interesting findings in this visualization:**\n",
     "\n",
     "1. Taxi pickup counts are high between 7-9am and 5-10pm during weekdays which business hours as expected. In contrast, during weekends, there is not much going on until 11am. \n",
-    "2. Friday and Saterday nights clearly stand out with the highest pickup densities as expected. \n",
+    "2. Friday and Saturday nights clearly stand out with the highest pickup densities as expected. \n",
     "3. Public holidays can be easily identified. For example, taxi pickup counts are comparetively low around Christmas and Thanksgiving.\n",
-    "4. Weather phenomena also influence taxi service. There is a very dark blue stripe at the beginning of the year starting at Saterday 23rd and lasting until Sunday 24th. Interestingly, there was one of the [biggest blizzards](https://www.weather.gov/okx/Blizzard_Jan2016) in the history of NYC."
+    "4. Weather phenomena also influence taxi service. There is a very dark blue stripe at the beginning of the year starting at Saturday 23rd and lasting until Sunday 24th. Interestingly, there was one of the [biggest blizzards](https://www.weather.gov/okx/Blizzard_Jan2016) in the history of NYC."
    ]
   }
  ],
diff --git a/examples/gallery/demos/matplotlib/nyc_radial_heatmap.ipynb b/examples/gallery/demos/matplotlib/nyc_radial_heatmap.ipynb
index 51995add4..6d0302db3 100644
--- a/examples/gallery/demos/matplotlib/nyc_radial_heatmap.ipynb
+++ b/examples/gallery/demos/matplotlib/nyc_radial_heatmap.ipynb
@@ -32,9 +32,9 @@
     "\n",
     "Let's dive into a concrete example, namely the New York - Taxi Data ([For-Hire Vehicle (“FHV”) records](http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml)). The following data contains hourly pickup counts for the entire year of 2016. \n",
     "\n",
-    "**Considerations**: Thinking about taxi pickup counts, we might expect higher taxi usage during business hours. In addition, public holidays should be clearly distinguishable from regular business days. Furthermore, we might expect high taxi pickup counts during Friday and Saterday nights.\n",
+    "**Considerations**: Thinking about taxi pickup counts, we might expect higher taxi usage during business hours. In addition, public holidays should be clearly distinguishable from regular business days. Furthermore, we might expect high taxi pickup counts during Friday and Saturday nights.\n",
     "\n",
-    "**Design**: In order model the above ideas, we decide to assign days with hourly split to the *radial segments* and week of year to the *annulars*. This will allow to detect daily/hourly periodicity and weekly trends. To get you more familiar with the mapping of segemnts and annulars, take a look at the following radial heatmap:"
+    "**Design**: In order model the above ideas, we decide to assign days with hourly split to the *radial segments* and week of year to the *annulars*. This will allow to detect daily/hourly periodicity and weekly trends. To get you more familiar with the mapping of segments and annulars, take a look at the following radial heatmap:"
    ]
   },
   {
@@ -71,7 +71,7 @@
     "\n",
     "- **Remove annular ticks**: The information about week of year is not very important. Therefore, we hide it via `yticks=None`.\n",
     "- **Custom segment ticks**: Right now, segment labels are given via day and hour. We don't need hourly information and we want every day to be labeled. We can use a tuple here which will be passed to `xticks=(\"Friday\", ..., \"Thursday\")`\n",
-    "- **Add segment markers**: Moreover, we want to aid the viewer in distingushing each day more clearly. Hence, we can provide marker lines via `xmarks=7`.\n",
+    "- **Add segment markers**: Moreover, we want to aid the viewer in distinguishing each day more clearly. Hence, we can provide marker lines via `xmarks=7`.\n",
     "- **Rotate heatmap**: The week starts with Monday and ends with Sunday. Accordingly, we want to rotate the plot to have Sunday and Monday be at the top. This can be done via `start_angle=np.pi*19/14`. The default order is defined by the global sort order which is present in the data. The default starting angle is at 12 o'clock.\n",
     "\n",
     "Let's see the result of these modifications:"
@@ -97,9 +97,9 @@
     "**There are many interesting findings in this visualization:**\n",
     "\n",
     "1. Taxi pickup counts are high between 7-9am and 5-10pm during weekdays which business hours as expected. In contrast, during weekends, there is not much going on until 11am. \n",
-    "2. Friday and Saterday nights clearly stand out with the highest pickup densities as expected. \n",
+    "2. Friday and Saturday nights clearly stand out with the highest pickup densities as expected. \n",
     "3. Public holidays can be easily identified. For example, taxi pickup counts are comparetively low around Christmas and Thanksgiving.\n",
-    "4. Weather phenomena also influence taxi service. There is a very dark blue stripe at the beginning of the year starting at Saterday 23rd and lasting until Sunday 24th. Interestingly, there was one of the [biggest blizzards](https://www.weather.gov/okx/Blizzard_Jan2016) in the history of NYC."
+    "4. Weather phenomena also influence taxi service. There is a very dark blue stripe at the beginning of the year starting at Saturday 23rd and lasting until Sunday 24th. Interestingly, there was one of the [biggest blizzards](https://www.weather.gov/okx/Blizzard_Jan2016) in the history of NYC."
    ]
   }
  ],
diff --git a/examples/getting_started/1-Introduction.ipynb b/examples/getting_started/1-Introduction.ipynb
index da5cf6033..12f772964 100644
--- a/examples/getting_started/1-Introduction.ipynb
+++ b/examples/getting_started/1-Introduction.ipynb
@@ -389,7 +389,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "``hv.save`` defaults to the currently selected backend, which can be overriden by adding an argument like ``backend='matplotlib'``. The Bokeh backend supports ``png`` output, and the Matplotlib backend supports ``png``, ``svg``, ``gif``, or ``mp4``, selected by using the appropriate extension instead of `.html`. In each case, rendering to a given format may require additional libraries to be installed."
+    "``hv.save`` defaults to the currently selected backend, which can be overridden by adding an argument like ``backend='matplotlib'``. The Bokeh backend supports ``png`` output, and the Matplotlib backend supports ``png``, ``svg``, ``gif``, or ``mp4``, selected by using the appropriate extension instead of `.html`. In each case, rendering to a given format may require additional libraries to be installed."
    ]
   },
   {
diff --git a/examples/getting_started/2-Customization.ipynb b/examples/getting_started/2-Customization.ipynb
index 31c345f25..6ff9e167f 100644
--- a/examples/getting_started/2-Customization.ipynb
+++ b/examples/getting_started/2-Customization.ipynb
@@ -266,7 +266,7 @@
     "* Some of the options are different because of differences in how the plotting backends work. For instance, matplotlib uses ``aspect`` instead of setting ``width`` and ``height``. In some cases, but not all, HoloViews can smooth over such differences in the *plotting* options to make it simpler to switch backends.\n",
     "* The Bokeh hover tool is not supported by the matplotlib backend, as you might expect, nor are there any other interactive controls, because the Matplotlib backend generates static PNG or SVG images.\n",
     "* Some options have different names; for instance, the Bokeh ``line_width`` option is called ``linewidth`` in matplotlib. These \"style\" options are directly inherited from the API of the plotting library backend, not defined by HoloViews.\n",
-    "* Containers like `Layout`s also have some options to control the arrangement of its components. Here we adjust the gap betwen the plots using ``vspace``.\n",
+    "* Containers like `Layout`s also have some options to control the arrangement of its components. Here we adjust the gap between the plots using ``vspace``.\n",
     "\n",
     "Now we can use the `hv.output` utility to to show the same elements in `layout` as rendered with these different customizations, in a different output format (SVG), with a completely different plotting library:"
    ]
diff --git a/examples/reference/apps/bokeh/player.py b/examples/reference/apps/bokeh/player.py
index 47a11a8d5..b7b034f74 100644
--- a/examples/reference/apps/bokeh/player.py
+++ b/examples/reference/apps/bokeh/player.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 """
 An example of a simple player widget animating an Image demonstrating
-how to connnect a simple HoloViews plot with custom widgets and
+how to connect a simple HoloViews plot with custom widgets and
 combine them into a bokeh layout.
 
 The app can be served using:
diff --git a/examples/reference/containers/bokeh/NdOverlay.ipynb b/examples/reference/containers/bokeh/NdOverlay.ipynb
index fac3805a0..49fabccd0 100644
--- a/examples/reference/containers/bokeh/NdOverlay.ipynb
+++ b/examples/reference/containers/bokeh/NdOverlay.ipynb
@@ -28,7 +28,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "An ``NdOverlay`` is a multi-dimensional dictionary of HoloViews elements presented overlayed in the same space. An ``NdOverlay`` can be considered as a special-case of ``HoloMap`` that can only hold a single type of element at a time. Unlike a regular ``Overlay`` that can be built with the ``*`` operator, the items in an ``NdOverlay`` container have corresponding keys and must all have the same type. See the [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) user guide for details on how to compose containers."
+    "An ``NdOverlay`` is a multi-dimensional dictionary of HoloViews elements presented overlaid in the same space. An ``NdOverlay`` can be considered as a special-case of ``HoloMap`` that can only hold a single type of element at a time. Unlike a regular ``Overlay`` that can be built with the ``*`` operator, the items in an ``NdOverlay`` container have corresponding keys and must all have the same type. See the [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) user guide for details on how to compose containers."
    ]
   },
   {
diff --git a/examples/reference/containers/bokeh/Overlay.ipynb b/examples/reference/containers/bokeh/Overlay.ipynb
index cbfd03aca..416934233 100644
--- a/examples/reference/containers/bokeh/Overlay.ipynb
+++ b/examples/reference/containers/bokeh/Overlay.ipynb
@@ -28,7 +28,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "A Overlay is a collection of HoloViews objects that are related in some way, to be displayed simultanously, overlaid in the same space. Like  [``Layout``](./Layout.ipynb) and unlike other containers such as [``HoloMap``](./HoloMap.ipynb) , [``GridSpace``](./GridSpace.ipynb) and [``NdOverlay``](./NdOverlay.ipynb) a ``Overlay`` is *not* dictionary like: it holds potentially heterogeneous types without any dimensioned keys.\n",
+    "A Overlay is a collection of HoloViews objects that are related in some way, to be displayed simultaneously, overlaid in the same space. Like  [``Layout``](./Layout.ipynb) and unlike other containers such as [``HoloMap``](./HoloMap.ipynb) , [``GridSpace``](./GridSpace.ipynb) and [``NdOverlay``](./NdOverlay.ipynb) a ``Overlay`` is *not* dictionary like: it holds potentially heterogeneous types without any dimensioned keys.\n",
     "\n",
     "\n",
     "A ``Overlay`` cannot contain any other container type other than ``NdOverlay`` but can contain any HoloViews elements. See [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) for more details on how to compose containers. It is best to learn about ``Overlay`` and [``Layout``](./Layout.ipynb) together as they are very closely related objects that share many core concepts."
diff --git a/examples/reference/containers/matplotlib/NdOverlay.ipynb b/examples/reference/containers/matplotlib/NdOverlay.ipynb
index e27325103..5d600c99f 100644
--- a/examples/reference/containers/matplotlib/NdOverlay.ipynb
+++ b/examples/reference/containers/matplotlib/NdOverlay.ipynb
@@ -28,7 +28,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "An ``NdOverlay`` is a multi-dimensional dictionary of HoloViews elements presented overlayed in the same space. An ``NdOverlay`` can be considered as a special-case of ``HoloMap`` that can only hold a single type of element at a time. Unlike a regular ``Overlay`` that can be built with the ``*`` operator, the items in an ``NdOverlay`` container have corresponding keys and must all have the same type. See the [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) user guide for details on how to compose containers."
+    "An ``NdOverlay`` is a multi-dimensional dictionary of HoloViews elements presented overlaid in the same space. An ``NdOverlay`` can be considered as a special-case of ``HoloMap`` that can only hold a single type of element at a time. Unlike a regular ``Overlay`` that can be built with the ``*`` operator, the items in an ``NdOverlay`` container have corresponding keys and must all have the same type. See the [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) user guide for details on how to compose containers."
    ]
   },
   {
diff --git a/examples/reference/containers/matplotlib/Overlay.ipynb b/examples/reference/containers/matplotlib/Overlay.ipynb
index 6e514c9b2..8cc1a99ac 100644
--- a/examples/reference/containers/matplotlib/Overlay.ipynb
+++ b/examples/reference/containers/matplotlib/Overlay.ipynb
@@ -28,7 +28,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "A Overlay is a collection of HoloViews objects that are related in some way, to be displayed simultanously, overlaid in the space space. Like [``Layout``](./Layout.ipynb) and unlike other containers such as [``HoloMap``](./HoloMap.ipynb) , [``GridSpace``](./GridSpace.ipynb) and [``NdOverlay``](./NdOverlay.ipynb) a ``Overlay`` is *not* dictionary like: it holds potentially heterogeneous types without any dimensioned keys.\n",
+    "A Overlay is a collection of HoloViews objects that are related in some way, to be displayed simultaneously, overlaid in the space space. Like [``Layout``](./Layout.ipynb) and unlike other containers such as [``HoloMap``](./HoloMap.ipynb) , [``GridSpace``](./GridSpace.ipynb) and [``NdOverlay``](./NdOverlay.ipynb) a ``Overlay`` is *not* dictionary like: it holds potentially heterogeneous types without any dimensioned keys.\n",
     "\n",
     "\n",
     "A ``Overlay`` cannot contain any other container type other than ``NdOverlay`` but can contain any HoloViews elements. See [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) for more details on how to compose containers. It is best to learn about ``Overlay`` and [``Layout``](./Layout.ipynb) together as they are very closely related objects that share many core concepts."
diff --git a/examples/reference/containers/plotly/NdOverlay.ipynb b/examples/reference/containers/plotly/NdOverlay.ipynb
index 8c5ca4249..76a24072d 100644
--- a/examples/reference/containers/plotly/NdOverlay.ipynb
+++ b/examples/reference/containers/plotly/NdOverlay.ipynb
@@ -28,7 +28,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "An ``NdOverlay`` is a multi-dimensional dictionary of HoloViews elements presented overlayed in the same space. An ``NdOverlay`` can be considered as a special-case of ``HoloMap`` that can only hold a single type of element at a time. Unlike a regular ``Overlay`` that can be built with the ``*`` operator, the items in an ``NdOverlay`` container have corresponding keys and must all have the same type. See the [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) user guide for details on how to compose containers."
+    "An ``NdOverlay`` is a multi-dimensional dictionary of HoloViews elements presented overlaid in the same space. An ``NdOverlay`` can be considered as a special-case of ``HoloMap`` that can only hold a single type of element at a time. Unlike a regular ``Overlay`` that can be built with the ``*`` operator, the items in an ``NdOverlay`` container have corresponding keys and must all have the same type. See the [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) user guide for details on how to compose containers."
    ]
   },
   {
diff --git a/examples/reference/containers/plotly/Overlay.ipynb b/examples/reference/containers/plotly/Overlay.ipynb
index 846090b39..af493f653 100644
--- a/examples/reference/containers/plotly/Overlay.ipynb
+++ b/examples/reference/containers/plotly/Overlay.ipynb
@@ -28,7 +28,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "A Overlay is a collection of HoloViews objects that are related in some way, to be displayed simultanously, overlaid in the same space. Like  [``Layout``](./Layout.ipynb) and unlike other containers such as [``HoloMap``](./HoloMap.ipynb) , [``GridSpace``](./GridSpace.ipynb) and [``NdOverlay``](./NdOverlay.ipynb) a ``Overlay`` is *not* dictionary like: it holds potentially heterogeneous types without any dimensioned keys.\n",
+    "A Overlay is a collection of HoloViews objects that are related in some way, to be displayed simultaneously, overlaid in the same space. Like  [``Layout``](./Layout.ipynb) and unlike other containers such as [``HoloMap``](./HoloMap.ipynb) , [``GridSpace``](./GridSpace.ipynb) and [``NdOverlay``](./NdOverlay.ipynb) a ``Overlay`` is *not* dictionary like: it holds potentially heterogeneous types without any dimensioned keys.\n",
     "\n",
     "\n",
     "A ``Overlay`` cannot contain any other container type other than ``NdOverlay`` but can contain any HoloViews elements. See [Building Composite Objects](../../../user_guide/06-Building_Composite_Objects.ipynb) for more details on how to compose containers. It is best to learn about ``Overlay`` and [``Layout``](./Layout.ipynb) together as they are very closely related objects that share many core concepts."
diff --git a/examples/reference/elements/bokeh/ErrorBars.ipynb b/examples/reference/elements/bokeh/ErrorBars.ipynb
index 6c5109e2c..e12619684 100644
--- a/examples/reference/elements/bokeh/ErrorBars.ipynb
+++ b/examples/reference/elements/bokeh/ErrorBars.ipynb
@@ -60,7 +60,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "#### Assymetric error"
+    "#### Asymmetric error"
    ]
   },
   {
diff --git a/examples/reference/elements/bokeh/Spread.ipynb b/examples/reference/elements/bokeh/Spread.ipynb
index 0b70f1c22..cad7a465e 100644
--- a/examples/reference/elements/bokeh/Spread.ipynb
+++ b/examples/reference/elements/bokeh/Spread.ipynb
@@ -69,7 +69,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Given three value dimensions corresponding to the position on the y-axis, the negative error and the positive error, ``Spread`` can be used to visualize assymmetric errors. In the next example, we also disable the default alpha value by setting `fill_alpha` to unity:"
+    "Given three value dimensions corresponding to the position on the y-axis, the negative error and the positive error, ``Spread`` can be used to visualize asymmetric errors. In the next example, we also disable the default alpha value by setting `fill_alpha` to unity:"
    ]
   },
   {
diff --git a/examples/reference/elements/bokeh/Table.ipynb b/examples/reference/elements/bokeh/Table.ipynb
index f43864e46..62e6d3be6 100644
--- a/examples/reference/elements/bokeh/Table.ipynb
+++ b/examples/reference/elements/bokeh/Table.ipynb
@@ -102,7 +102,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The ``Table`` is used as a common data structure that may be converted to any other HoloViews data structure via the ``to`` utility available on the object. Here we use this utility to show the weight of the males in our datset by age:"
+    "The ``Table`` is used as a common data structure that may be converted to any other HoloViews data structure via the ``to`` utility available on the object. Here we use this utility to show the weight of the males in our dataset by age:"
    ]
   },
   {
diff --git a/examples/reference/elements/bokeh/Tiles.ipynb b/examples/reference/elements/bokeh/Tiles.ipynb
index 8a4b9dacb..04aa4ea4e 100644
--- a/examples/reference/elements/bokeh/Tiles.ipynb
+++ b/examples/reference/elements/bokeh/Tiles.ipynb
@@ -52,7 +52,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "One thing to note about tile sources is that they are always defined in the [pseudo-Mercator projection](https://epsg.io/3857), which means that if you want to overlay any data on top of a tile source the values have to be expressed as eastings and northings. If you have data in another projection, e.g. latitudes and longitudes, it may make sense to use [GeoViews](http://geoviews.org/) for it to handle the projections for you.\n",
+    "One thing to note about tile sources is that they are always defined in the [pseudo-Mercator projection](https://epsg.io/3857), which means that if you want to overlay any data on top of a tile source the values have to be expressed as eastings and northings. If you have data in another projection, e.g. latitudes and longitudes, it may make sense to use [GeoViews](https://geoviews.org/) for it to handle the projections for you.\n",
     "\n",
     ">\n",
     "> **Note**: \n",
@@ -61,7 +61,7 @@
     ">   See [`hv.util.transform.lon_lat_to_easting_northing(longitude, latitude)`](https://holoviews.org/reference_manual/holoviews.element.html?highlight=lon_lat_to_easting_northing#holoviews.element.Tiles.lon_lat_to_easting_northing)\n",
     ">\n",
     "\n",
-    "Both HoloViews and GeoViews provides a number of tile sources by default, provided by CartoDB, Stamen, OpenStreetMap, Esri and Wikipedia. These can be imported from the ``holoviews.element.tiles`` module and are provided as callable functions which return a ``Tiles`` element:"
+    "Both HoloViews and GeoViews provides a number of tile sources by default, provided by CartoDB, Stamen, OpenStreetMap, and Esri. These can be imported from the ``holoviews.element.tiles`` module and are provided as callable functions which return a ``Tiles`` element:"
    ]
   },
   {
@@ -77,7 +77,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The full set of predefined tile sources can be accessed on the ``holoviews.element.tiles.tile_sources`` dictionary. Some tile sources from CartoDB are no longer publicly available and marked with a deprecation warning. These are filtered below:"
+    "The full set of predefined tile sources can be accessed on the ``holoviews.element.tiles.tile_sources`` dictionary."
    ]
   },
   {
diff --git a/examples/reference/elements/bokeh/TriMesh.ipynb b/examples/reference/elements/bokeh/TriMesh.ipynb
index 9ef9d9bb1..0f7735370 100644
--- a/examples/reference/elements/bokeh/TriMesh.ipynb
+++ b/examples/reference/elements/bokeh/TriMesh.ipynb
@@ -31,7 +31,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "A ``TriMesh`` represents a mesh of triangles represented as the simplexes and vertexes. The simplexes represent the indices into the vertex data, made up of three indices per triangle. The mesh therefore follows a datastructure very similar to a graph, with the abstract connectivity between nodes stored on the ``TriMesh`` element itself, the node or vertex positions stored on a ``Nodes`` element and the concrete ``EdgePaths`` making up each triangle generated when required by accessing the edgepaths attribute.\n",
+    "A ``TriMesh`` represents a mesh of triangles represented as the simplexes and vertices. The simplexes represent the indices into the vertex data, made up of three indices per triangle. The mesh therefore follows a datastructure very similar to a graph, with the abstract connectivity between nodes stored on the ``TriMesh`` element itself, the node or vertex positions stored on a ``Nodes`` element and the concrete ``EdgePaths`` making up each triangle generated when required by accessing the edgepaths attribute.\n",
     "\n",
     "Unlike a Graph each simplex is represented as the node indices of the three corners of each triangle rather than the usual source and target node.\n",
     "\n",
diff --git a/examples/reference/elements/matplotlib/ErrorBars.ipynb b/examples/reference/elements/matplotlib/ErrorBars.ipynb
index 0f70516f4..e221e7e0a 100644
--- a/examples/reference/elements/matplotlib/ErrorBars.ipynb
+++ b/examples/reference/elements/matplotlib/ErrorBars.ipynb
@@ -60,7 +60,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "#### Assymetric error"
+    "#### Asymmetric error"
    ]
   },
   {
diff --git a/examples/reference/elements/matplotlib/Path3D.ipynb b/examples/reference/elements/matplotlib/Path3D.ipynb
index 2c690cfd6..10ad111d7 100644
--- a/examples/reference/elements/matplotlib/Path3D.ipynb
+++ b/examples/reference/elements/matplotlib/Path3D.ipynb
@@ -33,7 +33,7 @@
    "source": [
     "A ``Path3D`` element represents one more lines, connecting arbitrary points in three-dimensional space. ``Path3D`` supports plotting an individual line or multiple subpaths, which should be supplied as a list. Each path should be defined in a columnar format such as NumPy arrays, DataFrames or dictionaries for each column. For a full description of the path geometry data model see the [Geometry Data User Guide](../user_guide/Geometry_Data.ipynb).\n",
     "\n",
-    "As a simple example we will genrate a random walk through 3D space as a single array with three columns:"
+    "As a simple example we will generate a random walk through 3D space as a single array with three columns:"
    ]
   },
   {
diff --git a/examples/reference/elements/matplotlib/Spread.ipynb b/examples/reference/elements/matplotlib/Spread.ipynb
index 8a1a0b9bd..7d0887b55 100644
--- a/examples/reference/elements/matplotlib/Spread.ipynb
+++ b/examples/reference/elements/matplotlib/Spread.ipynb
@@ -68,7 +68,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Given three value dimensions corresponding to the position on the y-axis, the negative error and the positive error, ``Spread`` can be used to visualize assymmetric errors:"
+    "Given three value dimensions corresponding to the position on the y-axis, the negative error and the positive error, ``Spread`` can be used to visualize asymmetric errors:"
    ]
   },
   {
diff --git a/examples/reference/elements/matplotlib/Table.ipynb b/examples/reference/elements/matplotlib/Table.ipynb
index a6f06d1d3..93eaac480 100644
--- a/examples/reference/elements/matplotlib/Table.ipynb
+++ b/examples/reference/elements/matplotlib/Table.ipynb
@@ -99,7 +99,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The ``Table`` is used as a common data structure that may be converted to any other HoloViews data structure via the ``to`` utility available on the object. Here we use this utility to show the weight of the males in our datset by age:"
+    "The ``Table`` is used as a common data structure that may be converted to any other HoloViews data structure via the ``to`` utility available on the object. Here we use this utility to show the weight of the males in our dataset by age:"
    ]
   },
   {
diff --git a/examples/reference/elements/matplotlib/TriMesh.ipynb b/examples/reference/elements/matplotlib/TriMesh.ipynb
index debf04339..3cecd0440 100644
--- a/examples/reference/elements/matplotlib/TriMesh.ipynb
+++ b/examples/reference/elements/matplotlib/TriMesh.ipynb
@@ -32,7 +32,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "A ``TriMesh`` represents a mesh of triangles represented as the simplexes and vertexes. The simplexes represent the indices into the vertex data, made up of three indices per triangle. The mesh therefore follows a datastructure very similar to a graph, with the abstract connectivity between nodes stored on the ``TriMesh`` element itself, the node or vertex positions stored on a ``Nodes`` element and the concrete ``EdgePaths`` making up each triangle generated when required by accessing the edgepaths attribute.\n",
+    "A ``TriMesh`` represents a mesh of triangles represented as the simplexes and vertices. The simplexes represent the indices into the vertex data, made up of three indices per triangle. The mesh therefore follows a datastructure very similar to a graph, with the abstract connectivity between nodes stored on the ``TriMesh`` element itself, the node or vertex positions stored on a ``Nodes`` element and the concrete ``EdgePaths`` making up each triangle generated when required by accessing the edgepaths attribute.\n",
     "\n",
     "Unlike a Graph each simplex is represented as the node indices of the three corners of each triangle rather than the usual source and target node.\n",
     "\n",
diff --git a/examples/reference/elements/plotly/ErrorBars.ipynb b/examples/reference/elements/plotly/ErrorBars.ipynb
index 952a74399..faa9bd466 100644
--- a/examples/reference/elements/plotly/ErrorBars.ipynb
+++ b/examples/reference/elements/plotly/ErrorBars.ipynb
@@ -60,7 +60,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "#### Assymetric error"
+    "#### Asymmetric error"
    ]
   },
   {
diff --git a/examples/reference/elements/plotly/Path3D.ipynb b/examples/reference/elements/plotly/Path3D.ipynb
index 28551ccd2..eb90ae131 100644
--- a/examples/reference/elements/plotly/Path3D.ipynb
+++ b/examples/reference/elements/plotly/Path3D.ipynb
@@ -33,7 +33,7 @@
    "source": [
     "A ``Path3D`` element represents one more lines, connecting arbitrary points in three-dimensional space. ``Path3D`` supports plotting an individual line or multiple subpaths, which should be supplied as a list. Each path should be defined in a columnar format such as NumPy arrays, DataFrames or dictionaries for each column. For a full description of the path geometry data model see the [Geometry Data User Guide](../user_guide/Geometry_Data.ipynb).\n",
     "\n",
-    "As a simple example we will genrate a random walk through 3D space as a single array with three columns:"
+    "As a simple example we will generate a random walk through 3D space as a single array with three columns:"
    ]
   },
   {
diff --git a/examples/reference/elements/plotly/Spread.ipynb b/examples/reference/elements/plotly/Spread.ipynb
index 1b95e61c2..9ae8e3625 100644
--- a/examples/reference/elements/plotly/Spread.ipynb
+++ b/examples/reference/elements/plotly/Spread.ipynb
@@ -66,7 +66,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Given three value dimensions corresponding to the position on the y-axis, the negative error and the positive error, ``Spread`` can be used to visualize assymmetric errors:"
+    "Given three value dimensions corresponding to the position on the y-axis, the negative error and the positive error, ``Spread`` can be used to visualize asymmetric errors:"
    ]
   },
   {
diff --git a/examples/reference/elements/plotly/Table.ipynb b/examples/reference/elements/plotly/Table.ipynb
index 3948f8b2c..623daadbb 100644
--- a/examples/reference/elements/plotly/Table.ipynb
+++ b/examples/reference/elements/plotly/Table.ipynb
@@ -96,7 +96,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The ``Table`` is used as a common data structure that may be converted to any other HoloViews data structure via the ``to`` utility available on the object. Here we use this utility to show the weight of the males in our datset by age:"
+    "The ``Table`` is used as a common data structure that may be converted to any other HoloViews data structure via the ``to`` utility available on the object. Here we use this utility to show the weight of the males in our dataset by age:"
    ]
   },
   {
diff --git a/examples/user_guide/01-Annotating_Data.ipynb b/examples/user_guide/01-Annotating_Data.ipynb
index cdad3dcdf..21892cfcb 100644
--- a/examples/user_guide/01-Annotating_Data.ipynb
+++ b/examples/user_guide/01-Annotating_Data.ipynb
@@ -137,7 +137,7 @@
    "source": [
     "### Dimension parameters\n",
     "\n",
-    "``Dimensions`` are not just names, they are rich objects with numerous parameters that can be used to describe the space in which the data resides. Only two of these are considered *core* parameters that uniquely identify the dimension object; the rest are auxilliary metadata. The most important parameters are:\n",
+    "``Dimensions`` are not just names, they are rich objects with numerous parameters that can be used to describe the space in which the data resides. Only two of these are considered *core* parameters that uniquely identify the dimension object; the rest are auxiliary metadata. The most important parameters are:\n",
     "\n",
     "<br>\n",
     "<dl class=\"dl-horizontal\">\n",
diff --git a/examples/user_guide/02-Composing_Elements.ipynb b/examples/user_guide/02-Composing_Elements.ipynb
index bbb076090..aa0c524fe 100644
--- a/examples/user_guide/02-Composing_Elements.ipynb
+++ b/examples/user_guide/02-Composing_Elements.ipynb
@@ -112,7 +112,7 @@
    "source": [
     "### A ``Layout`` has two-level attribute access\n",
     "\n",
-    "``Layout`` and ``Overlay`` are tree-based data structures that can hold arbitrarily heterogenous collections of HoloViews objects, and are quite different from the dictionary-like dimensioned containers (which will be described in later guides).\n",
+    "``Layout`` and ``Overlay`` are tree-based data structures that can hold arbitrarily heterogeneous collections of HoloViews objects, and are quite different from the dictionary-like dimensioned containers (which will be described in later guides).\n",
     "\n",
     "As mentioned previously in [Annotating Data](01-Annotating_Data.ipynb), HoloViews objects have string ``group`` and ``label`` parameters, which can be used to select objects in the ``Layout`` using two-level attribute access. First let us see how to index the above example, where the ``group`` and ``label`` parameters were left unspecified on creation:"
    ]
@@ -274,7 +274,7 @@
    "source": [
     "### ``Overlay`` also has two-level attribute access\n",
     "\n",
-    "Like ``Layout``, ``Overlay`` is fundamentally a tree structure holding arbitrarily heterogenous HoloViews objects, unlike the dimensioned containers. ``Overlay`` objects also make use of the ``group`` and ``label`` parameters, introduced in [Annotating Data](01-Annotating_Data.ipynb), for two-level attribute access.\n",
+    "Like ``Layout``, ``Overlay`` is fundamentally a tree structure holding arbitrarily heterogeneous HoloViews objects, unlike the dimensioned containers. ``Overlay`` objects also make use of the ``group`` and ``label`` parameters, introduced in [Annotating Data](01-Annotating_Data.ipynb), for two-level attribute access.\n",
     "\n",
     "Once again, let us see how to index the above example where the ``group`` and ``label`` parameters were left unspecified:"
    ]
diff --git a/examples/user_guide/03-Applying_Customizations.ipynb b/examples/user_guide/03-Applying_Customizations.ipynb
index de3b6bac5..c1c027f8c 100644
--- a/examples/user_guide/03-Applying_Customizations.ipynb
+++ b/examples/user_guide/03-Applying_Customizations.ipynb
@@ -480,7 +480,7 @@
     "* Use as few `.opts` calls as necessary to style the object the way you want.\n",
     "* You can inline keywords without an option builder if you only have a few common keywords. For instance, `hv.Image(...).opts(cmap='Reds')` is clearer to read than `hv.Image(...).opts(opts.Image(cmap='Reds'))`.\n",
     "* Conversely, you *should* use an option builder if you have more than four keywords.\n",
-    "* When you have multiple option builders, it is often clearest to list them on separate lines with a single intentation in both `.opts` and `opts.defaults`:\n",
+    "* When you have multiple option builders, it is often clearest to list them on separate lines with a single indentation in both `.opts` and `opts.defaults`:\n",
     "  \n",
     "**Not recommended**\n",
     "\n",
diff --git a/examples/user_guide/06-Building_Composite_Objects.ipynb b/examples/user_guide/06-Building_Composite_Objects.ipynb
index b71515710..6e83233a2 100644
--- a/examples/user_guide/06-Building_Composite_Objects.ipynb
+++ b/examples/user_guide/06-Building_Composite_Objects.ipynb
@@ -112,7 +112,7 @@
     "\n",
     "- Basic Element: elementary HoloViews object containing raw data in an external format like Numpy or pandas.\n",
     "- Homogeneous container (UniformNdMapping): collections of Elements or other HoloViews components that are all the same type.  These are indexed using array-style key access with values sorted along some dimension(s), e.g. ``[0.50]`` or ``[\"a\",7.6]``.\n",
-    "- Heterogenous container (AttrTree): collections of data of different types, e.g. different types of Element.  These are accessed by categories using attributes, e.g. ``.Parameters.Sines``, which does not assume any ordering of a dimension.\n",
+    "- Heterogeneous container (AttrTree): collections of data of different types, e.g. different types of Element.  These are accessed by categories using attributes, e.g. ``.Parameters.Sines``, which does not assume any ordering of a dimension.\n",
     "\n",
     "We will now go through each of the containers of these different types, at each level."
    ]
@@ -396,7 +396,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "As you can see, HoloViews lets you compose objects of heterogenous types, and objects covering many different numerical or other dimensions, laying them out spatially, temporally, or overlaid.  The resulting data structures are complex, but they are composed of simple elements with well-defined interactions, making it feasible to express nearly any relationship that will characterize your data.  In practice, you will probably not need this many levels, but given this complete example, you should be able to construct an appropriate hierarchy for whatever type of data that you want to represent or visualize. \n",
+    "As you can see, HoloViews lets you compose objects of heterogeneous types, and objects covering many different numerical or other dimensions, laying them out spatially, temporally, or overlaid.  The resulting data structures are complex, but they are composed of simple elements with well-defined interactions, making it feasible to express nearly any relationship that will characterize your data.  In practice, you will probably not need this many levels, but given this complete example, you should be able to construct an appropriate hierarchy for whatever type of data that you want to represent or visualize. \n",
     "\n",
     "As emphasized above, it is not recommended to combine these objects in other orderings.  Of course, any ``Element`` can be substituted for any other, which doesn't change the structure. But you should not e.g. display an ``Overlay`` of ``Layout`` objects. The display system will generally attempt to figure out the correct arrangement and warn you to call the `.collate` method to reorganize the objects in the recommended format.\n",
     "\n",
diff --git a/examples/user_guide/08-Tabular_Datasets.ipynb b/examples/user_guide/08-Tabular_Datasets.ipynb
index 84c9b3556..2ee138585 100644
--- a/examples/user_guide/08-Tabular_Datasets.ipynb
+++ b/examples/user_guide/08-Tabular_Datasets.ipynb
@@ -31,7 +31,7 @@
     "\n",
     "* cuDF Dataframes\n",
     "\n",
-    "A number of additonal standard constructors are supported:\n",
+    "A number of additional standard constructors are supported:\n",
     "\n",
     "* A tuple of array (or array-like) objects\n",
     "\n",
diff --git a/examples/user_guide/09-Gridded_Datasets.ipynb b/examples/user_guide/09-Gridded_Datasets.ipynb
index 5e05770e0..a423b0466 100644
--- a/examples/user_guide/09-Gridded_Datasets.ipynb
+++ b/examples/user_guide/09-Gridded_Datasets.ipynb
@@ -403,7 +403,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "We have used the usual slice notation in order to select one single day in the rather large dataset. Finally, let's visualize the dataset by converting it to a `HoloMap` of `Images` using the `to()` method. We need to specify which of the dataset's key dimensions will be consumed by the images (in this case \"lat\" and \"lon\"), where the remaing key dimensions will be associated with the HoloMap (here: \"time\"). We'll use the slice notation again to clip the longitude."
+    "We have used the usual slice notation in order to select one single day in the rather large dataset. Finally, let's visualize the dataset by converting it to a `HoloMap` of `Images` using the `to()` method. We need to specify which of the dataset's key dimensions will be consumed by the images (in this case \"lat\" and \"lon\"), where the remaining key dimensions will be associated with the HoloMap (here: \"time\"). We'll use the slice notation again to clip the longitude."
    ]
   },
   {
diff --git a/examples/user_guide/12-Responding_to_Events.ipynb b/examples/user_guide/12-Responding_to_Events.ipynb
index 8bcac2aea..d8788f9f4 100644
--- a/examples/user_guide/12-Responding_to_Events.ipynb
+++ b/examples/user_guide/12-Responding_to_Events.ipynb
@@ -24,7 +24,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "In the [Live Data](./07-Live_Data.ipynb) guide we saw how ``DynamicMap`` allows us to explore high dimensional data using the widgets in the same style as ``HoloMaps``. Although suitable for unbounded exploration of large parameter spaces, the ``DynamicMaps`` described in that notebook support exactly the same mode of interaction as ``HoloMaps``. In particular, the key dimensions are used to specify a set of widgets that when manipulated apply the appopriate indexing to invoke the user-supplied callable.\n",
+    "In the [Live Data](./07-Live_Data.ipynb) guide we saw how ``DynamicMap`` allows us to explore high dimensional data using the widgets in the same style as ``HoloMaps``. Although suitable for unbounded exploration of large parameter spaces, the ``DynamicMaps`` described in that notebook support exactly the same mode of interaction as ``HoloMaps``. In particular, the key dimensions are used to specify a set of widgets that when manipulated apply the appropriate indexing to invoke the user-supplied callable.\n",
     "\n",
     "In this user guide we will explore the HoloViews streams system that allows *any* sort of value to be supplied from *anywhere*. This system opens a huge set of new possible visualization types, including continuously updating plots that reflect live data as well as dynamic visualizations that can be interacted with directly, as described in the [Custom Interactivity](./13-Custom_Interactivity.ipynb) guide.\n",
     "\n",
@@ -617,7 +617,7 @@
    "source": [
     "First you might notice that the 'time' value is now shown in the title but that there is no corresponding time slider as its value is supplied by the stream.\n",
     "\n",
-    "The 'time' parameter is now an instance of what are called 'dimensioned streams' which reenable indexing of these dimensions:"
+    "The 'time' parameter is now an instance of what are called 'dimensioned streams' which re-enable indexing of these dimensions:"
    ]
   },
   {
@@ -971,7 +971,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "When you define a ``DynamicMap`` using streams, the HoloViews plotting system installs the necessary callbacks as subscibers to update the plot when the stream parameters change. The above example clears all subscribers (it is equivalent to ``clear('all')``. To clear only the subscribers you define yourself use ``clear('user')`` and to clear any subscribers installed by the HoloViews plotting system use ``clear('internal')``.\n",
+    "When you define a ``DynamicMap`` using streams, the HoloViews plotting system installs the necessary callbacks as subscribers to update the plot when the stream parameters change. The above example clears all subscribers (it is equivalent to ``clear('all')``. To clear only the subscribers you define yourself use ``clear('user')`` and to clear any subscribers installed by the HoloViews plotting system use ``clear('internal')``.\n",
     "\n",
     "When using linked streams as described in the [Custom Interactivity](13-Custom_Interactivity.ipynb) user guide, the plotting system recognizes the stream class and registers the necessary machinery with Bokeh to update the stream values based on direct interaction with the plot."
    ]
diff --git a/examples/user_guide/13-Custom_Interactivity.ipynb b/examples/user_guide/13-Custom_Interactivity.ipynb
index 5c18c9b74..371d3f574 100644
--- a/examples/user_guide/13-Custom_Interactivity.ipynb
+++ b/examples/user_guide/13-Custom_Interactivity.ipynb
@@ -336,7 +336,7 @@
     "\n",
     "This approach is not suitable for certain events which only have a well defined value at a particular point in time. For instance, when you hover your mouse over a plot, the hover position always has a well-defined value but the click position is only defined when a click occurs (if it occurs).\n",
     "\n",
-    "This latter case is an example of what are called 'transient' streams. These streams are supplied new values only when they occur and fall back to a default value at all other times. This default value is typically ``None`` to indicate that the event is not occuring and therefore has no data.\n",
+    "This latter case is an example of what are called 'transient' streams. These streams are supplied new values only when they occur and fall back to a default value at all other times. This default value is typically ``None`` to indicate that the event is not occurring and therefore has no data.\n",
     "\n",
     "\n",
     "Transient streams are particularly useful when you are subscribed to multiple streams, some of which are only occasionally triggered. A good example are the ``Tap`` and ``DoubleTap`` streams; while you sometimes just want to know the last tapped position, we can only tell the two events apart if their values are ``None`` when not active. \n",
diff --git a/examples/user_guide/Annotators.ipynb b/examples/user_guide/Annotators.ipynb
index f1fb3f179..4067671e8 100644
--- a/examples/user_guide/Annotators.ipynb
+++ b/examples/user_guide/Annotators.ipynb
@@ -56,7 +56,7 @@
     "points = hv.Points([(0.0, 0.0), (1.0, 1.0), (200000.0, 2000000.0)]).opts(size=10, min_height=500)\n",
     "\n",
     "annotator = hv.annotate.instance()\n",
-    "layout = annotator(hv.element.tiles.Wikipedia() * points, annotations=['Label'], name=\"Point Annotations\")\n",
+    "layout = annotator(hv.element.tiles.OSM() * points, annotations=['Label'], name=\"Point Annotations\")\n",
     "\n",
     "print(layout)"
    ]
@@ -357,7 +357,7 @@
     "poly_annotate = hv.annotate.instance()\n",
     "poly_layout = poly_annotate(hv.Polygons([]), annotations=['Label'])\n",
     "\n",
-    "hv.annotate.compose(hv.element.tiles.Wikipedia(), point_layout, poly_layout)"
+    "hv.annotate.compose(hv.element.tiles.OSM(), point_layout, poly_layout)"
    ]
   },
   {
diff --git a/examples/user_guide/Customizing_Plots.ipynb b/examples/user_guide/Customizing_Plots.ipynb
index d75ae1c3a..e8bd59e53 100644
--- a/examples/user_guide/Customizing_Plots.ipynb
+++ b/examples/user_guide/Customizing_Plots.ipynb
@@ -28,23 +28,23 @@
     "\n",
     "Plots have an overall hierarchy and here we will break down the different components:\n",
     "\n",
-    "* [**Plot**](#Customizing-the-plot): Refers to the overall plot which can consist of one or more axes\n",
-    "    - [Titles](#Title): Using title formatting and providing custom titles\n",
-    "    - [Background](#Background): Setting the plot background color\n",
-    "    - [Font sizes](#Font-sizes): Controlling the font sizes on a plot\n",
-    "    - [Legends](#Legend-customization): Controlling the position and styling of the legend\n",
-    "    - [Plot hooks](#Plot-hooks): Using custom hooks to modify plots\n",
-    "* [**Axes**](#Customizing-axes): A set of axes provides scales describing the mapping between data and the space on screen\n",
-    "    - [Types of axes](#Types-of-axes):\n",
-    "        - [Linear axes](#Linear-axes)\n",
-    "        - [Logarithmic axes](#Log-axes)\n",
-    "        - [Datetime axes](#Datetime-axes)\n",
-    "        - [Categorical axes](#Categorical-axes)\n",
-    "    - [Axis position](#Axis-position): Positioning and hiding axes\n",
-    "    - [Inverting axes](#Inverting-axes): Flipping the x-/y-axes and inverting an axis\n",
-    "    - [Axis labels](#Axis-labels): Setting axis labels using dimensions and options\n",
-    "    - [Axis ranges](#Axis-ranges): Controlling axes ranges using dimensions, padding and options\n",
-    "    - [Axis ticks](#Axis-ticks): Controlling axis tick locations, labels and formatting"
+    "* [**Plot**](#customizing-the-plot): Refers to the overall plot which can consist of one or more axes\n",
+    "    - [Titles](#title): Using title formatting and providing custom titles\n",
+    "    - [Background](#background): Setting the plot background color\n",
+    "    - [Font sizes](#font-sizes): Controlling the font sizes on a plot\n",
+    "    - [Legends](#legend-customization): Controlling the position and styling of the legend\n",
+    "    - [Plot hooks](#plot-hooks): Using custom hooks to modify plots\n",
+    "* [**Axes**](#customizing-axes): A set of axes provides scales describing the mapping between data and the space on screen\n",
+    "    - [Types of axes](#types-of-axes):\n",
+    "        - [Linear axes](#linear-axes)\n",
+    "        - [Logarithmic axes](#log-axes)\n",
+    "        - [Datetime axes](#datetime-axes)\n",
+    "        - [Categorical axes](#categorical-axes)\n",
+    "    - [Axis position](#axis-position): Positioning and hiding axes\n",
+    "    - [Inverting axes](#inverting-axes): Flipping the x-/y-axes and inverting an axis\n",
+    "    - [Axis labels](#axis-labels): Setting axis labels using dimensions and options\n",
+    "    - [Axis ranges](#axis-ranges): Controlling axes ranges using dimensions, padding and options\n",
+    "    - [Axis ticks](#axis-ticks): Controlling axis tick locations, labels and formatting"
    ]
   },
   {
@@ -80,7 +80,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The title formatter may however be overriden with an explicit title, which may include any combination of the three formatter variables:"
+    "The title formatter may however be overridden with an explicit title, which may include any combination of the three formatter variables:"
    ]
   },
   {
@@ -606,7 +606,7 @@
    "source": [
     "### Axis ticks\n",
     "\n",
-    "Setting tick locations differs a little bit dependening on the plotting extension, interactive backends such as bokeh or plotly dynamically update the ticks, which means fixed tick locations may not be appropriate and the formatters have to be applied in Javascript code. Nevertheless most options to control the ticking are consistent across extensions.\n",
+    "Setting tick locations differs a little bit depending on the plotting extension, interactive backends such as bokeh or plotly dynamically update the ticks, which means fixed tick locations may not be appropriate and the formatters have to be applied in Javascript code. Nevertheless most options to control the ticking are consistent across extensions.\n",
     "\n",
     "#### Tick locations\n",
     "\n",
diff --git a/examples/user_guide/Geometry_Data.ipynb b/examples/user_guide/Geometry_Data.ipynb
index 6e33e283a..9735a00be 100644
--- a/examples/user_guide/Geometry_Data.ipynb
+++ b/examples/user_guide/Geometry_Data.ipynb
@@ -184,7 +184,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "If a polygon has no holes at all the 'holes' key may be ommitted entirely:"
+    "If a polygon has no holes at all the 'holes' key may be omitted entirely:"
    ]
   },
   {
diff --git a/examples/user_guide/Linking_Plots.ipynb b/examples/user_guide/Linking_Plots.ipynb
index 424953b05..ef2aaf5c9 100644
--- a/examples/user_guide/Linking_Plots.ipynb
+++ b/examples/user_guide/Linking_Plots.ipynb
@@ -138,7 +138,7 @@
    "source": [
     "In this case we are interested in the 'cds' handle, but we still have to tell it which events should trigger the callback. Bokeh callbacks can be grouped into two types, model property changes and events. For more detail on these two types of callbacks see the [Bokeh user guide](https://bokeh.pydata.org/en/latest/docs/user_guide/interaction/callbacks.html#userguide-interaction-jscallbacks).\n",
     "\n",
-    "For this example we want to respond to changes to the ``ColumnDataSource.selected`` property. We can declare this in the ``on_source_changes`` class atttribute on our callback. So now that we have declared which model we want to listen to events on and which events we want to listen to, we have to declare the model on the target we want to change in response. \n",
+    "For this example we want to respond to changes to the ``ColumnDataSource.selected`` property. We can declare this in the ``on_source_changes`` class attribute on our callback. So now that we have declared which model we want to listen to events on and which events we want to listen to, we have to declare the model on the target we want to change in response. \n",
     "\n",
     "We can once again look at the handles on the plot corresponding to the ``HLine`` element:"
    ]
diff --git a/examples/user_guide/Network_Graphs.ipynb b/examples/user_guide/Network_Graphs.ipynb
index 2fcc4594d..41f68faa5 100644
--- a/examples/user_guide/Network_Graphs.ipynb
+++ b/examples/user_guide/Network_Graphs.ipynb
@@ -201,7 +201,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "If you want to supply additional node information without speciying explicit node positions you may pass in a ``Dataset`` object consisting of various value dimensions."
+    "If you want to supply additional node information without specifying explicit node positions you may pass in a ``Dataset`` object consisting of various value dimensions."
    ]
   },
   {
diff --git a/examples/user_guide/Plots_and_Renderers.ipynb b/examples/user_guide/Plots_and_Renderers.ipynb
index 280d593de..d5028815d 100644
--- a/examples/user_guide/Plots_and_Renderers.ipynb
+++ b/examples/user_guide/Plots_and_Renderers.ipynb
@@ -544,8 +544,8 @@
     "\n",
     "There a few methods shared by all plotting classes, which allow the renderer to easily create, update and render a plot. The three most important methods and attributes are:\n",
     "\n",
-    "* ``Plot.__init__`` - The constructor already handles a lot of the processing in a plot, it sets up all the subplots if there are any, computes ranges across the object to normalize the display, sets the options that were specified and instantiates the figure, axes, model graphs, or canvas objects dependening on the backend.\n",
-    "* ``Plot.initialize_plot`` - This method draws the initial frame to the appopriate figure, axis or canvas, setting up the various artists (matplotlib) or glyphs (bokeh).\n",
+    "* ``Plot.__init__`` - The constructor already handles a lot of the processing in a plot, it sets up all the subplots if there are any, computes ranges across the object to normalize the display, sets the options that were specified and instantiates the figure, axes, model graphs, or canvas objects depending on the backend.\n",
+    "* ``Plot.initialize_plot`` - This method draws the initial frame to the appropriate figure, axis or canvas, setting up the various artists (matplotlib) or glyphs (bokeh).\n",
     "* ``Plot.update`` - This method updates an already instantiated plot with the data corresponding to the supplied key. This key should match the key in the HoloMap.\n",
     "\n",
     "### Initializing\n",
diff --git a/examples/user_guide/Plotting_with_Bokeh.ipynb b/examples/user_guide/Plotting_with_Bokeh.ipynb
index 07696d438..d0a6e815f 100644
--- a/examples/user_guide/Plotting_with_Bokeh.ipynb
+++ b/examples/user_guide/Plotting_with_Bokeh.ipynb
@@ -334,10 +334,11 @@
    ]
   },
   {
+   "attachments": {},
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Grid lines can be controlled throught the combination of ``show_grid`` and ``gridstyle`` parameters. The ``gridstyle`` allows specifying a number of options including:\n",
+    "Grid lines can be controlled through the combination of ``show_grid`` and ``gridstyle`` parameters. The ``gridstyle`` allows specifying a number of options including:\n",
     "\n",
     "* ``grid_line_color``\n",
     "* ``grid_line_alpha``\n",
diff --git a/holoviews/__init__.py b/holoviews/__init__.py
index f8b430f11..80bfe48e8 100644
--- a/holoviews/__init__.py
+++ b/holoviews/__init__.py
@@ -103,6 +103,7 @@ from .selection import link_selections                   # noqa (API import)
 from .util import (extension, renderer, output, opts,    # noqa (API import)
                    render, save)
 from .util.transform import dim                          # noqa (API import)
+from .util.warnings import HoloviewsDeprecationWarning, HoloviewsUserWarning  # noqa: F401
 
 # Suppress warnings generated by NumPy in matplotlib
 # Expected to be fixed in next matplotlib release
diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py
index 9243b256b..63ff65d4c 100644
--- a/holoviews/core/accessors.py
+++ b/holoviews/core/accessors.py
@@ -560,10 +560,10 @@ class Opts(metaclass=AccessorPipelineMeta):
             apply_groups, _, _ = util.deprecated_opts_signature(args, kwargs)
             if apply_groups:
                 msg = ("Calling the .opts method with options broken down by options "
-                       "group (i.e. separate plot, style and norm groups) is deprecated. "
+                       "group (i.e. separate plot, style and norm groups) has been removed. "
                        "Use the .options method converting to the simplified format "
                        "instead or use hv.opts.apply_groups for backward compatibility.")
-                param.main.param.warning(msg)
+                raise ValueError(msg)
 
         return self._dispatch_opts( *args, **kwargs)
 
@@ -595,8 +595,7 @@ class Opts(metaclass=AccessorPipelineMeta):
         pprinter = PrettyPrinter(show_options=True, show_defaults=show_defaults)
         print(pprinter.pprint(self._obj))
 
-    def _holomap_opts(self, *args, **kwargs):
-        clone = kwargs.pop('clone', None)
+    def _holomap_opts(self, *args, clone=None, **kwargs):
         apply_groups, _, _ = util.deprecated_opts_signature(args, kwargs)
         data = OrderedDict([(k, v.opts(*args, **kwargs))
                              for k, v in self._obj.data.items()])
diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py
index 73dcac77d..cd73b7048 100644
--- a/holoviews/core/data/__init__.py
+++ b/holoviews/core/data/__init__.py
@@ -27,7 +27,7 @@ from .ibis import IbisInterface               # noqa (API import)
 from .interface import Interface, iloc, ndloc
 from .multipath import MultiInterface         # noqa (API import)
 from .image import ImageInterface             # noqa (API import)
-from .pandas import PandasInterface
+from .pandas import PandasAPI, PandasInterface        # noqa (API import)
 from .spatialpandas import SpatialPandasInterface     # noqa (API import)
 from .spatialpandas_dask import DaskSpatialPandasInterface # noqa (API import)
 from .xarray import XArrayInterface           # noqa (API import)
@@ -124,7 +124,7 @@ class DataConversion:
             else:
                 selected = self._element
         else:
-            if issubclass(self._element.interface, PandasInterface):
+            if issubclass(self._element.interface, PandasAPI):
                 ds_dims = self._element.dimensions()
                 ds_kdims = [self._element.get_dimension(d) if d in ds_dims else d
                             for d in groupby+kdims]
diff --git a/holoviews/core/data/ibis.py b/holoviews/core/data/ibis.py
index 9370e9d5b..a2a888b48 100644
--- a/holoviews/core/data/ibis.py
+++ b/holoviews/core/data/ibis.py
@@ -280,7 +280,7 @@ class IbisInterface(Interface):
 
     @classmethod
     def groupby(cls, dataset, dimensions, container_type, group_type, **kwargs):
-        # aggregate the necesary dimensions
+        # aggregate the necessary dimensions
         index_dims = [dataset.get_dimension(d, strict=True) for d in dimensions]
         element_dims = [kdim for kdim in dataset.kdims if kdim not in index_dims]
 
diff --git a/holoviews/core/data/pandas.py b/holoviews/core/data/pandas.py
index 383593d21..da5cf4403 100644
--- a/holoviews/core/data/pandas.py
+++ b/holoviews/core/data/pandas.py
@@ -5,7 +5,6 @@ import numpy as np
 import pandas as pd
 from pandas.api.types import is_numeric_dtype
 
-from ...util._exception import deprecation_warning
 from .interface import Interface, DataError
 from ..dimension import dimension_name, Dimension
 from ..element import Element
@@ -14,7 +13,20 @@ from .. import util
 from .util import finite_range
 
 
-class PandasInterface(Interface):
+class PandasAPI:
+    """
+    This class is used to describe the interface as having a pandas-like API.
+
+    The reason to have this class is that it is not always
+    possible to directly inherit from the PandasInterface.
+
+    This class should not have any logic as it should be used like:
+        if issubclass(interface, PandasAPI):
+            ...
+    """
+
+
+class PandasInterface(Interface, PandasAPI):
 
     types = (pd.DataFrame,)
 
@@ -60,9 +72,8 @@ class PandasInterface(Interface):
                 vdims = list(data.columns[:nvdim if nvdim else None])
 
             if any(not isinstance(d, (str, Dimension)) for d in kdims+vdims):
-                deprecation_warning(
-                    "Having a non-string as a column name in a DataFrame is deprecated "
-                    "and will not be supported in Holoviews version 1.16."
+                raise DataError(
+                    "Having a non-string as a column name in a DataFrame is not supported."
                 )
 
             # Handle reset of index if kdims reference index by name
@@ -74,9 +85,6 @@ class PandasInterface(Interface):
                        for name in index_names):
                     data = data.reset_index()
                     break
-            if any(isinstance(d, (np.int64, int)) for d in kdims+vdims):
-                raise DataError("pandas DataFrame column names used as dimensions "
-                                "must be strings not integers.", cls)
 
             if kdims:
                 kdim = dimension_name(kdims[0])
diff --git a/holoviews/core/data/xarray.py b/holoviews/core/data/xarray.py
index 8440ba7af..bb1fe922c 100644
--- a/holoviews/core/data/xarray.py
+++ b/holoviews/core/data/xarray.py
@@ -84,7 +84,7 @@ class XArrayInterface(GridInterface):
             unit = coord.attrs.get('units') if dim.unit is None else dim.unit
             if isinstance(unit, tuple):
                 unit = unit[0]
-            if 'long_name' in coord.attrs:
+            if isinstance(coord.attrs.get("long_name"), str):
                 spec = (dim.name, coord.attrs['long_name'])
             else:
                 spec = (dim.name, dim.label)
@@ -104,7 +104,7 @@ class XArrayInterface(GridInterface):
                 vdim.unit = data.attrs.get('units')
                 vdim.nodata = data.attrs.get('NODATA')
                 label = data.attrs.get('long_name')
-                if 'long_name' in data.attrs:
+                if isinstance(label, str):
                     vdim.label = label
             elif len(vdim_param.default) == 1:
                 vdim = vdim_param.default[0]
diff --git a/holoviews/core/decollate.py b/holoviews/core/decollate.py
index 42f866090..d054cf866 100644
--- a/holoviews/core/decollate.py
+++ b/holoviews/core/decollate.py
@@ -42,7 +42,7 @@ def to_expr_extract_streams(
             not in a container
     Returns:
         HoloviewsExpr expression representing hvobj if hvobj is dynamic. Otherwise,
-        reutrn hvobj itself
+        return hvobj itself
     """
     if isinstance(hvobj, DynamicMap):
         args = []
diff --git a/holoviews/core/io.py b/holoviews/core/io.py
index 39842ea0c..8a1324ba1 100644
--- a/holoviews/core/io.py
+++ b/holoviews/core/io.py
@@ -100,7 +100,7 @@ class Exporter(param.ParameterizedFunction):
       high-dimensional key of values associated with dimension labels.
 
       The returned dictionary must have string keys and simple
-      literals that may be conviently used for dictionary-style
+      literals that may be conveniently used for dictionary-style
       indexing. Returns an empty dictionary by default.""")
 
     info_fn = param.Callable(lambda x: {'repr':repr(x)}, doc="""
diff --git a/holoviews/core/options.py b/holoviews/core/options.py
index ad1f33cfa..d92092d39 100644
--- a/holoviews/core/options.py
+++ b/holoviews/core/options.py
@@ -877,7 +877,7 @@ class Compositor(param.Parameterized):
        The overlay pattern to be processed. An overlay pattern is a
        sequence of elements specified by dotted paths separated by * .
 
-       For instance the following pattern specifies three overlayed
+       For instance the following pattern specifies three overlaid
        matrices with values of 'RedChannel', 'GreenChannel' and
        'BlueChannel' respectively:
 
diff --git a/holoviews/core/overlay.py b/holoviews/core/overlay.py
index e25cf2d4b..7b81d72ec 100644
--- a/holoviews/core/overlay.py
+++ b/holoviews/core/overlay.py
@@ -1,7 +1,7 @@
 """
 Supplies Layer and related classes that allow overlaying of Views,
 including Overlay. A Layer is the final extension of View base class
-that allows Views to be overlayed on top of each other.
+that allows Views to be overlaid on top of each other.
 
 Also supplies ViewMap which is the primary multi-dimensional Map type
 for indexing, slicing and animating collections of Views.
diff --git a/holoviews/core/pprint.py b/holoviews/core/pprint.py
index 199c1c085..733b84bad 100644
--- a/holoviews/core/pprint.py
+++ b/holoviews/core/pprint.py
@@ -96,7 +96,7 @@ class InfoPrinter:
         if cls.ppager is None: return ''
         if pattern is not None:
             obj = ParamFilter(obj, ParamFilter.regexp_filter(pattern))
-            if len(obj.param.params()) <= 1:
+            if len(list(obj.param)) <= 1:
                 return None
         param_info = cls.ppager.get_param_info(obj)
         param_list = cls.ppager.param_docstrings(param_info)
@@ -152,7 +152,7 @@ class InfoPrinter:
         if visualization is False or plot_class is None:
             if pattern is not None:
                 obj = ParamFilter(obj, ParamFilter.regexp_filter(pattern))
-                if len(obj.param.params()) <= 1:
+                if len(list(obj.param)) <= 1:
                     return ('No %r parameters found matching specified pattern %r'
                             % (name, pattern))
             info = param.ipython.ParamPager()(obj)
diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py
index a1b511392..77eb98a90 100644
--- a/holoviews/core/spaces.py
+++ b/holoviews/core/spaces.py
@@ -535,7 +535,7 @@ class Callable(param.Parameterized):
         """Calls the callable function with supplied args and kwargs.
 
         If enabled uses memoization to avoid calling function
-        unneccessarily.
+        unnecessarily.
 
         Args:
             *args: Arguments passed to the callable function
@@ -573,7 +573,7 @@ class Callable(param.Parameterized):
             clashes = set(pos_kwargs.keys()) & set(kwargs.keys())
             if clashes:
                 self.param.warning(
-                    f'Positional arguments {list(clashes)!r} overriden by keywords')
+                    f'Positional arguments {list(clashes)!r} overridden by keywords')
             args, kwargs = (), dict(pos_kwargs, **kwargs)
 
         try:
@@ -832,7 +832,7 @@ class DynamicMap(HoloMap):
     def unbounded(self):
         """
         Returns a list of key dimensions that are unbounded, excluding
-        stream parameters. If any of theses key dimensions are
+        stream parameters. If any of these key dimensions are
         unbounded, the DynamicMap as a whole is also unbounded.
         """
         unbounded_dims = []
diff --git a/holoviews/core/util.py b/holoviews/core/util.py
index 34d63bde9..ebf524199 100644
--- a/holoviews/core/util.py
+++ b/holoviews/core/util.py
@@ -136,10 +136,6 @@ class Config(param.ParameterizedFunction):
        Global default colormap for HeatMap elements. Prior to HoloViews
        1.14.0, the default value was the 'RdYlBu_r' colormap.""")
 
-    raise_deprecated_tilesource_exception = param.Boolean(default=False,
-       doc=""" Whether deprecated tile sources should raise a
-       deprecation exception instead of issuing warnings.""")
-
     def __call__(self, **params):
         self.param.set_param(**params)
         return self
diff --git a/holoviews/element/annotation.py b/holoviews/element/annotation.py
index f439dc768..9b4e7d0cc 100644
--- a/holoviews/element/annotation.py
+++ b/holoviews/element/annotation.py
@@ -226,7 +226,7 @@ class VSpan(Annotation):
 
 
 class HSpan(Annotation):
-    """Horziontal span annotation at the given position."""
+    """Horizontal span annotation at the given position."""
 
     group = param.String(default='HSpan', constant=True)
 
diff --git a/holoviews/element/chart.py b/holoviews/element/chart.py
index cff1bcedc..437447057 100644
--- a/holoviews/element/chart.py
+++ b/holoviews/element/chart.py
@@ -83,7 +83,7 @@ class ErrorBars(Selection1DExpr, Chart):
     location along the x-axis and the first value dimension
     corresponds to the location along the y-axis and one or two
     extra value dimensions corresponding to the symmetric or
-    asymetric errors either along x-axis or y-axis. If two value
+    asymmetric errors either along x-axis or y-axis. If two value
     dimensions are given, then the last value dimension will be
     taken as symmetric errors. If three value dimensions are given
     then the last two value dimensions will be taken as negative and
@@ -105,7 +105,7 @@ class ErrorBars(Selection1DExpr, Chart):
     def range(self, dim, data_range=True, dimension_range=True):
         """Return the lower and upper bounds of values along dimension.
 
-        Range of the y-dimension includes the symmetric or assymetric
+        Range of the y-dimension includes the symmetric or asymmetric
         error.
 
         Args:
@@ -142,7 +142,7 @@ class Spread(ErrorBars):
     confidence band in a 1D coordinate system. The key dimension(s)
     corresponds to the location along the x-axis and the value
     dimensions define the location along the y-axis as well as the
-    symmetric or assymetric spread.
+    symmetric or asymmetric spread.
     """
 
     group = param.String(default='Spread', constant=True)
@@ -215,7 +215,7 @@ class Spikes(Selection1DExpr, Chart):
 
     kdims = param.List(default=[Dimension('x')], bounds=(1, 1))
 
-    vdims = param.List(default=[])
+    vdims = param.List(default=[], bounds=(0, None))
 
     _auto_indexable_1d = False
 
diff --git a/holoviews/element/chart3d.py b/holoviews/element/chart3d.py
index 49b91e910..fdce74623 100644
--- a/holoviews/element/chart3d.py
+++ b/holoviews/element/chart3d.py
@@ -54,7 +54,7 @@ class TriSurface(Element3D, Points):
     group = param.String(default='TriSurface', constant=True)
 
     kdims = param.List(default=[
-        Dimension('x'), Dimension('y'), Dimension('z')], doc="""
+        Dimension('x'), Dimension('y'), Dimension('z')], bounds=(3, 3), doc="""
         The key dimensions of a TriSurface represent the 3D coordinates
         of each point.""")
 
@@ -118,7 +118,7 @@ class Scatter3D(Element3D, Points):
 
     kdims = param.List(default=[Dimension('x'),
                                 Dimension('y'),
-                                Dimension('z')])
+                                Dimension('z')], bounds=(3, 3))
 
     vdims = param.List(default=[], doc="""
         Scatter3D can have optional value dimensions,
@@ -140,7 +140,7 @@ class Path3D(Element3D, Path):
 
     kdims = param.List(default=[Dimension('x'),
                                 Dimension('y'),
-                                Dimension('z')])
+                                Dimension('z')], bounds=(3, 3))
 
     vdims = param.List(default=[], doc="""
         Path3D can have optional value dimensions.""")
diff --git a/holoviews/element/comparison.py b/holoviews/element/comparison.py
index 1ac05c7d0..b7fac3606 100644
--- a/holoviews/element/comparison.py
+++ b/holoviews/element/comparison.py
@@ -749,13 +749,6 @@ class Comparison(ComparisonInterface):
     def compare_cycles(cls, cycle1, cycle2, msg=None):
         cls.assertEqual(cycle1.values, cycle2.values)
 
-    @classmethod
-    def compare_channelopts(cls, opt1, opt2, msg=None):
-        cls.assertEqual(opt1.mode, opt2.mode)
-        cls.assertEqual(opt1.pattern, opt2.pattern)
-        cls.assertEqual(opt1.patter, opt2.pattern)
-
-
 
 class ComparisonTestCase(Comparison, TestCase):
     """
diff --git a/holoviews/element/path.py b/holoviews/element/path.py
index f8c021dd4..9b8c781aa 100644
--- a/holoviews/element/path.py
+++ b/holoviews/element/path.py
@@ -254,7 +254,7 @@ class Polygons(Contours):
     for each coordinate array to be split into a multi-geometry
     through NaN-separators. Each sub-geometry separated by the NaNs
     therefore has an unambiguous mapping to a list of holes. If a
-    (multi-)polygon has no holes, the 'holes' key may be ommitted.
+    (multi-)polygon has no holes, the 'holes' key may be omitted.
 
     Any value dimensions stored on a Polygons geometry must be scalar,
     just like the Contours element. Since not all formats allow
diff --git a/holoviews/element/tabular.py b/holoviews/element/tabular.py
index 338bfc993..b0c333e0e 100644
--- a/holoviews/element/tabular.py
+++ b/holoviews/element/tabular.py
@@ -25,7 +25,7 @@ class ItemTable(Element):
        ItemTables hold an index Dimension for each value they contain, i.e.
        they are equivalent to the keys.""")
 
-    vdims = param.List(default=[Dimension('Default')], bounds=(00, None), doc="""
+    vdims = param.List(default=[Dimension('Default')], bounds=(0, None), doc="""
        ItemTables should have only index Dimensions.""")
 
     group = param.String(default="ItemTable", constant=True)
diff --git a/holoviews/element/tiles.py b/holoviews/element/tiles.py
index 3993e8dd6..2c2e81912 100644
--- a/holoviews/element/tiles.py
+++ b/holoviews/element/tiles.py
@@ -3,7 +3,6 @@ from types import FunctionType
 import param
 import numpy as np
 
-from ..core import util
 from ..core.dimension import Dimension
 from ..core.element import Element2D
 from ..util.transform import lon_lat_to_easting_northing, easting_northing_to_lon_lat
@@ -127,27 +126,10 @@ _ATTRIBUTIONS = {
     )
 }
 
-def deprecation_warning(name, url, reason):
-    def deprecated_tilesource_warning():
-        """
-        NOTE: The function name `deprecated_tilesource_warning' used to filter deprecated tile_sources
-        """
-        if util.config.raise_deprecated_tilesource_exception:
-            raise DeprecationWarning(f'{name} tile source is deprecated: {reason}')
-        param.main.param.warning(f'{name} tile source is deprecated and is likely to be unusable: {reason}')
-        return Tiles(url, name=name)
-    return deprecated_tilesource_warning
-
 
 # CartoDB basemaps
 CartoDark = lambda: Tiles('https://cartodb-basemaps-4.global.ssl.fastly.net/dark_all/{Z}/{X}/{Y}.png', name="CartoDark")
 CartoLight = lambda: Tiles('https://cartodb-basemaps-4.global.ssl.fastly.net/light_all/{Z}/{X}/{Y}.png', name="CartoLight")
-CartoMidnight = deprecation_warning('CartoMidnight',
-                                    'https://3.api.cartocdn.com/base-midnight/{Z}/{X}/{Y}.png',
-                                    'no longer publicly available.')
-CartoEco = deprecation_warning('CartoEco',
-                               'https://3.api.cartocdn.com/base-eco/{Z}/{X}/{Y}.png',
-                               'no longer publicly available.')
 
 
 # Stamen basemaps
@@ -171,25 +153,12 @@ EsriReference = lambda: Tiles('https://server.arcgisonline.com/ArcGIS/rest/servi
 ESRI = EsriImagery # For backwards compatibility with gv 1.5
 
 
-def wikimedia_replacement():
-    if util.config.raise_deprecated_tilesource_exception:
-        raise DeprecationWarning('Wikipedia tile source no longer available outside '
-                                 'wikimedia domain as of April 2021.')
-
-    param.main.param.warning('Wikipedia tile source no longer available outside '
-                             'wikimedia domain as of April 2021; switching '
-                             'to OpenStreetMap (OSM) tile source. '
-                             'See release notes for HoloViews'
-                             ' 1.14.4 for more details')
-    return Tiles('https://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png', name="OSM")
-
 # Miscellaneous
 OSM = lambda: Tiles('https://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png', name="OSM")
 OpenTopoMap = lambda: Tiles('https://a.tile.opentopomap.org/{Z}/{X}/{Y}.png', name="OpenTopoMap")
-Wikipedia = wikimedia_replacement
 
 _all_tile_sources = {k: v for k, v in locals().items() if isinstance(v, FunctionType) and k not in
                 ['ESRI', 'lon_lat_to_easting_northing', 'easting_northing_to_lon_lat',
                  'deprecation_warning', 'wikimedia_replacement']}
 
-tile_sources = {k: v for k, v in _all_tile_sources.items() if v.__name__ != 'deprecated_tilesource_warning'}
+tile_sources = {k: v for k, v in _all_tile_sources.items()}
diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py
index b92f85c1a..e43b87d61 100644
--- a/holoviews/plotting/bokeh/callbacks.py
+++ b/holoviews/plotting/bokeh/callbacks.py
@@ -102,7 +102,7 @@ class Callback:
     _transforms = []
 
     # Asyncio background task
-    _baskground_task = set()
+    _background_task = set()
 
     def __init__(self, plot, streams, source, **params):
         self.plot = plot
@@ -276,7 +276,7 @@ class Callback:
             self._set_busy(True)
             task = asyncio.create_task(self.process_on_change())
             self._background_task.add(task)
-            task.add_done_callback(self._baskground_task.discard)
+            task.add_done_callback(self._background_task.discard)
 
     async def on_event(self, event):
         """
@@ -289,7 +289,7 @@ class Callback:
             self._set_busy(True)
             task = asyncio.create_task(self.process_on_event())
             self._background_task.add(task)
-            task.add_done_callback(self._baskground_task.discard)
+            task.add_done_callback(self._background_task.discard)
 
     async def process_on_event(self, timeout=None):
         """
@@ -317,7 +317,7 @@ class Callback:
             self.on_msg(msg)
         task = asyncio.create_task(self.process_on_event())
         self._background_task.add(task)
-        task.add_done_callback(self._baskground_task.discard)
+        task.add_done_callback(self._background_task.discard)
 
     async def process_on_change(self):
         # Give on_change time to process new events
@@ -352,7 +352,7 @@ class Callback:
             self._prev_msg = msg
         task = asyncio.create_task(self.process_on_change())
         self._background_task.add(task)
-        task.add_done_callback(self._baskground_task.discard)
+        task.add_done_callback(self._background_task.discard)
 
     def set_callback(self, handle):
         """
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index d17026816..9a546902a 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -561,7 +561,7 @@ class ElementPlot(BokehPlot, GenericElementPlot):
         if self.border is not None:
             for p in ['left', 'right', 'top', 'bottom']:
                 plot_props['min_border_'+p] = self.border
-        lod = dict(self.param.defaults().get('lod', {}), **self.lod)
+        lod = dict(self.param["lod"].default, **self.lod) if "lod" in self.param else self.lod
         for lod_prop, v in lod.items():
             plot_props['lod_'+lod_prop] = v
         return plot_props
diff --git a/holoviews/plotting/bokeh/geometry.py b/holoviews/plotting/bokeh/geometry.py
index c5fa92301..5b56c8c2e 100644
--- a/holoviews/plotting/bokeh/geometry.py
+++ b/holoviews/plotting/bokeh/geometry.py
@@ -50,7 +50,7 @@ class RectanglesPlot(GeomMixin, LegendPlot, ColorbarPlot):
 
     _allow_implicit_categories = False
     _nonvectorized_styles = base_properties + ['cmap']
-    _plot_methods = dict(single='rect')
+    _plot_methods = dict(single='quad')
     _batched_style_opts = line_properties + fill_properties
     _color_style = 'fill_color'
 
@@ -60,11 +60,11 @@ class RectanglesPlot(GeomMixin, LegendPlot, ColorbarPlot):
         x0, x1 = np.min([x0, x1], axis=0), np.max([x0, x1], axis=0)
         y0, y1 = np.min([y0, y1], axis=0), np.max([y0, y1], axis=0)
         data = {
-            'x': x0+(x1-x0)/2.,
-            'y': y0+(y1-y0)/2.,
-            'width': x1-x0,
-            'height': y1-y0
+            'left': x0,
+            'right': x1,
+            'bottom': y0,
+            'top': y1
         }
-        mapping = {'x': 'x', 'y': 'y', 'width': 'width', 'height': 'height'}
+        mapping = {'left': 'left', 'right': 'right', 'bottom': 'bottom', 'top': 'top'}
         self._get_hover_data(data, element)
         return data, mapping, style
diff --git a/holoviews/plotting/bokeh/hex_tiles.py b/holoviews/plotting/bokeh/hex_tiles.py
index f1cd85513..5355cf91b 100644
--- a/holoviews/plotting/bokeh/hex_tiles.py
+++ b/holoviews/plotting/bokeh/hex_tiles.py
@@ -20,7 +20,7 @@ class hex_binning(Operation):
     Applies hex binning by computing aggregates on a hexagonal grid.
 
     Should not be user facing as the returned element is not directly
-    useable.
+    usable.
     """
 
     aggregator = param.ClassSelector(
diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py
index 93cc345a4..78f7408f3 100644
--- a/holoviews/plotting/bokeh/plot.py
+++ b/holoviews/plotting/bokeh/plot.py
@@ -31,7 +31,7 @@ from ..util import attach_streams, displayable, collate
 from .links import LinkCallback
 from .util import (
     bokeh3, filter_toolboxes, make_axis, update_shared_sources, empty_plot,
-    decode_bytes, theme_attr_json, cds_column_replace, get_default
+    decode_bytes, theme_attr_json, cds_column_replace, get_default, merge_tools
 )
 
 if bokeh3:
@@ -582,11 +582,13 @@ class GridPlot(CompositePlot, GenericCompositePlot):
             else:
                 passed_plots.append(None)
 
-        toolbar_location = None if bokeh3 else self.toolbar
-        plot = gridplot(plots[::-1], merge_tools=self.merge_tools,
+        plot = gridplot(plots[::-1],
+                        merge_tools=self.merge_tools,
                         sizing_mode=self.sizing_mode,
-                        toolbar_location=toolbar_location)
+                        toolbar_location=self.toolbar)
         plot = self._make_axes(plot)
+        if bokeh3 and hasattr(plot, "toolbar"):
+            plot.toolbar = merge_tools(plots)
 
         title = self._get_title_div(self.keys[-1])
         if title:
@@ -637,10 +639,7 @@ class GridPlot(CompositePlot, GenericCompositePlot):
             if self.shared_yaxis:
                 x_axis.margin = (0, 0, 0, 50)
                 r1, r2 = r1[::-1], r2[::-1]
-            if bokeh3:
-                plot = gridplot([r1, r2], toolbar_location=None)
-            else:
-                plot = gridplot([r1, r2])
+            plot = gridplot([r1, r2])
         elif y_axis:
             models = [y_axis, plot]
             if self.shared_yaxis: models = models[::-1]
@@ -917,15 +916,14 @@ class LayoutPlot(CompositePlot, GenericLayoutPlot):
 
                     if nsubplots == 1:
                         grid = subplots[0]
-                    elif nsubplots == 2:
-                        grid = gridplot([subplots], merge_tools=self.merge_tools,
-                                        toolbar_location=self.toolbar,
-                                        sizing_mode=sizing_mode)
                     else:
-                        grid = [[subplots[2], None], subplots[:2]]
-                        grid = gridplot(children=grid, merge_tools=self.merge_tools,
+                        children = [subplots] if nsubplots == 2 else [[subplots[2], None], subplots[:2]]
+                        grid = gridplot(children,
+                                        merge_tools=self.merge_tools,
                                         toolbar_location=self.toolbar,
                                         sizing_mode=sizing_mode)
+                        if bokeh3:
+                            grid.toolbar = merge_tools(children)
                     tab_plots.append((title, grid))
                     continue
 
@@ -958,10 +956,14 @@ class LayoutPlot(CompositePlot, GenericLayoutPlot):
             layout_plot = Tabs(tabs=panels, sizing_mode=sizing_mode)
         else:
             plot_grid = filter_toolboxes(plot_grid)
-            layout_plot = gridplot(children=plot_grid,
-                                   toolbar_location=self.toolbar,
-                                   merge_tools=self.merge_tools,
-                                   sizing_mode=sizing_mode)
+            layout_plot = gridplot(
+                children=plot_grid,
+                toolbar_location=self.toolbar,
+                merge_tools=self.merge_tools,
+                sizing_mode=sizing_mode
+            )
+            if bokeh3:
+                layout_plot.toolbar = merge_tools(plot_grid)
 
         title = self._get_title_div(self.keys[-1])
         if title:
diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py
index dd316e679..b3cfda6fc 100644
--- a/holoviews/plotting/bokeh/raster.py
+++ b/holoviews/plotting/bokeh/raster.py
@@ -269,6 +269,7 @@ class QuadMeshPlot(ColorbarPlot):
         x, y = dimension_sanitizer(x.name), dimension_sanitizer(y.name)
 
         zdata = element.dimension_values(z, flat=False)
+        hover_data = {}
 
         if irregular:
             dims = element.kdims
@@ -291,11 +292,15 @@ class QuadMeshPlot(ColorbarPlot):
                         yc.append(ys.mean())
                 else:
                     mask.append(False)
+            mask = np.array(mask)
 
-            data = {'xs': XS, 'ys': YS, z.name: zvals[np.array(mask)]}
+            data = {'xs': XS, 'ys': YS, z.name: zvals[mask]}
             if 'hover' in self.handles:
-                data[x] = np.array(xc)
-                data[y] = np.array(yc)
+                if not self.static_source:
+                    hover_data = self._collect_hover_data(
+                            element, mask, irregular=True)
+                hover_data[x] = np.array(xc)
+                hover_data[y] = np.array(yc)
         else:
             xc, yc = (element.interface.coords(element, x, edges=True, ordered=True),
                       element.interface.coords(element, y, edges=True, ordered=True))
@@ -307,17 +312,34 @@ class QuadMeshPlot(ColorbarPlot):
                     'bottom': y0, 'top': y1}
 
             if 'hover' in self.handles and not self.static_source:
-                hover_dims = element.dimensions()[3:]
-                hover_data = [element.dimension_values(hover_dim, flat=False)
-                              for hover_dim in hover_dims]
-                for hdim, hdat in zip(hover_dims, hover_data):
-                    data[dimension_sanitizer(hdim.name)] = (hdat.flatten()
-                        if self.invert_axes else hdat.T.flatten())
-                data[x] = element.dimension_values(x)
-                data[y] = element.dimension_values(y)
+                hover_data = self._collect_hover_data(element)
+                hover_data[x] = element.dimension_values(x)
+                hover_data[y] = element.dimension_values(y)
+
+        data.update(hover_data)
 
         return data, mapping, style
 
+    def _collect_hover_data(self, element, mask=(), irregular=False):
+        """
+        Returns a dict mapping hover dimension names to flattened arrays.
+
+        Note that `Quad` glyphs are used when given 1-D coords but `Patches` are
+        used for "irregular" 2-D coords, and Bokeh inserts data into these glyphs
+        in the opposite order such that the relationship b/w the `invert_axes`
+        parameter and the need to transpose the arrays before flattening is
+        reversed.
+        """
+        transpose = self.invert_axes if irregular else not self.invert_axes
+
+        hover_dims = element.dimensions()[3:]
+        hover_vals = [element.dimension_values(hover_dim, flat=False)
+                      for hover_dim in hover_dims]
+        hover_data = {}
+        for hdim, hvals in zip(hover_dims, hover_vals):
+            hdat = hvals.T.flatten() if transpose else hvals.flatten()
+            hover_data[dimension_sanitizer(hdim.name)] = hdat[mask]
+        return hover_data
 
     def _init_glyph(self, plot, mapping, properties):
         """
diff --git a/holoviews/plotting/bokeh/sankey.py b/holoviews/plotting/bokeh/sankey.py
index d91d21bc2..f6990052f 100644
--- a/holoviews/plotting/bokeh/sankey.py
+++ b/holoviews/plotting/bokeh/sankey.py
@@ -167,7 +167,7 @@ class SankeyPlot(GraphPlot):
 
         is_outer_inner = self.label_position in ['outer', 'inner']
 
-        # initalize label x-locations
+        # initialize label x-locations
         if self.label_position in ['right', 'outer']:
             xs = np.array([node['x1'] for node in nodes]) + offset
         else: # ['left', 'inner']
diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py
index c0c27f6b8..d1255091e 100644
--- a/holoviews/plotting/bokeh/util.py
+++ b/holoviews/plotting/bokeh/util.py
@@ -41,8 +41,9 @@ bokeh_version = Version(bokeh.__version__)
 bokeh3 = bokeh_version >= Version("3.0")
 
 if bokeh3:
+    from bokeh.layouts import group_tools
     from bokeh.models.formatters import CustomJSTickFormatter
-    from bokeh.models import Toolbar, Tabs, GridPlot
+    from bokeh.models import Toolbar, Tabs, GridPlot, SaveTool, CopyTool, ExamineTool, FullscreenTool, LayoutDOM
     from bokeh.plotting import figure
     class WidgetBox: pass  # Does not exist in Bokeh 3
 
@@ -374,6 +375,39 @@ def compute_layout_properties(
     return aspect_info, dimension_info
 
 
+def merge_tools(plot_grid, disambiguation_properties=None):
+    """
+    Merges tools defined on a grid of plots into a single toolbar.
+    All tools of the same type are merged unless they define one
+    of the disambiguation properties. By default `name`, `icon`, `tags`
+    and `description` can be used to prevent tools from being merged.
+    """
+    tools = []
+    for row in plot_grid:
+        for item in row:
+            if isinstance(item, LayoutDOM):
+                for p in item.select(dict(type=Plot)):
+                    tools.extend(p.toolbar.tools)
+            if isinstance(item, GridPlot):
+                item.toolbar_location = None
+
+    def merge(tool, group):
+        if issubclass(tool, (SaveTool, CopyTool, ExamineTool, FullscreenTool)):
+            return tool()
+        else:
+            return None
+
+    if not disambiguation_properties:
+        disambiguation_properties = {'name', 'icon', 'tags', 'description'}
+
+    ignore = set()
+    for tool in tools:
+        for p in tool.properties_with_values():
+            if p not in disambiguation_properties:
+                ignore.add(p)
+
+    return Toolbar(tools=group_tools(tools, merge=merge, ignore=ignore) if merge_tools else tools)
+
 @contextmanager
 def silence_warnings(*warnings):
     """
@@ -575,6 +609,8 @@ def filter_toolboxes(plots):
     """
     if isinstance(plots, list):
         plots = [filter_toolboxes(plot) for plot in plots]
+    elif hasattr(plots, 'toolbar'):
+        plots.toolbar_location = None
     elif hasattr(plots, 'children'):
         plots.children = [filter_toolboxes(child) for child in plots.children
                           if not isinstance(child, Toolbar)]
diff --git a/holoviews/plotting/mpl/geometry.py b/holoviews/plotting/mpl/geometry.py
index dd227cf45..a54a424e6 100644
--- a/holoviews/plotting/mpl/geometry.py
+++ b/holoviews/plotting/mpl/geometry.py
@@ -44,7 +44,7 @@ class SegmentPlot(GeomMixin, ColorbarPlot):
 
 class RectanglesPlot(GeomMixin, ColorbarPlot):
     """
-    Rectanlges are polygons in 2D space where the key dimensions represent
+    Rectangles are polygons in 2D space where the key dimensions represent
     the bottom-left and top-right corner of the rectangle.
     """
 
diff --git a/holoviews/plotting/mpl/plot.py b/holoviews/plotting/mpl/plot.py
index cfcf4f2a7..07262ce21 100644
--- a/holoviews/plotting/mpl/plot.py
+++ b/holoviews/plotting/mpl/plot.py
@@ -776,7 +776,7 @@ class LayoutPlot(GenericLayoutPlot, CompositePlot):
         appropriate embedded layout_types. The first element of the
         returned tuple is a dictionary of all the LayoutPlots indexed
         by row and column. The second dictionary in the tuple supplies
-        the grid indicies needed to instantiate the axes for each
+        the grid indices needed to instantiate the axes for each
         LayoutPlot.
         """
         layout_items = layout.grid_items()
diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py
index 7ea335c55..1b348b8ee 100644
--- a/holoviews/plotting/plot.py
+++ b/holoviews/plotting/plot.py
@@ -300,7 +300,7 @@ class PlotSelector:
         return getattr(plt_class, 'selection_display', None)
 
     def _define_interface(self, plots, allow_mismatch):
-        parameters = [{k:v.precedence for k,v in plot.param.params().items()
+        parameters = [{k:v.precedence for k,v in plot.param.objects().items()
                        if ((v.precedence is None) or (v.precedence >= 0))}
                       for plot in plots]
         param_sets = [set(params.keys()) for params in parameters]
diff --git a/holoviews/plotting/plotly/callbacks.py b/holoviews/plotting/plotly/callbacks.py
index c9423d4be..1d73678d6 100644
--- a/holoviews/plotting/plotly/callbacks.py
+++ b/holoviews/plotting/plotly/callbacks.py
@@ -29,7 +29,7 @@ class PlotlyCallbackMetaClass(type):
 
         # Store weak reference to the callback instance in the _instances
         # WeakValueDictionary. This will allow instances to be garbage collected and
-        # the references will be automatically removed from the colleciton when this
+        # the references will be automatically removed from the collection when this
         # happens.
         cls.instances[inst.plot.trace_uid] = inst
 
diff --git a/holoviews/plotting/plotly/element.py b/holoviews/plotting/plotly/element.py
index 3cfa9a271..261102931 100644
--- a/holoviews/plotting/plotly/element.py
+++ b/holoviews/plotting/plotly/element.py
@@ -700,7 +700,7 @@ class OverlayPlot(GenericOverlayPlot, ElementPlot):
         ranges = self.compute_ranges(self.hmap, key, ranges)
         figure = None
 
-        # Check if elements should be overlayed in geographic coordinates using mapbox
+        # Check if elements should be overlaid in geographic coordinates using mapbox
         #
         # Pass this through to generate_plot to build geo version of plot
         for _, el in items:
diff --git a/holoviews/plotting/plotly/util.py b/holoviews/plotting/plotly/util.py
index caf36d604..579f6e641 100644
--- a/holoviews/plotting/plotly/util.py
+++ b/holoviews/plotting/plotly/util.py
@@ -606,7 +606,7 @@ def _compute_subplot_domains(widths, spacing):
     Parameters
     ----------
     widths: list of float
-        List of the desired withs of each subplot. The length of this list
+        List of the desired widths of each subplot. The length of this list
         is also the specification of the number of desired subplots
     spacing: float
         Spacing between subplots in normalized coordinates
@@ -651,7 +651,7 @@ def figure_grid(figures_grid,
         produce the resulting figure.  None values maybe used to leave empty
         grid cells
     row_spacing: float (default 50)
-        Vertical spacing between rows in the gird in pixels
+        Vertical spacing between rows in the grid in pixels
     column_spacing: float (default 50)
         Horizontal spacing between columns in the grid in pixels
         coordinates
@@ -873,7 +873,7 @@ def configure_matching_axes_from_dims(fig, matching_prop='_dim'):
             # Get axis reference as used by matching ('xaxis3' -> 'x3')
             axis_ref = k.replace('axis', '')
 
-            # Append axis entry to maping
+            # Append axis entry to mapping
             axis_pair = (axis_ref, v)
             axis_map[matching_val].append(axis_pair)
 
diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py
index 24870210d..4426c9662 100644
--- a/holoviews/plotting/renderer.py
+++ b/holoviews/plotting/renderer.py
@@ -24,7 +24,12 @@ from panel.models.comm_manager import CommManager as PnCommManager
 from panel.pane import HoloViews as HoloViewsPane
 from panel.widgets.player import PlayerBase
 from panel.viewable import Viewable
-from pyviz_comms import CommManager, JupyterCommManager
+from pyviz_comms import CommManager
+try:
+    # Added in Panel 1.0 to support JS -> Python binary comms
+    from panel.io.notebook import JupyterCommManagerBinary as JupyterCommManager
+except ImportError:
+    from pyviz_comms import JupyterCommManager
 
 from ..core import Layout, HoloMap, AdjointLayout, DynamicMap
 from ..core.data import disable_pipeline
diff --git a/holoviews/selection.py b/holoviews/selection.py
index 0ea6ce0cd..1871cb593 100644
--- a/holoviews/selection.py
+++ b/holoviews/selection.py
@@ -137,7 +137,7 @@ class _base_link_selections(param.ParameterizedFunction):
         self.param.set_param(**kwargs)
 
         if Store.current_backend not in Store.renderers:
-            raise RuntimeError("Cannot peform link_selections operation "
+            raise RuntimeError("Cannot perform link_selections operation "
                                "since the selected backend %r is not "
                                "loaded. Load the plotting extension with "
                                "hv.extension or import the plotting "
diff --git a/holoviews/streams.py b/holoviews/streams.py
index d5eb39ff4..cad4b23dd 100644
--- a/holoviews/streams.py
+++ b/holoviews/streams.py
@@ -577,7 +577,7 @@ class Buffer(Pipe):
             if x.ndim != 2:
                 raise ValueError('Streamed array data must be two-dimensional')
             elif x.shape[1] != self.data.shape[1]:
-                raise ValueError("Streamed array data expeced to have %d columns, "
+                raise ValueError("Streamed array data expected to have %d columns, "
                                  "got %d." % (self.data.shape[1], x.shape[1]))
         elif isinstance(x, pd.DataFrame) and list(x.columns) != list(self.data.columns):
             raise IndexError("Input expected to have columns %s, got %s" %
diff --git a/holoviews/util/__init__.py b/holoviews/util/__init__.py
index 55d9dee7f..b3ee7a997 100644
--- a/holoviews/util/__init__.py
+++ b/holoviews/util/__init__.py
@@ -9,7 +9,7 @@ from types import FunctionType
 from pathlib import Path
 
 import param
-
+import panel as pn
 from pyviz_comms import extension as _pyviz_extension
 
 from ..core import (
@@ -719,6 +719,18 @@ class extension(_pyviz_extension):
             raise ImportError('None of the backends could be imported')
         Store.set_current_backend(selected_backend)
 
+        if pn.config.comms == "default":
+            try:
+                import google.colab  # noqa
+                pn.config.comms = "colab"
+                return
+            except ImportError:
+                pass
+
+            if "VSCODE_PID" in os.environ:
+                pn.config.comms = "vscode"
+                return
+
     @classmethod
     def register_backend_callback(cls, backend, callback):
         """Registers a hook which is run when a backend is loaded"""
@@ -815,7 +827,7 @@ def render(obj, backend=None, **kwargs):
 
     Returns
     -------
-    renderered:
+    rendered:
         The rendered representation of the HoloViews object, e.g.
         if backend='matplotlib' a matplotlib Figure or FuncAnimation
     """
diff --git a/holoviews/util/_exception.py b/holoviews/util/_exception.py
deleted file mode 100644
index ba64df18e..000000000
--- a/holoviews/util/_exception.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import os
-import inspect
-from functools import lru_cache
-from warnings import warn
-
-
-@lru_cache(maxsize=None)
-def deprecation_warning(msg, warning=FutureWarning):
-    "To only run the warning once"
-
-    # Finding the first stacklevel outside holoviews and param
-    # Inspired by: pandas.util._exceptions.find_stack_level
-    import holoviews as hv
-    import param
-
-    pkg_dir = os.path.dirname(hv.__file__)
-    test_dir = os.path.join(pkg_dir, "tests")
-    param_dir = os.path.dirname(param.__file__)
-
-    frame = inspect.currentframe()
-    stacklevel = 0
-    while frame:
-        fname = inspect.getfile(frame)
-        if (fname.startswith(pkg_dir) or fname.startswith(param_dir)) and not fname.startswith(test_dir):
-            frame = frame.f_back
-            stacklevel += 1
-        else:
-            break
-
-    warn(msg, warning, stacklevel=stacklevel)
diff --git a/holoviews/util/transform.py b/holoviews/util/transform.py
index cc12187b3..220dbdf3f 100644
--- a/holoviews/util/transform.py
+++ b/holoviews/util/transform.py
@@ -294,7 +294,7 @@ class dim:
         if (not self.ops or not isinstance(self.ops[-1]['fn'], str) or
             'accessor' not in self.ops[-1]['kwargs']):
             raise ValueError("Cannot call method on %r expression. "
-                             "Only methods accessed via namspaces, "
+                             "Only methods accessed via namespaces, "
                              "e.g. dim(...).df or dim(...).xr), "
                              "can be called. " % self)
         op = self.ops[-1]
diff --git a/holoviews/util/warnings.py b/holoviews/util/warnings.py
new file mode 100644
index 000000000..dad85e9a0
--- /dev/null
+++ b/holoviews/util/warnings.py
@@ -0,0 +1,90 @@
+import inspect
+import os
+import warnings
+
+import param
+
+from packaging.version import Version
+
+__all__ = (
+    "deprecated",
+    "find_stack_level",
+    "HoloviewsDeprecationWarning",
+    "HoloviewsUserWarning",
+    "warn",
+)
+
+
+def warn(message, category=None, stacklevel=None):
+    if stacklevel is None:
+        stacklevel = find_stack_level()
+
+    warnings.warn(message, category, stacklevel=stacklevel)
+
+
+def find_stack_level():
+    """
+    Find the first place in the stack that is not inside Holoviews and Param.
+    Inspired by: pandas.util._exceptions.find_stack_level
+    """
+
+    import holoviews as hv
+
+    pkg_dir = os.path.dirname(hv.__file__)
+    test_dir = os.path.join(pkg_dir, "tests")
+    param_dir = os.path.dirname(param.__file__)
+
+    frame = inspect.currentframe()
+    stacklevel = 0
+    while frame:
+        fname = inspect.getfile(frame)
+        if (
+            fname.startswith(pkg_dir) or fname.startswith(param_dir)
+        ) and not fname.startswith(test_dir):
+            frame = frame.f_back
+            stacklevel += 1
+        else:
+            break
+
+    return stacklevel
+
+
+def deprecated(remove_version, old, new=None, extra=None):
+    import holoviews as hv
+
+    current_version = Version(hv.__version__)
+
+    if isinstance(remove_version, str):
+        remove_version = Version(remove_version)
+
+    if remove_version < current_version.base_version:
+        # This error is mainly for developers to remove the deprecated.
+        raise ValueError(
+            f"{old!r} should have been removed in {remove_version}, current version {current_version}."
+        )
+
+    message = f"{old!r} is deprecated and will be removed in version {remove_version}."
+
+    if new:
+        message = f"{message[:-1]}, use {new!r} instead."
+
+    if extra:
+        message += " " + extra.strip()
+
+    warn(message, HoloviewsDeprecationWarning)
+
+
+class HoloviewsDeprecationWarning(DeprecationWarning):
+    """A Holoviews-specific ``DeprecationWarning`` subclass.
+    Used to selectively filter Holoviews deprecations for unconditional display.
+    """
+
+
+class HoloviewsUserWarning(UserWarning):
+    """A Holoviews-specific ``UserWarning`` subclass.
+    Used to selectively filter Holoviews warnings for unconditional display.
+    """
+
+
+warnings.simplefilter("always", HoloviewsDeprecationWarning)
+warnings.simplefilter("always", HoloviewsUserWarning)
diff --git a/pyproject.toml b/pyproject.toml
index fbe0c8c41..8d5e4cae0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -65,3 +65,8 @@ unfixable = [
 [tool.ruff.per-file-ignores]
 "__init__.py" = ["F403"]
 "holoviews/tests/*" = ["RUF001", "RUF002", "RUF003"]  # Ambiguous unicode character
+
+[tool.codespell]
+ignore-words-list = "lod,nd,ndoes,reenabled,spreaded,whn"
+skip = "doc/generate_modules.py"
+write-changes = true
diff --git a/setup.py b/setup.py
index d30977e6c..a2d8b8d6a 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@ import pyct.build
 
 setup_args = {}
 install_requires = [
-    "param >=1.9.3,<2.0",
+    "param >=1.12.0,<3.0",
     "numpy >=1.0",
     "pyviz_comms >=0.7.4",
     "panel >=0.13.1",


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index b47efcff2..0e597c2eb 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -10,6 +10,10 @@ on:
   schedule:
     - cron: '0 14 * * SUN'
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
 jobs:
   pre_commit:
     name: Run pre-commit hooks
diff --git a/holoviews/tests/core/data/test_ibisinterface.py b/holoviews/tests/core/data/test_ibisinterface.py
index 5ae3ca03f..ee83d605c 100644
--- a/holoviews/tests/core/data/test_ibisinterface.py
+++ b/holoviews/tests/core/data/test_ibisinterface.py
@@ -66,7 +66,7 @@ class IbisDatasetTest(HeterogeneousColumnTests, ScalarColumnTests, InterfaceTest
             hetero_db.table("hetero"), kdims=self.kdims, vdims=self.vdims
         )
 
-        # Create table with aliased dimenion names
+        # Create table with aliased dimension names
         self.alias_kdims = [("gender", "Gender"), ("age", "Age")]
         self.alias_vdims = [("weight", "Weight"), ("height", "Height")]
         alias_df = pd.DataFrame(
diff --git a/holoviews/tests/core/test_callable.py b/holoviews/tests/core/test_callable.py
index e35403bbd..34f8358ed 100644
--- a/holoviews/tests/core/test_callable.py
+++ b/holoviews/tests/core/test_callable.py
@@ -105,7 +105,7 @@ class TestSimpleCallableInvocation(LoggingComparisonTestCase):
         self.log_handler.assertContains('WARNING', substr)
 
     def test_callable_lambda_extras_kwargs(self):
-        substr = "['x'] overriden by keywords"
+        substr = "['x'] overridden by keywords"
         self.assertEqual(Callable(lambda x,y: x+y)(3,5,x=10), 15)
         self.log_handler.assertEndsWith('WARNING', substr)
 
diff --git a/holoviews/tests/core/test_options.py b/holoviews/tests/core/test_options.py
index 54a571a0f..0ee78b9d2 100644
--- a/holoviews/tests/core/test_options.py
+++ b/holoviews/tests/core/test_options.py
@@ -4,8 +4,7 @@ import pickle
 import numpy as np
 import pytest
 
-from holoviews import util
-from holoviews import Store, Histogram, Image, Curve, Points, DynamicMap, opts
+from holoviews import Store, Histogram, Image, Curve, Points, DynamicMap, opts, util
 from holoviews.core.options import (
     OptionError, Cycle, Options, OptionTree, StoreOptions, options_policy
 )
@@ -421,7 +420,7 @@ class TestStoreInheritanceDynamic(ComparisonTestCase):
         self.assertEqual(obj_lookup.kwargs, expected_obj)
 
         # Customize this particular object
-        custom_obj = obj.opts(style=dict(clims=(0, 0.5)))
+        custom_obj = opts.apply_groups(obj, style=dict(clims=(0, 0.5)))
         expected_custom_obj =  dict(clims=(0,0.5), **expected_obj)
         custom_obj_lookup = Store.lookup_options('matplotlib', custom_obj, 'style')
         self.assertEqual(custom_obj_lookup.kwargs, expected_custom_obj)
@@ -498,7 +497,7 @@ class TestStoreInheritance(ComparisonTestCase):
 
     def test_plot_inheritance_addition(self):
         "Adding an element"
-        hist2 = self.hist.opts(plot={'plot3':'plot3'})
+        hist2 = opts.apply_groups(self.hist, plot={'plot3':'plot3'})
         self.assertEqual(self.lookup_options(hist2, 'plot').options,
                          dict(plot1='plot1', plot2='plot2', plot3='plot3'))
         # Check style works as expected
@@ -506,7 +505,7 @@ class TestStoreInheritance(ComparisonTestCase):
 
     def test_plot_inheritance_override(self):
         "Overriding an element"
-        hist2 = self.hist.opts(plot={'plot1':'plot_child'})
+        hist2 = opts.apply_groups(self.hist, plot={'plot1':'plot_child'})
         self.assertEqual(self.lookup_options(hist2, 'plot').options,
                          dict(plot1='plot_child', plot2='plot2'))
         # Check style works as expected
@@ -514,7 +513,7 @@ class TestStoreInheritance(ComparisonTestCase):
 
     def test_style_inheritance_addition(self):
         "Adding an element"
-        hist2 = self.hist.opts(style={'style3':'style3'})
+        hist2 = opts.apply_groups(self.hist, style={'style3':'style3'})
         self.assertEqual(self.lookup_options(hist2, 'style').options,
                          dict(style1='style1', style2='style2', style3='style3'))
         # Check plot options works as expected
@@ -522,20 +521,20 @@ class TestStoreInheritance(ComparisonTestCase):
 
     def test_style_inheritance_override(self):
         "Overriding an element"
-        hist2 = self.hist.opts(style={'style1':'style_child'})
+        hist2 = opts.apply_groups(self.hist, style={'style1':'style_child'})
         self.assertEqual(self.lookup_options(hist2, 'style').options,
                          dict(style1='style_child', style2='style2'))
         # Check plot options works as expected
         self.assertEqual(self.lookup_options(hist2, 'plot').options, self.default_plot)
 
     def test_style_transfer(self):
-        hist = self.hist.opts(style={'style1':'style_child'})
+        hist = opts.apply_groups(self.hist, style={'style1':'style_child'})
         hist2 = self.hist.opts()
-        opts = Store.lookup_options('matplotlib', hist2, 'style').kwargs
-        self.assertEqual(opts, {'style1': 'style1', 'style2': 'style2'})
+        opts_kwargs = Store.lookup_options('matplotlib', hist2, 'style').kwargs
+        self.assertEqual(opts_kwargs, {'style1': 'style1', 'style2': 'style2'})
         Store.transfer_options(hist, hist2, 'matplotlib')
-        opts = Store.lookup_options('matplotlib', hist2, 'style').kwargs
-        self.assertEqual(opts, {'style1': 'style_child', 'style2': 'style2'})
+        opts_kwargs = Store.lookup_options('matplotlib', hist2, 'style').kwargs
+        self.assertEqual(opts_kwargs, {'style1': 'style_child', 'style2': 'style2'})
 
 
 
@@ -607,30 +606,6 @@ class TestOptsMethod(ComparisonTestCase):
     def lookup_options(self, obj, group):
         return Store.lookup_options(self.backend, obj, group)
 
-    def test_old_opts_clone_disabled(self):
-        im = Image(np.random.rand(10,10))
-        styled_im = im.opts(style=dict(interpolation='nearest', cmap='jet'), clone=False)
-
-        self.assertEqual(self.lookup_options(im, 'plot').options, {})
-        self.assertEqual(self.lookup_options(styled_im, 'plot').options, {})
-
-        assert styled_im is im
-        self.assertEqual(self.lookup_options(im, 'style').options,
-                         {'cmap': 'jet', 'interpolation': 'nearest'})
-
-    def test_old_opts_clone_enabled(self):
-        im = Image(np.random.rand(10,10))
-        styled_im = im.opts(style=dict(interpolation='nearest', cmap='jet'), clone=True)
-
-        self.assertEqual(self.lookup_options(im, 'plot').options, {})
-        self.assertEqual(self.lookup_options(styled_im, 'plot').options, {})
-
-        assert styled_im is not im
-        im_lookup = self.lookup_options(im, 'style').options
-        self.assertEqual(im_lookup['cmap'] == 'jet', False)
-        styled_im_lookup =  self.lookup_options(styled_im, 'style').options
-        self.assertEqual(styled_im_lookup['cmap'] == 'jet', True)
-
     def test_simple_clone_disabled(self):
         im = Image(np.random.rand(10,10))
         styled_im = im.opts(interpolation='nearest', cmap='jet', clone=False)
@@ -667,14 +642,14 @@ class TestOptsMethod(ComparisonTestCase):
     def test_opts_method_dynamicmap_grouped(self):
         dmap = DynamicMap(lambda X: Curve([1, 2, X]),
                           kdims=['X']).redim.range(X=(0, 3))
-        retval = dmap.opts({'plot': dict(width=700)})
+        retval = dmap.opts(padding=1, clone=True)
         assert retval is not dmap
         self.assertEqual(self.lookup_options(retval[0], 'plot').options,
-                         {'width':700})
+                         {'padding':1})
 
     def test_opts_clear(self):
         im = Image(np.random.rand(10,10))
-        styled_im = im.opts(style=dict(cmap='jet', interpolation='nearest',
+        styled_im = opts.apply_groups(im, style=dict(cmap='jet', interpolation='nearest',
                                        option1='A', option2='B'), clone=False)
         self.assertEqual(self.lookup_options(im, 'style').options,
                          {'cmap': 'jet', 'interpolation': 'nearest',
@@ -688,7 +663,7 @@ class TestOptsMethod(ComparisonTestCase):
 
     def test_opts_clear_clone(self):
         im = Image(np.random.rand(10,10))
-        styled_im = im.opts(style=dict(cmap='jet', interpolation='nearest',
+        styled_im = opts.apply_groups(im, style=dict(cmap='jet', interpolation='nearest',
                                        option1='A', option2='B'), clone=False)
         self.assertEqual(self.lookup_options(im, 'style').options,
                          {'cmap': 'jet', 'interpolation': 'nearest',
@@ -1053,10 +1028,8 @@ class TestCrossBackendOptionSpecification(ComparisonTestCase):
         overlay = img * curve
         Store.set_current_backend('matplotlib')
 
-        literal = {'Curve':
-                   {'style':dict(color='orange')},
-                   'Image':
-                   {'style':dict(cmap='jet'), 'output':dict(backend='bokeh')}
+        literal = {'Curve': dict(color='orange'),
+                   'Image': dict(cmap='jet', backend='bokeh'),
                    }
         styled = overlay.opts(literal)
         mpl_curve_lookup = Store.lookup_options('matplotlib', styled.Curve.I, 'style')
@@ -1078,31 +1051,6 @@ class TestCrossBackendOptionSpecification(ComparisonTestCase):
         overlay = img * curve
         Store.set_current_backend('matplotlib')
 
-        literal = {'Curve':
-                   {'style':dict(color='orange'), 'output':dict(backend='matplotlib')},
-                   'Image':
-                   {'style':dict(cmap='jet'), 'output':dict(backend='bokeh')}
-                   }
-        styled = overlay.opts(literal)
-        mpl_curve_lookup = Store.lookup_options('matplotlib', styled.Curve.I, 'style')
-        self.assertEqual(mpl_curve_lookup.kwargs['color'], 'orange')
-
-        mpl_img_lookup = Store.lookup_options('matplotlib', styled.Image.I, 'style')
-        self.assertNotEqual(mpl_img_lookup.kwargs['cmap'], 'jet')
-
-        bokeh_curve_lookup = Store.lookup_options('bokeh', styled.Curve.I, 'style')
-        self.assertNotEqual(bokeh_curve_lookup.kwargs['color'], 'orange')
-
-        bokeh_img_lookup = Store.lookup_options('bokeh', styled.Image.I, 'style')
-        self.assertEqual(bokeh_img_lookup.kwargs['cmap'], 'jet')
-
-
-    def test_mpl_bokeh_mpl_via_builders_opts_method_flat_literal_explicit_backend(self):
-        img = Image(np.random.rand(10,10))
-        curve = Curve([1,2,3])
-        overlay = img * curve
-        Store.set_current_backend('matplotlib')
-
         literal = {'Curve': dict(color='orange', backend='matplotlib'),
                    'Image': dict(cmap='jet', backend='bokeh')
                    }
diff --git a/holoviews/tests/core/test_storeoptions.py b/holoviews/tests/core/test_storeoptions.py
index 88ec57d92..fd686b264 100644
--- a/holoviews/tests/core/test_storeoptions.py
+++ b/holoviews/tests/core/test_storeoptions.py
@@ -52,8 +52,8 @@ class TestStoreOptsMethod(ComparisonTestCase):
         """
         data = [zip(range(10),range(10)), zip(range(5),range(5))]
         o = Overlay([Curve(c) for c in data]).opts(
-            dict(plot={'Curve.Curve':{'show_grid':False}},
-                 style={'Curve.Curve':{'color':'k'}}))
+            {'Curve.Curve': {'show_grid': False, 'color':'k'}}
+        )
 
         self.assertEqual(Store.lookup_options('matplotlib',
             o.Curve.I, 'plot').kwargs['show_grid'], False)
@@ -70,8 +70,7 @@ class TestStoreOptsMethod(ComparisonTestCase):
         """
         data = [zip(range(10),range(10)), zip(range(5),range(5))]
         o = Overlay([Curve(c) for c in data]).opts(
-            {'Curve.Curve':{'plot':{'show_grid':True},
-                            'style':{'color':'b'}}})
+            {'Curve.Curve': {'show_grid':True, 'color':'b'}})
 
         self.assertEqual(Store.lookup_options('matplotlib',
             o.Curve.I, 'plot').kwargs['show_grid'], True)
@@ -87,7 +86,7 @@ class TestStoreOptsMethod(ComparisonTestCase):
         Short __call__ syntax.
         """
         im = Image(np.random.rand(10,10))
-        layout = (im + im).opts({'Layout':dict(plot={'hspace':5})})
+        layout = (im + im).opts({'Layout':dict({'hspace':5})})
         self.assertEqual(Store.lookup_options('matplotlib',
             layout, 'plot').kwargs['hspace'], 5)
 
@@ -96,12 +95,12 @@ class TestStoreOptsMethod(ComparisonTestCase):
         The old (longer) syntax in __call__
         """
         im = Image(np.random.rand(10,10))
-        layout = (im + im).opts({'Layout':dict(plot={'hspace':10})})
+        layout = (im + im).opts({'Layout':dict({'hspace':10})})
         self.assertEqual(Store.lookup_options('matplotlib',
             layout, 'plot').kwargs['hspace'], 10)
 
     def test_holomap_opts(self):
-        hmap = HoloMap({0: Image(np.random.rand(10,10))}).opts(plot=dict(xaxis=None))
+        hmap = HoloMap({0: Image(np.random.rand(10,10))}).opts(xaxis=None)
         opts = Store.lookup_options('matplotlib', hmap.last, 'plot')
         self.assertIs(opts.kwargs['xaxis'], None)
 
diff --git a/holoviews/tests/element/test_statselements.py b/holoviews/tests/element/test_statselements.py
index 3232cab86..d0648d9da 100644
--- a/holoviews/tests/element/test_statselements.py
+++ b/holoviews/tests/element/test_statselements.py
@@ -125,13 +125,13 @@ class StatisticalCompositorTest(ComparisonTestCase):
         self.assertEqual(area.vdims, [Dimension(('Value_density', 'Density'))])
 
     def test_distribution_composite_transfer_opts(self):
-        dist = Distribution(np.array([0, 1, 2])).opts(style=dict(color='red'))
+        dist = Distribution(np.array([0, 1, 2])).opts(color='red')
         area = Compositor.collapse_element(dist, backend='matplotlib')
         opts = Store.lookup_options('matplotlib', area, 'style').kwargs
         self.assertEqual(opts.get('color', None), 'red')
 
     def test_distribution_composite_transfer_opts_with_group(self):
-        dist = Distribution(np.array([0, 1, 2]), group='Test').opts(style=dict(color='red'))
+        dist = Distribution(np.array([0, 1, 2]), group='Test').opts(color='red')
         area = Compositor.collapse_element(dist, backend='matplotlib')
         opts = Store.lookup_options('matplotlib', area, 'style').kwargs
         self.assertEqual(opts.get('color', None), 'red')
@@ -143,13 +143,13 @@ class StatisticalCompositorTest(ComparisonTestCase):
         self.assertEqual(area.vdims, [Dimension('Test')])
 
     def test_distribution_composite_not_filled(self):
-        dist = Distribution(np.array([0, 1, 2]), ).opts(plot=dict(filled=False))
+        dist = Distribution(np.array([0, 1, 2]), ).opts(filled=False)
         curve = Compositor.collapse_element(dist, backend='matplotlib')
         self.assertIsInstance(curve, Curve)
         self.assertEqual(curve.vdims, [Dimension(('Value_density', 'Density'))])
 
     def test_distribution_composite_empty_not_filled(self):
-        dist = Distribution([]).opts(plot=dict(filled=False))
+        dist = Distribution([]).opts(filled=False)
         curve = Compositor.collapse_element(dist, backend='matplotlib')
         self.assertIsInstance(curve, Curve)
         self.assertEqual(curve.vdims, [Dimension(('Value_density', 'Density'))])
@@ -161,13 +161,13 @@ class StatisticalCompositorTest(ComparisonTestCase):
         self.assertEqual(contours.vdims, [Dimension('Density')])
 
     def test_bivariate_composite_transfer_opts(self):
-        dist = Bivariate(np.random.rand(10, 2)).opts(style=dict(cmap='Blues'))
+        dist = Bivariate(np.random.rand(10, 2)).opts(cmap='Blues')
         contours = Compositor.collapse_element(dist, backend='matplotlib')
         opts = Store.lookup_options('matplotlib', contours, 'style').kwargs
         self.assertEqual(opts.get('cmap', None), 'Blues')
 
     def test_bivariate_composite_transfer_opts_with_group(self):
-        dist = Bivariate(np.random.rand(10, 2), group='Test').opts(style=dict(cmap='Blues'))
+        dist = Bivariate(np.random.rand(10, 2), group='Test').opts(cmap='Blues')
         contours = Compositor.collapse_element(dist, backend='matplotlib')
         opts = Store.lookup_options('matplotlib', contours, 'style').kwargs
         self.assertEqual(opts.get('cmap', None), 'Blues')
@@ -179,20 +179,20 @@ class StatisticalCompositorTest(ComparisonTestCase):
         self.assertEqual(contours.vdims, [Dimension('Test')])
 
     def test_bivariate_composite_filled(self):
-        dist = Bivariate(np.random.rand(10, 2)).opts(plot=dict(filled=True))
+        dist = Bivariate(np.random.rand(10, 2)).opts(filled=True)
         contours = Compositor.collapse_element(dist, backend='matplotlib')
         self.assertIsInstance(contours, Polygons)
         self.assertEqual(contours.vdims[0].name, 'Density')
 
     def test_bivariate_composite_empty_filled(self):
-        dist = Bivariate([]).opts(plot=dict(filled=True))
+        dist = Bivariate([]).opts(filled=True)
         contours = Compositor.collapse_element(dist, backend='matplotlib')
         self.assertIsInstance(contours, Polygons)
         self.assertEqual(contours.vdims, [Dimension('Density')])
         self.assertEqual(len(contours), 0)
 
     def test_bivariate_composite_empty_not_filled(self):
-        dist = Bivariate([]).opts(plot=dict(filled=True))
+        dist = Bivariate([]).opts(filled=True)
         contours = Compositor.collapse_element(dist, backend='matplotlib')
         self.assertIsInstance(contours, Contours)
         self.assertEqual(contours.vdims, [Dimension('Density')])
diff --git a/holoviews/tests/ipython/test_magics.py b/holoviews/tests/ipython/test_magics.py
index e7d5a259d..808680e97 100644
--- a/holoviews/tests/ipython/test_magics.py
+++ b/holoviews/tests/ipython/test_magics.py
@@ -53,7 +53,7 @@ class TestOptsMagic(ExtensionTestCase):
     def test_cell_opts_style_dynamic(self):
 
         self.cell("dmap = DynamicMap(lambda X: Curve(np.random.rand(5,2), name='dmap'), kdims=['x'])"
-                  ".redim.range(x=(0, 10)).opts(style={'Curve': dict(linewidth=2, color='black')})")
+                  ".redim.range(x=(0, 10)).opts({'Curve': dict(linewidth=2, color='black')})")
 
         self.assertEqual(self.get_object('dmap').id, None)
         self.cell_magic('opts', " Curve (linewidth=3 alpha=0.5)", 'dmap')
@@ -95,7 +95,7 @@ class TestOptsMagic(ExtensionTestCase):
     def test_cell_opts_plot_dynamic(self):
 
         self.cell("dmap = DynamicMap(lambda X: Image(np.random.rand(5,5), name='dmap'), kdims=['x'])"
-                  ".redim.range(x=(0, 10)).opts(plot={'Image': dict(xaxis='top', xticks=3)})")
+                  ".redim.range(x=(0, 10)).opts({'Image': dict(xaxis='top', xticks=3)})")
 
         self.assertEqual(self.get_object('dmap').id, None)
         self.cell_magic('opts', " Image [xaxis=None yaxis='right']", 'dmap')
diff --git a/holoviews/tests/plotting/bokeh/test_callbacks.py b/holoviews/tests/plotting/bokeh/test_callbacks.py
index 5b4c8df64..cfba3e8f6 100644
--- a/holoviews/tests/plotting/bokeh/test_callbacks.py
+++ b/holoviews/tests/plotting/bokeh/test_callbacks.py
@@ -256,10 +256,10 @@ class TestEditToolCallbacks(CallbackTestCase):
         self.assertIsInstance(plot.callbacks[0], BoxEditCallback)
         callback = plot.callbacks[0]
         source = plot.handles['cds']
-        self.assertEqual(source.data['x'], [0])
-        self.assertEqual(source.data['y'], [0])
-        self.assertEqual(source.data['width'], [1])
-        self.assertEqual(source.data['height'], [1])
+        self.assertEqual(source.data['left'], [-0.5])
+        self.assertEqual(source.data['bottom'], [-0.5])
+        self.assertEqual(source.data['right'], [0.5])
+        self.assertEqual(source.data['top'], [0.5])
         data = {'x': [0, 1], 'y': [0, 1], 'width': [0.5, 2], 'height': [2, 0.5]}
         callback.on_msg({'data': data})
         element = Rectangles([(-0.25, -1, 0.25, 1), (0, 0.75, 2, 1.25)])
diff --git a/holoviews/tests/plotting/bokeh/test_curveplot.py b/holoviews/tests/plotting/bokeh/test_curveplot.py
index cdbdff520..1ee8b9109 100644
--- a/holoviews/tests/plotting/bokeh/test_curveplot.py
+++ b/holoviews/tests/plotting/bokeh/test_curveplot.py
@@ -45,8 +45,7 @@ class TestCurvePlot(TestBokehPlot):
 
     def test_cyclic_palette_curves(self):
         palette = Palette('Set1')
-        opts = dict(color=palette)
-        hmap = HoloMap({i: NdOverlay({j: Curve(np.random.rand(3)).opts(style=opts)
+        hmap = HoloMap({i: NdOverlay({j: Curve(np.random.rand(3)).opts(color=palette)
                                       for j in range(3)})
                         for i in range(3)})
         colors = palette[3].values
@@ -106,7 +105,7 @@ class TestCurvePlot(TestBokehPlot):
         obj = NdOverlay({i: Curve(np.random.rand(10,2)) for i in range(5)},
                         kdims=['Test'])
         opts = {'Curve': {'tools': ['hover']}}
-        obj = obj.opts(plot=opts)
+        obj = obj.opts(opts)
         self._test_hover_info(obj, [('Test', '@{Test}'), ('x', '@{x}'), ('y', '@{y}')], 'nearest')
 
     def test_curve_categorical_xaxis(self):
diff --git a/holoviews/tests/plotting/bokeh/test_gridplot.py b/holoviews/tests/plotting/bokeh/test_gridplot.py
index 30e21e911..b8f44359b 100644
--- a/holoviews/tests/plotting/bokeh/test_gridplot.py
+++ b/holoviews/tests/plotting/bokeh/test_gridplot.py
@@ -81,7 +81,7 @@ class TestGridPlot(TestBokehPlot):
 
         # Pop key (1,) for one of the HoloMaps and make GridSpace
         hmap2.pop(1)
-        grid = GridSpace({0: hmap1, 2: hmap2}, kdims=['X']).opts(plot=dict(shared_datasource=True))
+        grid = GridSpace({0: hmap1, 2: hmap2}, kdims=['X']).opts(shared_datasource=True)
 
         # Get plot
         plot = bokeh_renderer.get_plot(grid)
diff --git a/holoviews/tests/plotting/bokeh/test_pathplot.py b/holoviews/tests/plotting/bokeh/test_pathplot.py
index 8f6b99d60..ba0864710 100644
--- a/holoviews/tests/plotting/bokeh/test_pathplot.py
+++ b/holoviews/tests/plotting/bokeh/test_pathplot.py
@@ -202,7 +202,7 @@ class TestPolygonPlot(TestBokehPlot):
                          for i in range(5)}, kdims=['Test'])
         opts = {'Polygons': {'tools': ['hover']},
                 'NdOverlay': {'legend_limit': 0}}
-        obj = obj.opts(plot=opts)
+        obj = obj.opts(opts)
         self._test_hover_info(obj, [('Test', '@{Test}'), ('z', '@{z}')])
 
     def test_polygons_colored(self):
@@ -218,7 +218,7 @@ class TestPolygonPlot(TestBokehPlot):
 
     def test_polygons_colored_batched(self):
         polygons = NdOverlay({j: Polygons([[(i**j, i, j) for i in range(10)]], vdims='Value')
-                              for j in range(5)}).opts(plot=dict(legend_limit=0))
+                              for j in range(5)}).opts(legend_limit=0)
         plot = list(bokeh_renderer.get_plot(polygons).subplots.values())[0]
         cmapper = plot.handles['color_mapper']
         self.assertEqual(cmapper.low, 0)
@@ -230,7 +230,7 @@ class TestPolygonPlot(TestBokehPlot):
     def test_polygons_colored_batched_unsanitized(self):
         polygons = NdOverlay({j: Polygons([[(i**j, i, j) for i in range(10)] for i in range(2)],
                                           vdims=['some ? unescaped name'])
-                              for j in range(5)}).opts(plot=dict(legend_limit=0))
+                              for j in range(5)}).opts(legend_limit=0)
         plot = list(bokeh_renderer.get_plot(polygons).subplots.values())[0]
         cmapper = plot.handles['color_mapper']
         self.assertEqual(cmapper.low, 0)
diff --git a/holoviews/tests/plotting/bokeh/test_pointplot.py b/holoviews/tests/plotting/bokeh/test_pointplot.py
index 156d75cc0..c86e9e4e3 100644
--- a/holoviews/tests/plotting/bokeh/test_pointplot.py
+++ b/holoviews/tests/plotting/bokeh/test_pointplot.py
@@ -161,7 +161,7 @@ class TestPointPlot(TestBokehPlot):
 
     def test_points_non_numeric_size_warning(self):
         data = (np.arange(10), np.arange(10), list(map(chr, range(94,104))))
-        points = Points(data, vdims=['z']).opts(plot=dict(size_index=2))
+        points = Points(data, vdims=['z']).opts(size_index=2)
         with ParamLogStream() as log:
             bokeh_renderer.get_plot(points)
         log_msg = log.stream.read()
diff --git a/holoviews/tests/plotting/bokeh/test_quadmeshplot.py b/holoviews/tests/plotting/bokeh/test_quadmeshplot.py
index 96a041abe..8a43c7fd9 100644
--- a/holoviews/tests/plotting/bokeh/test_quadmeshplot.py
+++ b/holoviews/tests/plotting/bokeh/test_quadmeshplot.py
@@ -65,3 +65,64 @@ class TestQuadMeshPlot(TestBokehPlot):
         plot = bokeh_renderer.get_plot(qmesh)
         source = plot.handles['source']
         self.assertEqual(source.data['z'], flattened)
+
+    def test_quadmesh_regular_centers(self):
+        X = [0.5, 1.5]
+        Y = [0.5, 1.5, 2.5]
+        Z = np.array([[1., 2., 3.], [4., 5., np.nan]]).T
+        LABELS = np.array([['0-0', '0-1', '0-2'], ['1-0', '1-1', '1-2']]).T
+        qmesh = QuadMesh((X, Y, Z, LABELS), vdims=['Value', 'Label'])
+        plot = bokeh_renderer.get_plot(qmesh.opts(tools=['hover']))
+        source = plot.handles['source']
+        expected = {
+                'left': [0., 0., 0., 1., 1., 1.],
+                'right': [1., 1., 1., 2., 2., 2.],
+                'Value': [ 1.,  2.,  3.,  4.,  5., np.nan],
+                'bottom': [0., 1., 2., 0., 1., 2.],
+                'top': [1., 2., 3., 1., 2., 3.],
+                'Label': ['0-0', '0-1', '0-2', '1-0', '1-1', '1-2'],
+                'x': [0.5, 0.5, 0.5, 1.5, 1.5, 1.5],
+                'y': [0.5, 1.5, 2.5, 0.5, 1.5, 2.5]}
+        self.assertEqual(source.data.keys(), expected.keys())
+        for key in expected:
+            self.assertEqual(list(source.data[key]), expected[key])
+
+    def test_quadmesh_irregular_centers(self):
+        X = [[0.5, 0.5, 0.5], [1.5, 1.5, 1.5]]
+        Y = [[0.5, 1.5, 2.5], [0.5, 1.5, 2.5]]
+        Z = np.array([[1., 2., 3.], [4., 5., np.nan]])
+        LABELS = np.array([['0-0', '0-1', '0-2'], ['1-0', '1-1', '1-2']])
+        qmesh = QuadMesh((X, Y, Z, LABELS), vdims=['Value', 'Label'])
+        plot = bokeh_renderer.get_plot(qmesh.opts(tools=['hover']))
+        source = plot.handles['source']
+        expected = {'xs': [[0., 1., 1., 0.], [0., 1., 1., 0.], [0., 1., 1., 0.],
+                           [1., 2., 2., 1.], [1., 2., 2., 1.]],
+                    'ys': [[0., 0., 1., 1.], [1., 1., 2., 2.], [2., 2., 3., 3.],
+                           [0., 0., 1., 1.], [1., 1., 2., 2.]],
+                    'Value': [1., 2., 3., 4., 5.],
+                    'Label': ['0-0', '0-1', '0-2', '1-0', '1-1'],
+                    'x': [0.5, 0.5, 0.5, 1.5, 1.5],
+                    'y': [0.5, 1.5, 2.5, 0.5, 1.5]}
+        self.assertEqual(source.data.keys(), expected.keys())
+        for key in expected:
+            self.assertEqual(list(source.data[key]), expected[key])
+
+    def test_quadmesh_irregular_edges(self):
+        X = [[0., 0., 0., 0.], [1., 1., 1., 1.], [2., 2., 2., 2.]]
+        Y = [[0., 1., 2., 3.], [0., 1., 2., 3.], [0., 1., 2., 3.]]
+        Z = np.array([[1., 2., 3.], [4., 5., np.nan]])
+        LABELS = np.array([['0-0', '0-1', '0-2'], ['1-0', '1-1', '1-2']])
+        qmesh = QuadMesh((X, Y, Z, LABELS), vdims=['Value', 'Label'])
+        plot = bokeh_renderer.get_plot(qmesh.opts(tools=['hover']))
+        source = plot.handles['source']
+        expected = {'xs': [[0., 1., 1., 0.], [0., 1., 1., 0.], [0., 1., 1., 0.],
+                           [1., 2., 2., 1.], [1., 2., 2., 1.]],
+                    'ys': [[0., 0., 1., 1.], [1., 1., 2., 2.], [2., 2., 3., 3.],
+                           [0., 0., 1., 1.], [1., 1., 2., 2.]],
+                    'Value': [1., 2., 3., 4., 5.],
+                    'Label': ['0-0', '0-1', '0-2', '1-0', '1-1'],
+                    'x': [0.5, 0.5, 0.5, 1.5, 1.5],
+                    'y': [0.5, 1.5, 2.5, 0.5, 1.5]}
+        self.assertEqual(source.data.keys(), expected.keys())
+        for key in expected:
+            self.assertEqual(list(source.data[key]), expected[key])
diff --git a/holoviews/tests/plotting/bokeh/test_radialheatmap.py b/holoviews/tests/plotting/bokeh/test_radialheatmap.py
index 63a9080a0..79ab2877f 100644
--- a/holoviews/tests/plotting/bokeh/test_radialheatmap.py
+++ b/holoviews/tests/plotting/bokeh/test_radialheatmap.py
@@ -34,7 +34,7 @@ class BokehRadialHeatMapPlotTests(TestBokehPlot):
                          radius_outer=0.2,
                          radial=True)
 
-        opts = dict(HeatMap=dict(plot=plot_opts))
+        opts = dict(HeatMap=plot_opts)
 
         # provide element and plot instances for tests
         self.element = HeatMap((self.x, self.y, self.z)).opts(opts)
diff --git a/holoviews/tests/plotting/matplotlib/test_graphplot.py b/holoviews/tests/plotting/matplotlib/test_graphplot.py
index 11cd13fce..ca16d1db4 100644
--- a/holoviews/tests/plotting/matplotlib/test_graphplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_graphplot.py
@@ -316,7 +316,7 @@ class TestMplTriMeshPlot(TestMPLPlot):
                          [p.array() for p in self.trimesh._split_edgepaths.split()])
 
     def test_plot_simple_trimesh_filled(self):
-        plot = mpl_renderer.get_plot(self.trimesh.opts(plot=dict(filled=True)))
+        plot = mpl_renderer.get_plot(self.trimesh.opts(filled=True))
         nodes = plot.handles['nodes']
         edges = plot.handles['edges']
         self.assertIsInstance(edges, PolyCollection)
@@ -479,13 +479,13 @@ class TestMplChordPlot(TestMPLPlot):
         return Chord((edges, nodes), vdims='weight')
 
     def test_chord_nodes_label_text(self):
-        g = self.chord.opts(plot=dict(label_index='Label'))
+        g = self.chord.opts(label_index='Label')
         plot = mpl_renderer.get_plot(g)
         labels = plot.handles['labels']
         self.assertEqual([l.get_text() for l in labels], ['A', 'B', 'C'])
 
     def test_chord_nodes_labels_mapping(self):
-        g = self.chord.opts(plot=dict(labels='Label'))
+        g = self.chord.opts(labels='Label')
         plot = mpl_renderer.get_plot(g)
         labels = plot.handles['labels']
         self.assertEqual([l.get_text() for l in labels], ['A', 'B', 'C'])
@@ -512,8 +512,7 @@ class TestMplChordPlot(TestMPLPlot):
         self.assertEqual(arcs.get_clim(), (0, 2))
 
     def test_chord_edges_categorically_colormapped(self):
-        g = self.chord.opts(plot=dict(edge_color_index='start'),
-                            style=dict(edge_cmap=['#FFFFFF', '#000000']))
+        g = self.chord.opts(edge_color_index='start', edge_cmap=['#FFFFFF', '#000000'])
         plot = mpl_renderer.get_plot(g)
         edges = plot.handles['edges']
         colors = np.array([[ 1., 1., 1., 1. ],
diff --git a/holoviews/tests/plotting/matplotlib/test_pointplot.py b/holoviews/tests/plotting/matplotlib/test_pointplot.py
index 42298ede4..da8998c75 100644
--- a/holoviews/tests/plotting/matplotlib/test_pointplot.py
+++ b/holoviews/tests/plotting/matplotlib/test_pointplot.py
@@ -45,7 +45,7 @@ class TestPointPlot(TestMPLPlot):
 
     def test_points_rcparams_do_not_persist(self):
         opts = dict(fig_rcparams={'text.usetex': True})
-        points = Points(([0, 1], [0, 3])).opts(plot=opts)
+        points = Points(([0, 1], [0, 3])).opts(**opts)
         mpl_renderer.get_plot(points)
         self.assertFalse(plt.rcParams['text.usetex'])
 
diff --git a/holoviews/tests/plotting/matplotlib/test_radialheatmap.py b/holoviews/tests/plotting/matplotlib/test_radialheatmap.py
index fe379a7b9..af4a0126e 100644
--- a/holoviews/tests/plotting/matplotlib/test_radialheatmap.py
+++ b/holoviews/tests/plotting/matplotlib/test_radialheatmap.py
@@ -39,7 +39,7 @@ class RadialHeatMapPlotTests(TestMPLPlot):
                          radius_outer=0.2,
                          radial=True)
 
-        opts = dict(HeatMap=dict(plot=plot_opts))
+        opts = dict(HeatMap=plot_opts)
 
         # provide element and plot instances for tests
         self.element = HeatMap((self.x, self.y, self.z)).opts(opts)
diff --git a/holoviews/tests/plotting/plotly/test_dash.py b/holoviews/tests/plotting/plotly/test_dash.py
index 940b5cfcd..633874710 100644
--- a/holoviews/tests/plotting/plotly/test_dash.py
+++ b/holoviews/tests/plotting/plotly/test_dash.py
@@ -161,7 +161,7 @@ class TestHoloViewsDash(TestPlotlyPlot):
         # First figure is the scatter trace
         self.assertEqual(fig1["data"][0]["type"], "scatter")
 
-        # Second figure holds reset bounds elemnt
+        # Second figure holds reset bounds element
         self.assertEqual(len(fig2["data"]), 0)
         self.assertEqual(len(fig2["layout"]["shapes"]), 1)
         self.assertEqual(
@@ -348,7 +348,7 @@ class TestHoloViewsDash(TestPlotlyPlot):
             "streams": {id(selection1d): selection1d.contents}
         })
 
-        # Update store, then mimick a selection on scatter figure
+        # Update store, then mimic a selection on scatter figure
         # store_value = new_store
         with patch.object(
                 CallbackContext, "triggered",
diff --git a/holoviews/tests/test_streams.py b/holoviews/tests/test_streams.py
index e1d79ada7..909ce05ea 100644
--- a/holoviews/tests/test_streams.py
+++ b/holoviews/tests/test_streams.py
@@ -23,7 +23,7 @@ def test_all_stream_parameters_constant():
     all_stream_cls = [v for v in globals().values() if
                       isinstance(v, type) and issubclass(v, Stream)]
     for stream_cls in all_stream_cls:
-        for name, p in stream_cls.param.params().items():
+        for name, p in stream_cls.param.objects().items():
             if name == 'name': continue
             if p.constant != True:
                 raise TypeError('Parameter %s of stream %s not declared constant'
@@ -34,7 +34,7 @@ def test_all_linked_stream_parameters_owners():
     "Test to ensure operations can accept parameters in streams dictionary"
     stream_classes = param.concrete_descendents(LinkedStream)
     for stream_class in stream_classes.values():
-        for name, p in stream_class.param.params().items():
+        for name, p in stream_class.param.objects().items():
             if name != 'name' and (p.owner != stream_class):
                 msg = ("Linked stream %r has parameter %r which is "
                        "inherited from %s. Parameter needs to be redeclared "
@@ -844,7 +844,7 @@ class TestBufferArrayStream(ComparisonTestCase):
 
     def test_buffer_array_send_verify_shape_fail(self):
         buff = Buffer(np.array([[0, 1]]))
-        error = "Streamed array data expeced to have 2 columns, got 3."
+        error = "Streamed array data expected to have 2 columns, got 3."
         with self.assertRaisesRegex(ValueError, error):
             buff.send(np.array([[1, 2, 3]]))