Infinite horizontal line in Bokeh - python

Is there a way to plot an infinite horizontal line with Bokeh?
The endpoints of the line should never become visible, no matter how far out the user is zooming.
This is what I've tried so far. It just prints an empty canvas:
import bokeh.plotting as bk
import numpy as np
p = bk.figure()
p.line([-np.inf,np.inf], [0,0], legend="y(x) = 0")
bk.show(p)
One way would be to set the endpoints extremely high/low and the figure's x_range and y_range very small in relation to them.
import bokeh.plotting as bk
import numpy as np
p = bk.figure(x_range=[-10,10])
p.line([-np.iinfo(np.int64).max, np.iinfo(np.int64).max], [0,0], legend="y(x) = 0")
bk.show(p)
However, I am hoping that somebody has a more elegant solution.
Edit: removed outdated solution

You are looking for "spans":
Spans (line-type annotations) have a single dimension (width or height) and extend to the edge of the plot area.
Please, take a look at
http://docs.bokeh.org/en/latest/docs/user_guide/annotations.html#spans
So, the code will look like:
import numpy as np
import bokeh.plotting as bk
from bokeh.models import Span
p = bk.figure()
# Vertical line
vline = Span(location=0, dimension='height', line_color='red', line_width=3)
# Horizontal line
hline = Span(location=0, dimension='width', line_color='green', line_width=3)
p.renderers.extend([vline, hline])
bk.show(p)
With this solution users are allowed to pan and zoom at will. The end of the lines will never show up.

The Bokeh documentation on segments and rays indicates the following solution (using ray):
To have an “infinite” ray, that always extends to the edge of the
plot, specify 0 for the length.
And indeed, the following code produces an infinite, horizontal line:
import numpy as np
import bokeh.plotting as bk
p = bk.figure()
p.ray(x=[0], y=[0], length=0, angle=0, line_width=1)
p.ray(x=[0], y=[0], length=0, angle=np.pi, line_width=1)
bk.show(p)

If you plot two rays from the middle they won't get smaller as you zoom in or out since the length is in pixel. So something like this:
p.ray(x=[0],y=[0],length=300, angle=0, legend="y(x) = 0")
p.ray(x=[0],y=[0],length=300, angle=np.pi, legend="y(x) = 0")
But if the user pans in either direction the end of the ray will show up. If you can prevent the user from panning at all (even when they zoom) then this is a little nicer code for a horizontal line.
If the user is able to zoom and pan anywhere they please, there is no good way (as far as I can tell) to get a horizontal line as you describe.

In case you are wondering how to use spans in combination with time series, convert your dates to unix timestamps:
start_date = time.mktime(datetime.date(2018, 3, 19).timetuple())*1000
vline = Span(location=start_date,dimension='height', line_color='red',line_width=3)
Or see this link for a full example.

Related

How to draw a separator or lines between subplots (when subplots have titles, axis labels etc)

I want to draw a line or some kind of separator between subplots.
I am well aware of this question, but I couldn't get the solution given there to work, as I explain below.
I (sort of) understand these transformations, but the fundamental question I have, which would clarify everything else, is: is there a way to identify the "real" bottom left of each subplot as the (0,0) point of some transformation? Or, once you start adding titles, labels, etc, can these items mes up the coordinate system, eg an axis label may end up with y coordinates < 0?
Plotting a line using the plot() method of each axis, and using transform=axis.transAxes, so that (0,0) is the bottom left and (1,1) is the top right does draw a line, but:
drawing it from (0,0) to (1,0) draws it at the bottom of the axis; I need it farther below, below the x_axis label etc
determining the exact coordinates where to plot it, exactly how much below, is cumbersome, as that depends on what elements are present in the figure: titles,
labels etc.
The line end ups beneath the other elements of the
figure, and the output is very messy
The second solution given at that link uses blended_transform_factory , but, to be honest, I couldn't get the syntax to work.
A toy example is:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
sns.set(style='darkgrid')
n = int(100)
x = np.arange(0,n)
fig, ax = plt.subplots(3,2)
for i,a in enumerate(ax.flatten() ):
y = np.random.rand(n)
sns.lineplot(x, y, ax=a)
a.set_title('Chart # ' + str(i+1))
a.set_xlabel('my x')
a.set_ylabel('my y')
# this is the first solution; messy output, line behind other elements of the figure
a.plot([-1, 1.5], [-0.2, -0.2], color='black', lw=0.5, transform=a.transAxes, clip_on=False)
The second solution would mean replacing the last line with those below (always within the for loop):
trans = matplotlib.transforms.blended_transform_factory(fig.transFigure, a.transAxes)
line = matplotlib.lines.Line2D([0, 1], [0,0], color='w', transform=trans)
fig.lines.append(line)
I must have done this incorrectly, because, while the first solution at least draws something, this draws nothing at all - but no errors are given.

