2

The x-axis on my bokeh plot represents a time duration like five seconds rather than a time like 2016-01-01 12:00:00. Is there a way to render the ticks on my Bokeh x-axis appropriately? Setting x_axis_type='datetime' doesn't do quite the right thing, as can be seen from the repetition of 0ms in the plot below:

enter image description here

MRocklin
  • 55,641
  • 23
  • 163
  • 235
  • 1
    "You should probably make a custom tick formatter" (from @bigreddot via gitter). Perhaps someone has time to submit a example demonstration as an answer. – Steven C. Howell Dec 13 '16 at 14:27
  • There's an example in this answer: http://stackoverflow.com/a/37182788/1736679 – Efren Mar 16 '17 at 05:08

1 Answers1

1

On Bokeh 0.12.6, you can use PrintfTickFormatter.

from bokeh.plotting import figure, output_file, show
from bokeh.models import PrintfTickFormatter

output_file('output.html')

p = figure(plot_width=400, plot_height=400) p.line(x, y, size=1)

# must be applied to the 1st element, not the axis itself 
p.xaxis[0].formatter = PrintfTickFormatter(format="%sms")

show(p)

You don't even have to set x_axis_type='datetime', it will work even with a linear axis.

EDIT: To apply custom formatting of units, such as ms/s/min, you have to use FuncTickFormatter, because it is too sophisticated for Bokeh to handle at the moment. There are two ways to use it as of 0.12.6.

First, by using the transpiler to convert a Python function to Javascript code, via Flexx (pip install flexx). It keeps everything under the Python syntax, but requires an additional dependence.

from bokeh.plotting import figure, output_file, show
from bokeh.models import FuncTickFormatter

output_file('output.html')

p = figure(plot_width=400, plot_height=400) p.line(x, y, size=1)

# custom formatter function
def custom_formatter():
    units = [
        ('min', 60000.0),
        ('s', 1000.0),
        ('ms', 1.0),
    ]
    for u in units:
        if tick >= u[1]:
            return '{}{}'.format(tick / u[1], u[0])


# must be applied to the 1st element, not the axis itself 
p.xaxis[0].formatter = FuncTickFormatter.from_py_func(custom_formatter)

show(p)

Lastly, by writing actual Javascript code as a string and passing as a parameter to the formatter. Bokeh does it natively. Bear in mind you have no control over the client environment, so avoid using anything other than pure vanilla Javascript.

from bokeh.plotting import figure, output_file, show
from bokeh.models import FuncTickFormatter

output_file('output.html')

p = figure(plot_width=400, plot_height=400) p.line(x, y, size=1)

units = [
        ('min', 60000.0),
        ('s', 1000.0),
        ('ms', 1.0),
    ]

# must be applied to the 1st element, not the axis itself 
p.xaxis[0].formatter = FuncTickFormatter(code=""" var units = {'min':
60000.0, 's': 1000.0, 'ms': 1.0}; for (u in units) {
    if (tick >= units[u]) {
        return (tick / units[u] + u);
    } } """)

show(p)

I find it a bit annoying, but that's how I fixed the axis for my application. I find the need to hardcode a variable named tick a terrible programming practice. Hopefully, Bokeh will provide a better solution in the near future.

Ramon Melo
  • 270
  • 2
  • 9
  • 21
  • This is great. Any thoughts on how to handle datetime units, like when things go from millseconds to seconds or minutes? – MRocklin Jun 17 '17 at 23:09
  • @MRocklin Yes, I have had a similar issue for my application as well (I deal with days/months/years, however). I have edited the question to add the solution I ended up using, although it was a lot more _hacky_ than I had desired. – Ramon Melo Jun 18 '17 at 13:44