0

System - Python 3.6.5, Bokeh 1.1.0, Shapely 1.6.4

I'm trying to plot a polygon with voids/holes in Bokeh. Here's my code:

from shapely.geometry import LineString
import numpy as np
from bokeh.plotting import figure
from bokeh.io import output_file, show

coords = np.array([
    [0, 0, 0],
    [0.000796326711, 0.999999683, 0],
    [-0.999202405, 1.00159234, 0],
    [-1.98359251, 1.1775927, 0],
    [-2.92219092, 1.52260431, 0],
    [-3.78621865, 2.02604854, 0],
    [-4.75089683, 2.28947977, 0],
    [-5.75088129, 2.29505403, 0],
    [-6.71843634, 2.04239373, 0],
    [-7.58802294, 1.54861327, 0],
    [-7.93751519, 0.611674017, 0],
    [-7.60373939, -0.330978472, 0],
    [-6.74253141, -0.839231188, 0],
    [-5.75597881, -0.675787108, 0],
    [-5.48640738, 0.287193288, 0],
    [-6.24471932, 0.939085149, 0],
    [-6.49121061, 1.90823016, 0],
    [-6.13650105, 2.8432067, 0],
    [-5.30921186, 3.40498298, 0],
    [-4.39661375, 3.81384077, 0],
    [-3.42668851, 4.05724379, 0],
    [-2.42917568, 4.12772889, 0],
    [-1.43466068, 4.02313489, 0],
    [-0.44683782, 4.17871769, 0],
    [0.467380974, 4.58393862, 0],
    [1.2460695, 5.21134931, 0],
    [1.83648192, 6.01845103, 0],
    [1.77076367, 7.01628925, 0],
    [1.07959782, 7.73898542, 0],
    [1.36065246, 8.69867719, 0],
    [2.33248521, 8.9343488, 0],
    [3.02192258, 8.21000351, 0],
    [3.17042178, 7.22109098, 0],
    [2.72413551, 6.32620072, 0],
    [2.40767989, 7.27480804, 0],
    [3.30185823, 6.82709709, 0],
    [2.35274811, 6.51215267, 0],
    [2.38220794, 7.51171864, 0],
    [3.31112226, 7.14142393, 0],
    [2.69243164, 7.92705865, 0],
    [2.83462789, 6.93722016, 0],
    [3.2071407, 7.86524721, 0],
    [2.75445232, 6.97357842, 0],
    [3.28380791, 7.82197848, 0],
    [2.68188764, 7.02342229, 0],
    [3.35170769, 7.76594576, 0],
    [2.97550277, 6.83940927, 0],
    [3.01292139, 7.83870895, 0],
    [3.31878194, 6.88663263, 0],
    [2.70636709, 7.67716919, 0],
    [3.69830293, 7.55042818, 0],
    [2.90679201, 6.93927315, 0],
    [3.86117545, 6.64068939, 0],
    [3.55910813, 7.59397599, 0],
    [2.95084789, 6.80023832, 0],
    [3.87479046, 7.18276954, 0],
    [2.8834715, 7.31424852, 0],
    [3.67575378, 6.7040938, 0],
    [3.29543092, 7.62894759, 0],
    [3.59294004, 6.67422861, 0],
    [3.38055037, 7.65141366, 0],
    [3.50617457, 6.65933576, 0],
    [3.46828944, 7.65861786, 0],
    [3.84492686, 6.7322571, 0],
    [3.17476024, 7.47446779, 0],
    [2.55957061, 6.68608863, 0],
    [1.94375336, 5.8981996, 0],
    [1.32730889, 5.11080121, 0],
    [0.710237596, 4.32389397, 0],
    [0.482766922, 3.35010904, 0],
    [0.687368358, 2.37126367, 0],
    [1.28584038, 1.57011996, 0],
    [2.16644112, 1.09626099, 0],
    [3.09577174, 1.46550968, 0],
    [3.41102886, 2.41451598, 0],
    [2.88736947, 3.26644371, 0],
    [1.89827015, 3.41369362, 0],
    [1.49930958, 2.49672554, 0],
    [2.28127802, 1.87340752, 0],
    [3.08621808, 2.46676365, 0],
    [2.72215545, 3.39813812, 0],
    [1.86773544, 2.87855517, 0],
    [2.52719481, 2.12681496, 0],
    [3.15362139, 2.90629539, 0],
    [2.27758894, 3.38854755, 0],
    [2.38423092, 2.39425007, 0],
    [3.13806789, 3.05131157, 0],
    [2.16764342, 3.29271652, 0],
    [2.52576528, 2.35904167, 0],
    [2.68324652, 3.34656364, 0],
    [2.05248464, 2.57058719, 0],
    [2.24068913, 1.58845733, 0],
    [1.6086922, 0.813486444, 0],
    [1.7693182, 1.80050179, 0],
    [2.12297492, 0.865126504, 0],
    [1.34951607, 1.49897303, 0],
    [2.31258524, 1.76822716, 0],
    [1.98000043, 0.825153807, 0],
    [2.88237044, 1.25611595, 0],
    [1.93982797, 1.59020233, 0],
    [2.36935241, 0.687147091, 0],
    [2.27538732, 1.68272258, 0],
    [2.02240825, 0.715250833, 0],
    [2.73321392, 0.0118623958, 0],
    [2.82480034, -0.983934737, 0],
    [2.25421938, -1.8051761, 0],
    [1.28903664, -2.0667526, 0],
    [0.289038757, -2.06881209, 0],
    [-0.677213231, -1.81121338, 0],
    [-0.034783313, -1.04486894, 0],
    [-1.00079436, -1.30336971, 0],
    [-1.50176903, -2.16883166, 0],
    [-1.2448071, -3.13525319, 0],
    [-0.38014413, -3.6376056, 0],
    [0.388154841, -2.99751444, 0],
    [0.0501889901, -2.05635607, 0],
    [-0.949797967, -2.05124865, 0],
    [-1.29735985, -2.98890569, 0],
    [-0.333189804, -3.25419068, 0],
    [-0.152164036, -2.27071233, 0],
    [-1.14760173, -2.17529848, 0],
    [-1.15669068, -3.17525717, 0],
    [-0.28576497, -2.68384248, 0],
    [-1.1464002, -2.17462052, 0],
    [-1.15787801, -3.17455465, 0],
    [-0.285780804, -2.68522198, 0],
    [-1.28075125, -2.58505308, 0],
    [-0.523693737, -3.23840132, 0],
    [-0.76832174, -2.26878431, 0],
    [-1.12482779, -3.20307732, 0],
    [-0.61081635, -2.34529399, 0],
    [-1.26657273, -3.10026656, 0],
    [-0.489178007, -2.47125349, 0],
    [-1.36437484, -2.95502047, 0],
    [-0.643655461, -2.26179354, 0],
    [-1.1610782, -3.11752347, 0],
    [-0.88200064, -2.15725491, 0],
    [-0.903829214, -3.15701664, 0],
    [-1.52912076, -2.3766254, 0],
    [-0.548650127, -2.57329095, 0],
    [-1.42653001, -3.05217184, 0],
    [-1.06111234, -2.12132818, 0],
    [-0.372490874, -2.84644919, 0],
    [-1.32095035, -3.16334762, 0],
    [-0.362479159, -3.44853698, 0],
    [-0.983410887, -2.66467233, 0],
    [-0.925174642, -3.66297516, 0],
    [-0.808381766, -2.66981886, 0],
    [-1.0966227, -3.62737678, 0],
    [-0.295873537, -4.2263766, 0],
    [0.0973636203, -5.14581368, 0],
    [-0.022592165, -6.13859291, 0],
    [-0.623503247, -6.93790879, 0],
    [-1.29238483, -7.68127777, 0],
    [-2.02405304, -8.36293869, 0],
    [-2.81283743, -8.97760868, 0],
    [-3.65262488, -9.52052399, 0],
    [-4.25671377, -10.3174409, 0],
    [-4.55257078, -11.2726732, 0],
    [-4.50467215, -12.2715254, 0],
    [-4.1187691, -13.1940647, 0],
    [-3.19386309, -13.5742605, 0],
    [-2.2707103, -13.1898273, 0],
    [-1.88904191, -12.2655279, 0],
    [-2.27200443, -11.3417641, 0],
    [-3.27107409, -11.3848897, 0],
    [-3.57301046, -12.3382178, 0],
    [-2.78092926, -12.9486335, 0],
    [-1.93599218, -12.4137678, 0],
    [-2.54183032, -11.6181799, 0],
    [-3.28212411, -12.2904634, 0],
    [-4.07867591, -12.8950337, 0],
    [-4.92531242, -13.4272053, 0],
    [-5.81547216, -13.8828539, 0],
    [-6.49669089, -14.6149338, 0],
    [-6.88717425, -15.5355438, 0],
    [-6.94003665, -16.5341456, 0],
    [-6.64893087, -17.4908365, 0],
    [-5.76698046, -17.9621787, 0],
    [-4.80982717, -17.6725969, 0],
    [-4.02410748, -18.2911796, 0],
    [-4.08094552, -19.289563, 0],
    [-4.93179296, -19.8149759, 0],
    [-5.93137812, -19.843777, 0],
    [-6.81106642, -19.3682263, 0],
    [-5.88102777, -19.0007646, 0],
    [-6.75995757, -18.5238134, 0],
    [-7.7596294, -18.5494304, 0],
    [-8.61297752, -19.0707719, 0],
    [-9.09202707, -19.9485598, 0],
    [-8.64847045, -20.8448061, 0],
    [-7.66001513, -20.9963188, 0],
    [-6.96837319, -20.2740782, 0],
    [-7.16250739, -19.2931032, 0],
    [-8.16230648, -19.3131474, 0],
    [-8.3169669, -20.3011151, 0],
    [-8.80090203, -21.176219, 0],
    [-9.55547824, -21.8324314, 0],
    [-10.489271, -22.1902456, 0],
    [-11.3886733, -22.6273676, 0],
    [-12.2469643, -23.1405309, 0],
    [-12.6826532, -22.2404336, 0],
    [-12.4955545, -23.2227747, 0],
    [-11.5263757, -23.4691329, 0],
    [-10.8928903, -22.6953782, 0],
    [-11.32571, -21.7938977, 0],
    [-12.2275349, -22.2259993, 0],
    [-11.7961515, -23.128168, 0],
    [-11.528749, -24.091753, 0],
    [-11.4335264, -25.087209, 0],
    [-11.5134034, -26.0840137, 0],
    [-11.3333499, -27.0676705, 0],
    [-10.905562, -27.9715497, 0],
    [-11.7691193, -28.4758004, 0],
    [-10.86456, -28.0494527, 0],
    [-11.803111, -28.3945932, 0],
    [-10.8378421, -28.1333347, 0],
    [-11.6560006, -28.7083275, 0],
    [-11.0831893, -27.8886402, 0],
    [-11.9895551, -27.4661465, 0],
    [-12.2490106, -28.4319015, 0],
    [-11.2529438, -28.5205069, 0],
    [-11.7509926, -27.6533579, 0],
    [-12.1763713, -28.5583734, 0],
    [-12.1802155, -29.558366, 0],
    [-11.7618076, -30.4666252, 0],
    [-10.9992693, -31.1135682, 0],
    [-10.0133983, -30.9460619, 0],
    [-9.50732286, -30.0835727, 0],
    [-8.52118632, -29.9176368, 0],
    [-7.76071255, -30.5670054, 0],
    [-7.77013098, -31.5669611, 0],
    [-8.20198486, -32.4689047, 0],
    [-8.9750606, -33.1032184, 0],
    [-9.05044793, -32.1060641, 0],
    [-8.19079308, -32.6169393, 0],
    [-9.10268249, -33.0273753, 0],
    [-9.34776313, -32.0578726, 0],
    [-8.35037373, -31.9856619, 0],
    [-8.45324705, -32.9803564, 0],
    [-9.41474758, -32.7055533, 0],
    [-9.39736662, -31.7057043, 0],
    [-8.42689579, -31.4644858, 0],
    [-7.9434172, -32.339842, 0],
    [-8.66436495, -33.0328314, 0],
    [-9.65855654, -32.9252067, 0],
    [-10.2145035, -32.0939889, 0],
    [-9.93434503, -31.1340352, 0],
    [-9.01857521, -30.7323317, 0],
    [-8.01885729, -30.756082, 0],
    [-7.12319629, -31.2008194, 0],
    [-8.00161475, -31.6787117, 0],
    [-7.8884498, -30.6851354, 0],
    [-7.13999644, -31.3483229, 0],
    [-7.92357113, -31.9696205, 0],
    [-8.39866294, -31.0896842, 0],
    [-7.44934549, -30.7753653, 0],
    [-7.30543703, -31.7649563, 0],
    [-8.22437664, -32.1593546, 0],
    [-8.84254942, -31.3733123, 0],
    [-8.15880176, -32.1030309, 0],
    [-7.37177597, -31.4861108, 0],
    [-7.91708735, -30.6478772, 0],
    [-8.80003233, -31.1173537, 0],
    [-8.05741687, -31.7870717, 0],
    [-7.68133922, -30.8604836, 0],
    [-7.47109679, -29.8828343, 0],
    [-7.43301967, -28.8835595, 0],
    [-7.5682543, -27.8927459, 0],
    [-8.2467546, -27.1581458, 0],
    [-9.22372923, -26.9447903, 0],
    [-9.44247386, -25.9690081, 0],
    [-8.9793374, -26.8552951, 0],
    [-9.65549433, -26.1185375, 0],
    [-8.8121176, -26.6558602, 0],
    [-9.80366964, -26.7855697, 0],
])