How to add multiple Y axis with chartify to draw Elbow curves

I'd like to create a line chart but with 2 distinct Y axis with a different scale to replace this piece of code which generates 2 charts:
ch = chartify.Chart(blank_labels=True)
ch.set_title("Elbow method with Euclidian distance")
ch.plot.line(
data_frame=df_elbow,
x_column='K',
y_column='Distortion',
line_width=1)
ch.show()
ch = chartify.Chart(blank_labels=True)
ch.set_title("Elbow method with sum of squared errors")
ch.plot.line(
data_frame=df_elbow,
x_column='K',
y_column='SSE',
line_width=1)
ch.show()
Thanks !
Update:
2nd y-axis plots have been implemented! See chartify.examples.chart_second_axis()
Old answer:
At the moment there isn't support for 2nd y-axis plots, but I'll add in an issue for it. Thanks for the suggestion!
For now I'd suggest falling back on Bokeh. See an example here.
Thanks, here is what I did using the Bokeh figure while waiting for chartify to support 2 axis:
import bokeh.plotting
from bokeh.models import LinearAxis, Range1d
ch = chartify.Chart(blank_labels=True)
ch.set_title("Elbow method to find optimal K")
ch.set_subtitle("Euclidian distance (Blue) and sum of squared errors (Red)")
ch.figure.y_range = Range1d(5, 14)
ch.figure.line(x=df_elbow['K'], y=df_elbow['Distortion'], line_width=1, line_color="Blue")
ch.figure.extra_y_ranges = {"sum": Range1d(start=200000, end=1200000)}
ch.figure.add_layout(LinearAxis(y_range_name="sum"), 'right')
ch.figure.line(x=df_elbow['K'], y=df_elbow['SSE'], line_width=1, y_range_name='sum', line_color="Red")
ch.show()

Bokeh - Apply color map to set of lines

