Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Secondary axis for Holoviews(Bokeh) graph

I am trying to plot a set of graphs overlaid with a plot showing the % difference between them.

The code I have for plotting:

%%output size = 200
%%opts Curve[height=200, width=400,show_grid=True,tools=['hover','box_select'], xrotation=90]
%%opts Curve(line_width=1)


from bokeh.models import Range1d, LinearAxis

new_df = new_df.astype('float')
percent_diff_df = percent_diff_df.astype('float')

def twinx(plot, element):
    # Setting the second y axis range name and range
    start, end = (element.range(1))
    label = element.dimensions()[1].pprint_label
    plot.state.extra_y_ranges = {"foo": Range1d(start=0, end=150)}
    # Adding the second axis to the plot. 
    linaxis = LinearAxis(axis_label='% Difference', y_range_name='foo')
    plot.state.add_layout(linaxis, 'right')

wavelength = hv.Dimension('wavelength', label = 'Wavelength', unit = 'nm')
radiance = hv.Dimension('radiance', label = 'Radiance', unit = 'W/m^2/sr/nm')
curve = hv.Curve((new_df['Wave'], new_df['Level_9']), wavelength, radiance,label = 'Level_9', group = 'Requirements')*\
    hv.Curve((new_df['gcal_wave'],new_df['gcal_9']),wavelength, radiance,label = 'GCAL_9', group = 'Requirements')
curve2 = hv.Curve((percent_diff_df['pdiff_wave'],percent_diff_df['pdiff_9']), label = '% Difference', group = 'Percentage Difference').opts(plot=dict(finalize_hooks=[twinx]), style=dict(color='purple'))
curve * curve2

The result looks like this: enter image description here

The flat blue graphs are actually supposed to look like this: enter image description here

I need to plot the two graphs on two scales. I seem to be able to add a scale but not attach any of the plots to the scale.

like image 387
Brain_overflowed Avatar asked Nov 26 '18 21:11

Brain_overflowed


1 Answers

Your solution is almost complete. To actually use the range & axis you create in your hook, you need to access underlying bokeh glyph and set its y_range_name.

A general example would look like this:

import pandas as pd
import holoviews as hv
from bokeh.models.renderers import GlyphRenderer

hv.extension('bokeh')

def apply_formatter(plot, element):

    p = plot.state

    # create secondary range and axis
    p.extra_y_ranges = {"twiny": Range1d(start=0, end=35)}
    p.add_layout(LinearAxis(y_range_name="twiny"), 'right')

    # set glyph y_range_name to the one we've just created
    glyph = p.select(dict(type=GlyphRenderer))[0]
    glyph.y_range_name = 'twiny'

dts = pd.date_range('2015-01-01', end='2015-01-10').values

c_def = hv.Curve((dts, np.arange(10)), name='default_axis').options(color='red', width=300)
c_sec = hv.Curve((dts, np.arange(10)), name='secondary_axis').options(color='blue',width=300, hooks=[apply_formatter])
c_def + c_def * c_sec + c_sec

For further details, please refer to original github issue here: https://github.com/pyviz/holoviews/issues/396

EDITED - An Advanced example

Its been some time since the original question was answered, and as thread at github advances, i'd like to cross post a more advanced example taking care of some inconsistencies shown in original answer. I still encourage you to to through the thread at github to dig in the details, as multiple axis plots in holoviews currently require understanding of how bokeh handes multiple axis.

import pandas as pd
import streamz
import streamz.dataframe
import holoviews as hv
from holoviews import opts
from holoviews.streams import Buffer
from bokeh.models import Range1d, LinearAxis

hv.extension('bokeh')

def plot_secondary(plot, element):
    ''' 
    A hook to put data on secondary axis
    '''
    p = plot.state

    # create secondary range and axis
    if 'twiny' not in [t for t in p.extra_y_ranges]:
        # you need to manually recreate primary axis to avoid weird behavior if you are going to 
        # use secondary_axis in your plots. From what i know this also relates to the way axis
        # behave in bokeh and unfortunately cannot be modified from hv unless you are 
        # willing to rewrite quite a bit of code
        p.y_range = Range1d(start=0, end=10)
        p.y_range.name = 'default'
        p.extra_y_ranges = {"twiny": Range1d(start=0, end=10)}
        p.add_layout(LinearAxis(y_range_name="twiny"), 'right')

    # set glyph y_range_name to the one we've just created
    glyph = p.renderers[-1]
    glyph.y_range_name = 'twiny'

    # set proper range
    glyph = p.renderers[-1]
    vals = glyph.data_source.data['y'] # ugly hardcoded solution, see notes below
    p.extra_y_ranges["twiny"].start = vals.min()* 0.99
    p.extra_y_ranges["twiny"].end = vals.max()* 1.01

# define two streamz random dfs to sim data for primary and secondary plots
simple_sdf = streamz.dataframe.Random(freq='10ms', interval='100ms')
secondary_sdf = streamz.dataframe.Random(freq='10ms', interval='100ms')

# do some transformation
pdf = (simple_sdf-0.5).cumsum()
sdf = (secondary_sdf-0.5).cumsum()

# create streams for holoviews from these dfs
prim_stream = Buffer(pdf.y)
sec_stream = Buffer(sdf.y)

# create dynamic maps to plot streaming data
primary = hv.DynamicMap(hv.Curve, streams=[prim_stream]).opts(width=400, show_grid=True, framewise=True)
secondary = hv.DynamicMap(hv.Curve, streams=[sec_stream]).opts(width=400, color='red', show_grid=True, framewise=True, hooks=[plot_secondary])
secondary_2 = hv.DynamicMap(hv.Curve, streams=[prim_stream]).opts(width=400, color='yellow', show_grid=True, framewise=True, hooks=[plot_secondary])

# plot these maps on the same figure
primary * secondary * secondary_2
like image 127
Adam Lansky Avatar answered Nov 03 '22 22:11

Adam Lansky