Here are the examples of the python api bokeh.models.CrosshairTool taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.
3 Examples
3
View Source File : figure.py
License : GNU General Public License v3.0
Project Creator : happydasch
License : GNU General Public License v3.0
Project Creator : happydasch
def _set_linked_crosshairs(self, figures):
'''
Link crosshairs across all figures
src:
https://stackoverflow.com/questions/37965669/how-do-i-link-the-crosshairtool-in-bokeh-over-several-plots
'''
crosshair_shared = CrosshairTool(dimensions='height')
for f in figures:
crosshair = CrosshairTool(dimensions='width')
f.figure.add_tools(crosshair, crosshair_shared)
def set_cds_columns_from_df(self, df):
0
View Source File : figures.py
License : GNU General Public License v3.0
Project Creator : gotzl
License : GNU General Public License v3.0
Project Creator : gotzl
def getFigure(sources, x='dist_lap', width=800):
TOOLS = "crosshair,pan,reset,save,wheel_zoom"
# TOOLS = "pan,reset,save,wheel_zoom"
# define height of each figure
heights= [
400,150,150,100,150,150
]
# define data for each figure
ys = [
['speedkmh'],
['steerangle'],
['rpms'],
['gear'],
['throttle','brake'],
['g_lon','g_lat','g_sum']
]
# define the tooltips
tools = [ [
("Lap", "@lap"),
("Time", "@time_lap{%0.1f} s"),
("Dist", "@dist_lap{%0.1f} m"),
("Speed", "@speedkmh km/h")],
[("Steerangle","@steerangle")],
[("RPMs","@rpms")],
[("Gear","@gear")],
[("Throttle","@throttle"), ("Brake","@brake")],
[("G lon","@g_lon"), ("G lat","@g_lat"), ("G sum","@g_sum")],
]
# some JS needed to link crosshairs
# TODO: replace with
# https://stackoverflow.com/questions/37965669/how-do-i-link-the-crosshairtool-in-bokeh-over-several-plots
js_move = '''
if(cb_obj.x >= fig.x_range.start && cb_obj.x < = fig.x_range.end &&
cb_obj.y >= fig.y_range.start && cb_obj.y < = fig.y_range.end)
{
c.spans.height.computed_location = cb_obj.sx
}
else
{
c.spans.height.computed_location = null
}
'''
js_leave = 'c.spans.height.computed_location = null'
# some JS needed to link tooltips
# code = "source.set('selected', cb_data['index']);"
# callback = CustomJS(args={'source': source}, code=code)
colors = itertools.cycle(palette)
muted_colors = itertools.cycle(Spectral4)
p,c,h = [],[],[]
for height, y, tool in zip(heights, ys, tools):
p_ = figure(plot_height=height, plot_width=width, tools=TOOLS,
title=None, x_axis_label='Dist [m]', y_axis_label='|'.join(y))
# creat crosshair
c_ = CrosshairTool()
# link the crosshairs together
for cc_ in c:
args = {'c': cc_, 'fig': p_}
p_.js_on_event('mousemove', CustomJS(args=args, code=js_move))
p_.js_on_event('mouseleave', CustomJS(args=args, code=js_leave))
for pp_ in p:
args = {'c': c_, 'fig': pp_}
pp_.js_on_event('mousemove', CustomJS(args=args, code=js_move))
pp_.js_on_event('mouseleave', CustomJS(args=args, code=js_leave))
# toolbar ontop for the smaller plots at the bottom
if len(p)>0:
p_.x_range = p[0].x_range
p_.toolbar_location="above"
# creat tooltip
h_ = HoverTool(
tooltips=tool if len(h)==0 else tools[0]+tool,
formatters={
'@dist': 'printf',
'@dist_lap': 'printf',
'@time_lap': 'printf',
},
mode='mouse' if len(y)>1 or len(sources)>1 else 'vline')
p_.add_tools(c_)
p_.add_tools(h_)
# create the actual plot;
# if multiple sources, use same color for all plots of one source
if (len(sources)>1):
colors = itertools.cycle(palette)
muted_colors = itertools.cycle(Spectral4)
for datetime, lap, lap_t, source in sources:
if not isinstance(lap_t, str):
lap_t = "%i:%02i.%03i"%(lap_t//60,lap_t%60,(lap_t*1e3)%1000)
for yi in y:
p_.line(x=x, y=yi, source=source,
legend_label='%s | lap %s | %s'%(datetime, lap, lap_t) + (' | %s'%yi if len(y)>1 else ''),
muted_color=next(muted_colors), muted_alpha=0.2,
line_width=3, line_alpha=0.6,
line_color=next(colors))
p_.legend.location = "top_right"
p_.legend.click_policy="mute"
p.append(p_)
c.append(c_)
h.append(h_)
return gridplot(p, ncols=1, sizing_mode='scale_width')
def getRPMFigure(df):
0
View Source File : _plotting.py
License : GNU Affero General Public License v3.0
Project Creator : kernc
License : GNU Affero General Public License v3.0
Project Creator : kernc
def plot(*, results: pd.Series,
df: pd.DataFrame,
indicators: List[_Indicator],
filename='', plot_width=None,
plot_equity=True, plot_return=False, plot_pl=True,
plot_volume=True, plot_drawdown=False,
smooth_equity=False, relative_equity=True,
superimpose=True, resample=True,
reverse_indicators=True,
show_legend=True, open_browser=True):
"""
Like much of GUI code everywhere, this is a mess.
"""
# We need to reset global Bokeh state, otherwise subsequent runs of
# plot() contain some previous run's cruft data (was noticed when
# TestPlot.test_file_size() test was failing).
if not filename and not IS_JUPYTER_NOTEBOOK:
filename = _windos_safe_filename(str(results._strategy))
_bokeh_reset(filename)
COLORS = [BEAR_COLOR, BULL_COLOR]
BAR_WIDTH = .8
assert df.index.equals(results['_equity_curve'].index)
equity_data = results['_equity_curve'].copy(deep=False)
trades = results['_trades']
plot_volume = plot_volume and not df.Volume.isnull().all()
plot_equity = plot_equity and not trades.empty
plot_return = plot_return and not trades.empty
plot_pl = plot_pl and not trades.empty
is_datetime_index = isinstance(df.index, pd.DatetimeIndex)
from .lib import OHLCV_AGG
# ohlc df may contain many columns. We're only interested in, and pass on to Bokeh, these
df = df[list(OHLCV_AGG.keys())].copy(deep=False)
# Limit data to max_candles
if is_datetime_index:
df, indicators, equity_data, trades = _maybe_resample_data(
resample, df, indicators, equity_data, trades)
df.index.name = None # Provides source name @index
df['datetime'] = df.index # Save original, maybe datetime index
df = df.reset_index(drop=True)
equity_data = equity_data.reset_index(drop=True)
index = df.index
new_bokeh_figure = partial(
_figure,
x_axis_type='linear',
width=plot_width,
height=400,
tools="xpan,xwheel_zoom,box_zoom,undo,redo,reset,save",
active_drag='xpan',
active_scroll='xwheel_zoom')
pad = (index[-1] - index[0]) / 20
fig_ohlc = new_bokeh_figure(
x_range=Range1d(index[0], index[-1],
min_interval=10,
bounds=(index[0] - pad,
index[-1] + pad)) if index.size > 1 else None)
figs_above_ohlc, figs_below_ohlc = [], []
source = ColumnDataSource(df)
source.add((df.Close >= df.Open).values.astype(np.uint8).astype(str), 'inc')
trade_source = ColumnDataSource(dict(
index=trades['ExitBar'],
datetime=trades['ExitTime'],
exit_price=trades['ExitPrice'],
size=trades['Size'],
returns_positive=(trades['ReturnPct'] > 0).astype(int).astype(str),
))
inc_cmap = factor_cmap('inc', COLORS, ['0', '1'])
cmap = factor_cmap('returns_positive', COLORS, ['0', '1'])
colors_darker = [lightness(BEAR_COLOR, .35),
lightness(BULL_COLOR, .35)]
trades_cmap = factor_cmap('returns_positive', colors_darker, ['0', '1'])
if is_datetime_index:
fig_ohlc.xaxis.formatter = CustomJSTickFormatter(
args=dict(axis=fig_ohlc.xaxis[0],
formatter=DatetimeTickFormatter(days=['%d %b', '%a %d'],
months=['%m/%Y', "%b'%y"]),
source=source),
code='''
this.labels = this.labels || formatter.doFormat(ticks
.map(i => source.data.datetime[i])
.filter(t => t !== undefined));
return this.labels[index] || "";
''')
NBSP = '\N{NBSP}' * 4
ohlc_extreme_values = df[['High', 'Low']].copy(deep=False)
ohlc_tooltips = [
('x, y', NBSP.join(('$index',
'$y{0,0.0[0000]}'))),
('OHLC', NBSP.join(('@Open{0,0.0[0000]}',
'@High{0,0.0[0000]}',
'@Low{0,0.0[0000]}',
'@Close{0,0.0[0000]}'))),
('Volume', '@Volume{0,0}')]
def new_indicator_figure(**kwargs):
kwargs.setdefault('height', 90)
fig = new_bokeh_figure(x_range=fig_ohlc.x_range,
active_scroll='xwheel_zoom',
active_drag='xpan',
**kwargs)
fig.xaxis.visible = False
fig.yaxis.minor_tick_line_color = None
return fig
def set_tooltips(fig, tooltips=(), vline=True, renderers=()):
tooltips = list(tooltips)
renderers = list(renderers)
if is_datetime_index:
formatters = {'@datetime': 'datetime'}
tooltips = [("Date", "@datetime{%c}")] + tooltips
else:
formatters = {}
tooltips = [("#", "@index")] + tooltips
fig.add_tools(HoverTool(
point_policy='follow_mouse',
renderers=renderers, formatters=formatters,
tooltips=tooltips, mode='vline' if vline else 'mouse'))
def _plot_equity_section(is_return=False):
"""Equity section"""
# Max DD Dur. line
equity = equity_data['Equity'].copy()
dd_end = equity_data['DrawdownDuration'].idxmax()
if np.isnan(dd_end):
dd_start = dd_end = equity.index[0]
else:
dd_start = equity[:dd_end].idxmax()
# If DD not extending into the future, get exact point of intersection with equity
if dd_end != equity.index[-1]:
dd_end = np.interp(equity[dd_start],
(equity[dd_end - 1], equity[dd_end]),
(dd_end - 1, dd_end))
if smooth_equity:
interest_points = pd.Index([
# Beginning and end
equity.index[0], equity.index[-1],
# Peak equity and peak DD
equity.idxmax(), equity_data['DrawdownPct'].idxmax(),
# Include max dd end points. Otherwise the MaxDD line looks amiss.
dd_start, int(dd_end), min(int(dd_end + 1), equity.size - 1),
])
select = pd.Index(trades['ExitBar']).union(interest_points)
select = select.unique().dropna()
equity = equity.iloc[select].reindex(equity.index)
equity.interpolate(inplace=True)
assert equity.index.equals(equity_data.index)
if relative_equity:
equity /= equity.iloc[0]
if is_return:
equity -= equity.iloc[0]
yaxis_label = 'Return' if is_return else 'Equity'
source_key = 'eq_return' if is_return else 'equity'
source.add(equity, source_key)
fig = new_indicator_figure(
y_axis_label=yaxis_label,
**({} if plot_drawdown else dict(height=110)))
# High-watermark drawdown dents
fig.patch('index', 'equity_dd',
source=ColumnDataSource(dict(
index=np.r_[index, index[::-1]],
equity_dd=np.r_[equity, equity.cummax()[::-1]]
)),
fill_color='#ffffea', line_color='#ffcb66')
# Equity line
r = fig.line('index', source_key, source=source, line_width=1.5, line_alpha=1)
if relative_equity:
tooltip_format = f'@{source_key}{{+0,0.[000]%}}'
tick_format = '0,0.[00]%'
legend_format = '{:,.0f}%'
else:
tooltip_format = f'@{source_key}{{$ 0,0}}'
tick_format = '$ 0.0 a'
legend_format = '${:,.0f}'
set_tooltips(fig, [(yaxis_label, tooltip_format)], renderers=[r])
fig.yaxis.formatter = NumeralTickFormatter(format=tick_format)
# Peaks
argmax = equity.idxmax()
fig.scatter(argmax, equity[argmax],
legend_label='Peak ({})'.format(
legend_format.format(equity[argmax] * (100 if relative_equity else 1))),
color='cyan', size=8)
fig.scatter(index[-1], equity.values[-1],
legend_label='Final ({})'.format(
legend_format.format(equity.iloc[-1] * (100 if relative_equity else 1))),
color='blue', size=8)
if not plot_drawdown:
drawdown = equity_data['DrawdownPct']
argmax = drawdown.idxmax()
fig.scatter(argmax, equity[argmax],
legend_label='Max Drawdown (-{:.1f}%)'.format(100 * drawdown[argmax]),
color='red', size=8)
dd_timedelta_label = df['datetime'].iloc[int(round(dd_end))] - df['datetime'].iloc[dd_start]
fig.line([dd_start, dd_end], equity.iloc[dd_start],
line_color='red', line_width=2,
legend_label=f'Max Dd Dur. ({dd_timedelta_label})'
.replace(' 00:00:00', '')
.replace('(0 days ', '('))
figs_above_ohlc.append(fig)
def _plot_drawdown_section():
"""Drawdown section"""
fig = new_indicator_figure(y_axis_label="Drawdown")
drawdown = equity_data['DrawdownPct']
argmax = drawdown.idxmax()
source.add(drawdown, 'drawdown')
r = fig.line('index', 'drawdown', source=source, line_width=1.3)
fig.scatter(argmax, drawdown[argmax],
legend_label='Peak (-{:.1f}%)'.format(100 * drawdown[argmax]),
color='red', size=8)
set_tooltips(fig, [('Drawdown', '@drawdown{-0.[0]%}')], renderers=[r])
fig.yaxis.formatter = NumeralTickFormatter(format="-0.[0]%")
return fig
def _plot_pl_section():
"""Profit/Loss markers section"""
fig = new_indicator_figure(y_axis_label="Profit / Loss")
fig.add_layout(Span(location=0, dimension='width', line_color='#666666',
line_dash='dashed', line_width=1))
returns_long = np.where(trades['Size'] > 0, trades['ReturnPct'], np.nan)
returns_short = np.where(trades['Size'] < 0, trades['ReturnPct'], np.nan)
size = trades['Size'].abs()
size = np.interp(size, (size.min(), size.max()), (8, 20))
trade_source.add(returns_long, 'returns_long')
trade_source.add(returns_short, 'returns_short')
trade_source.add(size, 'marker_size')
if 'count' in trades:
trade_source.add(trades['count'], 'count')
r1 = fig.scatter('index', 'returns_long', source=trade_source, fill_color=cmap,
marker='triangle', line_color='black', size='marker_size')
r2 = fig.scatter('index', 'returns_short', source=trade_source, fill_color=cmap,
marker='inverted_triangle', line_color='black', size='marker_size')
tooltips = [("Size", "@size{0,0}")]
if 'count' in trades:
tooltips.append(("Count", "@count{0,0}"))
set_tooltips(fig, tooltips + [("P/L", "@returns_long{+0.[000]%}")],
vline=False, renderers=[r1])
set_tooltips(fig, tooltips + [("P/L", "@returns_short{+0.[000]%}")],
vline=False, renderers=[r2])
fig.yaxis.formatter = NumeralTickFormatter(format="0.[00]%")
return fig
def _plot_volume_section():
"""Volume section"""
fig = new_indicator_figure(y_axis_label="Volume")
fig.xaxis.formatter = fig_ohlc.xaxis[0].formatter
fig.xaxis.visible = True
fig_ohlc.xaxis.visible = False # Show only Volume's xaxis
r = fig.vbar('index', BAR_WIDTH, 'Volume', source=source, color=inc_cmap)
set_tooltips(fig, [('Volume', '@Volume{0.00 a}')], renderers=[r])
fig.yaxis.formatter = NumeralTickFormatter(format="0 a")
return fig
def _plot_superimposed_ohlc():
"""Superimposed, downsampled vbars"""
time_resolution = pd.DatetimeIndex(df['datetime']).resolution
resample_rule = (superimpose if isinstance(superimpose, str) else
dict(day='M',
hour='D',
minute='H',
second='T',
millisecond='S').get(time_resolution))
if not resample_rule:
warnings.warn(
f"'Can't superimpose OHLC data with rule '{resample_rule}'"
f"(index datetime resolution: '{time_resolution}'). Skipping.",
stacklevel=4)
return
df2 = (df.assign(_width=1).set_index('datetime')
.resample(resample_rule, label='left')
.agg(dict(OHLCV_AGG, _width='count')))
# Check if resampling was downsampling; error on upsampling
orig_freq = _data_period(df['datetime'])
resample_freq = _data_period(df2.index)
if resample_freq < orig_freq:
raise ValueError('Invalid value for `superimpose`: Upsampling not supported.')
if resample_freq == orig_freq:
warnings.warn('Superimposed OHLC plot matches the original plot. Skipping.',
stacklevel=4)
return
df2.index = df2['_width'].cumsum().shift(1).fillna(0)
df2.index += df2['_width'] / 2 - .5
df2['_width'] -= .1 # Candles don't touch
df2['inc'] = (df2.Close >= df2.Open).astype(int).astype(str)
df2.index.name = None
source2 = ColumnDataSource(df2)
fig_ohlc.segment('index', 'High', 'index', 'Low', source=source2, color='#bbbbbb')
colors_lighter = [lightness(BEAR_COLOR, .92),
lightness(BULL_COLOR, .92)]
fig_ohlc.vbar('index', '_width', 'Open', 'Close', source=source2, line_color=None,
fill_color=factor_cmap('inc', colors_lighter, ['0', '1']))
def _plot_ohlc():
"""Main OHLC bars"""
fig_ohlc.segment('index', 'High', 'index', 'Low', source=source, color="black")
r = fig_ohlc.vbar('index', BAR_WIDTH, 'Open', 'Close', source=source,
line_color="black", fill_color=inc_cmap)
return r
def _plot_ohlc_trades():
"""Trade entry / exit markers on OHLC plot"""
trade_source.add(trades[['EntryBar', 'ExitBar']].values.tolist(), 'position_lines_xs')
trade_source.add(trades[['EntryPrice', 'ExitPrice']].values.tolist(), 'position_lines_ys')
fig_ohlc.multi_line(xs='position_lines_xs', ys='position_lines_ys',
source=trade_source, line_color=trades_cmap,
legend_label=f'Trades ({len(trades)})',
line_width=8, line_alpha=1, line_dash='dotted')
def _plot_indicators():
"""Strategy indicators"""
def _too_many_dims(value):
assert value.ndim >= 2
if value.ndim > 2:
warnings.warn(f"Can't plot indicators with >2D ('{value.name}')",
stacklevel=5)
return True
return False
class LegendStr(str):
# The legend string is such a string that only matches
# itself if it's the exact same object. This ensures
# legend items are listed separately even when they have the
# same string contents. Otherwise, Bokeh would always consider
# equal strings as one and the same legend item.
def __eq__(self, other):
return self is other
ohlc_colors = colorgen()
indicator_figs = []
for i, value in enumerate(indicators):
value = np.atleast_2d(value)
# Use .get()! A user might have assigned a Strategy.data-evolved
# _Array without Strategy.I()
if not value._opts.get('plot') or _too_many_dims(value):
continue
is_overlay = value._opts['overlay']
is_scatter = value._opts['scatter']
if is_overlay:
fig = fig_ohlc
else:
fig = new_indicator_figure()
indicator_figs.append(fig)
tooltips = []
colors = value._opts['color']
colors = colors and cycle(_as_list(colors)) or (
cycle([next(ohlc_colors)]) if is_overlay else colorgen())
legend_label = LegendStr(value.name)
for j, arr in enumerate(value, 1):
color = next(colors)
source_name = f'{legend_label}_{i}_{j}'
if arr.dtype == bool:
arr = arr.astype(int)
source.add(arr, source_name)
tooltips.append(f'@{{{source_name}}}{{0,0.0[0000]}}')
if is_overlay:
ohlc_extreme_values[source_name] = arr
if is_scatter:
fig.scatter(
'index', source_name, source=source,
legend_label=legend_label, color=color,
line_color='black', fill_alpha=.8,
marker='circle', radius=BAR_WIDTH / 2 * 1.5)
else:
fig.line(
'index', source_name, source=source,
legend_label=legend_label, line_color=color,
line_width=1.3)
else:
if is_scatter:
r = fig.scatter(
'index', source_name, source=source,
legend_label=LegendStr(legend_label), color=color,
marker='circle', radius=BAR_WIDTH / 2 * .9)
else:
r = fig.line(
'index', source_name, source=source,
legend_label=LegendStr(legend_label), line_color=color,
line_width=1.3)
# Add dashed centerline just because
mean = float(pd.Series(arr).mean())
if not np.isnan(mean) and (abs(mean) < .1 or
round(abs(mean), 1) == .5 or
round(abs(mean), -1) in (50, 100, 200)):
fig.add_layout(Span(location=float(mean), dimension='width',
line_color='#666666', line_dash='dashed',
line_width=.5))
if is_overlay:
ohlc_tooltips.append((legend_label, NBSP.join(tooltips)))
else:
set_tooltips(fig, [(legend_label, NBSP.join(tooltips))], vline=True, renderers=[r])
# If the sole indicator line on this figure,
# have the legend only contain text without the glyph
if len(value) == 1:
fig.legend.glyph_width = 0
return indicator_figs
# Construct figure ...
if plot_equity:
_plot_equity_section()
if plot_return:
_plot_equity_section(is_return=True)
if plot_drawdown:
figs_above_ohlc.append(_plot_drawdown_section())
if plot_pl:
figs_above_ohlc.append(_plot_pl_section())
if plot_volume:
fig_volume = _plot_volume_section()
figs_below_ohlc.append(fig_volume)
if superimpose and is_datetime_index:
_plot_superimposed_ohlc()
ohlc_bars = _plot_ohlc()
_plot_ohlc_trades()
indicator_figs = _plot_indicators()
if reverse_indicators:
indicator_figs = indicator_figs[::-1]
figs_below_ohlc.extend(indicator_figs)
set_tooltips(fig_ohlc, ohlc_tooltips, vline=True, renderers=[ohlc_bars])
source.add(ohlc_extreme_values.min(1), 'ohlc_low')
source.add(ohlc_extreme_values.max(1), 'ohlc_high')
custom_js_args = dict(ohlc_range=fig_ohlc.y_range,
source=source)
if plot_volume:
custom_js_args.update(volume_range=fig_volume.y_range)
fig_ohlc.x_range.js_on_change('end', CustomJS(args=custom_js_args,
code=_AUTOSCALE_JS_CALLBACK))
plots = figs_above_ohlc + [fig_ohlc] + figs_below_ohlc
linked_crosshair = CrosshairTool(dimensions='both')
for f in plots:
if f.legend:
f.legend.visible = show_legend
f.legend.location = 'top_left'
f.legend.border_line_width = 1
f.legend.border_line_color = '#333333'
f.legend.padding = 5
f.legend.spacing = 0
f.legend.margin = 0
f.legend.label_text_font_size = '8pt'
f.legend.click_policy = "hide"
f.min_border_left = 0
f.min_border_top = 3
f.min_border_bottom = 6
f.min_border_right = 10
f.outline_line_color = '#666666'
f.add_tools(linked_crosshair)
wheelzoom_tool = next(wz for wz in f.tools if isinstance(wz, WheelZoomTool))
wheelzoom_tool.maintain_focus = False
kwargs = {}
if plot_width is None:
kwargs['sizing_mode'] = 'stretch_width'
fig = gridplot(
plots,
ncols=1,
toolbar_location='right',
toolbar_options=dict(logo=None),
merge_tools=True,
**kwargs
)
show(fig, browser=None if open_browser else 'none')
return fig
def plot_heatmaps(heatmap: pd.Series, agg: Union[Callable, str], ncols: int,