creature_morphology = LineString(coords[:, 0:2])
creature_patch = creature_morphology.buffer(0.5)
patch_x, patch_y = creature_patch.exterior.coords.xy

p3 = figure(plot_width=500, plot_height=500,
            title="Best Creature", output_backend="webgl")
p3.line(x=coords[:, 0], y=coords[:, 1], line_color='red')
p3.patch(x=patch_x, y=patch_y)

output_file('p3.html')
show(p3)

which works fine and produces a plot like so (zoomed in): enter image description here

However, there should be a void in the middle of the circle. I've tested this with other examples as well and each time the voids are missing. Suspected solutions:

  1. Plot patch using Descartes. Unsure if Bokeh has Descartes compatibility.
  2. Coordinates from creature_patch.exterior.coords.xy are wrong, use others. Which though?

Any help is appreciated thanks!

Izak Joubert
  • 906
  • 11
  • 29

2 Answers2

1

There is a user guide section on Polygons With Holes As well as a nice example in the reference guide.

multi_polygon data is 4-level list:

  1. list of multi-polygons
  2. each multi-polygon is a list of polygons
  3. each polygon is a list with one exterior and zero or more holes
  4. each exterior/hole is a list of coordinates

Here is an example:

from bokeh.plotting import figure, show, output_file

