from bokeh.plotting import figure, output_notebook, show

output_notebook()

p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')
p.multi_polygons(xs=[[[[1, 2, 2, 1, 1]]]],
                 ys=[[[[3, 3, 4, 4, 3]]]])
show(p)

"""
Explanation: MultiPolygons
The MultiPolygons glyphs is modeled closely on the GeoJSON spec for Polygon and MultiPolygon. The data that are used to construct MultiPolygons are nested 3 deep. In the top level of nesting, each item in the list represents a MultiPolygon - an entity like a state or a contour level. Each MultiPolygon is composed of Polygons representing different parts of the MultiPolygon. Each Polygon contains a list of coordinates representing the exterior bounds of the Polygon followed by lists of coordinates of any holes contained within the Polygon. 
Polygon with no holes
We'll start with one square with bottom left corner at (1, 3) and top right corner at (2, 4). The simple case of one Polygon with no holes is represented in geojson as follows:
geojson
 {
     "type": "Polygon",
     "coordinates": [
         [
             [1, 3],
             [2, 3],
             [2, 4],
             [1, 4],
             [1, 3]
         ]
     ]
 }
In geojson this list of coordinates is nested 1 deep to allow for passing lists of holes within the polygon. In bokeh (using MultiPolygon) the coordinates for this same polygon will be nested 3 deep to allow space for other entities and for other parts of the MultiPolygon.
End of explanation
"""


p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')
p.multi_polygons(xs=[[[[1, 1, 2, 2]]]],
                 ys=[[[[3, 4, 4, 3]]]])
show(p)

"""
Explanation: Notice that in the geojson Polygon always starts and ends at the same point and that the direction in which the Polygon is drawn (winding) must be counter-clockwise. In bokeh we don't have these two restrictions, the direction doesn't matter, and the polygon will be closed even if the starting end ending point are not the same.
End of explanation
"""


p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')
p.multi_polygons(xs=[[[ [1, 2, 2, 1], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ]]],
                 ys=[[[ [3, 3, 4, 4], [3.2, 3.6, 3.2], [3.4, 3.8, 3.8] ]]])
show(p)

"""
Explanation: Polygon with holes
Now we'll add some holes to the square polygon defined above. We'll add a triangle in the lower left corner and another in the upper right corner. In geojson this can be represented as follows:
geojson
 {
     "type": "Polygon",
     "coordinates": [
         [
             [1, 3],
             [2, 3],
             [2, 4],
             [1, 4],
             [1, 3]
         ],
         [
             [1.2, 3.2],
             [1.6, 3.6],
             [1.6, 3.2],
             [1.2, 3.2]
         ],
         [
             [1.8, 3.8],
             [1.8, 3.4],
             [1.6, 3.8],
             [1.8, 3.8]
         ]
     ]
 }
Once again notice that the direction in which the polygons are drawn doesn't matter and the last point in a polygon does not need to match the first. Hover over the holes to demonstrate that they aren't considered part of the Polygon.
End of explanation
"""


p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')
p.multi_polygons(xs=[[[ [1, 1, 2, 2], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ], [ [3, 4, 3] ]]],
                 ys=[[[ [4, 3, 3, 4], [3.2, 3.2, 3.6], [3.4, 3.8, 3.8] ], [ [1, 1, 3] ]]])
show(p)

"""
Explanation: MultiPolygon
Now we'll examine a MultiPolygon. A MultiPolygon is composed of different parts each of which is a Polygon and each of which can have or not have holes. To create a MultiPolygon from the Polygon that we are using above, we'll add a triangle below the square with holes. Here is how this shape would be represented in geojson:
geojson
 {
     "type": "MultiPolygon",
     "coordinates": [
         [
             [
                 [1, 3],
                 [2, 3],
                 [2, 4],
                 [1, 4],
                 [1, 3]
             ],
             [
                 [1.2, 3.2],
                 [1.6, 3.6],
                 [1.6, 3.2],
                 [1.2, 3.2]
             ],
             [
                 [1.8, 3.8],
                 [1.8, 3.4],
                 [1.6, 3.8],
                 [1.8, 3.8]
             ]
         ],
         [
             [
                 [3, 1],
                 [4, 1],
                 [3, 3],
                 [3, 1]
             ]
         ]
     ]
 }
End of explanation
"""


p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')
p.multi_polygons(
    xs=[
        [[ [1, 1, 2, 2], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ], [ [3, 3, 4] ]],
        [[ [1, 2, 2, 1], [1.3, 1.3, 1.7, 1.7] ]]],
    ys=[
        [[ [4, 3, 3, 4], [3.2, 3.2, 3.6], [3.4, 3.8, 3.8] ], [ [1, 3, 1] ]],
        [[ [1, 1, 2, 2], [1.3, 1.7, 1.7, 1.3] ]]],
    color=['blue', 'red'])
show(p)

