0

I have been trying to create a line chart that represents 'values' as y axis and 'time' as x axis. I have successfully done so.

However, when there's a large time gap between values, there exist lines connected two dots from those values which create an ugly looking scenario.

Therefore, I would like to get rid of those lines.

Here's my runnable code:

# Import Library
import random
import pandas as pd
from bokeh.core.properties import value
from bokeh.io import show, output_notebook, export_png
from bokeh.plotting import figure, output_file
from bokeh.models import ColumnDataSource, Legend, HoverTool, layouts, CustomJS, Select, Circle, RangeTool
from bokeh.layouts import column
output_notebook() # Render inline in a Jupyter Notebook


# Create Line Chart
def create_line_chart_bokeh():

    # Set data
    dates = ['24-11-2017', '28-11-2017', '29-11-2017',
            '23-03-2018', '27-03-2018', '12-08-2018']
    dates = list(map(lambda i: pd.Timestamp(i), dates))
    values = [random.randrange(101) for _ in range(len(dates))]
    source = ColumnDataSource(data=dict(date=dates, close=values))

    # Set Hover
    TOOLTIPS = [
    ("Average Score", "@close")
    ]

    # Set plot
    p = figure(plot_height=300, plot_width=800, tools="xpan", toolbar_location=None,
               tooltips=TOOLTIPS, x_axis_type="datetime", x_axis_location="above",
               background_fill_color="white", x_range=(dates[0], dates[int(len(dates)-1)]), title='Test Line Chart')

    # Set Line and Circle colours
    line_colour = '#59819a'
    circle_colour = '#ef5b45'

    p.line('date', 'close', source=source, color=line_colour)
    p.circle('date', 'close', source=source, color=circle_colour, size=4)
    p.yaxis.axis_label = 'Scores' # set y axis label

    # Set Time Selection Box
    select = figure(title="Drag the middle and edges of the selection box to change the range above",
                    plot_height=130, plot_width=800, y_range=p.y_range,
                    x_axis_type="datetime", y_axis_type=None,
                    tools="", toolbar_location=None, background_fill_color="#efefef")

    range_tool = RangeTool(x_range=p.x_range)
    range_tool.overlay.fill_color = 'navy'
    range_tool.overlay.fill_alpha = 0.2

    select.line('date', 'close', source=source, color=line_colour)
    select.ygrid.grid_line_color = None
    select.add_tools(range_tool)
    select.toolbar.active_multi = range_tool

    # Show the result
    show(column(p, select))

    return None


# Call Function
create_line_chart_bokeh()

LineChart

As can be seen above, those circled areas are not wanted. Is there anyone who has experiences or ideas to solve this problem?

This is a draft for what I want to achieve. I am thinking a time gap for deleting lines can be adjustable, or fixed as 2-or-3 month gap.

Example

Thank you in advance,

Update!!

According to the answer below, the line now looks proper. However, there'a slight problem on hovering line itself. Hovering on circles work perfectly fine.

# Import Library
import random
import pandas as pd
from bokeh.core.properties import value
from bokeh.io import show, export_png, output_notebook
from bokeh.plotting import figure, output_file
from bokeh.models import ColumnDataSource, Legend, HoverTool, layouts, CustomJS, Select, Circle, RangeTool
from bokeh.layouts import column
from datetime import date
output_notebook() # Render inline in a Jupyter Notebook


