Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Polygons with holes #3092

Merged
merged 37 commits into from Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2e7c6e1
Do not validate unreferenced dict columns
philippjfr Oct 19, 2018
8b94889
Add API to return holes from MultiInterface
philippjfr Oct 19, 2018
5a324f7
Unpack holes in bokeh ContourPlot
philippjfr Oct 19, 2018
9fd0e6a
Simplified DictInterface constructor
philippjfr Oct 21, 2018
9e0a455
Handle nans when detecting scalar dimensions
philippjfr Oct 21, 2018
9d26fac
Fixed nan warning in range calculation
philippjfr Oct 21, 2018
53bf65e
Allow ContoursPlot to declare plot_method explicitly
philippjfr Oct 21, 2018
978bb9c
Always compute Contours from expanded coordinates
philippjfr Oct 21, 2018
67466ba
Always convert holes to arrays
philippjfr Oct 21, 2018
a51a795
Contours operation handles holes and fixes levels
philippjfr Oct 21, 2018
6fc24f6
ContourPlot displays colorbar
philippjfr Oct 21, 2018
03f4d45
Handle MultiPolygons correctly
philippjfr Oct 22, 2018
df854c1
Simplify bokeh PathPlot
philippjfr Oct 22, 2018
49d87aa
Convert polygons to matplotlib paths
philippjfr Oct 22, 2018
0e4c9d5
Optimization for dictionary range
philippjfr Oct 22, 2018
7b46eb2
Fixed issue handling empty contourf holes
philippjfr Oct 22, 2018
2828290
Overhauled Path/Contours/Polygons docstrings
philippjfr Oct 22, 2018
7fd1c80
Fixed issue with multi-geometries declaring no holes
philippjfr Oct 22, 2018
8155fc1
Added Geometry_Data user guide
philippjfr Oct 22, 2018
92828ab
Small fixes
philippjfr Oct 22, 2018
de5dcc5
Make holes_key a class attribute of Polygons
philippjfr Oct 22, 2018
0754dbf
Add validation to ensure holes are correctly constructed
philippjfr Oct 22, 2018
07dd752
Removed check added for development
philippjfr Oct 22, 2018
f4379fb
Fixed contours operation
philippjfr Oct 22, 2018
90226eb
Added unit tests for hole handling
philippjfr Oct 22, 2018
95eaee1
Ignore holes if bokeh version <1.0
philippjfr Oct 22, 2018
8f5ee4e
Refactoring of hole handling
philippjfr Oct 22, 2018
43b26d7
Added unit tests for bokeh plots of Polygons with holes
philippjfr Oct 22, 2018
4cc6ae1
Various fixes for hole handling
philippjfr Oct 22, 2018
fa49912
Added unit test for matplotlib hole plotting
philippjfr Oct 22, 2018
f77829f
Addressed documentation comments
philippjfr Oct 22, 2018
c4ebc25
Small contours operation fix
philippjfr Oct 22, 2018
a29a903
Fixed flakes
philippjfr Oct 22, 2018
18d960b
Updated Bivariate element test
philippjfr Oct 22, 2018
026d700
Cast contour levels to array
philippjfr Oct 22, 2018
0147b3e
Fixed contours levels integer kwarg
philippjfr Oct 22, 2018
ff319a6
Updated contours tests
philippjfr Oct 22, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/user_guide/Geometry_Data.rst
@@ -0,0 +1,5 @@
Geometry Data
_____________

.. notebook:: holoviews ../../examples/user_guide/Geometry_Data.ipynb
:offset: 1
4 changes: 4 additions & 0 deletions doc/user_guide/index.rst
Expand Up @@ -39,6 +39,9 @@ concepts in HoloViews:
* `Gridded Datasets <Gridded_Datasets.html>`_
Explore gridded data (n-dimensional arrays) with `NumPy <http://www.numpy.org/>`_ and `XArray <http://xarray.pydata.org/>`_.

* `Geometry Data <Geometry_Data.html>`_
Working with and representing geometry data such as lines, multi-lines, polygons, multi-polygons and contours.

* `Indexing and Selecting Data <Indexing_and_Selecting_Data.html>`_
Select and index subsets of your data with HoloViews.

