1

The Goal: To generate a scatter plot from a pandas DataFrame with 3 columns: x, y, type (either a, b or c). The data points should have different colors based on the type. Every data point should have a hover effect. However, data points with type c should have a tap effect too. The data file (data_file.csv) looks something like:

x y z
1 4 a
2 3 b
3 2 a
4 4 c
.. .. ..

My attempt: First, I imported the dataframe and divided into two parts: one with c type data and another with the everything else. Then I created two columndatasource and plotted the data. Is there a shortcut or better way than this? Also, I couldn't achieve some feature (see below).

Code:

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, OpenURL, TapTool
from bokeh.models.tools import HoverTool
from bokeh.transform import factor_cmap

file = "data.csv"

df = read_csv(file, skiprows=1, header=None, sep="\t")      

# now I will seperate the dataframe into two: one with type **a** & **b**
# and another dataframe containing with type **c** 

c_df = df.drop(df[df[2] != 'c'].index)
ab_df = df.drop(df[df[2] == 'c'].index)

ab_source = ColumnDataSource(data=dict(                
                Independent = ab_df[0],                           
                Dependent = ab_df[1],                        
                Type = ab_df[2]
            ))
c_source = ColumnDataSource(data=dict(                
                Independent = c_df[0],                           
                Dependent = c_df[1],                        
                Type = c_df[2],
                link = "http://example.com/" + c_df[0].apply(str) + ".php"
            ))

p = figure(title="Random PLot")

p.circle('Independent', 'Dependent',                                          
                size=10,                                           
                source=ab_source,                                     
                color=factor_cmap('Type',
                                ['red', 'blue'],
                                ['a', 'b']),
                legend_group='Type'
            )

p.circle('Independent', 'Dependent',               
                size=12,               
                source=c_source,
                color=factor_cmap('Type',
                                ['green'],
                                ['c']),
                name='needsTapTool'
            )

p.legend.title = "Type"


hover = HoverTool()     
hover.tooltips = """
          <div>
            <h3>Type: @Type</h3>
            <p> @Independent and @Dependent </p>
          </div>
                 """
p.add_tools(hover)

url = "@link"                              
tap = TapTool(names=['needsTapTool'])         
tap.callback = OpenURL(url=url)
p.add_tools(tap)

show(p)

Problems: (1) How can I add two different hover tools so that different data points will behave differently depending on their type? Whenever I add another hover tool, only the last one is getting effective.

(2) How can I take part of a data in CDS? For example, imagine I have a column called 'href' which contains a link but have a "http://www" part. Now how can I set the 'link' variable inside a CDS that doesn't contain this part? when I try:

c_source = ColumnDataSource(data=dict(
                link = c_df[3].apply(str)[10:]
            ))

I get a keyError. Any help will be appreciated.

1 Answers1

1

It is possible to define multiple Tools, even multiple HoverTools in one plot. The trick is the collect the renderers and apply them to a specific tool.

In the example below, two HoverTools are added and on TapTool.

import pandas as pd

from bokeh.plotting import figure, show, output_notebook
from bokeh.models import ColumnDataSource, OpenURL, TapTool, HoverTool
output_notebook()

df = pd.DataFrame({'x':[1,2,3,4], 'y':[4,3,2,1], 'z':['a','b','a','c']})
>>
   x  y  z
0  1  4  a
1  2  3  b
2  3  2  a
3  4  1  c

color = {'a':'red', 'b':'blue', 'c':'green'}


p = figure(width=300, height=300)

# list to collect the renderers
renderers = []
for item in df['z'].unique():
    df_group = df[df['z']==item].copy()
    # if group 'c', add some urls
    if item == 'c':
        # url with "https"
        df_group['link'] = df_group.loc[:,'x'].apply(lambda x: f"https://www.example.com/{x}.php")
        # url without "https"
        df_group['plain_link'] = df_group.loc[:,'x'].apply(lambda x: f"example.com/{x}.php")
    renderers.append(
        p.circle(
            'x',
            'y',
            size=10,
            source=ColumnDataSource(df_group),
            color=color[item],
            legend_label=item
        )
    )

p.legend.title = "Type"

# HoverTool for renderers of group 'a' and 'b'
hover = HoverTool(renderers=renderers[:2])
hover.tooltips = """
          <div>
            <h3>Type: @z</h3>
            <p> @x and @y </p>
          </div>
                 """
p.add_tools(hover)

# HoverTool for renderer of group 'c'
hover_c = HoverTool(renderers=[renderers[-1]])
hover_c.tooltips = """
          <div>
            <h3>Type: @z</h3>
            <p> @x and @y </p>
            <p> @plain_link </p>
          </div>
                 """
p.add_tools(hover_c)

# TapTool for renderer of group 'c'
tap = TapTool(renderers=[renderers[-1]], callback=OpenURL(url="@link"))
p.add_tools(tap)

show(p)

plot with 3 tools

mosc9575
  • 5,618
  • 2
  • 9
  • 32