Yasir Azeem
Yasir Azeem

Reputation: 329

Python: Bokeh hover date time

I am trying to get a line plot via Bokeh in Python. I am new to Bokeh and I am trying to apply hover tool tips over the plot. The x-axis of the plot has Timestamp values which are converted into epoch string. I've reviewed some same problems here and tried to use the workaround for my case but it doesn't seem to work. On the plot it gives ??? where the time should show up.

Any suggestions for my code?

Timestamp is in format 2016-12-29 02:49:12

Also can someone tell how do I format x-axis ticks to show up vertically ?

p = figure(width=1100,height=300,tools='resize,pan,wheel_zoom,box_zoom,reset,previewsave,hover',logo=None)
p.title.text = "Time Series for Price in Euros"
p.grid.grid_line_alpha = 0
p.xaxis.axis_label = "Day"
p.yaxis.axis_label = "Euros"
p.ygrid.band_fill_color = "olive"
p.ygrid.band_fill_alpha = 0.1
p.circle(df['DateTime'],df['EuP'], size=4, legend='close',
  color='darkgrey', alpha=0.2)
p.xaxis.formatter = DatetimeTickFormatter(formats=dict(
hours=["%d %B %Y"],
days=["%d %B %Y"],
months=["%d %B %Y"],
years=["%d %B %Y"],
))

source = ColumnDataSource(data=dict(time=[x.strftime("%Y-%m-%d %H:%M:%S")for x in df['DateTime']]))
hover = p.select(dict(type=HoverTool))
hover.tooltips = {"time":'@time', "y":"$y"}
hover.mode = 'mouse'
p.line(df['DateTime'],df['EuP'],legend='Price',color='navy',alpha=0.7)

Upvotes: 18

Views: 26272

Answers (3)

Alex G Rice
Alex G Rice

Reputation: 1579

Since this answer was originally posted, new work has gone into Bokeh to make things simpler. A datetime field can be formatted as a datetime directly by the hover tool, by specifying a formatter, e.g.:

HoverTool(tooltips=[('date', '@DateTime{%F}')],
          formatters={'@DateTime': 'datetime'})

It is no longer necessary to pre-format date fields in the data source as below. For more information see Formatting Tooltip Fields


OLD ANSWER:

The problem with your tooltip is you created a source with the string representation of the dates, but the p.line() call is unaware of it. So you have to pass in a columndatasource that has the tooltip, the x and y values.

Here is a working variant of your code:

from bokeh.plotting import figure, show
from bokeh.models.formatters import DatetimeTickFormatter
from bokeh.models import ColumnDataSource
from bokeh.models.tools import HoverTool
import pandas as pd
import numpy as np

data = {
    'DateTime' : pd.Series(
        ['2016-12-29 02:49:12',
        '2016-12-30 02:49:12',
        '2016-12-31 02:49:12'],
        dtype='datetime64[ns]'),
    'EuP' : [20,40,15]
}
df = pd.DataFrame(data)
df['tooltip'] = [x.strftime("%Y-%m-%d %H:%M:%S") for x in df['DateTime']]
p = figure(width=1100,height=300,tools='resize,pan,wheel_zoom,box_zoom,reset,previewsave,hover',logo=None)
p.title.text = "Time Series for Price in Euros"
p.grid.grid_line_alpha = 0
p.xaxis.axis_label = "Day"
p.yaxis.axis_label = "Euros"
p.ygrid.band_fill_color = "olive"
p.ygrid.band_fill_alpha = 0.1
p.circle(df['DateTime'],df['EuP'], size=4, legend='close',
  color='darkgrey', alpha=0.2)
p.xaxis.formatter = DatetimeTickFormatter(formats=dict(
 hours=["%d %B %Y"],
 days=["%d %B %Y"],
 months=["%d %B %Y"],
 years=["%d %B %Y"],
))
hover = p.select(dict(type=HoverTool))
tips = [('when','@tooltip'), ('y','$y')]
hover.tooltips = tips
hover.mode = 'mouse'
p.line(x='DateTime', y='EuP', source=ColumnDataSource(df),
       legend='Price',color='navy',alpha=0.7)