I have a piece of code that uses matplotlib to plot a set of lines and applies colors to those lines using a color map. The code excerpt and result are as follows:
cm = plt.cm.get_cmap('jet')
step = 15
xi = np.linspace(data[data.columns[0]].min(), data[data.columns[0]].max(), 2)
colors_l = np.linspace(0.1, 1, len(state_means[::step]))
for i, beta in enumerate(state_means[::step]):
plt.plot(xi, beta[0] * xi + beta[1], alpha=.2, lw=1, c=cm(colors_l[i]))
The relevant part of the code here is
c=cm(colors_l[i])
which is within the plt.plot() function. Here its is possible to index the color map using a parameter (i in this case).
However, if i try to accomplish something similar using bokeh, with its ColorMapper and line() glyph, i run into and error. The relevant code lines and output are
call_color_mapper = LinearColorMapper(palette="Viridis256", low=min(call_strike_vals), high=max(call_strike_vals))
call_lines=dict()
call_chain_plot = figure(y_axis_label='Call OI', x_axis_label='Dates', x_axis_type = 'datetime')
for strike in call_strikes:
call_lines[strike] = call_chain_plot.line('TIMESTAMP', strike, line_color=call_color_mapper(int(strike[2:])), source=callSource)
TypeError: 'LinearColorMapper' object is not callable
Is there a way to color a set of line glyphs using a color mapper in bokeh?
LinearColorMapper does not compute colors in Python. Rather, LinearColorMapper represents a color-mapping that happens in the browser, in JavaScript. If you really need the colors in Python, you will have to map them by hand, there are lots of ways to do this.
But you probably don't, so the best way to do this in Bokeh would be to use multi_line instead of repeated calls to line. This partially for performance reasons, Bokeh is optimized to perform best over "vectorized" operations. But also, it allows you to use the linear_cmap convenience function to make a color mapper for any data column you like. Here is a complete example:
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.transform import linear_cmap
output_file("cmap.html")
p = figure()
source = ColumnDataSource(data=dict(
xs=[[0,1] for i in range(256)], # x coords for each line (list of lists)
ys=[[i, i+10] for i in range(256)], # y coords for each line (list of lists)
foo=list(range(256)) # data to use for colormapping
))
p.multi_line('xs', 'ys', source=source,
color=linear_cmap('foo', "Viridis256", 0, 255))
show(p)
While #bigreddot 's solution does provide a great alternative to the line() glyph to plot a set of lines using a linear_cmap(), it does not provide a way to capture handles for the individual lines should the handles be needed for further processing (e.g. plotting a secondary y-axis for some of them). Which is the reason why i collect the handles to each line in a dictionary in my OP.
Well, another way to plot the lines one at a time while looping through a list is as follows
from bokeh.palettes import viridis #here viridis is a function that takes\
#parameter n and provides a palette with n equally(almost) spaced colors.
call_colors = viridis(len(call_strikes))
color_key_value_pairs = list(zip(call_strikes, call_colors))
color_dict = dict(color_key_value_pairs)
Now, the dictionary color_dict can be used to access colors based on the values in the dictionary. So, I run the code from the OP has follows:
call_lines=dict()
for index, strike in enumerate(call_strikes):
call_lines[strike] = call_chain_plot.line('xs', strike, color=color_dict[strike], source=callSource)
I guess this is what #bigreddot meant when he wrote, 'If you really need the colors in Python, you will have to map them by hand, there are lots of ways to do this'.

Datashader canvas.line() aliasing

