def get_colormap(colormap, N_cols): """Returns a colormap with <N_cols> colors. <colormap> can be either None, a string with the name of a Bokeh color palette or a list/tuple of colors.""" if colormap is None: if N_cols <= 10: colormap = all_palettes["Category10"][10][:N_cols] elif N_cols <= 20: colormap = all_palettes["Category20"][N_cols] else: colormap = all_palettes["Category20"][20] * int(N_cols / 20 + 1) colormap = colormap[:N_cols] elif isinstance(colormap, str): if colormap in all_palettes: colormap = all_palettes[colormap] max_key = max(colormap.keys()) if N_cols <= max_key: colormap = colormap[N_cols] else: colormap = colormap[max_key] colormap = colormap * int(N_cols / len(colormap) + 1) colormap = colormap[:N_cols] else: raise ValueError( "Could not find <colormap> with name %s. The following predefined colormaps are supported (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (colormap, list(all_palettes.keys()))) elif isinstance(colormap, (list, tuple)): colormap = colormap * int(N_cols / len(colormap) + 1) colormap = colormap[:N_cols] else: raise ValueError( "<colormap> can onyl be None, a name of a colorpalette as string( see https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ) or a list/tuple of colors." ) return colormap
def __init__(self, colorName, num=None, *args, **kwargs): if kwargs.get('loggingLevel') is not None: logger.setLevel(kwargs.get('loggingLevel')) debug('Creating palette for colorName: %s with %s colors.' % (colorName, num)) paletteFamilies = all_palettes.keys() self.callGroups = {} try: # See if the colorName self._Palette = [eval("bc.named." + colorName + ".to_hex()")] debug('Creating one color palette: %s' % self._Palette) return except: if colorName not in paletteFamilies: warning( '"%s" is neither a known color name nor a known color palette name; using Dark2[8].' % colorName) return pass _num = num if _num is None or _num < 1: _num = 8 pf = all_palettes[colorName] # if _num > max(pf.keys()): # debug('The number of lines in the graph exceeds the number of colors in the palette. Switch to a larger palette.') # pf = all_palettes['Viridis'] pfk = pf.keys() if _num in pfk: self._Palette = pf[_num] debug('Creating predefined palette: %s' % str(self._Palette)) return maxp = max(pfk) rptcnt = ((_num - 1) // maxp) + 1 if _num > maxp: minContainPalette = maxp else: minContainPalette = min( list(it.filterfalse(lambda x: x < _num, pfk))) if rptcnt == 1: debug('Generating pallette of %s colors from %s of %s.' % (_num, minContainPalette, pf[minContainPalette])) self._Palette = pf[minContainPalette][ 0:_num] # Take first _num colors from palette. # self._Palette = linear_palette(pf[minContainPalette], _num) # Take evenly spaced colors form palette. else: self._Palette = linear_palette(pf[maxp] * rptcnt, _num) debug('Creating palette: %s' % str(self._Palette))
def set_color_palette_by_num_plots(self): """ Set the colormap for the configured number of plots according to the set colormap or color copied from https://github.com/PatrikHlobil/Pandas-Bokeh/blob/master/pandas_bokeh/plot.py credits to PatrikHlobil modified for use in this Plotter class """ from bokeh.palettes import all_palettes #pylint: disable=no-name-in-module if self['color'] is not None: color = self['color'] if not isinstance(self['color'], (list, tuple)): color = [color] color = color * int(self.num_plots / len(color) + 1) color = color[:self.num_plots] elif self['color_palette'] is not None: if self['color_palette'] in all_palettes: color = all_palettes[self['color_palette']] max_key = max(color.keys()) if self.num_plots <= max_key: color = color[self.num_plots] else: color = color[max_key] color = color * int(self.num_plots / len(color) + 1) color = color[:self.num_plots] else: raise ValueError( 'Could not find <colormap> with name %s. The following predefined colormaps are ' 'supported (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s' % (self['color_palette'], list(all_palettes.keys()))) else: if self.num_plots <= 10: color = all_palettes['Category10'][10][:self.num_plots] elif self.num_plots <= 20: color = all_palettes['Category20'][self.num_plots] else: color = all_palettes['Category20'][20] * int(self.num_plots / 20 + 1) color = color[:self.num_plots] self['color'] = color
def get_palette(palette_name): if palette_name not in all_palettes.keys(): return RdYlBu[11] key = max(all_palettes[palette_name].keys()) return all_palettes[palette_name][key]
def geoplot(gdf_in, fig=None, figsize=None, title="", xlabel="Longitude", ylabel="Latitude", color="blue", colormap=None, colormap_uselog=False, colormap_range=None, category=None, dropdown=None, slider=None, slider_range=None, slider_name="", show_colorbar=True, xrange=None, yrange=None, hovertool=True, hovertool_columns=[], simplify_shapes=None, tile_provider="CARTODBPOSITRON_RETINA", tile_provider_url=None, toolbar_location=None, show_figure=True, return_figure=True, return_html=False, legend=True, **kwargs): """Doc-String: TODO""" gdf = gdf_in.copy() #Check layertypes: layertypes = [] if "Point" in str(gdf.geom_type.unique()): layertypes.append("Point") if "Line" in str(gdf.geom_type.unique()): layertypes.append("Line") if "Polygon" in str(gdf.geom_type.unique()): layertypes.append("Polygon") if len(layertypes) > 1: raise Exception( "Can only plot GeoDataFrames/Series with single type of geometry (either Point, Line or Polygon). Provided is a GeoDataFrame/Series with types: %s" % layertypes) #Get and check provided parameters for geoplot: figure_options = { "title": title, "x_axis_label": xlabel, "y_axis_label": ylabel, "plot_width": 600, "plot_height": 400, "toolbar_location": toolbar_location, "active_scroll": "wheel_zoom" } if not isinstance(figsize, type(None)): width, height = figsize figure_options["plot_width"] = width figure_options["plot_height"] = height if not isinstance(fig, type(None)): raise NotImplementedError("Parameter <figure> not yet implemented.") #Convert GeoDataFrame to Web Mercador Projection: gdf.to_crs({'init': 'epsg:3857'}, inplace=True) #Simplify shapes if wanted: if isinstance(simplify_shapes, numbers.Number): if layertypes[0] in ["Line", "Polygon"]: gdf["geometry"] = gdf["geometry"].simplify(simplify_shapes) elif not isinstance(simplify_shapes, type(None)): raise ValueError( "<simplify_shapes> parameter only accepts numbers or None.") #Check for category, dropdown or slider (choropleth map column): category_options = 0 if not isinstance(category, type(None)): category_options += 1 category_columns = [category] if not isinstance(dropdown, type(None)): category_options += 1 category_columns = dropdown if not isinstance(slider, type(None)): category_options += 1 category_columns = slider if category_options > 1: raise ValueError( "Only one of <category>, <dropdown> or <slider> parameters is allowed to be used at once." ) #Check for category (single choropleth plot): if isinstance(category, type(None)): pass elif isinstance(category, (list, tuple)): raise ValueError( "For <category>, please provide an existing single column of the GeoDataFrame." ) elif category in gdf.columns: pass else: raise ValueError( "Could not find column '%s' in GeoDataFrame. For <category>, please provide an existing single column of the GeoDataFrame." % category) #Check for dropdown (multiple choropleth plots via dropdown selection): if isinstance(dropdown, type(None)): pass elif not isinstance(dropdown, (list, tuple)): raise ValueError( "For <dropdown>, please provide a list/tuple of existing columns of the GeoDataFrame." ) else: for col in dropdown: if col not in gdf.columns: raise ValueError( "Could not find column '%s' for <dropdown> in GeoDataFrame. " % col) #Check for slider (multiple choropleth plots via slider selection): if isinstance(slider, type(None)): pass elif not isinstance(slider, (list, tuple)): raise ValueError( "For <slider>, please provide a list/tuple of existing columns of the GeoDataFrame." ) else: for col in slider: if col not in gdf.columns: raise ValueError( "Could not find column '%s' for <slider> in GeoDataFrame. " % col) if not isinstance(slider_range, type(None)): if not isinstance(slider_range, Iterable): raise ValueError( "<slider_range> has to be a type that is iterable like list, tuple, range, ..." ) else: slider_range = list(slider_range) if len(slider_range) != len(slider): raise ValueError( "The number of elements in <slider_range> has to be the same as in <slider>." ) steps = [] for i in range(len(slider_range) - 1): steps.append(slider_range[i + 1] - slider_range[i]) if len(set(steps)) > 1: raise ValueError( "<slider_range> has to have equal step size between each elements (like a range-object)." ) else: slider_step = steps[0] slider_start = slider_range[0] slider_end = slider_range[-1] #Check colormap if either <category>, <dropdown> or <slider> is choosen: if category_options == 1: if isinstance(colormap, type(None)): colormap = blue_colormap elif isinstance(colormap, (tuple, list)): if len(colormap) > 1: pass else: raise ValueError( "<colormap> only accepts a list/tuple of at least two colors or the name of one of the following predefined colormaps (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (list(all_palettes.keys()))) elif isinstance(colormap, str): if colormap in all_palettes: colormap = all_palettes[colormap] colormap = colormap[max(colormap.keys())] else: raise ValueError( "Could not find <colormap> with name %s. The following predefined colormaps are supported (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (colormap, list(all_palettes.keys()))) else: raise ValueError( "<colormap> only accepts a list/tuple of at least two colors or the name of one of the following predefined colormaps (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (list(all_palettes.keys()))) else: if isinstance(color, str): colormap = [color] else: raise ValueError( "<color> has to be a string specifying the fill_color of the map glyph." ) #Create Figure to draw: if "Point" in layertypes or "Line" in layertypes: figure_options["output_backend"] = "webgl" p = figure(x_axis_type="mercator", y_axis_type="mercator", **figure_options) #Get ridd of zoom on axes: for t in p.tools: if type(t) == WheelZoomTool: t.zoom_on_axis = False #Add Tile Source as Background: p = _add_backgroundtile(p, tile_provider, tile_provider_url) # Hide legend if wanted: if not legend: p.legend.visible = False legend = "GeoLayer" #Define colormapper: if len(colormap) == 1: kwargs["fill_color"] = colormap[0] elif not isinstance(category, type(None)): #Check if category column is numerical: if not issubclass(gdf[category].dtype.type, np.number): raise NotImplementedError( "<category> plot only yet implemented for numerical columns. Column '%s' is not numerical." % category) field = category colormapper_options = {"palette": colormap} if not isinstance(colormap_range, type(None)): if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[field].min() colormapper_options["high"] = gdf[field].max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {'field': "Colormap", 'transform': colormapper} legend = str(field) elif not isinstance(dropdown, type(None)): #Check if all columns in dropdown selection are numerical: for col in dropdown: if not issubclass(gdf[col].dtype.type, np.number): raise NotImplementedError( "<dropdown> plot only yet implemented for numerical columns. Column '%s' is not numerical." % col) field = dropdown[0] colormapper_options = {"palette": colormap} if not isinstance(colormap_range, type(None)): if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[dropdown].min().min() colormapper_options["high"] = gdf[dropdown].max().max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {'field': "Colormap", 'transform': colormapper} legend = "Geolayer" ##str(field) elif not isinstance(slider, type(None)): #Check if all columns in dropdown selection are numerical: for col in slider: if not issubclass(gdf[col].dtype.type, np.number): raise NotImplementedError( "<slider> plot only yet implemented for numerical columns. Column '%s' is not numerical." % col) field = slider[0] colormapper_options = {"palette": colormap} if not isinstance(colormap_range, type(None)): if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[slider].min().min() colormapper_options["high"] = gdf[slider].max().max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {'field': "Colormap", 'transform': colormapper} legend = "Geolayer" ##str(field) #Check for Hovertool columns: if hovertool: if not isinstance(hovertool_columns, (list, tuple)): if hovertool_columns == "all": hovertool_columns = list( filter(lambda col: col != "geometry", df_shapes.columns)) else: raise ValueError( "<hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." ) elif len(hovertool_columns) == 0: if not isinstance(category, type(None)): hovertool_columns = [category] elif not isinstance(dropdown, type(None)): hovertool_columns = dropdown elif not isinstance(slider, type(None)): hovertool_columns = slider else: hovertool_columns = [] else: for col in hovertool_columns: if col not in gdf.columns: raise ValueError( "Could not find columns '%s' in GeoDataFrame. <hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." % col) else: if isinstance(category, type(None)): hovertool_columns = [] else: hovertool_columns = [category] #Reduce DataFrame to needed columns: if category_options == 0: gdf = gdf[hovertool_columns + ["geometry"]] else: gdf = gdf[list(set(hovertool_columns) | set(category_columns)) + ["geometry"]] gdf["Colormap"] = gdf[field] field = "Colormap" #Create GeoJSON DataSource for Plot: geo_source = GeoJSONDataSource(geojson=gdf.to_json()) #Draw Glyph on Figure: layout = None if "Point" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = kwargs["fill_color"] p.scatter(x="x", y="y", source=geo_source, legend=legend, **kwargs) if "Line" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = kwargs["fill_color"] p.multi_line(xs="xs", ys="ys", source=geo_source, legend=legend, **kwargs) if "Polygon" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = "black" #Plot polygons: p.patches(xs="xs", ys="ys", source=geo_source, legend=legend, **kwargs) if hovertool and (category_options == 1 or len(hovertool_columns) > 0): my_hover = HoverTool() my_hover.tooltips = [(str(col), "@{%s}" % col) for col in hovertool_columns] p.add_tools(my_hover) if show_colorbar and category_options == 1: colorbar_options = { "color_mapper": colormapper, "label_standoff": 12, "border_line_color": None, "location": (0, 0) } if colormap_uselog: colorbar_options["ticker"] = LogTicker() colorbar = ColorBar(**colorbar_options) p.add_layout(colorbar, "right") if not isinstance(dropdown, type(None)): #Define Dropdown widget: dropdown_widget = Dropdown(label="Select Choropleth Layer", menu=list(zip(dropdown, dropdown))) #Define Callback for Dropdown widget: callback = CustomJS(args=dict(dropdown_widget=dropdown_widget, geo_source=geo_source, p=p), code=""" //Change selection of field for Colormapper for choropleth plot: geo_source.data["Colormap"] = geo_source.data[dropdown_widget.value]; geo_source.change.emit(); //p.legend[0].items[0]["label"] = dropdown_widget.value; """) dropdown_widget.js_on_change("value", callback) #Add Dropdown widget above the plot: layout = column(dropdown_widget, p) if not isinstance(slider, type(None)): if slider_range is None: slider_start = 0 slider_end = len(slider) - 1 slider_step = 1 value2name = ColumnDataSource({ "Values": np.arange(slider_start, slider_end + slider_step, slider_step), "Names": slider }) #Define Slider widget: slider_widget = Slider(start=slider_start, end=slider_end, value=slider_start, step=slider_step, title=slider_name) #Define Callback for Slider widget: callback = CustomJS(args=dict( slider_widget=slider_widget, geo_source=geo_source, value2name=value2name, ), code=""" //Change selection of field for Colormapper for choropleth plot: var slider_value = slider_widget.value; for(i=0; i<value2name.data["Names"].length; i++) { if (value2name.data["Values"][i] == slider_value) { var name = value2name.data["Names"][i]; } } geo_source.data["Colormap"] = geo_source.data[name]; geo_source.change.emit(); """) slider_widget.js_on_change("value", callback) #Add Slider widget above the plot: layout = column(slider_widget, p) # Set click policy for legend: p.legend.click_policy = "hide" # Display plot and if wanted return plot: if isinstance(layout, type(None)): layout = p # Display plot if wanted if show_figure: show(layout) #Return as (embeddable) HTML if wanted: if return_html: return embedded_html(layout) #Return plot: if return_figure: return layout
def scatterplot(p, x, y, category, category_values, colormap, hovertool, x_axis_type, xlabelname, ylabelname, **kwargs): """Adds a scatterplot to figure p for each data_col.""" # Set standard size and linecolor of markers: if "size" not in kwargs: kwargs["size"] = 10 if "line_color" not in kwargs: kwargs["line_color"] = "black" # Define source: source = ColumnDataSource({"x": x, "y": y}) # Define Colormapper for categorical scatterplot: if not category is None: category = str(category) source.data[category] = category_values # Make numerical categorical scatterplot: if check_type(category_values) == "numeric": kwargs["legend"] = category + " " # Define colormapper for numerical scatterplot: if colormap == None: colormap = Inferno256 elif isinstance(colormap, str): if colormap in all_palettes: colormap = all_palettes[colormap] max_key = max(colormap.keys()) colormap = colormap[max_key] else: raise ValueError( "Could not find <colormap> with name %s. The following predefined colormaps are supported (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (colormap, list(all_palettes.keys()))) elif isinstance(colormap, (list, tuple)): pass else: raise ValueError( "<colormap> can onyl be None, a name of a colorpalette as string( see https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ) or a list/tuple of colors." ) colormapper = LinearColorMapper(palette=colormap) # Set fill-color to colormapper: kwargs["fill_color"] = { "field": category, "transform": colormapper } # Define Colorbar: colorbar_options = { "color_mapper": colormapper, "label_standoff": 0, "border_line_color": None, "location": (0, 0), } colorbar = ColorBar(**colorbar_options) p.add_layout(colorbar, "right") # Draw glyph: glyph = p.scatter(x="x", y="y", source=source, **kwargs) # Add Hovertool if hovertool: my_hover = HoverTool(renderers=[glyph]) if x_axis_type == "datetime": my_hover.tooltips = [(xlabelname, "@x{%F}"), (ylabelname, "@y")] my_hover.formatters = {"x": "datetime"} else: my_hover.tooltips = [(xlabelname, "@x"), (ylabelname, "@y")] my_hover.tooltips.append((str(category), "@{%s}" % category)) p.add_tools(my_hover) # Make categorical scatterplot: elif check_type(category_values) == "object": # Define colormapper for categorical scatterplot: labels, categories = pd.factorize(category_values) colormap = get_colormap(colormap, len(categories)) # Draw each category as separate glyph: x, y = source.data["x"], source.data["y"] for cat, color in zip(categories, colormap): x_cat = x[category_values == cat] y_cat = y[category_values == cat] cat_cat = category_values[category_values == cat] source = ColumnDataSource({ "x": x_cat, "y": y_cat, "category": cat_cat }) # Draw glyph: glyph = p.scatter(x="x", y="y", legend=str(cat) + " ", source=source, color=color, **kwargs) # Add Hovertool if hovertool: my_hover = HoverTool(renderers=[glyph]) if x_axis_type == "datetime": my_hover.tooltips = [(xlabelname, "@x{%F}"), (ylabelname, "@y")] my_hover.formatters = {"x": "datetime"} else: my_hover.tooltips = [(xlabelname, "@x"), (ylabelname, "@y")] my_hover.tooltips.append((str(category), "@category")) p.add_tools(my_hover) if len(categories) > 5: warnings.warn( "There are more than 5 categories in the scatterplot. The legend might be crowded, to hide the axis you can pass 'legend=False' as an optional argument." ) else: raise ValueError( "<category> is not supported with datetime objects. Consider casting the datetime objects to strings, which can be used as <category> values." ) # Draw non categorical plot: else: # Draw glyph: glyph = p.scatter(x="x", y="y", source=source, **kwargs) # Add Hovertool: if hovertool: my_hover = HoverTool(renderers=[glyph]) if x_axis_type == "datetime": my_hover.tooltips = [(xlabelname, "@x{%F}"), (ylabelname, "@y")] my_hover.formatters = {"x": "datetime"} else: my_hover.tooltips = [(xlabelname, "@x"), (ylabelname, "@y")] p.add_tools(my_hover) return p
def geoplot( gdf_in, figure=None, figsize=None, title="", xlabel="Longitude", ylabel="Latitude", xlim=None, ylim=None, color="blue", colormap=None, colormap_uselog=False, colormap_range=None, category=None, dropdown=None, slider=None, slider_range=None, slider_name="", show_colorbar=True, colorbar_tick_format=None, xrange=None, yrange=None, hovertool=True, hovertool_columns=[], hovertool_string=None, simplify_shapes=None, tile_provider="CARTODBPOSITRON_RETINA", tile_provider_url=None, tile_attribution="", tile_alpha=1, panning=True, zooming=True, toolbar_location="right", show_figure=True, return_figure=True, return_html=False, legend=True, webgl=True, **kwargs ): """Doc-String: TODO""" # Imports: import bokeh.plotting from bokeh.plotting import show from bokeh.models import ( HoverTool, LogColorMapper, LinearColorMapper, GeoJSONDataSource, WheelZoomTool, ColorBar, BasicTicker, LogTicker, Dropdown, Slider, ColumnDataSource, ) from bokeh.models.callbacks import CustomJS from bokeh.models.widgets import Dropdown from bokeh.palettes import all_palettes from bokeh.layouts import row, column # Make a copy of the input geodataframe: gdf = gdf_in.copy() # Check layertypes: if type(gdf) != pd.DataFrame: layertypes = [] if "Point" in str(gdf.geom_type.unique()): layertypes.append("Point") if "Line" in str(gdf.geom_type.unique()): layertypes.append("Line") if "Polygon" in str(gdf.geom_type.unique()): layertypes.append("Polygon") if len(layertypes) > 1: raise Exception( "Can only plot GeoDataFrames/Series with single type of geometry (either Point, Line or Polygon). Provided is a GeoDataFrame/Series with types: %s" % layertypes ) else: layertypes = ["Point"] # Get and check provided parameters for geoplot: figure_options = { "title": title, "x_axis_label": xlabel, "y_axis_label": ylabel, "plot_width": 600, "plot_height": 400, "toolbar_location": toolbar_location, "active_scroll": "wheel_zoom", "x_axis_type": "mercator", "y_axis_type": "mercator", } if not figsize is None: width, height = figsize figure_options["plot_width"] = width figure_options["plot_height"] = height if webgl: figure_options["output_backend"] = "webgl" if type(gdf) != pd.DataFrame: # Convert GeoDataFrame to Web Mercator Projection: gdf.to_crs({"init": "epsg:3857"}, inplace=True) # Simplify shapes if wanted: if isinstance(simplify_shapes, numbers.Number): if layertypes[0] in ["Line", "Polygon"]: gdf["geometry"] = gdf["geometry"].simplify(simplify_shapes) elif not simplify_shapes is None: raise ValueError( "<simplify_shapes> parameter only accepts numbers or None." ) # Check for category, dropdown or slider (choropleth map column): category_options = 0 if not category is None: category_options += 1 category_columns = [category] if not dropdown is None: category_options += 1 category_columns = dropdown if not slider is None: category_options += 1 category_columns = slider if category_options > 1: raise ValueError( "Only one of <category>, <dropdown> or <slider> parameters is allowed to be used at once." ) # Check for category (single choropleth plot): if category is None: pass elif isinstance(category, (list, tuple)): raise ValueError( "For <category>, please provide an existing single column of the GeoDataFrame." ) elif category in gdf.columns: pass else: raise ValueError( "Could not find column '%s' in GeoDataFrame. For <category>, please provide an existing single column of the GeoDataFrame." % category ) # Check for dropdown (multiple choropleth plots via dropdown selection): if dropdown is None: pass elif not isinstance(dropdown, (list, tuple)): raise ValueError( "For <dropdown>, please provide a list/tuple of existing columns of the GeoDataFrame." ) else: for col in dropdown: if col not in gdf.columns: raise ValueError( "Could not find column '%s' for <dropdown> in GeoDataFrame. " % col ) # Check for slider (multiple choropleth plots via slider selection): if slider is None: pass elif not isinstance(slider, (list, tuple)): raise ValueError( "For <slider>, please provide a list/tuple of existing columns of the GeoDataFrame." ) else: for col in slider: if col not in gdf.columns: raise ValueError( "Could not find column '%s' for <slider> in GeoDataFrame. " % col ) if not slider_range is None: if not isinstance(slider_range, Iterable): raise ValueError( "<slider_range> has to be a type that is iterable like list, tuple, range, ..." ) else: slider_range = list(slider_range) if len(slider_range) != len(slider): raise ValueError( "The number of elements in <slider_range> has to be the same as in <slider>." ) steps = [] for i in range(len(slider_range) - 1): steps.append(slider_range[i + 1] - slider_range[i]) if len(set(steps)) > 1: raise ValueError( "<slider_range> has to have equal step size between each elements (like a range-object)." ) else: slider_step = steps[0] slider_start = slider_range[0] slider_end = slider_range[-1] # Check colormap if either <category>, <dropdown> or <slider> is choosen: if category_options == 1: if colormap is None: colormap = blue_colormap elif isinstance(colormap, (tuple, list)): if len(colormap) > 1: pass else: raise ValueError( "<colormap> only accepts a list/tuple of at least two colors or the name of one of the following predefined colormaps (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (list(all_palettes.keys())) ) elif isinstance(colormap, str): if colormap in all_palettes: colormap = all_palettes[colormap] colormap = colormap[max(colormap.keys())] else: raise ValueError( "Could not find <colormap> with name %s. The following predefined colormaps are supported (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (colormap, list(all_palettes.keys())) ) else: raise ValueError( "<colormap> only accepts a list/tuple of at least two colors or the name of one of the following predefined colormaps (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (list(all_palettes.keys())) ) else: if isinstance(color, str): colormap = [color] elif color is None: colormap = ["blue"] else: raise ValueError( "<color> has to be a string specifying the fill_color of the map glyph." ) # Check xlim & ylim: if xlim is not None: if isinstance(xlim, (tuple, list)): if len(xlim) == 2: from pyproj import Proj, transform inProj = Proj(init="epsg:4326") outProj = Proj(init="epsg:3857") xmin, xmax = xlim for _ in [xmin, xmax]: if not -180 < _ <= 180: raise ValueError( "Limits for x-axis (=Longitude) have to be between -180 and 180." ) if not xmin < xmax: raise ValueError("xmin has to be smaller than xmax.") xmin = transform(inProj, outProj, xmin, 0)[0] xmax = transform(inProj, outProj, xmax, 0)[0] figure_options["x_range"] = (xmin, xmax) else: raise ValueError( "Limits for x-axis (=Longitude) have to be of form [xmin, xmax] with values between -180 and 180." ) else: raise ValueError( "Limits for x-axis (=Longitude) have to be of form [xmin, xmax] with values between -180 and 180." ) if ylim is not None: if isinstance(ylim, (tuple, list)): if len(ylim) == 2: from pyproj import Proj, transform inProj = Proj(init="epsg:4326") outProj = Proj(init="epsg:3857") ymin, ymax = ylim for _ in [ymin, ymax]: if not -90 < _ <= 90: raise ValueError( "Limits for y-axis (=Latitude) have to be between -90 and 90." ) if not ymin < ymax: raise ValueError("ymin has to be smaller than ymax.") ymin = transform(inProj, outProj, 0, ymin)[1] ymax = transform(inProj, outProj, 0, ymax)[1] figure_options["y_range"] = (ymin, ymax) else: raise ValueError( "Limits for y-axis (=Latitude) have to be of form [ymin, ymax] with values between -90 and 90." ) else: raise ValueError( "Limits for y-axis (=Latitude) have to be of form [ymin, ymax] with values between -90 and 90." ) # Create Figure to draw: old_layout = None if figure is None: p = bokeh.plotting.figure(**figure_options) # Add Tile Source as Background: p = _add_backgroundtile( p, tile_provider, tile_provider_url, tile_attribution, tile_alpha ) elif isinstance(figure, type(bokeh.plotting.figure())): p = figure elif isinstance(figure, type(column())): old_layout = figure p = _get_figure(old_layout) else: raise ValueError( "Parameter <figure> has to be of type bokeh.plotting.figure or bokeh.layouts.column." ) # Get ridd of zoom on axes: for t in p.tools: if type(t) == WheelZoomTool: t.zoom_on_axis = False # Hide legend if wanted: legend_input = legend if isinstance(legend, str): pass else: legend = "GeoLayer" # Define colormapper: if len(colormap) == 1: kwargs["fill_color"] = colormap[0] elif not category is None: # Check if category column is numerical: if not issubclass(gdf[category].dtype.type, np.number): raise NotImplementedError( "<category> plot only yet implemented for numerical columns. Column '%s' is not numerical." % category ) field = category colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[field].min() colormapper_options["high"] = gdf[field].max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} if not isinstance(legend, str): legend = str(field) elif not dropdown is None: # Check if all columns in dropdown selection are numerical: for col in dropdown: if not issubclass(gdf[col].dtype.type, np.number): raise NotImplementedError( "<dropdown> plot only yet implemented for numerical columns. Column '%s' is not numerical." % col ) field = dropdown[0] colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[dropdown].min().min() colormapper_options["high"] = gdf[dropdown].max().max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} legend = " " + field elif not slider is None: # Check if all columns in dropdown selection are numerical: for col in slider: if not issubclass(gdf[col].dtype.type, np.number): raise NotImplementedError( "<slider> plot only yet implemented for numerical columns. Column '%s' is not numerical." % col ) field = slider[0] colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[slider].min().min() colormapper_options["high"] = gdf[slider].max().max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} if not isinstance(legend, str): legend = "Geolayer" # Check that only hovertool_columns or hovertool_string is used: if isinstance(hovertool_columns, (list, tuple, str)): if len(hovertool_columns) > 0 and hovertool_string is not None: raise ValueError( "Either <hovertool_columns> or <hovertool_string> can be used, but not both at the same time." ) else: raise ValueError( "<hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." ) if hovertool_string is not None: hovertool_columns = "all" # Check for Hovertool columns: if hovertool: if not isinstance(hovertool_columns, (list, tuple)): if hovertool_columns == "all": hovertool_columns = list( filter(lambda col: col != "geometry", gdf.columns) ) else: raise ValueError( "<hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." ) elif len(hovertool_columns) == 0: if not category is None: hovertool_columns = [category] elif not dropdown is None: hovertool_columns = dropdown elif not slider is None: hovertool_columns = slider else: hovertool_columns = [] else: for col in hovertool_columns: if col not in gdf.columns: raise ValueError( "Could not find columns '%s' in GeoDataFrame. <hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." % col ) else: if category is None: hovertool_columns = [] else: hovertool_columns = [category] # Reduce DataFrame to needed columns: if type(gdf) == pd.DataFrame: gdf["Geometry"] = 0 additional_columns = ["x", "y"] else: additional_columns = ["geometry"] for kwarg, value in kwargs.items(): if isinstance(value, Hashable): if value in gdf.columns: additional_columns.append(value) if category_options == 0: gdf = gdf[list(set(hovertool_columns) | set(additional_columns))] else: gdf = gdf[ list( set(hovertool_columns) | set(category_columns) | set(additional_columns) ) ] gdf["Colormap"] = gdf[field] field = "Colormap" # Create GeoJSON DataSource for Plot: if type(gdf) != pd.DataFrame: geo_source = GeoJSONDataSource(geojson=gdf.to_json()) else: geo_source = gdf # Draw Glyph on Figure: layout = None if "Point" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = kwargs["fill_color"] glyph = p.scatter(x="x", y="y", source=geo_source, legend=legend, **kwargs) if "Line" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = kwargs["fill_color"] del kwargs["fill_color"] glyph = p.multi_line( xs="xs", ys="ys", source=geo_source, legend=legend, **kwargs ) if "Polygon" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = "black" # Creates from a geoDataFrame with Polygons and Multipolygons a Pandas DataFrame # with x any y columns specifying the geometry of the Polygons: geo_source = ColumnDataSource(convert_geoDataFrame_to_patches(gdf)) # Plot polygons: glyph = p.multi_polygons( xs="__x__", ys="__y__", source=geo_source, legend=legend, **kwargs ) # Add hovertool: if hovertool and (category_options == 1 or len(hovertool_columns) > 0): my_hover = HoverTool(renderers=[glyph]) if hovertool_string is None: my_hover.tooltips = [(str(col), "@{%s}" % col) for col in hovertool_columns] else: my_hover.tooltips = hovertool_string p.add_tools(my_hover) # Add colorbar: if show_colorbar and category_options == 1: colorbar_options = { "color_mapper": colormapper, "label_standoff": 12, "border_line_color": None, "location": (0, 0), } if colormap_uselog: colorbar_options["ticker"] = LogTicker() if colorbar_tick_format: colorbar_options["formatter"] = get_tick_formatter(colorbar_tick_format) colorbar = ColorBar(**colorbar_options) p.add_layout(colorbar, "right") # Add Dropdown Widget: if not dropdown is None: # Define Dropdown widget: dropdown_widget = Dropdown( label="Select Choropleth Layer", menu=list(zip(dropdown, dropdown)) ) # Define Callback for Dropdown widget: callback = CustomJS( args=dict( dropdown_widget=dropdown_widget, geo_source=geo_source, legend=p.legend[0].items[0], ), code=""" //Change selection of field for Colormapper for choropleth plot: geo_source.data["Colormap"] = geo_source.data[dropdown_widget.value]; geo_source.change.emit(); //Change label of Legend: legend.label["value"] = " " + dropdown_widget.value; """, ) dropdown_widget.js_on_change("value", callback) # Add Dropdown widget above the plot: if old_layout is None: layout = column(dropdown_widget, p) else: layout = column(dropdown_widget, old_layout) # Add Slider Widget: if not slider is None: if slider_range is None: slider_start = 0 slider_end = len(slider) - 1 slider_step = 1 value2name = ColumnDataSource( { "Values": np.arange( slider_start, slider_end + slider_step, slider_step ), "Names": slider, } ) # Define Slider widget: slider_widget = Slider( start=slider_start, end=slider_end, value=slider_start, step=slider_step, title=slider_name, ) # Define Callback for Slider widget: callback = CustomJS( args=dict( slider_widget=slider_widget, geo_source=geo_source, value2name=value2name, ), code=""" //Change selection of field for Colormapper for choropleth plot: var slider_value = slider_widget.value; for(i=0; i<value2name.data["Names"].length; i++) { if (value2name.data["Values"][i] == slider_value) { var name = value2name.data["Names"][i]; } } geo_source.data["Colormap"] = geo_source.data[name]; geo_source.change.emit(); """, ) slider_widget.js_on_change("value", callback) # Add Slider widget above the plot: if old_layout is None: layout = column(slider_widget, p) else: layout = column(slider_widget, old_layout) # Hide legend if user wants: if legend_input is False: p.legend.visible = False # Set click policy for legend: p.legend.click_policy = "hide" # Set panning option: if panning is False: p.toolbar.active_drag = None # Set zooming option: if zooming is False: p.toolbar.active_scroll = None # Display plot and if wanted return plot: if layout is None: if old_layout is None: layout = p else: layout = old_layout # Display plot if wanted if show_figure: show(layout) # Return as (embeddable) HTML if wanted: if return_html: return embedded_html(layout) # Return plot: if return_figure: return layout