Expand Down Expand Up @@ -119,6 +122,7 @@ These guides provide detail about specific additional features in HoloViews:
Live Data <Live_Data>
Tabular Datasets <Tabular_Datasets>
Gridded Datasets <Gridded_Datasets>
Geometry Data <Geometry_Data>
Indexing and Selecting Data <Indexing_and_Selecting_Data>
Transforming Elements <Transforming_Elements>
Responding to Events <Responding_to_Events>
Expand Down
2 changes: 1 addition & 1 deletion examples/reference/elements/bokeh/Contours.ipynb
Expand Up @@ -28,7 +28,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"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. For a full description of the path geometry data model see the [Geometry Data User Guide](../user_guide/Geometry_Data.ipynb).\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 Down
2 changes: 1 addition & 1 deletion examples/reference/elements/bokeh/Path.ipynb
Expand Up @@ -28,7 +28,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"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. For a full description of the path geometry data model see the [Geometry Data User Guide](../user_guide/Geometry_Data.ipynb). \n",
"\n",
"In this example we will create a Lissajous curve, which describe complex harmonic motion:"
]
Expand Down
2 changes: 1 addition & 1 deletion examples/reference/elements/bokeh/Polygons.ipynb
Expand Up @@ -28,7 +28,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"A ``Polygons`` represents a contiguous filled area in a 2D space as a list of polygon geometries. 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), but also supports a special 'holes' key to represent empty interior regions. For a full description of the polygon geometry data model see the [Geometry Data User Guide](../user_guide/Geometry_Data.ipynb). \n",
"\n",
"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",
Expand Down
2 changes: 1 addition & 1 deletion examples/reference/elements/matplotlib/Contours.ipynb
Expand Up @@ -28,7 +28,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"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. For a full description of the path geometry data model see the [Geometry Data User Guide](../user_guide/Geometry_Data.ipynb). \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 Down
2 changes: 1 addition & 1 deletion examples/reference/elements/matplotlib/Path.ipynb
Expand Up @@ -28,7 +28,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"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. For a full description of the path geometry data model see the [Geometry Data User Guide](../user_guide/Geometry_Data.ipynb). \n",
"\n",
"In this example we will create a Lissajous curve, which describe complex harmonic motion:"
]
Expand Down
2 changes: 1 addition & 1 deletion examples/reference/elements/matplotlib/Polygons.ipynb
Expand Up @@ -28,7 +28,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"A ``Polygons`` represents a contiguous filled area in a 2D space as a list of polygon geometries. 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), but also supports a special 'holes' key to represent empty interior regions. For a full description of the polygon geometry data model see the [Geometry Data User Guide](../user_guide/Geometry_Data.ipynb). \n",
"\n",
"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",
Expand Down
273 changes: 273 additions & 0 deletions examples/user_guide/Geometry_Data.ipynb
@@ -0,0 +1,273 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In addition to the two main types of data, namely tabular/columnar and gridded data HoloViews also provide extensible interfaces to represent path geometry data. Specifically it has three main element types used to representing different types of geometries. In this section we will cover the HoloViews data model for representing different kinds of geometries.\n",
"\n",
"There are many different ways of representing path geometries but HoloViews' data model is oriented on GEOS geometry definitions and allows faithfully round-tripping data between its element types and GEOS geometry definitions such as ``LinearString``, ``Polygon``, ``MultiLineString`` and ``MultiPolygon`` geometries (even if this is not implemented in HoloViews itself). Since HoloViews interfaces are extensible many different formats for representing geometries could be supported (see [GeoViews](http://geoviews.org/user_guide/Geometries.html) for other representations) but here we will cover the native formats used by HoloViews to represent this data."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import holoviews as hv\n",
"\n",
"hv.extension('bokeh')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Representing paths\n",
"\n",
"The ``Path`` element represents a collection of path geometries with associated values. Each path geometry may be split into sub-geometries on NaN-values and may be associated with scalar values or array values varying along its length. In analogy to GEOS geometry types a Path is a collection of LineString and MultiLineString geometries with associated values.\n",
"\n",
"While many different formats are accepted in theory, natively HoloViews provides the ``MultiInterface`` which allows representing paths as lists of regular columnar data objects including arrays, dataframes and dictionaries of column arrays and scalars. A simple path geometry may therefore be drawn using:"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are interfaces discussed elsewhere in the user guide? If not, I wouldn't mention MultiInterface explicitly, if so, I might want to point to where interfaces are discussed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay removing this.

]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.Path([{'x': [1, 2, 3, 4, 5], 'y': [0, 0, 1, 1, 2]}]).options(padding=0.1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here the dictionary of x- and y-coordinates could also be an NumPy array with two columns or a dataframe with 'x' and 'y' columns. Since the format supports lists any number of geometries may be drawn in this way. Additionally, it is also possible to associate a value with each path by declaring it as a value dimension:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.Path([{'x': [1, 2, 3, 4, 5], 'y': [0, 0, 1, 1, 2], 'value': 0},\n",
" {'x': [5, 4, 3, 2, 1], 'y': [2, 2, 1, 1, 0], 'value': 1}], vdims='value').options(padding=0.1, color_index='value')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Multi-geometry"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Splitting the geometries in this way allows assigning separate values to each geometry, however often multiple geometries share the same value in which case it may be desirable to represent them as a multi-geometry by combining the coordinates and separating them by a NaN value:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.Path([{'x': [1, 2, 3, 4, 5, np.nan, 5, 4, 3, 2, 1],\n",
" 'y': [0, 0, 1, 1, 2, np.nan, 2, 2, 1, 1, 0], 'value': 0}],\n",
" vdims='value').options(padding=0.1, color_index='value')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This represents a more efficient format particularly when there are very many small geometries with the same value.\n",
"\n",
"#### Scalar vs. continuously varying value dimensions\n",
"\n",
"Unlike ``Contours`` which are limited to representing iso-contours or isoclines, i.e. a function of two variables which describes a curve along which the function has a constant value, a ``Path`` element may also have continuously varying values along its path. Below we will declare a path with a value that varies along its path:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"a, b, delta = 3, 5, np.pi/2.\n",
"\n",
"vs = np.linspace(0, np.pi*2, 200)\n",
"xs = np.sin(a * vs + delta)\n",
"ys = np.sin(b * vs)\n",
"\n",
"hv.Path([{'x': xs, 'y': ys, 'value': vs}], vdims='value').options(\n",
" color_index='value', padding=0.1, cmap='hsv'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that since not all data formats allow storing scalar values as actual scalars, 1D-arrays matching the length of the coordinates but with only one unique value are also considered scalar. For example the following is a valid ``Contours`` element despite the fact that the value dimension is not a scalar variable:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.Contours([{'x': xs, 'y': ys, 'value': np.ones(200)}], vdims='value').options(\n",
" color_index='value', padding=0.1\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Representing Polygons\n",
"\n",
"The ``Polygons`` element represents a collection of polygon geometries with associated scalar values. Each polygon geometry may be split into sub-geometries on NaN-values and may be associated with scalar values. In analogy to GEOS geometry types a ``Polygons`` element is a collection of Polygon and MultiPolygon geometries. Polygon geometries are defined as a set of coordinates describing the exterior bounding ring and any number of interior holes.\n",
"\n",
"In summary ``Polygons`` can be represented in much the same way as ``Paths`` above but have a special reserved key to store the polygon interiors or 'holes'. The holes are stored as a list-of-lists of arrays. This nested format is necessary to unambiguously associate holes with the sub-geometries in a multi-geometry. In the simplest case of a single Polygon geometry the format looks like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"xs = [1, 2, 3]\n",
"ys = [2, 0, 7]\n",
"holes = [[[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]]]\n",
"\n",
"hv.Polygons([{'x': xs, 'y': ys, 'holes': holes}]).options(padding=0.1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The 'x' and 'y' coordinates represent the exterior of the Polygon and the list-of-list of holes defines two interior regions inside the polygon.\n",
"\n",
"In a multi-Polygon arrangement where two Polygon geometries are separated by NaNs, the purpose of the nested format becomes a bit clearer. Here the polygon from above still has the two holes but the second polygon does not have any holes, which we declare with an empty list:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"xs = [1, 2, 3, np.nan, 6, 7, 3]\n",
"ys = [2, 0, 7, np.nan, 7, 5, 2]\n",
"\n",
"holes = [\n",
" [[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]],\n",
" []\n",
"]\n",
"\n",
"hv.Polygons([{'x': xs, 'y': ys, 'holes': holes}]).options(padding=0.1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If a polygon has no holes at all the 'holes' key may be ommitted entirely:"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth doing this first (no holes) before introducing holes? I.e a reminded of the Polygon constructor before holes were supported...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think saying that the format is exactly the same as the paths one is enough, otherwise it gets a bit repetitive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.Polygons([{'x': xs, 'y': ys, 'holes': holes, 'value': 0},\n",
" {'x': [4, 6, 6], 'y': [0, 2, 1], 'value': 1}], vdims='value').options(padding=0.1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Accessing the data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To access the underlying data the geometry elements (``Path``/``Contours``/``Polygons``) implement a ``split`` method. By default it simply returns a list of elements, where each contains only one geometry:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"poly = hv.Polygons([\n",
" {'x': xs, 'y': ys, 'holes': holes, 'value': 0},\n",
" {'x': [4, 6, 6], 'y': [0, 2, 1], 'value': 1}\n",
"], vdims='value')\n",
"\n",
"polys = poly.split()\n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think it is worth introducing the polys handle here (just inline it to the Layout). Looks to me like the handle that is reused is just poly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

"hv.Layout(polys)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Using the ``datatype`` argument the data may instead be returned in the desired format, e.g. a list of arrays:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"poly.split(datatype='array')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that this conversion may be lossy if the converted format has no way of representing 'holes' or other data."
]
}
],
"metadata": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to clear out this metadata before this PR can be merged...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}