"""
Explanation: It is important to understand that the Polygons that make up this MultiPolygon are part of the same entity. It can be helpful to think of representing physically separate areas that are part of the same entity such as the islands of Hawaii.
MultiPolygons
Finally, we'll take a look at how we can represent a list of MultiPolygons. Each Mulipolygon represents a different entity. In geojson this would be a FeatureCollection:
geojson
{
    "type": "FeatureCollection",
    "features": [
    {
        "type": "Feature",
        "properties": {
            "fill": "blue"
        },
        "geometry": {
            "type": "MultiPolygon",
            "coordinates": [
                [
                    [
                        [1, 3],
                        [2, 3],
                        [2, 4],
                        [1, 4],
                        [1, 3]
                    ],
                    [
                        [1.2, 3.2],
                        [1.6, 3.6],
                        [1.6, 3.2],
                        [1.2, 3.2]
                    ],
                    [
                        [1.8, 3.8],
                        [1.8, 3.4],
                        [1.6, 3.8],
                        [1.8, 3.8]
                    ]
                ],
                [
                    [
                        [3, 1],
                        [4, 1],
                        [3, 3],
                        [3, 1]
                    ]
                ]
            ]
        }
    },
    {
        "type": "Feature",
        "properties": {
            "fill": "red"
        },
         "geometry": {
            "type": "Polygon",
            "coordinates": [
                [
                    [1, 1],
                    [2, 1],
                    [2, 2],
                    [1, 2],
                    [1, 1]
                ],
                [
                    [1.3, 1.3],
                    [1.3, 1.7],
                    [1.7, 1.7],
                    [1.7, 1.3]
                    [1.3, 1.3]
                ]
            ]
        }
    }
]}
End of explanation
"""


from bokeh.models import ColumnDataSource, Plot, LinearAxis, Grid
from bokeh.models.glyphs import MultiPolygons
from bokeh.models.tools import TapTool, WheelZoomTool, ResetTool, HoverTool

source = ColumnDataSource(dict(
    xs=[
        [
            [ 
                [1, 1, 2, 2], 
                [1.2, 1.6, 1.6], 
                [1.8, 1.8, 1.6] 
            ], 
            [ 
                [3, 3, 4] 
            ]
        ],
        [
            [ 
                [1, 2, 2, 1], 
                [1.3, 1.3, 1.7, 1.7] 
            ]
        ]
    ],
    ys=[
        [
            [ 
                [4, 3, 3, 4], 
                [3.2, 3.2, 3.6], 
                [3.4, 3.8, 3.8] 
            ], 
            [ 
                [1, 3, 1] 
            ]
        ],
        [
            [ 
                [1, 1, 2, 2], 
                [1.3, 1.7, 1.7, 1.3] 
            ]
        ]
    ],
    color=["blue", "red"],
    label=["A", "B"]
))

"""
Explanation: Using MultiPolygons glyph directly
End of explanation
"""


source.to_df()

hover = HoverTool(tooltips=[("Label", "@label")])
plot = Plot(plot_width=300, plot_height=300, tools=[hover, TapTool(), WheelZoomTool()])

glyph = MultiPolygons(xs="xs", ys="ys", fill_color='color')
plot.add_glyph(source, glyph)

xaxis = LinearAxis()
plot.add_layout(xaxis, 'below')

yaxis = LinearAxis()
plot.add_layout(yaxis, 'left')

plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))

show(plot)

"""
Explanation: By looking at the dataframe for this ColumnDataSource object, we can see that each MultiPolygon is represented by one row.
End of explanation
"""


import numpy as np

from bokeh.palettes import Viridis10 as palette

def circle(radius):
    angles = np.linspace(0, 2*np.pi, 100)
    return {'x': radius*np.sin(angles), 'y': radius*np.cos(angles), 'radius': radius}

radii = np.geomspace(1, 100, 10)
source = dict(xs=[], 
              ys=[], 
              color=[palette[i] for i in range(10)], 
              outer_radius=radii)

for i, r in enumerate(radii):
    exterior = circle(r)
    if i == 0:
        polygon_xs = [exterior['x']]
        polygon_ys = [exterior['y']]
    else:
        hole = circle(radii[i-1])

        polygon_xs = [exterior['x'], hole['x']]
        polygon_ys = [exterior['y'], hole['y']]

    source['xs'].append([polygon_xs])
    source['ys'].append([polygon_ys])

p = figure(plot_width=300, plot_height=300, 
           tools='hover,tap,wheel_zoom,pan,reset,help',
           tooltips=[("Outer Radius", "@outer_radius")])

p.multi_polygons('xs', 'ys', fill_color='color', source=source)
show(p)

"""
Explanation: Using numpy arrays with MultiPolygons
Numpy arrays can be used instead of python native lists. In the following example, we'll generate concentric circles and used them to make rings. Similar methods could be used to generate contours.
End of explanation
"""