show(p)

Also note there is an open issue about the lack of formatting options in the bokeh tooltip. There might be an easier way to not have to format the datestrings as a separate column:

https://github.com/bokeh/bokeh/issues/1239

Also can someone tell how do I format x-axis ticks to show up vertically ?

They look fine to me, sorry I cannot help on that one.

Hope this helps!

PS it would be better next time if you posted a working script with import statements, and a mocked up dataframe to make it possible to test. It took some time to sort it all out. But I am learning Bokeh so that is fine :)

Upvotes: 33

s_mj
s_mj

Reputation: 580

I have created a wrapper for ScatterPlot in bokeh.

class Visualization():
    WIDTH = 1000
    TOOLS = "pan,wheel_zoom,box_zoom,reset,save"
 
class ScatterChart(Visualization):
    def __init__(self, data, spec:Dict):
        self.data = data
        self.x_column = spec["x_axis"]
        self.y_column = spec["y_axis"]    
        self.series_ = spec["series_column"]
        
        self.xlabel = spec['xlabel'] 
        self.ylabel = spec['ylabel'] 
        self.title = spec['title'] 
        
    def prepare_data(self):
        
        # Get  Axis Type
        self.xtype = 'datetime' if self.data.dtypes[self.x_column].type is np.datetime64 else 'linear'
        self.ytype = 'datetime' if self.data.dtypes[self.x_column].type is np.datetime64 else 'linear'
        
        return self.data
    
    def render(self):
        df_ = self.prepare_data()
        
        format_= {}
        tool_tip=[]
        
        # For axis
        for col in [self.x_column, self.y_column , self.series_]:
            
            if self.data.dtypes[col].type is np.datetime64:
                format_['@' + str(col) ] = "datetime" # formatter
                tool_tip.append(tuple([str(col) , '@' + str(col) + '{%F}'])) # tool-tip
                
            else:
                format_['@' + str(col) ] = "numeral" # 
                tool_tip.append(tuple([str(col) , '@' + str(col)]))
            
        
        # print(format_)
        # print(tool_tip)
        
        
        # Add Hover parameters
        hover = HoverTool(tooltips= tool_tip
            , formatters=format_   )
        
        
        
        p=figure(
                 width = super().WIDTH,
                 height = 500,
                 x_axis_label = self.xlabel,
                 x_axis_type=self.xtype,
                 y_axis_label = self.ylabel,
                 y_axis_type=self.ytype,
                 title = self.title,
                 tools = super().TOOLS
                 )
        
        # Get Only Top 10 groups/series to display
        for value, color in zip(islice(self.data.groupby(by=[self.series_]
                            )[self.series_].count().sort_values(ascending=False).rename('cnt').reset_index()[self.series_].tolist(), 10), Category10[10]):
            
            p.scatter(
                    x=self.x_column,
                    y=self.y_column,
                    source=df_.loc[(df_[self.series_]==value)],
                    color=color,
                    legend_group=self.series_
                      )
            
        p.add_tools(hover)
        p.toolbar.logo = None
        p.legend.location = "top_left"
        p.legend.click_policy="hide"
                
        return p
    
# end of ScatterChart

This is how I initialize this

from visualization import ScatterChart
sc = ScatterChart( 
    df,
    {'x_axis' :'ts', 
     'y_axis': 'Discus',
     'series_column': 'Competition',
      'xlabel':'Discus',
      'ylabel':'Javeline',
     'title':'Discus Vs Javeline'
     })
d = sc.render()
show(d)

Upvotes: 0

Don Fruendo
Don Fruendo

Reputation: 350

Sorry for not commenting, I don't have enough reputation for that.

The accepted answer by @Alex doesn't work for me (Bokeh 2.0.1), because it is lacking a simple @-sign in the formatter. The working code is this:

HoverTool(tooltips=[('date', '@DateTime{%F}')],
          formatters={'@DateTime': 'datetime'})

Upvotes: 15

Related Questions