I use bokeh to plot temperature curves, but in some cases the dataset is quite big (> 500k measurements) and I'm have a laggy user experience with bokeh (event with output_backend="webgl"). So I'm experimenting datashader to get a faster rendering and a smoother user experience.
But the visual result given by datashader is not as beautiful as bokeh's result, datashader result has aliasing :
I obtain this side-by-side comparison with the following code :
import pandas as pd
import datashader as ds
import datashader.transfer_functions as tf
from bokeh.plotting import figure
from bokeh.io import output_notebook, show
from bokeh.models import ColumnDataSource
from bokeh.layouts import row
import numpy as np
output_notebook()
# generate signal
n = 2000
start = 0
end = 70
signal = [np.sin(x) for x in np.arange(start, end, step=(end-start)/n)]
signal = pd.DataFrame(signal, columns=["signal"])
signal = signal.reset_index()
# create a bokeh plot
source = ColumnDataSource(signal)
p = figure(plot_height=300, plot_width=400, title="bokeh plot")
p.line(source=source, x="index", y="signal")
# create a datashader image and put it in a bokeh plot
x_range = (signal["index"].min(), signal["index"].max())
y_range = (signal["signal"].min(), signal["signal"].max())
cvs = ds.Canvas(x_range=x_range, y_range=y_range, plot_height=300, plot_width=400)
agg = cvs.line(signal, 'index', 'signal')
img = tf.shade(agg)
image_source = ColumnDataSource(data=dict(image = [img.data]))
q = figure(x_range=x_range, y_range=y_range, plot_height=300, plot_width=400, title="datashader + bokeh")
q.image_rgba(source = image_source,
image="image",
dh=(y_range[1] - y_range[0]),
dw=(x_range[1] - x_range[0]),
x=x_range[0],
y=y_range[0],
dilate=False)
# visualize both plot, bokeh on left
show(row(p, q))
Have you any idea how to fix this aliasing and get a smooth result ? (similar to bokeh's result)
Here's a runnable version of your code, using HoloViews in a Jupyter notebook:
import pandas as pd, numpy as np, holoviews as hv
from holoviews.operation.datashader import datashade, dynspread
hv.extension("bokeh")
%opts Curve RGB [width=400]
n, start, end = 2000, 0, 70
sine = [np.sin(x) for x in np.arange(start, end, step=(end-start)/n)]
signal = pd.DataFrame(sine, columns=["signal"]).reset_index()
curve = hv.Curve(signal)
curve + datashade(curve)
It's true that the datashaded output here doesn't look very nice. Datashader's timeseries support, like the rest of datashader, was designed to allow accurate accumulation and summation of huge numbers of mathematically perfect (i.e., infinitely thin) curves on a raster grid, so that every x location on every curve will fall into one and only one y location in the grid. Here you just seem to want server-side rendering of a large timeseries, which requires partial incrementing of multiple nearby bins in the grid and isn't something that datashader is optimized for yet.
One thing you can do already is to render the curve at a high resolution then "spread" it so that each non-zero pixel will show up in neighboring pixels as well:
curve + dynspread(datashade(curve, height=1200, width=1200, dynamic=False, \
cmap=["#30a2da"]), max_px=3, threshold=1)
Here I set the color to match Bokeh's default, then forced HoloView's "dynspread" function to spread by 3 pixels. Using Datashader+Bokeh as in your version you would do ``img = tf.spread(tf.shade(agg), px=3)` and increase the plot size in the Canvas call to get a similar result.
I haven't tried running a simple smoothing filter over the result of tf.shade() or tf.spread(), but those both just return RGB images, so some filter like that would probably give good results.
The real solution would be to implement an optional antialiased line-drawing function for datashader, operating when the lines are drawn first rather than fixing up the pixels later, but that would take some work. Contributions welcome!

Removing wireframe without gaps in matplotlib plot_trisurf

I want to create a smooth cylinder using matplotlib/pyplot. I've adapted a tutorial online and produced the following minimal example:
from numpy import meshgrid,linspace,pi,sin,cos,shape
from matplotlib import pyplot
import matplotlib.tri as mtri
from mpl_toolkits.mplot3d import Axes3D
u,v = meshgrid(linspace(0,10,10),linspace(0,2*pi,20))
u = u.flatten()
v = v.flatten()
x = u
z = sin(v)
y = cos(v)
tri = mtri.Triangulation(u, v)
fig = pyplot.figure()
ax = fig.add_axes([0,0,1,1],projection='3d')
ax.plot_trisurf(x,y,z,triangles=tri.triangles,linewidth=0)
pyplot.show()
which produces a cylinder. I set linewidth=0 to remove the wireframe, however, there is now the "ghost" of the wireframe because the triangulation has (presumably) been spaced assuming the wireframe is there to fill in the gaps. This looks to be specific to plot_trisurf, because there are other 3d plotting examples (e.g., using plot_surface) which set linewidth=0 without these gaps showing up.
Doing an mtri.Triangulation?, it seems like it might not be possible to "perfectly" fill in the gaps, since it states
>Notes
> -----
> For a Triangulation to be valid it must not have duplicate points,
> triangles formed from colinear points, or overlapping triangles.
One partial solution is to just color the wireframe the same shade of blue, but after I've fixed this problem I also want to add a light source/shading on the surface, which would put me back at square one.
Is there a way to make this work? Or can someone suggest a different approach? Thanks for any help.
ax.plot_trisurf(x,y,z,triangles=tri.triangles,linewidth=0, antialiased=False)

Categories

Resources