output_file('multipolygon_with_holes.html')

p = figure(plot_width=400, plot_height=400)
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)

enter image description here

bigreddot
  • 33,642
  • 5
  • 69
  • 122
  • Thanks for the input bigreddot. I'll have to check if shapely can give me the _hole_ coordinates otherwise I won't know where the holes are – Izak Joubert Jun 06 '19 at 06:48
  • I don't really understand. If *you* don't know where the holes are supposed to be located, Bokeh certainly doesn't. It can only plot what you tell it to explicity. – bigreddot Jun 06 '19 at 07:10
  • I'm using `Shapely` to actually define the polygon so I'm hoping it knows – Izak Joubert Jun 06 '19 at 07:21
  • Got it working although it's more of an effort than using the white void overlay method. Also, using the `glyp.data_source.data` instance did not work. Had to create a `ColumnDataSource` – Izak Joubert Jun 10 '19 at 09:28
  • Glad to know you were using `shapely`. `geopandas` stores nation boundary data using `shapely` primitives, and bokeh's `GeoJSONDataSource` represents most shapes correctly, but it fails on South Africa. The problem must be the same. I wonder if I should open an issue for this. Mapping compatibility with `geopandas` seems important for the project. – senderle May 20 '20 at 19:38
0

To anyone else who might want to do this. Shapely does provide the coordinates for the voids. However, you need to iterate over the voids list to plot them separately like so:

# creature_patch.interiors is the list of all the interior voids that may exist
for i, _ in enumerate(creature_patch.interiors):
    # iterate over the list and add a patch of white for each void
    inside_x, inside_y = creature_patch.interiors[i].coords.xy
    p3.patch(x=inside_x, y=inside_y, fill_color='white')
Izak Joubert
  • 906
  • 11
  • 29
  • Drawing white patches to "fake" the holes is certainly one way to approach things, however it definitely has drawbacks. E.g. if you want to be able to "see through" the holes, to other glyphs or maps or grid lines underneath. Or if you want hit testing for hover tools to properly ignore the holes, this won't work. That's the reason the `multi_polygons` glyph described above was added. It knows and and understands the holes as first-class components and a real but important part of the data to be handled in its own proper way. – bigreddot Jun 06 '19 at 15:29
  • Okay, let me try adding everything to a list and using multi_polygons – Izak Joubert Jun 07 '19 at 06:18
  • @bigreddot, if you glyph source is a literal, will it update when the data is changed? – Izak Joubert Jun 07 '19 at 06:44
  • Glyphs will always update when their data source is changed. But you have to be aware when you pass literal values to a glyph, Bokeh creates that data source automatically and internally for you, so you have to take steps to get ahold of it. It's available as the `data_source` property of the `GlyphRenderer` (the thing returned by `p.multi_line`) – bigreddot Jun 07 '19 at 07:30
  • Ah so this: `p3.multi_polygons(xs=x_points, ys=y_points)` `x_points = new_data_x` `y_points = new_data_y` will not work. I have to call `p3.multi_polygons.data = new_data` – Izak Joubert Jun 07 '19 at 07:34
  • Not quite. `p3.multi_polygons` is a function, it dos not have `.data` property. But calling that function returns an object (glyph renderer) that has a `data_source` property, and that dat source has the `.data` dict you can update. – bigreddot Jun 07 '19 at 15:58
  • However, all that said, it's usually easier to just create and save a reference to a `ColumnDataSource` explicitly up front, that's how all the examples and docs usually do things: https://bokeh.pydata.org/en/latest/docs/user_guide/data.html#columndatasource Then you have the `source` you want to update, and can update it directly whenever. – bigreddot Jun 07 '19 at 16:01