Skip to content

Commit

Permalink
Reimplement Path, Contours and Polygons plots (#1991)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored and jlstevens committed Oct 22, 2017
1 parent 0326ee9 commit 73038d7
Show file tree
Hide file tree
Showing 32 changed files with 525 additions and 347 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
Version 1.9.0
=============

Changes affecting backwards compatibility:

- The contours operation no longer overlays the contours on top of
the supplied Image by default and returns a single
Contours/Polygons rather than an NdOverlay of them
([\#1991](https://github.com/ioam/holoviews/pull/1991))


Version 1.8.4
=============

Expand Down
11 changes: 5 additions & 6 deletions doc/Tutorials/Introduction.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -640,10 +640,9 @@
"metadata": {},
"outputs": [],
"source": [
"%%opts Contours.Red (color=Palette('Reds')) Contours.Green (color=Palette('Greens')) Contours.Blue (color=Palette('Blues'))\n",
"data = {lvl:(contours(chans.RedChannel.Macaw, levels=[lvl], group='Red') +\\\n",
" contours(chans.Channel.Green, levels=[lvl], group='Green') +\\\n",
" contours(chans.Channel.Blue, levels=[lvl], group='Blue'))\n",
"data = {lvl:(contours(chans.RedChannel.Macaw, levels=[lvl]).opts(style=dict(cmap='Reds')) +\\\n",
" contours(chans.Channel.Green, levels=[lvl]).opts(style=dict(cmap='Greens')) +\\\n",
" contours(chans.Channel.Blue, levels=[lvl]).opts(style=dict(cmap='Blues')))\n",
" for lvl in np.linspace(0.1,0.9,9)}\n",
"levels = hv.HoloMap(data, kdims=['Levels']).collate()\n",
"levels"
Expand All @@ -662,8 +661,8 @@
"metadata": {},
"outputs": [],
"source": [
"green05 = levels.Overlay.Green[0.5]\n",
"green05 + green05.Channel + green05.Channel.Green.sample(y=0.0)"
"green05 = levels.Contours.Green\n",
"green05 + chans.Channel.Green + chans.Channel.Green.sample(y=0.0)"
]
},
{
Expand Down
22 changes: 7 additions & 15 deletions examples/gallery/demos/bokeh/texas_choropleth_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,11 @@
"from bokeh.sampledata.us_counties import data as counties\n",
"from bokeh.sampledata.unemployment import data as unemployment\n",
"\n",
"counties = {\n",
" code: county for code, county in counties.items() if county[\"state\"] == \"tx\"\n",
"}\n",
"counties = [dict(county, Unemployment=unemployment[cid])\n",
" for cid, county in counties.items()\n",
" if county[\"state\"] == \"tx\"]\n",
"\n",
"county_xs = [county[\"lons\"] for county in counties.values()]\n",
"county_ys = [county[\"lats\"] for county in counties.values()]\n",
"\n",
"county_names = [county['name'] for county in counties.values()]\n",
"county_rates = [unemployment[county_id] for county_id in counties]\n",
"\n",
"county_polys = {name: hv.Polygons((xs, ys), level=rate, vdims=['Unemployment'])\n",
" for name, xs, ys, rate in zip(county_names, county_xs, county_ys, county_rates)}\n",
"\n",
"choropleth = hv.NdOverlay(county_polys, kdims=['County'])"
"choropleth = hv.Polygons(counties, ['lons', 'lats'], [('detailed name', 'County'), 'Unemployment'])"
]
},
{
Expand All @@ -68,10 +59,11 @@
"outputs": [],
"source": [
"plot_opts = dict(logz=True, tools=['hover'], xaxis=None, yaxis=None,\n",
" show_grid=False, show_frame=False, width=500, height=500)\n",
" show_grid=False, show_frame=False, width=500, height=500,\n",
" color_index='Unemployment', colorbar=True, toolbar='above')\n",
"style = dict(line_color='white')\n",
"\n",
"choropleth({'Polygons': {'style': style, 'plot': plot_opts}})"
"choropleth.opts(style=style, plot=plot_opts)"
]
}
],
Expand Down
22 changes: 7 additions & 15 deletions examples/gallery/demos/matplotlib/texas_choropleth_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,11 @@
"from bokeh.sampledata.us_counties import data as counties\n",
"from bokeh.sampledata.unemployment import data as unemployment\n",
"\n",
"counties = {\n",
" code: county for code, county in counties.items() if county[\"state\"] == \"tx\"\n",
"}\n",
"counties = [dict(county, Unemployment=unemployment[cid])\n",
" for cid, county in counties.items()\n",
" if county[\"state\"] == \"tx\"]\n",
"\n",
"county_xs = [county[\"lons\"] for county in counties.values()]\n",
"county_ys = [county[\"lats\"] for county in counties.values()]\n",
"\n",
"county_names = [county['name'] for county in counties.values()]\n",
"county_rates = [unemployment[county_id] for county_id in counties]\n",
"\n",
"county_polys = {name: hv.Polygons((xs, ys), level=rate, vdims=['Unemployment'])\n",
" for name, xs, ys, rate in zip(county_names, county_xs, county_ys, county_rates)}\n",
"\n",
"choropleth = hv.NdOverlay(county_polys, kdims=['County'])"
"choropleth = hv.Polygons(counties, ['lons', 'lats'], [('detailed name', 'County'), 'Unemployment'])"
]
},
{
Expand All @@ -69,10 +60,11 @@
"outputs": [],
"source": [
"plot_opts = dict(logz=True, xaxis=None, yaxis=None,\n",
" show_grid=False, show_frame=False, fig_size=200, bgcolor='white')\n",
" show_grid=False, show_frame=False, colorbar=True,\n",
" fig_size=200, color_index='Unemployment')\n",
"style = dict(edgecolor='white')\n",
"\n",
"choropleth({'Polygons': {'style': style, 'plot': plot_opts}})"
"choropleth.opts(style=style, plot=plot_opts)"
]
}
],
Expand Down
4 changes: 2 additions & 2 deletions examples/getting_started/4-Gridded_Datasets.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
"outputs": [],
"source": [
"ROIs = data['ROIs']\n",
"roi_bounds = hv.NdOverlay({i: hv.Bounds(tuple(roi)) for i, roi in enumerate(ROIs)})\n",
"roi_bounds = hv.Path([hv.Bounds(tuple(roi)) for roi in ROIs])\n",
"print(ROIs.shape)"
]
},
Expand All @@ -176,7 +176,7 @@
"outputs": [],
"source": [
"%%opts Image [width=400 height=400 xaxis=None yaxis=None] \n",
"%%opts Bounds (color='white') Text (text_color='white' text_font_size='8pt')\n",
"%%opts Path (color='white') Text (text_color='white' text_font_size='8pt')\n",
"\n",
"opts = dict(halign='left', valign='bottom')\n",
"roi_text = hv.NdOverlay({i: hv.Text(roi[0], roi[1], str(i), **opts) for i, roi in enumerate(ROIs)})\n",
Expand Down
20 changes: 9 additions & 11 deletions examples/reference/elements/bokeh/Contours.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"A ``Contours`` object is similar to ``Path`` object except it may be associated with a numeric value (the ``level``), which can be used to apply colormapping the ``Contours``. To see the effect of this we can create a number of ``Contours`` with varying shapes and ``level`` values. In this case we will create a number of concentric rings with increasing radii and level values and colormap the ``Contours`` with the viridis colormap:"
"A ``Contours`` object is similar to a ``Path`` element but allows each individual path to be associated with one or more scalar values declared as value dimensions (``vdims``), which can be used to apply colormapping the ``Contours``. Just like the ``Path`` element ``Contours`` will accept a list of arrays, dataframes, a dictionaries of columns (or any of the other literal formats including tuples of columns and lists of tuples). In order to efficiently represent the scalar values associated with each path the dictionary format is preferable since it can store the scalar values without expanding them into a whole column.\n",
"\n",
"To see the effect we will create a number of concentric rings with increasing radii and define a colormap to apply color the circles: "
]
},
{
Expand All @@ -37,20 +39,18 @@
"metadata": {},
"outputs": [],
"source": [
"%%opts Contours (cmap='viridis')\n",
"\n",
"def circle(radius, x=0, y=0):\n",
"def circle(radius):\n",
" angles = np.linspace(0, 2*np.pi, 100)\n",
" return np.array(list(zip(x+radius*np.sin(angles), y+radius*np.cos(angles))))\n",
" return {'x': radius*np.sin(angles), 'y': radius*np.cos(angles), 'radius': radius}\n",
"\n",
"hv.Overlay([hv.Contours([circle(i+0.05)], level=i) for i in np.linspace(0, 1, 10)])"
"hv.Contours([circle(i) for i in np.linspace(0, 1, 10)], vdims=['radius'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Often ``Contours`` will be directly computed from an underlying ``Image``, which is made easy using the ``contours`` operation. The operation accepts an ``Image`` type as input and will compute an ``NdOverlay`` containing a ``Contours`` Element for each of the specified ``levels``. We will declare an ``Image`` of sine rings\n",
"Often ``Contours`` will be directly computed from an underlying ``Image``, which is made easy using the ``contours`` operation. The operation accepts an ``Image`` type as input and will return ``Contours`` containing iso-contours for each of the specified ``levels``. We will declare an ``Image`` of sine rings\n",
"and then compute ``Contours`` at 5 levels spaced linearly over the range of values in the Image:"
]
},
Expand All @@ -60,12 +60,10 @@
"metadata": {},
"outputs": [],
"source": [
"%%opts Contours [show_legend=False colorbar=True width=325] (cmap='fire')\n",
"%%opts Contours [colorbar=True width=325 tools=['hover']] (cmap='fire')\n",
"x,y = np.mgrid[-50:51, -50:51] * 0.05\n",
"img = hv.Image(np.sin(x**2+y**3))\n",
"\n",
"z0, z1 = img.range('z')\n",
"img + hv.operation.contours(img, levels=np.linspace(z0, z1, 5), overlaid=False)"
"img + hv.operation.contours(img, levels=5)"
]
}
],
Expand Down
34 changes: 27 additions & 7 deletions examples/reference/elements/bokeh/Path.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"A ``Path`` object is actually a collection of lines, which are all plotted with the same style. Unlike ``Curve`` where the y-axis is the dependent variable, a ``Path`` consists of lines connecting arbitrary points in two-dimensional space as is not expected to be a function.\n",
"\n"
"A ``Path`` object is actually a collection of lines, unlike ``Curve`` where the y-axis is the dependent variable, a ``Path`` consists of lines connecting arbitrary points in two-dimensional space. The individual subpaths should be supplied as a list and will be stored as NumPy arrays, DataFrames or dictionaries for each column, i.e. any of the formats accepted by columnar data formats.\n",
"\n",
"In this example we will create a Lissajous curve, which describe complex harmonic motion:"
]
},
{
Expand All @@ -39,19 +40,38 @@
"outputs": [],
"source": [
"%%opts Path (color='black' line_width=4)\n",
"lin = np.linspace(-np.pi,np.pi,300)\n",
"\n",
"def lissajous(t, a,b, delta):\n",
" return (np.sin(a * t + delta), np.sin(b * t))\n",
"lin = np.linspace(0, np.pi*2, 200)\n",
"\n",
"def lissajous(t, a, b, delta):\n",
" return (np.sin(a * t + delta), np.sin(b * t), t)\n",
"\n",
"hv.Path([lissajous(lin, 3, 5, np.pi/2)])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"hv.Path(lissajous(np.linspace(-np.pi, np.pi, 1000),3,5,np.pi/2))"
"If you looked carefully the ``lissajous`` function actually returns three columns, respectively for the x, y columns and a third column describing the point in time. By declaring a value dimension for that third column we can also color the Path by time. Since the value is cyclical we will also use a cyclic colormap (``'hsv'``) to represent this variable:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%opts Path [color_index='time'] (line_width=4 cmap='hsv')\n",
"hv.Path([lissajous(lin, 3, 5, np.pi/2)], vdims=['time'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Unlike ``Curve`` as single ``Path`` element can contain multiple lines that are disconnected from each other which will all be plotted in the same style. Only by overlaying multiple ``Path`` objects do you iterate through the defined color cycle (or any other style options that have been defined). A ``Path`` is often useful to draw arbitrary annotations on top of an existing plot.\n",
"If we do not provide a ``color_index`` overlaid ``Path`` elements will cycle colors just like other elements do unlike ``Curve`` a single ``Path`` element can contain multiple lines that are disconnected from each other. A ``Path`` can therefore often useful to draw arbitrary annotations on top of an existing plot.\n",
"\n",
"A ``Path`` Element accepts multiple formats for specifying the paths, the simplest of which is passing a list of ``Nx2`` arrays of the x- and y-coordinates, alternative we can pass lists of coordinates. In this example we will create some coordinates representing rectangles and ellipses annotating an ``RGB`` image:"
]
Expand Down
46 changes: 31 additions & 15 deletions examples/reference/elements/bokeh/Polygons.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"A ``Polygons`` object is similar to a ``Contours`` object except that each supplied path is closed and filled. Just like ``Contours``, an optional ``level`` value may be supplied; the Polygons will then be colored according to the supplied ``cmap``. Non-finite values such as ``np.NaN`` or ``np.inf`` will default to the supplied ``facecolor``.\n",
"A ``Polygons`` represents a contiguous filled area in a 2D space as a list of paths. Just like the ``Contours`` element additional scalar value dimensions maybe may be supplied, which can be used to color the ``Polygons`` with the defined ``cmap``. Like other ``Path`` types it accepts a list of arrays, dataframes, a dictionary of columns (or any of the other literal formats including tuples of columns and lists of tuples).\n",
"\n",
"Polygons with values can be used to build heatmaps with arbitrary shapes."
"In order to efficiently represent the scalar values associated with each path the dictionary format is preferable since it can store the scalar values without expanding them into a whole column. Additionally it allows passing multiple columns as a single array by specifying the dimension names as a tuple.\n",
"\n",
"In this example we will create a list of random polygons each with an associated ``level`` value. Polygons will default to using the first value dimension as the ``color_index`` but for clarity we will define the ``color_index`` explicitly:"
]
},
{
Expand All @@ -39,19 +41,21 @@
"metadata": {},
"outputs": [],
"source": [
"%%opts Polygons (cmap='hot' line_color='black' line_width=2)\n",
"np.random.seed(35)\n",
"hv.Polygons([np.random.rand(4,2)], level=0.5) *\\\n",
"hv.Polygons([np.random.rand(4,2)], level=1.0) *\\\n",
"hv.Polygons([np.random.rand(4,2)], level=1.5) *\\\n",
"hv.Polygons([np.random.rand(4,2)], level=2.0)"
"%%opts Polygons [color_index='level'] (line_color='black' line_width=1)\n",
"np.random.seed(1)\n",
"\n",
"def rectangle(x=0, y=0, width=.05, height=.05):\n",
" return np.array([(x,y), (x+width, y), (x+width, y+height), (x, y+height)])\n",
"\n",
"hv.Polygons([{('x', 'y'): rectangle(x, y), 'level': z}\n",
" for x, y, z in np.random.rand(100, 3)], vdims=['level']).redim.range(x=(-.1,1.1), y=(-0.1, 1.1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"``Polygons`` without a value are useful as annotation, but also allow us to draw arbitrary shapes."
"``Polygons`` is a very versatile element which may be used to draw custom annotations, choropleth maps (as can be seen in the [texas_unemploment example](../../../gallery/demos/bokeh/texas_choropleth_example.ipynb)) among many other examples. We can also use some of the other path based annotations to quickly generate polygons, including ``Box``, ``Bounds`` and ``Ellipse`` elements. In the simple case we can simply pass a list of these elements:"
]
},
{
Expand All @@ -60,12 +64,24 @@
"metadata": {},
"outputs": [],
"source": [
"def rectangle(x=0, y=0, width=1, height=1):\n",
" return np.array([(x,y), (x+width, y), (x+width, y+height), (x, y+height)])\n",
"\n",
"(hv.Polygons([rectangle(width=2), rectangle(x=6, width=2)]).opts(style={'fill_color': '#a50d0d'})\n",
"* hv.Polygons([rectangle(x=2, height=2), rectangle(x=5, height=2)]).opts(style={'fill_color': '#ffcc00'})\n",
"* hv.Polygons([rectangle(x=3, height=2, width=2)]).opts(style={'fill_color': 'cyan'}))"
"hv.Polygons([hv.Box(i, i, i) for i in range(1, 10)])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Alternatively we can use the ``array`` method to return the x/y-coordinates of the annotations and define additional z-values by declaring a dictionary:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.Polygons([{('x', 'y'): hv.Box(0, 0, i).array(), 'z': i} for i in range(1, 10)[::-1]], vdims=['z']) +\\\n",
"hv.Polygons([{('x', 'y'): hv.Ellipse(0, 0, (i, i)).array(), 'z': i} for i in range(1, 10)[::-1]], vdims=['z'])"
]
}
],
Expand Down
20 changes: 10 additions & 10 deletions examples/reference/elements/matplotlib/Contours.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"A ``Contours`` object is similar to ``Path`` object except it may be associated with a numeric value (the ``level``), which can be used to apply colormapping the ``Contours``. To see the effect of this we can create a number of ``Contours`` with varying shapes and ``level`` values. In this case we will create a number of concentric rings with increasing radii and level values and colormap the ``Contours`` with the viridis colormap:"
"A ``Contours`` object is similar to a ``Path`` element but allows each individual path to be associated with one or more scalar values declared as value dimensions (``vdims``), which can be used to apply colormapping the ``Contours``. Just like the ``Path`` element ``Contours`` will accept a list of arrays, dataframes, a dictionaries of columns (or any of the other literal formats including tuples of columns and lists of tuples). In order to efficiently represent the scalar values associated with each path the dictionary format is preferable since it can store the scalar values without expanding them into a whole column.\n",
"\n",
"To see the effect we will create a number of concentric rings with increasing radii and define a colormap to apply color the circles: "
]
},
{
Expand All @@ -37,20 +39,18 @@
"metadata": {},
"outputs": [],
"source": [
"%%opts Contours (cmap='viridis')\n",
"\n",
"def circle(radius, x=0, y=0):\n",
" angles = np.linspace(0, 2*np.pi, 100)\n",
" return np.array(list(zip(x+radius*np.sin(angles), y+radius*np.cos(angles))))\n",
"def circle(radius):\n",
" angles = np.linspace(0, 2*np.pi, 50)\n",
" return {'x': radius*np.sin(angles), 'y': radius*np.cos(angles), 'radius': radius}\n",
"\n",
"hv.Overlay([hv.Contours([circle(i+0.05)], level=i) for i in np.linspace(0, 1, 10)])"
"hv.Contours([circle(i) for i in np.linspace(0, 1, 10)], vdims=['radius'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Often ``Contours`` will be directly computed from an underlying ``Image``, which is made easy using the ``contours`` operation. The operation accepts an ``Image`` type as input and will compute an ``NdOverlay`` containing a ``Contours`` Element for each of the specified ``levels``. We will declare an ``Image`` of sine rings\n",
"Often ``Contours`` will be directly computed from an underlying ``Image``, which is made easy using the ``contours`` operation. The operation accepts an ``Image`` type as input and will return ``Contours`` containing iso-contours for each of the specified ``levels``. We will declare an ``Image`` of sine rings\n",
"and then compute ``Contours`` at 5 levels spaced linearly over the range of values in the Image:"
]
},
Expand All @@ -60,12 +60,12 @@
"metadata": {},
"outputs": [],
"source": [
"%%opts Contours [show_legend=False colorbar=True width=325] (cmap='fire')\n",
"%%opts Contours [colorbar=True] (cmap='fire')\n",
"x,y = np.mgrid[-50:51, -50:51] * 0.05\n",
"img = hv.Image(np.sin(x**2+y**3))\n",
"\n",
"z0, z1 = img.range('z')\n",
"img + hv.operation.contours(img, levels=np.linspace(z0, z1, 5), overlaid=False)"
"img + hv.operation.contours(img, levels=5)"
]
}
],
Expand Down
Loading

0 comments on commit 73038d7

Please sign in to comment.