# Create Line Chart
def create_line_chart_bokeh():

    # Set data
    dates = ['24-11-2017', '28-11-2017', '29-11-2017',
            '23-03-2018', '27-03-2018', '12-08-2018']
    dates = list(map(lambda i: pd.Timestamp(i), dates))
    values = [random.randrange(101) for _ in range(len(dates))]
    source = ColumnDataSource(data=dict(date=dates, close=values))

    # Set Hover
    TOOLTIPS = [
    ("Average Score", "@close")
    ]

    # Set plot
    p = figure(plot_height=300, plot_width=800, tools="xpan", toolbar_location=None,
               tooltips=TOOLTIPS, x_axis_type="datetime", x_axis_location="above",
               background_fill_color="white", x_range=(dates[0], dates[int(len(dates)-1)]), title='Test Line Chart')

    # Set Line and Circle colours
    line_colour = '#59819a'
    circle_colour = '#ef5b45'
    for i in range(len(dates)):
        try:
            diff = dates[i+1] - dates[i] #Compute difference between dates in days
            if diff.days < 30:
                p.line([dates[i], dates[i+1]], [values[i], values[i+1]], color=line_colour) #Only plot a line if difference in days < 30
        except IndexError:
            pass

    p.circle('date', 'close', source=source, color=circle_colour, size=4)
    p.yaxis.axis_label = 'Scores' # set y axis label

    # Set Time Selection Box
    select = figure(title="Drag the middle and edges of the selection box to change the range above",
                    plot_height=130, plot_width=800, y_range=p.y_range,
                    x_axis_type="datetime", y_axis_type=None,
                    tools="", toolbar_location=None, background_fill_color="#efefef")

    range_tool = RangeTool(x_range=p.x_range)
    range_tool.overlay.fill_color = 'navy'
    range_tool.overlay.fill_alpha = 0.2

    for i in range(len(dates)):
        try:
            diff = dates[i+1] - dates[i] #Compute difference between dates in days
            if diff.days < 30:
                select.line([dates[i], dates[i+1]], [values[i], values[i+1]], color=line_colour) #Only plot a line if difference in days < 30
        except IndexError:
            pass
    select.ygrid.grid_line_color = None
    select.add_tools(range_tool)
    select.toolbar.active_multi = range_tool

    # Show the result
    show(column(p, select))

    return None


# Call Function
create_line_chart_bokeh()

Hovering on Circles

Circle

Hovering on the Line

Line

Solved!!

Based on the code below, I am now able to accomplish all the requirements. I took some pieces of the code and modified it further for my project.

# Import Library
import random
import pandas as pd
from bokeh.core.properties import value
from bokeh.io import show, export_png, output_notebook
from bokeh.plotting import figure, output_file
from bokeh.models import ColumnDataSource, Legend, HoverTool, layouts, CustomJS, Select, Circle, RangeTool
from bokeh.layouts import column
from datetime import date
output_notebook() # Render inline in a Jupyter Notebook


# Create Line Chart
def create_line_chart_bokeh():

    # Set data
    dates = ['24-11-2017', '28-11-2017', '29-11-2017',
            '23-03-2018', '27-03-2018', '12-08-2018']
    dates = list(map(lambda i: pd.Timestamp(i), dates))
    values = [random.randrange(101) for _ in range(len(dates))]
    source = ColumnDataSource(data=dict(date=dates, close=values))

    # Set plot
    p = figure(plot_height=300, plot_width=800, tools="xpan", toolbar_location="right",
               x_axis_type="datetime", x_axis_location="above",
               background_fill_color="white", x_range=(dates[0], dates[int(len(dates)-1)]), title='Test Line Chart')

    # Set Line and Circle colours
    line_colour = '#59819a'
    circle_colour = '#ef5b45'
    lineSource = {'date': [], 'close': []}
    date, close = [], []
    for i in range(len(dates)):
        try:
            diff = dates[i+1] - dates[i]
            if diff.days < 30:
                date.extend([dates[i], dates[i+1]])
                close.extend([values[i], values[i+1]])
            else:
                lineSource['date'].append(date)
                lineSource['close'].append(close)
                date, close = [], []
        except IndexError:
            pass
    lines = p.multi_line(xs='date', ys='close', source=ColumnDataSource(lineSource), color=line_colour)

    circles = p.circle('date', 'close', source=source, color=circle_colour, size=4)
    p.yaxis.axis_label = 'Scores' # set y axis label

    hoverCircles = HoverTool(renderers=[circles], tooltips=[("Average Score", "@close")])
    p.add_tools(hoverCircles)
    hoverLines = HoverTool(renderers=[lines], tooltips=[("Average Score", "$y")])
    p.add_tools(hoverLines)

    # Set Time Selection Box
    select = figure(title="Drag the middle and edges of the selection box to change the range above",
                    plot_height=130, plot_width=800, y_range=p.y_range,
                    x_axis_type="datetime", y_axis_type=None,
                    tools="", toolbar_location=None, background_fill_color="#efefef")

    range_tool = RangeTool(x_range=p.x_range)
    range_tool.overlay.fill_color = 'navy'
    range_tool.overlay.fill_alpha = 0.2

    select.line('date', 'close', source=source, color=line_colour)
    select.ygrid.grid_line_color = None
    select.add_tools(range_tool)
    select.toolbar.active_multi = range_tool

    # Show the result
    show(column(p, select))

    return None


