Repository: bokeh/bokeh

Issue:
desired_num_ticks not working when using custom x_range
I'm not privy to `desired_num_ticks`' implementation (which seems fairly recent), but apparently it is ignored whenever custom tick labels (x/y_range) are used. Take for instance the example [posted on the discussion topic](https://github.com/bokeh/bokeh/pull/2186), which works fine:

![working](http://s29.postimg.org/om9ntg9vr/ticks_example_working.png)

However, this is what it looks like when passing a custom x_range (gist [here](https://gist.github.com/lbrito1/690809f54f217e6f6b9f)):

![not working](http://s29.postimg.org/f0g3d5ipz/ticks_example.png)

The reason this is important to me is that I'm working on a financial data, and I'm using custom x tick labels in order to avoid weekend/holiday data gaps (as far as I know, those gaps are unavoidable using `x_axis_type="datetime"`). 

The standard (at least in matplotlib) way of doing this is indexing data by integers, then setting x ticks to the appropriate datetime strings. It's also necessary to limit the number of x ticks in some way so that they are visible when plotting larger amounts of data.

I'll be happy to improve the [current example](http://bokeh.pydata.org/en/latest/docs/gallery/candlestick.html) in case we manage to work out this situation. In fact, it would be nice to include a library function that would automate the gap-removal process (and also a specific candlestick function which receives a df and interprets it as ohlc, but that's another topic of discussion).



Discussion:
Giving string values for the range actually creates a categorical axis, which is probably not what you want. What you want is more like custom labeling for standard (numerical) tickers. This might be currently accomplishable with a custom `TickFormatter`. In case you'd like to try creating one as a custom model, here are some relevant links:

http://bokeh.pydata.org/en/latest/docs/user_guide/styling.html#tick-label-formats
https://github.com/bokeh/bokeh/blob/master/examples/glyphs/customjs.py
https://github.com/bokeh/bokeh/blob/master/bokeh/models/formatters.py
https://github.com/bokeh/bokeh/tree/master/bokehjs/src/coffee/ticking

BTW support for "broken" axes like this for datetime data is definitely a feature we'd like to have built in at some point.  If you do have the ability to make a simple tick formatter with a user-defined custom model, it might make a nice contribution eventually. 

Thanks @bryevdv . I cloned & built the repo and for the last couple of hours I've been playing around with the formatters, but I'm having some trouble "registering" a new one. 

I've defined a new TickFormatter in `formatters.py` and the corresponding coffeescript file in `coffee/ticking`. I just copy&pasted the corresponding code from PrintfTickFormatter, changing names where appropriate, but I'm getting the "module does not exist" JS console error:

> Module `DateGapTickFormatter' does not exists. The problem may be two fold. Either a model was requested that's available in an extra bundle, e.g. a widget, or a custom model was requested, but it wasn't registered before first usage.

So I'm guessing I need to "register" the new formatter somewhere. Any thoughts?

HI @lbrito1 I actually had not imagined building a new model into BokehJS, so there would be nothing to add under `bokehjs/src/coffee` and no need to compile a new BokehJS at all. I had in mind following this example:

https://github.com/bokeh/bokeh/blob/master/examples/glyphs/custom.py

Which lets you add new custom models dynamically to Bokeh, without needing to rebuild anything. (I just realized I linked the wrong example above, this is the one to study).

We are a holiday in the US but I can say more next week if you have more questions. 

Hi @bryevdv , in your example `callback=Popup(message="Selected color: @color")` is passed as an argument for `TapTool()`; however,  `p.xaxis[0].formatter =` expects a `TickFormatter`. Even if I implement a custom TickFormatter model, say `class DateGapTickFormatter(Callback)`, how would I proceed on actually using it as the TickFormatter for an axis?

Thanks

@lbrito1 That example is meant to merely be generally instructive. If you were to implement a custom `DateGapTickFormatter` then it would need to be a subclass of `TickFormatter`, and not be a subclass of `Callback`. i.e., `class DateGapTickFormatter(TickFormatter)` then you could assign instances of this new class to `p.xaxis[0].formatter` just like any of the existing "built in" tick formatters. 

@bryevdv I see. I modified the example and implemented `DateGapTickFormatter(TickFormatter)` and set `__implementation__` to the default PrintfTickFormatter's coffeescript. [Here's](https://gist.github.com/lbrito1/13faf9bf531fda8a975c) the snippet. I'm hoping this is the right direction.

My question now is: how should the `__implementation__` string handle requires? e.g. the PrintfTickFormatter coffescript has the following require: `HasProperties = require "../common/has_properties"`, which of course fails with an `Uncaught Error: Cannot find module '../common/has_properties'`.

ping @mattpap can you offer some guidance?

@lbrito1, in custom models, `require()` invocations should be written as if they were happening at the root of bokehjs, so use `common/has_properties`. See also https://github.com/bokeh/bokeh/blob/master/examples/glyphs/custom.py#L17. 

@mattpap Great, thanks! That worked. I believe now I'll be able to finish the custom model. I'll post it here when it's done.

@bryevdv I noticed that custom models defined through `__impementation__` don't work with the current production release (0.10), is that right? JS throws an "ncaught TypeError: Cannot read property 'Collection' of undefined" error. Code works fine with the dev build though.

@lbrito1, yes, this new stuff that will be released in 0.11.

So this is what I came up with:

![](http://i.imgur.com/4UH3MSL.png)

The custom TickFormatter only renders every `n` ticks, printing empty strings in between. 
As far as this approach goes, it would be nice to hide the "minor ticks", i.e., the ticks skipped by the custom TickFormatter. But I'm not sure if this is possible.

In case anyone's interested, [here's the commit](https://github.com/lbrito1/bokeh/commit/d0ba357947a0f9f23af6fcfbde5d943cfeeed4f7) with a short explanation.

@lbrito1 this is awesome, if you set the `minor_tick_line_color` to `None` on the axis that should suppress the minor ticks. 

Hi @bryevdv , thanks. I took a step further: following your original suggestion, I changed `DateGapTickFormatter` so that it receives the list of date Strings, which means we can keep using the default numerical ticker instead of a `CategoricalTicker`. As a result, `DateGapTickFormatter` doesn't have to mess with ticker spacing or any of that, and can rely on Bokeh's magic to the hard work. `desired_num_ticks` works as expected and is included in the example. 

`DateGapTickFormatter` is in effect quite close to what one would expect from the default `DatetimeTickFormatter` in this example, except of course we are dealing with immutable strings, so zooming out won't change the labels from "Sep 01" to "September".

[Here's the commit.](https://github.com/lbrito1/bokeh/commit/1e0e403a694ea333a1d7532c7eac68bbd1849bab) I'll be happy to brush up the code and follow the PR protocol if people think this is a good addition to Bokeh.

![](http://i.imgur.com/RIm7QDT.png)
![](http://i.imgur.com/huuGmqp.png)

Hey @bryevdv and whoever else this might concern, I'm sorry to "bump" this, but just wanted to know you had a chance to take a look at this. Thanks.

Hi @lbrito1 I apologize, `0.11` is an enormous release for us and we are all heads down on various aspects getting ready for the early January deadline.  I think this is actually an incredibly important and motivating example. We'd like to move Bokeh more in the direction of being an open platform that anyone can easily extend in the ways that they need. There is more work to do to improve the support for custom models, but the fact that you created this one so easily is definitely vindication of the idea (for me at least). 

If you are up for iterating a bit on a PR, I would definitely like to include this in the `0.11` release as an example, perhaps `plotting/file/custom_datetime_axis.py` or similar. 


Resolving patch:
diff --git a/examples/plotting/file/custom_datetime_axis.py b/examples/plotting/file/custom_datetime_axis.py
new file mode 100644
--- /dev/null
+++ b/examples/plotting/file/custom_datetime_axis.py
@@ -0,0 +1,61 @@
+from math import pi
+
+import pandas as pd
+
+from bokeh.sampledata.stocks import MSFT
+from bokeh.plotting import figure, show, output_file
+from bokeh.models.formatters import TickFormatter, String, List
+
+# In this custom TickFormatter, xaxis labels are taken from an array of date
+# Strings (e.g. ['Sep 01', 'Sep 02', ...]) passed to the date_labels property. 
+class DateGapTickFormatter(TickFormatter):
+    date_labels = List(String)
+
+    __implementation__ = """
+_ = require "underscore"
+HasProperties = require "common/has_properties"
+
+class DateGapTickFormatter extends HasProperties
+  type: 'DateGapTickFormatter'
+
+  format: (ticks) ->
+    date_labels = @get("date_labels")
+    return (date_labels[tick] ? "" for tick in ticks)
+
+module.exports =
+  Model: DateGapTickFormatter
+"""
+
+df = pd.DataFrame(MSFT)[:50]
+
+# xaxis date labels used in the custom TickFormatter
+date_labels = [date.strftime('%b %d') for date in pd.to_datetime(df["date"])]
+
+mids = (df.open + df.close)/2
+spans = abs(df.close-df.open)
+
+inc = df.close > df.open
+dec = df.open > df.close
+w = 0.5
+
+output_file("custom_datetime_axis.html", title="custom_datetime_axis.py example")
+
+TOOLS = "pan,wheel_zoom,box_zoom,reset,save"
+
+p = figure(tools=TOOLS, plot_width=1000, toolbar_location="left")
+
+# Using the custom TickFormatter. You must always define date_labels
+p.xaxis[0].formatter = DateGapTickFormatter(date_labels = date_labels)
+
+# x coordinates must be integers. If for example df.index are 
+# datetimes, you should replace them with a integer sequence
+p.segment(df.index, df.high, df.index, df.low, color="black")
+p.rect(df.index[inc], mids[inc], w, spans[inc], fill_color="#D5E1DD", line_color="black")
+p.rect(df.index[dec], mids[dec], w, spans[dec], fill_color="#F2583E", line_color="black")
+
+p.title = "MSFT Candlestick with custom x axis"
+p.xaxis.major_label_orientation = pi/4
+
+p.grid[0].ticker.desired_num_ticks = 6
+
+show(p)  # open a browser


Test patch: