diff --git a/examples/reference/elements/plotly/Scatter3D.ipynb b/examples/reference/elements/plotly/Scatter3D.ipynb index 92b89aef80..fd5fa31023 100644 --- a/examples/reference/elements/plotly/Scatter3D.ipynb +++ b/examples/reference/elements/plotly/Scatter3D.ipynb @@ -20,6 +20,7 @@ "outputs": [], "source": [ "import numpy as np\n", + "import pandas as pd\n", "import holoviews as hv\n", "from holoviews import dim, opts\n", "\n", @@ -30,7 +31,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "``Scatter3D`` represents three-dimensional coordinates which may be colormapped or scaled in size according to a value. They are therefore very similar to [``Points``](Points.ipynb) and [``Scatter``](Scatter.ipynb) types but have one additional coordinate dimension. Like other 3D elements the camera angle can be controlled using ``azimuth``, ``elevation`` and ``distance`` plot options:" + "``Scatter3D`` plots represents three-dimensional coordinates which are specified as key dimensions (`kdims`, default `kdims` are x, y, z). The points can be given a `marker`, `color` and `size`. Additionally a `colorbar` can be added.\n", + "\n", + "``Scatter3D`` plots are therefore very similar to [``Points``](Points.ipynb) and [``Scatter``](Scatter.ipynb) types, but have one additional coordinate dimension.\n", + "\n", + "Like other 3D elements the camera angle can be controlled using `azimuth`, `elevation` and `distance` plot options" ] }, { @@ -40,9 +45,27 @@ "outputs": [], "source": [ "y,x = np.mgrid[-5:5, -5:5] * 0.1\n", - "heights = np.sin(x**2+y**2)\n", - "hv.Scatter3D((x.flat, y.flat, heights.flat)).opts(\n", - " cmap='fire', color='z', size=5)" + "z=np.sin(x**2+y**2)\n", + "\n", + "hv.Scatter3D((x.flat,y.flat,z.flat)).opts(cmap='fire', color='z', size=5, colorbar=True, height=600, width=600)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also provide your data in a container and specify the `kdims` to use." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pd.DataFrame(dict(lat=x.flat, lon=y.flat, height=z.flat))\n", + "\n", + "hv.Scatter3D(data, kdims=[\"lat\", \"lon\", \"height\"]).opts(cmap='blues', color='height', size=3, colorbar=True, height=600, width=600, colorbar_opts={'title': 'height (m)'}, marker=\"diamond\")" ] }, { @@ -59,7 +82,7 @@ "outputs": [], "source": [ "(hv.Scatter3D(np.random.randn(100,4), vdims='Size') * hv.Scatter3D(np.random.randn(100,4)+2, vdims='Size')).opts(\n", - " opts.Scatter3D(size=(5+dim('Size'))*2, marker='diamond')\n", + " opts.Scatter3D(size=(5+dim('Size'))*2, marker='diamond', height=600, width=600)\n", ")" ] }, @@ -69,6 +92,15 @@ "source": [ "For full documentation and the available style and plot options, use ``hv.help(hv.Scatter3D).``" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hv.help(hv.Scatter3D)" + ] } ], "metadata": { @@ -78,5 +110,5 @@ } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/holoviews/element/chart3d.py b/holoviews/element/chart3d.py index c52a97715a..49b91e9106 100644 --- a/holoviews/element/chart3d.py +++ b/holoviews/element/chart3d.py @@ -70,8 +70,50 @@ class Scatter3D(Element3D, Points): """ Scatter3D is a 3D element representing the position of a collection of coordinates in a 3D space. The key dimensions represent the - position of each coordinate along the x-, y- and z-axis while the - value dimensions can optionally supply additional information. + position of each coordinate along the x-, y- and z-axis. + + Scatter3D is not available for the default Bokeh backend. + + Example - Matplotlib + -------------------- + + .. code-block:: + + import holoviews as hv + from bokeh.sampledata.iris import flowers + + hv.extension("matplotlib") + + hv.Scatter3D( + flowers, kdims=["sepal_length", "sepal_width", "petal_length"] + ).opts( + color="petal_width", + alpha=0.7, + size=5, + cmap="fire", + marker='^' + ) + + Example - Plotly + ---------------- + + .. code-block:: + + import holoviews as hv + from bokeh.sampledata.iris import flowers + + hv.extension("plotly") + + hv.Scatter3D( + flowers, kdims=["sepal_length", "sepal_width", "petal_length"] + ).opts( + color="petal_width", + alpha=0.7, + size=5, + cmap="Portland", + colorbar=True, + marker="circle", + ) """ kdims = param.List(default=[Dimension('x'), diff --git a/holoviews/plotting/plotly/element.py b/holoviews/plotting/plotly/element.py index 2b027c2a3c..460f0085ca 100644 --- a/holoviews/plotting/plotly/element.py +++ b/holoviews/plotting/plotly/element.py @@ -604,11 +604,19 @@ def get_color_opts(self, eldim, element, ranges, style): opts = {} dim_name = dim_range_key(eldim) if self.colorbar: - if isinstance(eldim, dim): - title = str(eldim) if eldim.ops else str(eldim)[1:-1] - else: - title = eldim.pprint_label - opts['colorbar'] = dict(title=title, **self.colorbar_opts) + opts['colorbar'] = dict(**self.colorbar_opts) + if not "title" in opts['colorbar']: + if isinstance(eldim, dim): + title = str(eldim) + if eldim.ops: + title = title + elif title.startswith("dim('") and title.endswith("')"): + title = title[5:-2] + else: + title = title[1:-1] + else: + title = eldim.pprint_label + opts['colorbar']['title']=title opts['showscale'] = True else: opts['showscale'] = False diff --git a/holoviews/tests/plotting/plotly/test_elementplot.py b/holoviews/tests/plotting/plotly/test_elementplot.py index 213324302a..5f1460c6b1 100644 --- a/holoviews/tests/plotting/plotly/test_elementplot.py +++ b/holoviews/tests/plotting/plotly/test_elementplot.py @@ -1,6 +1,7 @@ from collections import deque import numpy as np +import pandas as pd from holoviews.core.spaces import DynamicMap from holoviews.element import Curve, Scatter3D, Path3D @@ -223,3 +224,28 @@ def test_overlay_plot_zlabel(self): scatter = Path3D([]) * Scatter3D([(10, 1, 2), (100, 2, 3), (1000, 3, 5)]).opts(zlabel='Z-Axis') state = self._get_plot_state(scatter) self.assertEqual(state['layout']['scene']['zaxis']['title']['text'], 'Z-Axis') + +class TestColorbarPlot(TestPlotlyPlot): + def test_base(self): + df = pd.DataFrame(np.random.random((10, 4)), columns=list("XYZT")) + scatter = Scatter3D(data=df) + state = self._get_plot_state(scatter) + assert "colorbar" not in state["data"][0]["marker"] + + def test_colorbar(self): + df = pd.DataFrame(np.random.random((10, 4)), columns=list("XYZT")) + scatter = Scatter3D(data=df).opts(color="T", colorbar=True) + state = self._get_plot_state(scatter) + assert "colorbar" in state["data"][0]["marker"] + assert state["data"][0]["marker"]["colorbar"]["title"]["text"] == "T" + + def test_colorbar_opts_title(self): + df = pd.DataFrame(np.random.random((10, 4)), columns=list("XYZT")) + scatter = Scatter3D(data=df).opts( + color="T", + colorbar=True, + colorbar_opts={"title": "some-title"} + ) + state = self._get_plot_state(scatter) + assert "colorbar" in state["data"][0]["marker"] + assert state["data"][0]["marker"]["colorbar"]["title"]["text"] == "some-title"