Using semi-transparent palettes in bokeh - python

I would like to create a color palette in bokeh that has varying alpha values, so some colors are semi-transparent.
I tried to pass some RGBA value as hex (#33eedd777) or CSS colors (rgba(129, 23, 43, 90)) as a palette but bokeh raised a ValueError.
Here is a code example:
import numpy as np
from bokeh.io import output_file, show
from bokeh.plotting import figure
from bokeh.transform import linear_cmap
from bokeh.util.hex import hexbin
n = 50000
x = np.random.standard_normal(n)
y = np.random.standard_normal(n)
bins = hexbin(x, y, 0.1)
p = figure(tools="wheel_zoom,reset", match_aspect=True, background_fill_color='#440154')
p.grid.visible = False
cm = ['#08459400', '#f7fbffff']
cm = ['rgba(80, 80, 80, 10)', 'rgba(8, 8, 8, 255)', ]
p.hex_tile(q="q", r="r", size=0.1, line_color=None, source=bins,
fill_color=linear_cmap('counts', cm, 0, max(bins.counts)))
show(p)

As of Bokeh 0.13.0, this appears to be a bug. The Seq(Color) property of LinearColorMap is supposed to accept lists of RGBA tuples, e.g. [(100, 80, 200, 0.2), ...] and it does accept them without a validation error, but does not generate correct output. That is the answer, there is no other information to add, or workarounds to give at this time (CSS color strings cannot be used, and hex color codes cannot have an alpha).
I would encourage you to file a bug report on GitHub with this information so that it may be fixed for all users:
https://github.com/bokeh/bokeh/issues

Related

How to get the values at the pixels of a bokeh image glyph in hover tool?

I have generated a bokeh 2d-histogram plot as mentioned in this StackOverflow answer. The code and respective image is given below. In the code, the count of data-points per bin is in the entries in H. How can I get access to an individual pixel-index to get the value at respective index in H and show it in the hover-text? I want to show count: 2 in the hover-text below x and y values, say. I tried using image_index mentioned here; probably new in Bokeh 3.0.1 but not sure how it works.
Note: It would be better to get an answer compatible with Bokeh 2.4.3 due to legacy code issues. Including holoviews tag as I know their outputs can be rendered as bokeh figure.
import numpy as np
from bokeh.plotting import figure, show
from bokeh.palettes import Turbo256
from bokeh.models import ColorBar, LinearColorMapper
a = np.array([1, 1.5, 2, 3, 4, 5])
b = np.array([15, 16, 20, 35, 45, 50])
H, xe, ye = np.histogram2d(a, b, bins=5)
data=dict(
image=[H],
x=[xe[0]],
y=[ye[0]],
dw=[xe[-1]-xe[0]],
dh=[ye[-1]-ye[0]]
)
TOOLTIPS = [
("x", "$x"),
("y", "$y"),
# ("image_index", "$image_index"),
]
p = figure(x_range=(min(xe),max(xe)), y_range=(min(ye),max(ye)), tools="pan,reset,hover", tooltips=TOOLTIPS)
color_map = LinearColorMapper(palette=Turbo256, low=1, high=H.max(), low_color='white')
color_bar = ColorBar(color_mapper=color_map, label_standoff=12)
p.image(source=data,image='image',x='x',y='y',dw='dw',dh='dh',color_mapper=color_map,)
p.add_layout(color_bar, 'right')
show(p)
I don't know the raw Bokeh code to do this, but in HoloViews it's:
import numpy as np, holoviews as hv
hv.extension('bokeh')
a = np.array([1, 1.5, 2, 3, 4, 5])
b = np.array([15, 16, 20, 35, 45, 50])
H, xe, ye = np.histogram2d(a, b, bins=5)
img = hv.Image(H[::-1], bounds=(-1,-1,1,1), vdims=['image_index'])
img.opts(tools=['hover'], width=500, cmap="Turbo", colorbar=True,
clim=(1,None), clipping_colors={'min': 'white'})

Plotly: How to extend the colorscheme of a plotly express scatterplot?

This is my code. But some colors are recycled. I need 52 different kinds of color. How can I solve this problem?
fig = px.scatter(Xt[:,0], Xt[:,1], color=DF["ScientifName"])
fig.show()
There is a function to create arbitrary color types. The desired graph will be created by setting the created color list to color. See this for details.
import plotly.express as px
from plotly.colors import n_colors
import numpy as np
x = np.random.randint(0,53,52)
y = np.random.randint(0,53,52)
red_blue = n_colors('rgb(0, 0, 255)', 'rgb(255, 0, 0)', 52, colortype = 'rgb')
fig = px.scatter(x, y, color=red_blue)
fig.show()

Difference between specified and measured colours, matplotlib colormap

I'm having trouble replicating an old colormap I've used in matplotlib. It seems as if it was the default colormap because in the original code, no colormap was specified.
So looking at the old figure I made I've measured the colours from the colorbar using gpick. I've inputted these into a custom colormap as follows:
blue_red1 = LinearSegmentedColormap.from_list('mycmap', [
(0, '#6666de'),
(0.1428, '#668cff'),
(0.2856, '#66d9ff'),
(0.4284, '#92ffce'),
(0.5712, '#d0ff90'),
(0.714, '#ffe366'),
(0.8568, '#ff9b66'),
(1, '#db6666')])
CS = plt.contourf(H, temps, diff_list, cmap=blue_red1)
plt.savefig('out.png')
Yet when I measure the output colours with gpick again they have different hex values (and I can tell they're different).
What could be causing this?
The original I'm trying to replicate, and the output from the custom colour map are linked below:
You may get much closer to the desired result using the following.
The logic is that each color in the colorbar is the value corresponding to the mean of its interval.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
X,Y=np.meshgrid(np.linspace(0,1),np.linspace(0,1) )
Z = X+Y
blue_red1 = LinearSegmentedColormap.from_list('mycmap', [
(0.0000, '#6666de'),
(0.0625, '#6666de'),
(0.1875, '#668cff'),
(0.3125, '#66d9ff'),
(0.4375, '#92ffce'),
(0.5625, '#d0ff90'),
(0.6875, '#ffe366'),
(0.8125, '#ff9b66'),
(0.9375, '#db6666'),
(1.0000, '#db6666')])
CS = plt.contourf(X,Y,Z, cmap=blue_red1)
plt.colorbar()
plt.show()
The other option is to use a ListedColormap. This gives the accurate colors.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
X,Y=np.meshgrid(np.linspace(0,1),np.linspace(0,1) )
Z = X+Y
blue_red1 = ListedColormap(['#6666de','#668cff','#66d9ff','#92ffce','#d0ff90',
'#ffe366','#ff9b66','#db6666'],'mycmap')
CS = plt.contourf(X,Y,Z, cmap=blue_red1)
plt.colorbar()
plt.show()

Change format string of axis picker in matplotlib [duplicate]

Short version: is there a Python method for displaying an image which shows, in real time, the pixel indices and intensities? So that as I move the cursor over the image, I have a continually updated display such as pixel[103,214] = 198 (for grayscale) or pixel[103,214] = (138,24,211) for rgb?
Long version:
Suppose I open a grayscale image saved as an ndarray im and display it with imshow from matplotlib:
im = plt.imread('image.png')
plt.imshow(im,cm.gray)
What I get is the image, and in the bottom right of the window frame, an interactive display of the pixel indices. Except that they're not quite, as the values are not integers: x=134.64 y=129.169 for example.
If I set the display with correct resolution:
plt.axis('equal')
the x and y values are still not integers.
The imshow method from the spectral package does a better job:
import spectral as spc
spc.imshow(im)
Then in the bottom right I now have pixel=[103,152] for example.
However, none of these methods also shows the pixel values. So I have two questions:
Can the imshow from matplotlib (and the imshow from scikit-image) be coerced into showing the correct (integer) pixel indices?
Can any of these methods be extended to show the pixel values as well?
There a couple of different ways to go about this.
You can monkey-patch ax.format_coord, similar to this official example. I'm going to use a slightly more "pythonic" approach here that doesn't rely on global variables. (Note that I'm assuming no extent kwarg was specified, similar to the matplotlib example. To be fully general, you need to do a touch more work.)
import numpy as np
import matplotlib.pyplot as plt
class Formatter(object):
def __init__(self, im):
self.im = im
def __call__(self, x, y):
z = self.im.get_array()[int(y), int(x)]
return 'x={:.01f}, y={:.01f}, z={:.01f}'.format(x, y, z)
data = np.random.random((10,10))
fig, ax = plt.subplots()
im = ax.imshow(data, interpolation='none')
ax.format_coord = Formatter(im)
plt.show()
Alternatively, just to plug one of my own projects, you can use mpldatacursor for this. If you specify hover=True, the box will pop up whenever you hover over an enabled artist. (By default it only pops up when clicked.) Note that mpldatacursor does handle the extent and origin kwargs to imshow correctly.
import numpy as np
import matplotlib.pyplot as plt
import mpldatacursor
data = np.random.random((10,10))
fig, ax = plt.subplots()
ax.imshow(data, interpolation='none')
mpldatacursor.datacursor(hover=True, bbox=dict(alpha=1, fc='w'))
plt.show()
Also, I forgot to mention how to show the pixel indices. In the first example, it's just assuming that i, j = int(y), int(x). You can add those in place of x and y, if you'd prefer.
With mpldatacursor, you can specify them with a custom formatter. The i and j arguments are the correct pixel indices, regardless of the extent and origin of the image plotted.
For example (note the extent of the image vs. the i,j coordinates displayed):
import numpy as np
import matplotlib.pyplot as plt
import mpldatacursor
data = np.random.random((10,10))
fig, ax = plt.subplots()
ax.imshow(data, interpolation='none', extent=[0, 1.5*np.pi, 0, np.pi])
mpldatacursor.datacursor(hover=True, bbox=dict(alpha=1, fc='w'),
formatter='i, j = {i}, {j}\nz = {z:.02g}'.format)
plt.show()
An absolute bare-bones "one-liner" to do this: (without relying on datacursor)
def val_shower(im):
return lambda x,y: '%dx%d = %d' % (x,y,im[int(y+.5),int(x+.5)])
plt.imshow(image)
plt.gca().format_coord = val_shower(ims)
It puts the image in closure so makes sure if you have multiple images each will display its own values.
All of the examples that I have seen only work if your x and y extents start from 0. Here is code that uses your image extents to find the z value.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
d = np.array([[i+j for i in range(-5, 6)] for j in range(-5, 6)])
im = ax.imshow(d)
im.set_extent((-5, 5, -5, 5))
def format_coord(x, y):
"""Format the x and y string display."""
imgs = ax.get_images()
if len(imgs) > 0:
for img in imgs:
try:
array = img.get_array()
extent = img.get_extent()
# Get the x and y index spacing
x_space = np.linspace(extent[0], extent[1], array.shape[1])
y_space = np.linspace(extent[3], extent[2], array.shape[0])
# Find the closest index
x_idx= (np.abs(x_space - x)).argmin()
y_idx= (np.abs(y_space - y)).argmin()
# Grab z
z = array[y_idx, x_idx]
return 'x={:1.4f}, y={:1.4f}, z={:1.4f}'.format(x, y, z)
except (TypeError, ValueError):
pass
return 'x={:1.4f}, y={:1.4f}, z={:1.4f}'.format(x, y, 0)
return 'x={:1.4f}, y={:1.4f}'.format(x, y)
# end format_coord
ax.format_coord = format_coord
If you are using PySide/PyQT here is an example to have a mouse hover tooltip for the data
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Mouse tooltip
from PySide import QtGui, QtCore
mouse_tooltip = QtGui.QLabel()
mouse_tooltip.setFrameShape(QtGui.QFrame.StyledPanel)
mouse_tooltip.setWindowFlags(QtCore.Qt.ToolTip)
mouse_tooltip.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
mouse_tooltip.show()
def show_tooltip(msg):
msg = msg.replace(', ', '\n')
mouse_tooltip.setText(msg)
pos = QtGui.QCursor.pos()
mouse_tooltip.move(pos.x()+20, pos.y()+15)
mouse_tooltip.adjustSize()
fig.canvas.toolbar.message.connect(show_tooltip)
# Show the plot
plt.show()
with Jupyter you can do so either with datacursor(myax)or by ax.format_coord.
Sample code:
%matplotlib nbagg
import numpy as np
import matplotlib.pyplot as plt
X = 10*np.random.rand(5,3)
fig,ax = plt.subplots()
myax = ax.imshow(X, cmap=cm.jet,interpolation='nearest')
ax.set_title('hover over the image')
datacursor(myax)
plt.show()
the datacursor(myax) can also be replaced with ax.format_coord = lambda x,y : "x=%g y=%g" % (x, y)
In case you, like me, work on Google Colab, this solutions do not work as Colab disabled interactive feature of images for matplotlib.
Then you might simply use Plotly:
https://plotly.com/python/imshow/
import plotly.express as px
import numpy as np
img_rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
[[0, 255, 0], [0, 0, 255], [255, 0, 0]]
], dtype=np.uint8)
fig = px.imshow(img_rgb)
fig.show()
Matplotlib has built-in interactive plot which logs pixel values at the corner of the screen.
To setup first install pip install ipympl
Then use either %matplotlib notebook or %matplotlib widget instead of %matplotlib inline
The drawback with plotly or Bokeh is that they don't work on Pycharm.
For more information take a look at the doc
To get interactive pixel information of an image use the module imagetoolbox
To download the module open the command prompt and write
pip install imagetoolbox
Write the given code to get interactive pixel information of an image
enter image description here
Output:enter image description here

How do I create a pie chart using Bokeh?

All I'd like to do is create a pie chart. The Bokeh documentation covers a number of sophisticated charts, including a donut chart, but it doesn't seem to cover pie chart.
Is there any example of this?
Ultimately, the chart will need to be to be embedded in a webpage, so I'll need to take advantage of Bokeh's html embed capabilities.
The answer below is very outdated. The Donut function was part of the old bokeh.charts API that was deprecated and removed long ago. For any modern version of Bokeh (e.g. 0.13 or newer) you can create a pie chart using the wedge glyphs, as follows:
from math import pi
import pandas as pd
from bokeh.io import output_file, show
from bokeh.palettes import Category20c
from bokeh.plotting import figure
from bokeh.transform import cumsum
x = { 'United States': 157, 'United Kingdom': 93, 'Japan': 89, 'China': 63,
'Germany': 44, 'India': 42, 'Italy': 40, 'Australia': 35,
'Brazil': 32, 'France': 31, 'Taiwan': 31, 'Spain': 29 }
data = pd.Series(x).reset_index(name='value').rename(columns={'index':'country'})
data['angle'] = data['value']/data['value'].sum() * 2*pi
data['color'] = Category20c[len(x)]
p = figure(plot_height=350, title="Pie Chart", toolbar_location=None,
tools="hover", tooltips="#country: #value")
p.wedge(x=0, y=1, radius=0.4,
start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
line_color="white", fill_color='color', legend='country', source=data)
show(p)
OUTDATED BELOW
An example for Bokeh 0.8.1 using the bokeh.plotting interface:
from bokeh.plotting import *
from numpy import pi
# define starts/ends for wedges from percentages of a circle
percents = [0, 0.3, 0.4, 0.6, 0.9, 1]
starts = [p*2*pi for p in percents[:-1]]
ends = [p*2*pi for p in percents[1:]]
# a color for each pie piece
colors = ["red", "green", "blue", "orange", "yellow"]
p = figure(x_range=(-1,1), y_range=(-1,1))
p.wedge(x=0, y=0, radius=1, start_angle=starts, end_angle=ends, color=colors)
# display/save everythin
output_file("pie.html")
show(p)
Bokeh >0.9 will correctly compute the bounding area of all glyphs, not just "pointlike" marker glyphs, and explicitly setting the ranges like this will not be required.
NOTE from project maintainers: This answer refers to an old bokeh.charts API that was remove from bokeh a long time ago
A Donut chart will return a simple pie chart if you input a pandas series rather than a dataframe. And it will display labels too!
from bokeh.charts import Donut, show
import pandas as pd
data = pd.Series([0.15,0.4,0.7,1.0], index = list('abcd'))
pie_chart = Donut(data)
show(pie_chart)
Thanks to the answers above for helping me as well. I want to add how to add a legend to your pie-chart as I had some trouble with that. Below is just a snippet. My piechart just had 2 sections. Thus, I just made a pie chart figure and called wedge on it twice:
import numpy as np
percentAchieved = .6
pieFigure = figure(x_range=(-1, 1), y_range=(-1, 1))
starts = [np.pi / 2, np.pi * 2 * percentAchieved + np.pi / 2]
ends = [np.pi / 2+ np.pi * 2 * percentAchieved, np.pi / 2 + 2*np.pi]
pieColors = ['blue', 'red']
#therefore, this first wedge will add a legend entry for the first color 'blue' and label it 'hello'
pieFigure.wedge(x=0, y=0, radius=.7, start_angle=starts, end_angle=ends, color=pieColors, legend="hello")
#this will add a legend entry for the 'red' color and label it 'bye'. Made radius zero to not make
#another piechart overlapping the original one
pieFigure.wedge(x=0, y=0, radius=0, start_angle=starts, end_angle=ends, color=pieColors[1], legend="bye")

Categories

Resources