Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to keep the same amount of x axis tick labels visible as the zoom level changes on a bokeh bar plot?

I'm using Django and generating a bokeh bar plot using data from a database. I've put a basic code example below. I'm generating x axis tick labels from a datetime variable to get a tick at every hour with a datetime formatted string. This causes all the tick label to be visible all the time. It's fine when zoomed in but not pretty when zoomed out because the labels are on top of each other.

Is there a way to make bokeh automatically adjust which tick labels are visible as the zoom level changes to keep a constant amount of ticks labels visible ?

views.py :

# -*- coding: utf-8 -*-
from django.shortcuts import render

from bokeh.plotting import *
from bokeh.resources import CDN
from bokeh.embed import components
from bokeh.charts import Bar
from bokeh.models import Range1d

from collections import OrderedDict
import datetime

def plot_1_bar(data, y, y_lab, title, xlab, ylab, yrange, palette):
    x0_list_str = []

    y_list = []

    # x_axis_type == "datetime":
    i_x = 'x1'
    i_y = y[0]
    for idx, val in enumerate(data):
        x0_list_str.append(val[i_x].strftime("%Y-%m-%d %H h"))
        y_list.append(val[i_y])

    data_x = x0_list_str    
    data_y = OrderedDict()
    data_y[y_lab[0]] = y_list

    plot = Bar(data_y, data_x, title=title, xlabel=xlab, 
               ylabel=ylab, stacked=True, continuous_range=yrange, 
               palette=palette, legend=True, height=400)
    return components(plot, CDN)

def plot1(request):

    script_bokeh = ""
    div_bokeh = ""

    data = []
    data.append({'x1': datetime.datetime(2015,1,1,8,0,0,0),'y1': 0})
    data.append({'x1': datetime.datetime(2015,1,1,9,0,0,0),'y1': 0})
    data.append({'x1': datetime.datetime(2015,1,1,10,0,0,0),'y1': 0})
    data.append({'x1': datetime.datetime(2015,1,1,11,0,0,0),'y1': 0})
    data.append({'x1': datetime.datetime(2015,1,1,12,0,0,0),'y1':8})
    data.append({'x1': datetime.datetime(2015,1,1,13,0,0,0),'y1':12})
    data.append({'x1': datetime.datetime(2015,1,1,14,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,1,15,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,1,16,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,1,17,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,1,18,0,0,0),'y1':2})
    data.append({'x1': datetime.datetime(2015,1,1,19,0,0,0),'y1':6})
    data.append({'x1': datetime.datetime(2015,1,1,20,0,0,0),'y1':4})
    data.append({'x1': datetime.datetime(2015,1,1,21,0,0,0),'y1':22})
    data.append({'x1': datetime.datetime(2015,1,1,22,0,0,0),'y1':36})
    data.append({'x1': datetime.datetime(2015,1,1,23,0,0,0),'y1':32})
    data.append({'x1': datetime.datetime(2015,1,2,0,0,0,0),'y1':21})
    data.append({'x1': datetime.datetime(2015,1,2,1,0,0,0),'y1':15})
    data.append({'x1': datetime.datetime(2015,1,2,2,0,0,0),'y1':4})
    data.append({'x1': datetime.datetime(2015,1,2,3,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,2,4,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,2,5,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,2,6,0,0,0),'y1':45})
    data.append({'x1': datetime.datetime(2015,1,2,7,0,0,0),'y1':47})
    data.append({'x1': datetime.datetime(2015,1,2,8,0,0,0),'y1':52})
    data.append({'x1': datetime.datetime(2015,1,2,9,0,0,0),'y1':44})
    data.append({'x1': datetime.datetime(2015,1,2,10,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,2,11,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,2,12,0,0,0),'y1':0})
    data.append({'x1': datetime.datetime(2015,1,2,13,0,0,0),'y1':0})

    script_bokeh, div_bokeh = plot_1_bar(data, 
                                         ['y1'],
                                         ['y legend label'],
                                         "",
                                         'x label',
                                         'y label',
                                         Range1d(0, 70),
                                         ['red'])

    return render(request, 
                  'tests/plot1.html', 
                  {'script_bokeh': script_bokeh, 
                   'div_bokeh': div_bokeh})

plot1.html :

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>bokeh bar plot</title>
        <link rel="stylesheet" type="text/css" href="/static/tests/css/bokeh-0.9.2.min.css" />
        <script src="/static/tests/js/bokeh-0.9.2.min.js" type="text/javascript"></script>
    </head>
    <body>
        <h1>Bokeh bar plot</h1>
        <div id="div_graph">{{div_bokeh|safe}}{{script_bokeh|safe}}</div>
    </body>
</html>

x axis zoomed in

x axis zoomed out

like image 592
Tril Avatar asked Nov 10 '22 06:11

Tril


1 Answers

First off, the bokeh.charts API, including Bar, was deprecated and removed in 2017 and should not be used. The stable and supported bokeh.plotting API can now be used to create many kinds of categorical and bar charts, and is more capable than bokeh.charts ever was. See the User's Guide chapter Handling Categorical Data for many live examples with complete code


There is no built in ticker that can be configured to do this, and there is not any CustomJSTicker (yet) either So, for now to do something like this you will have to resort to a custom extension. Additionally, you will need CustomJS callbacks on the range, that set configure the custom ticker based on the policy that you want. Here is a complete script that shows one possible way (this code is tested with Bokeh 0.12.16):

from bokeh.core.properties import Int
from bokeh.models import  CategoricalTicker, CustomJS
from bokeh.io import show
from bokeh.plotting import figure

class MyTicker(CategoricalTicker):
    __implementation__ = """
    import {CategoricalTicker} from "models/tickers/categorical_ticker"
    import * as p from "core/properties"

    export class MyTicker extends CategoricalTicker
      type: "MyTicker"

      @define {
        nth: [ p.Int, 1 ]
      }

      get_ticks: (start, end, range, cross_loc) ->
        ticks = super(start, end, range, cross_loc)
        ticks.major = ticks.major.filter((element, index) => index % this.nth == 0)
        return ticks

    """

    nth = Int(default=1)

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']

p = figure(x_range=fruits, plot_height=250, title="Fruit Counts",
           tools="xwheel_zoom")

p.vbar(x=fruits, top=[5, 3, 4, 2, 4, 6], width=0.9)

p.xgrid.grid_line_color = None
p.y_range.start = 0

p.xaxis.ticker = MyTicker(nth=1)

cb = CustomJS(args=dict(ticker=p.xaxis[0].ticker), code="""
    if (Math.abs(cb_obj.start-cb_obj.end) > 8) {
        ticker.nth = 2
    } else {
        ticker.nth = 1
    }
""")
p.x_range.js_on_change('start', cb)
p.x_range.js_on_change('end', cb)

show(p)

enter image description here

like image 132
bigreddot Avatar answered Dec 04 '22 14:12

bigreddot