How to place images using image_url in Bokeh - python

I have a chart that uses datetime for the x-axis and dollars for the y-axis in Bokeh. I want to place a logo in the upper left corner of the plot area. Bokeh documentations seems especially cryptic on placing images. This code works:
from bokeh.plotting import figure, show
#p = figure(x_range=(0,1200), y_range=(0,600))
p = figure(plot_width=1200, plot_height=600,
sizing_mode = 'scale_width',
toolbar_location='above',
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value',
)
p.image_url(x=0, y=1, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
show(p)
But when I place this into my main chart, where the data is in datetime, I can not get an image to appear. Here are the key excerpts from the code within the primary chart:
plot = figure(plot_width=1200, plot_height=600,
sizing_mode = 'scale_width',
toolbar_location='above',
tools=tools,
title=plot_dict['chart_title'],
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value',
)
plot.x_range.end=plot_dict['end_data'] + extend_time
if plot_dict['start_chart'] == 'auto':
plot.x_range.start=plot_dict['start_user_data']
else:
plot.x_range.start = plot_dict['start_chart']
plot.y_range.start=0
plot.y_range.end= extend_y * plot_dict['max_value']
plot.left[0].formatter.use_scientific = False
plot.title.text_font_size = "16pt"
I have tried various approaches to plot the image such as:
plot.image_url(x=0, y=0, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
plot.image_url(x=plot_dict['start_user_data'], y=10000000, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
I have several labels in the chart that work quite nicely. Is there a way to specify image location and size using screen units, in the same manner as you specify locations for labels?

Thought I would post how I got this working in order to move forward. I used the following for my Bokeh plot that places my logo with some generic math to convert data space to screen space. It does this without using numpy arrays or ColumnDataSource (neither of which are bad, but trying to keep simple):
from bokeh.plotting import figure, show
# chart size and ranges need defined for dataspace location
# chart size
chart_width = 900
chart_height = 600
aspect_ratio = chart_width/chart_height
# limits of data ranges
x1 = 300
x2 = 1200
y1 = 0
y2 = 600
plot = figure(
plot_width=chart_width,
plot_height=chart_height,
x_range=(x1, x2),
y_range=(y1, y2),
sizing_mode = 'stretch_both',
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value')
plot.image_url(url=['my_image.png'], x=(.01*(x2-x1))+x1, y=(.98*(y2-y1))+y1,
w=.35*(x2-x1)/aspect_ratio, h=.1*(y2-y1), anchor="top_left")
show(plot)
Note the x_axis_type can be any type with this schema, datetime was just the issue I was dealing with.

Related

How do you change the size of the numbers on axes in Bokeh?

For the plot in the image colour magnitude diagram in Bokeh
I am trying to get the numbers along the x and y axes to be larger. I have made the labels larger but I can't seem to find how to make the tick-mark numbers larger. I've tried p1.yaxis.major_label_text_font_size but that didn't work. Here's what I have so far. Thanks for any help!
from bokeh.models.tools import HoverTool
p1 = figure(title = "Colour Magnitude diagram of M15 - 64s exposure using PIRATE",
x_axis_label='Colour index (B-V)',
y_axis_label='Apparent V magnitude')
data_source = ColumnDataSource({'B_V':ClusterDataM15['B_V'],'V':ClusterDataM15['Vmag']})
color_mapper = LinearColorMapper(palette='Magma256', low=max(ClusterDataM15['B_V']), high=min(ClusterDataM15['B_V']))
p1.scatter('B_V', 'V', source = data_source, color={'field': 'B_V', 'transform': color_mapper})
p1.y_range.flipped = True
p1.sizing_mode="stretch_both"
p1.add_tools(HoverTool())
p1.title.text_font_size = '20pt'
p1.xaxis.axis_label_text_font_size = "20pt"
p1.yaxis.axis_label_text_font_size = "20pt"
p1.xaxis.major_label_text_font_size = '20px'
p1.yaxis.major_label_text_font_size = '20px'
show(p1)
Running your code in a JupyterNotebook is working fine using the latest stable release bokeh 2.4.3.
The very basic example below
from bokeh.plotting import figure, show, output_notebook
output_notebook()
p = figure(
title = "Title",
x_axis_label='X-Axis',
y_axis_label='Y-Axis',
width=300,
height=300
)
p.line([1,2,3,4,5],[1,2,3,4,5])
p.title.text_font_size = '20pt'
p.xaxis.axis_label_text_font_size = "20pt"
p.yaxis.axis_label_text_font_size = "20pt"
p.xaxis.major_label_text_font_size = '20px'
p.yaxis.major_label_text_font_size = '20px'
show(p)
will result in this plot:
Please check if you are running the lastes version. If not run pip install bokeh -U. If this does not help, please add some information about you computer and programming environment. If there is any error message or hint, please add this, too.

How to set initial zoom of bokeh box chart of pandas group with a large number of categories

I'm plotting covid-19 data for countries grouped by World Bank regions using pandas and Bokeh.
from bokeh.io import output_file, show
from bokeh.palettes import Spectral5
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
group = data.groupby(["region", "CountryName"])
index_cmap = factor_cmap(
'region_CountryName',
palette=Spectral5,
factors=sorted(data.region.unique()),
end=1
)
p = figure(plot_width=800, plot_height=600, title="Confirmed cases per 100k people by country",
x_range=group, toolbar_location="left")
p.vbar(x='region_CountryName', top='ConfirmedPer100k_max', width=1, source=group,
line_color="white", fill_color=index_cmap, )
p.y_range.start = 0
p.xgrid.grid_line_color = None
p.xaxis.major_label_orientation = 3.14159/2
p.xaxis.group_label_orientation = 3.14159/2
p.outline_line_color = None
show(p)
And I get a
I would like to set some sort of initial zoom into the x-axis to get a more manageable image
, which I got by manually zooming in.
Any suggestions?
You should be able to accomplish this with the x_range parameter. In this example, the plot's x range would be the first 20 countries. You can adjust as needed. You might also have to mess around a bit to get the group_cn_list correct. It's hard to say without seeing your data. If you can post a df example for reproducibility, it would help.
group_cn_list = group["CountryName"].tolist()
p = figure(plot_width=800, plot_height=600, title="Confirmed cases per 100k people by country",
x_range=group_cn_list[0:20], toolbar_location="left")

Bokeh: disable Auto-ranging while using Edit Tools

I've included the PolyDrawTool in my Bokeh plot to let users circle points. When a user draws a line near the edge of the plot the tool expands the axes which often messes up the shape. Is there a way to freeze the axes while a user is drawing on the plot?
I'm using bokeh 1.3.4
MRE:
import numpy as np
import pandas as pd
import string
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, LabelSet
from bokeh.models import PolyDrawTool, MultiLine
def prepare_plot():
embedding_df = pd.DataFrame(np.random.random((100, 2)), columns=['x', 'y'])
embedding_df['word'] = embedding_df.apply(lambda x: ''.join(np.random.choice(list(string.ascii_lowercase), (8,))), axis=1)
# Plot preparation configuration Data source
source = ColumnDataSource(ColumnDataSource.from_df(embedding_df))
labels = LabelSet(x="x", y="y", text="word", y_offset=-10,x_offset = 5,
text_font_size="10pt", text_color="#555555",
source=source, text_align='center')
plot = figure(plot_width=1000, plot_height=500, active_scroll="wheel_zoom",
tools='pan, box_select, wheel_zoom, save, reset')
# Configure free-hand draw
draw_source = ColumnDataSource(data={'xs': [], 'ys': [], 'color': []})
renderer = plot.multi_line('xs', 'ys', line_width=5, alpha=0.4, color='color', source=draw_source)
renderer.selection_glyph = MultiLine(line_color='color', line_width=5, line_alpha=0.8)
draw_tool = PolyDrawTool(renderers=[renderer], empty_value='red')
plot.add_tools(draw_tool)
# Add the data and labels to plot
plot.circle("x", "y", size=0, source=source, line_color="black", fill_alpha=0.8)
plot.add_layout(labels)
return plot
if __name__ == '__main__':
plot = prepare_plot()
show(plot)
The PolyDrawTool actually updates a ColumnDataSource to drive a glyph that draws what the users indicates. The behavior you are seeing is a natural consequence of that fact, combined with Bokeh's default auto-ranging DataRange1d (which by default also consider every glyph when computing the auto-bounds). So, you have two options:
Don't use DataRange1d at all, e.g. you can provide fixed axis bounds when you call figure:
p = figure(..., x_range=(0,10), y_range=(-20, 20)
or you can set them after the fact:
p.x_range = Range1d(0, 10)
p.y_range = Range1d(-20, 20)
Of course, with this approach you will no longer get any auto-ranging at all; you will need to set the axis ranges to exactly the start/end that you want.
Make DataRange1d be more selective by explicitly setting its renderers property:
r = p.circle(...)
p.x_range.renderers = [r]
p.y_range.renderers = [r]
Now the DataRange models will only consider the circle renderer when computing the auto-ranged start/end.

Drawing a heatmap in BokeH displays empty graph

I am trying to draw a heatmap(spectrogram) in bokeh but when the heatmap displays it is empty..
This is the code which has some simply sample data, but this would be extended to fetch a large dataset via json.
from math import pi
import pandas as pd
from bokeh.io import show
from bokeh.models import LinearColorMapper, BasicTicker, PrintfTickFormatter, ColorBar
from bokeh.plotting import figure
# initialise data of lists.
data = {'epoch':[63745131000000, 63745131000000, 63745131100000,63745131100000], 'energy':[1.06811, 1.22078, 1.59495, 1.82245],'value':[3981.9308143034305, 2868.5202872178324, 1330.887696894385, 745.6847248644897]}
# Creates pandas DataFrame.
df = pd.DataFrame(data)
# print the data
print(df)
# this is the colormap from the original NYTimes plot
colors = ['#00007F', '#0000ff', '#007FFF', '#00ffff', '#7FFF7F', '#ffff00', '#FF7F00', '#ff0000', '#7F0000']
mapper = LinearColorMapper(palette=colors, low=df.value.min(), high=df.value.max())
TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom"
epochs = list(df.epoch.drop_duplicates())
print(epochs)
energies = list(df.energy.drop_duplicates())
print(energies)
p = figure(title="My Plot",
x_axis_location="below",
tools=TOOLS, toolbar_location='below',
tooltips=[('epoch', '#epoch'), ('energy', '#energy'), ('value', '#value')])
p.xaxis.ticker = epochs
p.yaxis.ticker = energies
p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = "5pt"
p.axis.major_label_standoff = 0
p.xaxis.major_label_orientation = pi / 3
p.rect(x="epoch", y="energy", width=1, height=1,
source=df,
fill_color={'field': 'value', 'transform': mapper},
line_color=None)
color_bar = ColorBar(color_mapper=mapper, major_label_text_font_size="5pt",
ticker=BasicTicker(desired_num_ticks=len(colors)),
label_standoff=6, border_line_color=None, location=(0, 0))
p.add_layout(color_bar, 'right')
show(p)
The frame which is output looks correct:
epoch energy value
0 63745131000000 1.06811 3981.930814
1 63745131000000 1.22078 2868.520287
2 63745131100000 1.59495 1330.887697
3 63745131100000 1.82245 745.684725
and the ranges for the x and y look ok as well:
[63745131000000, 63745131100000]
[1.06811, 1.22078, 1.59495, 1.82245]
But the image that appears has no points plotted:
I should mention, if I simply change the second epoch to one after e.g)
'epoch':[63745131000000, 63745131000000, 63745131000001,63745131000001]
Then the chart seems to be displayed correctly:
Grateful for any help.
Thanks
The reason why there is no information showing up is because at the edge bokeh apparently does not think your part of the data is something that deserves a color.
What you should change is the limits in you mapper:
mapper = LinearColorMapper(palette=colors, low=df.value.min()-1, high=df.value.max()+1) # this will make sure your data is inside the mapping
Also your width is defined in the figure a being 1. When your epochs are differing with a million you will still see almost nothing when you are plotting this so change
p.rect(x="epoch", y="energy", width=100000, height=1, # here width is set to an adequate level.
source=df,
fill_color={'field': 'value', 'transform': mapper},
line_color=None)

How do I overlay two images with Chaco?

Note: I will be answering this question myself to help other people who come across this problem in the future. Feel free to submit your own answers if you want, but know that it's already answered!
How can I overlay a masked image with one colormap onto another image with a different colormap in Chaco? Also, how can I add colorbars for each of these?
I'm not taking 100% credit for this, with a quick search online i've found that you can do a simple overlay with the code below:
Source where found:
http://docs.enthought.com/chaco/user_manual/containers.html#overlayplotcontainer
Ref Code:
class OverlayImageExample(HasTraits):
plot = Instance(OverlayImage)
traits_view = View(
Item('plot', editor=ComponentEditor(), show_label=False),
width=800, height=600, resizable=True
)
def _plot_default(self):
# Create data
x = linspace(-5, 15.0, 100)
y = jn(3, x)
pd = ArrayPlotData(index=x, value=y)
zoomable_plot = Plot(pd)
zoomable_plot.plot(('index', 'value'),
name='external', color='red', line_width=3)
# Attach tools to the plot
zoom = ZoomTool(component=zoomable_plot,
tool_mode="box", always_on=False)
zoomable_plot.overlays.append(zoom)
zoomable_plot.tools.append(PanTool(zoomable_plot))
# Create a second inset plot, not resizable, not zoom-able
inset_plot = Plot(pd)
inset_plot.plot(('index', 'value'), color='blue')
inset_plot.set(resizable = '',
bounds = [250, 150],
position = [450, 350],
border_visible = True
)
# Create a container and add our plots
container = OverlayPlotContainer()
container.add(zoomable_plot)
container.add(inset_plot)
return container
Overlaying images in this manner in Chaco is not well documented, but definitely possible. Firstly, how do you plot a masked image with chaco? When plotting with Plot().img_plot(), Chaco uses np.nan values as transparent pixels. For example, plotting:
img = np.eye(100)
img[img==0] = np.nan
would plot a diagonal line with a transparent background.
But how do you actually overlay this image on another image?
There are two main methods to do this.
Make two separate plots and stack them using an OverlayPlotContainer
Make one plot and plot them both in the one plot
The advantage to the second method is that both images will use the same axes. Also if you plot a second image in the same plot as the first, it keeps the same pixel aspect ratio. This means that if you plot a 100x100 image and then overlay a 50x50 image on top of it, the overlaying image will only take up 25% of the whole plot starting at (0,0).
There are some problems with the second method, so I will explain how to correct them.
When you plot multiple images on the same Plot object (using img_plot()), they will both use the same color_mapper by default. This means that both will be scaled to the same range. This may not be the required result, so you must create new color_mappers for both images.
Here's some example code with TraitsUI, which was adapted from the Qt code.
from traits.api import HasTraits, Instance
from traitsui.api import Item, View
from enable.api import ComponentEditor
from chaco.api import ArrayPlotData, Plot, ColorBar, LinearMapper, HPlotContainer, DataRange1D, ImageData
import chaco.default_colormaps
#
import numpy as np
class ImagePlot(HasTraits):
plot = Instance(HPlotContainer)
traits_view = View(
Item('plot', editor=ComponentEditor(), show_label=False), width=500, height=500, resizable=True, title="Chaco Plot")
def _plot_default(self):
bottomImage = np.reshape(np.repeat(np.linspace(0, 5, 100),100), (100,100))
topImage = np.eye(50)
topImage = topImage*np.reshape(np.repeat(np.linspace(-2, 2, 50),50), (50,50))
topImage[topImage==0] = np.nan
#
bottomImageData = ImageData()
bottomImageData.set_data(bottomImage)
#
topImageData = ImageData()
topImageData.set_data(topImage)
#
plotData = ArrayPlotData(imgData=bottomImageData, imgData2=topImageData)
plot = Plot(plotData, name='My Plot')
plot.img_plot("imgData")
plot.img_plot("imgData2")
# Note: DO NOT specify a colormap in the img_plot!
plot.aspect_ratio = 1.0
#
bottomRange = DataRange1D()
bottomRange.sources = [plotData.get_data("imgData")]
topRange = DataRange1D()
topRange.sources = [plotData.get_data("imgData2")]
plot.plots['plot0'][0].color_mapper = chaco.default_colormaps.gray(bottomRange)
plot.plots['plot1'][0].color_mapper = chaco.default_colormaps.jet(topRange)
#
colormapperBottom = plot.plots['plot0'][0].color_mapper
colormapperTop = plot.plots['plot1'][0].color_mapper
#
colorbarBottom = ColorBar(index_mapper=LinearMapper(range=colormapperBottom.range), color_mapper=colormapperBottom, orientation='v', resizable='v', width=30, padding=20)
colorbarBottom.padding_top = plot.padding_top
colorbarBottom.padding_bottom = plot.padding_bottom
#
colorbarTop = ColorBar(index_mapper=LinearMapper(range=colormapperTop.range), color_mapper=colormapperTop, orientation='v', resizable='v', width=30, padding=20)
colorbarTop.padding_top = plot.padding_top
colorbarTop.padding_bottom = plot.padding_bottom
#
container = HPlotContainer(resizable = "hv", bgcolor='transparent', fill_padding=True, padding=0)
container.spacing = 0
container.add(plot)
container.add(colorbarBottom)
container.add(colorbarTop)
#
return container
if __name__ == "__main__":
ImagePlot().configure_traits()

Categories

Resources