# Call Function
create_line_chart_bokeh()
N. Arunoprayoch
  • 922
  • 12
  • 20
  • https://stackoverflow.com/questions/13976491/split-a-series-on-time-gaps-in-pandas and put them in seperate views(CDSView) or `fig.line(...)` – Joran Beasley Apr 17 '19 at 02:18

1 Answers1

0

Just plot the line with a for loop. Check every time if the distance between dates < 30 days and plot the line if true.

# Import Library
import random
import pandas as pd
from bokeh.core.properties import value
from bokeh.io import show, export_png, output_notebook
from bokeh.plotting import figure, output_file
from bokeh.models import ColumnDataSource, Legend, HoverTool, layouts, CustomJS, Select, Circle, RangeTool
from bokeh.layouts import column
from datetime import date
output_notebook() # Render inline in a Jupyter Notebook


# Create Line Chart
def create_line_chart_bokeh():

    # Set data
    dates = ['24-11-2017', '28-11-2017', '29-11-2017',
            '23-03-2018', '27-03-2018', '12-08-2018']
    dates = list(map(lambda i: pd.Timestamp(i), dates))
    values = [random.randrange(101) for _ in range(len(dates))]
    source = ColumnDataSource(data=dict(date=dates, close=values))

    # Set plot
    p = figure(plot_height=300, plot_width=800, tools="xpan", toolbar_location="right",
               x_axis_type="datetime", x_axis_location="above",
               background_fill_color="white", x_range=(dates[0], dates[int(len(dates)-1)]), title='Test Line Chart')

    # Set Line and Circle colours
    line_colour = '#59819a'
    circle_colour = '#ef5b45'
    lineSource = {'date': [], 'close': []}
    date, close = [], []
    for i in range(len(dates)):
        try:
            diff = dates[i+1] - dates[i]
            if diff.days < 30:
                date.extend([dates[i], dates[i+1]])
                close.extend([values[i], values[i+1]])
            else:
                lineSource['date'].append(date)
                lineSource['close'].append(close)
                date, close = [], []
        except IndexError:
            pass
    lines = p.multi_line(xs='date', ys='close', source=ColumnDataSource(lineSource), color=line_colour)

    circles = p.circle('date', 'close', source=source, color=circle_colour, size=4)
    p.yaxis.axis_label = 'Scores' # set y axis label

    hoverCircles = HoverTool(renderers=[circles], tooltips=[("Average Score", "@close")])
    p.add_tools(hoverCircles)
    hoverLines = HoverTool(renderers=[lines], tooltips=[("Average Score", "$y")])
    p.add_tools(hoverLines)

    # Set Time Selection Box
    select = figure(title="Drag the middle and edges of the selection box to change the range above",
                    plot_height=130, plot_width=800, y_range=p.y_range,
                    x_axis_type="datetime", y_axis_type=None,
                    tools="", toolbar_location=None, background_fill_color="#efefef")

    range_tool = RangeTool(x_range=p.x_range)
    range_tool.overlay.fill_color = 'navy'
    range_tool.overlay.fill_alpha = 0.2

    select.line('date', 'close', source=source, color=line_colour)
    select.ygrid.grid_line_color = None
    select.add_tools(range_tool)
    select.toolbar.active_multi = range_tool

    # Show the result
    show(column(p, select))

    return None


# Call Function
create_line_chart_bokeh()

enter image description here

Jasper
  • 1,795
  • 1
  • 12
  • 17
  • Thank you for your answer. Now the plot looks as expected. However, there's a slight issue on hover. When I try to hover on a line, it shows Average Score: ??? It works normally when I hover on a circle. Is there any solution for this? – N. Arunoprayoch Apr 17 '19 at 08:15
  • Edited my answer. I added a hovertool for each type of glyph so it won't display the ???. You do have to disable the second hovertool (lines) if you want to get the exact average score for the circles. – Jasper Apr 17 '19 at 08:52
  • Oh, I see that you also want to display the date in the line hover. There are some pitfalls since I had to use multi_line to get one renderer to assign the hovertool to. If you want to display the date it will show a list of dates that were specified in the columndatasource, so you have to use hovertooltips that are not associated with the columndatasource such as $x, $y, $name etc... https://bokeh.pydata.org/en/latest/docs/user_guide/tools.html#basic-tooltips – Jasper Apr 17 '19 at 09:02
  • I have decided to use only 'Circle hover', I think that should suffice. Thank you again. – N. Arunoprayoch Apr 17 '19 at 09:08