import pandas as pd

from bokeh.io import show
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import ColumnDataSource, \
                         FuncTickFormatter, \
                         DatetimeTickFormatter

from bokeh.sampledata.stocks import GOOG


df = pd.DataFrame(GOOG)
df['inc'] = (df.open > df.close).astype(int).astype(str)
df['date'] = pd.to_datetime(df['date'])  # Have real dates in 'date' column
df.reset_index(drop=True, inplace=True)  # And a simple range(0,n) index

source = ColumnDataSource(df)

# Axis type must be linear and df.index a simple range index
p = figure(x_axis_type='linear',
           plot_width=1000,
           tools='pan,wheel_zoom',
           active_scroll='wheel_zoom',
           active_drag='pan')

# Plot high-low segment and candles, colored appropriately
p.segment('index', 'high', 'index', 'low', source=source, color="black")
p.vbar('index', .9, 'open', 'close', source=source, line_color='black',
       fill_color=factor_cmap('inc', ['tomato', 'lime'], ['0', '1']))

# Override x axis formatter with a custom JS function formatter
# Could avoid using FuncTickFormatter if GH-4272 were available
p.xaxis.formatter = FuncTickFormatter(
    args=dict(
        # We pass in the x axis itself, so we can access its
        # ticks values
        axis=p.xaxis[0],

        # An instance of DatetimeTickFormatter to nicely format
        # arbitrary precision datetimes
        formatter=DatetimeTickFormatter(days=['%d %b', '%a %d'],
                                        months=['%m/%Y', "%b %y"]),

        # Our column data source with 'date' column we will
        # map indexes through
        source=source,
    ),
    code='''
        // We override this axis' formatter's `doFormat` method
        // with one that maps index ticks to dates. Some of those dates
        // are undefined (e.g. those whose ticks fall out of defined data
        // range) and we must filter out and account for those, otherwise
        // the formatter computes invalid visible span and returns some
        // labels as "ERR".
        // Note, after this assignment statement, on next plot redrawing,
        // our override `doFormat` will be called directly
        // -- FunctionTickFormatter.doFormat(), i.e. _this_ code, no longer
        // executes.   
        
        axis.formatter.doFormat = function (ticks) {
            const dates = ticks.map(i => source.data.date[i]),
                  valid = t => t !== undefined,
                  labels = formatter.doFormat(dates.filter(valid));
            let i = 0;
            return dates.map(t => valid(t) ? labels[i++] : "");
        };

        // But before that second redrawing, when above doFormat is called,
        // we are still within this current labels formatting.
        // FuncTickFormatter gets passed a single `tick` at a time, but
        // DatetimeTickFormatter requires all ticks at once to work.
        // We handle that by formatting all axis' ticks with the function
        // we constructed above and then just taking out the current tick.
        // Note: .tick_coords probably not public API
        
        const ticks = axis.tick_coords.major[0],
              labels = axis.formatter.doFormat(ticks);
        return labels[ticks.indexOf(tick)];
''')

show(p)
