# SOLVED: Datetime axis, missing values skipped, adaptive formatting

**URL:** <https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766>\
**Category:** Community Support\
**Created:** [February 1, 2018, 11:44am UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766 "2018-02-01T11:44:25Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![Kernc](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/59ef9b/32.png) [@Kernc](https://discourse.bokeh.org/u/Kernc)\
**Post date:** [February 1, 2018, 11:44am UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/1 "2018-02-01T11:44:25Z")

</div>

First post. Bokeh \<3.

Having datetime axes formatted without missing data linearly interpolated (i.e. with missing entries omitted from the chart completely) is [not](https://stackoverflow.com/questions/23585545/how-do-i-make-bokeh-omit-missing-dates-when-using-datetime-as-x-axis/) [an](https://stackoverflow.com/questions/45893822/when-plotting-a-graph-using-bokeh-how-to-remove-missing-date-while-x-axis-type) [uncommon](https://stackoverflow.com/questions/37373975/example-how-do-i-make-bokeh-omit-missing-dates-when-using-datetime-as-x-axis) [request](https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/D-tk12C0peE).

Finding a solution that:

- works for me,
- doesn’t use extension models end-users would need NodeJS for, and
- has the labels formatted in a resolution-adapted manner instead of a fixed string manner as with solutions involving p.xaxis.major\_label\_overrides,  
I’d like to share it with the community. The code works in evergreen browsers by using a FuncTickFormatter with JavaScript code to switch out label formatting function for one that first maps indexes to their corresponding dates, then uses DatetimeTickFormatter to format the labels  
concisely, regardless of their datetime resolution. Please find comments interspersed.

import pandas as pd

from [bokeh.io](http://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++] : '');
    };

    // Before the second redrawing when above doFormat will be 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)

``

The code exhibits some drawbacks:

- ticks are not positioned as informedly rounded as would be if DatetimeTicker were used,
- ticks outside of defined range are not labeled, and
- when a single major tick is visible, it is formatted as %Y, because there is no range span for DatetimeTickFormatter to compute.  
The approach would benefit somewhat if FuncTickFormatter could be configured to accept the whole array of ticks at once instead of a single tick at a time. In particular, this would let us avoid the use of non-public-API axis.formatter.doFormat() and axis.tick\_coords.major.

I’d like some input:

- Could the above code or the approach be improved?
- Might a PR extending FuncTickFormatter with something like returns\_array = Bool(False, help=…) be reviewed favorably?
- How do we proceed to such datetime axes stop being of workarounds and becoming API?  
Thanks!

[datetime-skip-missing.py](https://discourse-uploads.bokeh.org/original/1X/f0f788b745a15843e27c441dbe7cc81a3e7b9ab9.py) (3.26 KB)

---

<div class="post-metadata">

**Author:** ![kkketzal](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/e274bd/32.png) [@kkketzal](https://discourse.bokeh.org/u/kkketzal)\
**Post date:** [February 2, 2018, 8:51pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/2 "2018-02-02T20:51:52Z")

</div>

Wow! Thanks a lot, a lot, a lot…You are my headache medicine!!! My hero!!

I have a OHLC stock chart with 15 minutes candlesticks…

I had weekends gaps and it’s so ugly in a stock chart.

Your solution works fine for me!! ;-)…I only change the

[‘tomato’, ‘lime’]

``

to

[‘lime’, ‘tomato’]

``

because the colors were inverted (bearish candlestick was “lime” and bullish candlesticj was “tomato”)

Thanks a lot, again…

I think like you, this type of issues must be resolved in Bokeh API in a native way.

> **···**
>
> El jueves, 1 de febrero de 2018, 12:44:25 (UTC+1), Kernc escribió:
> 
> > First post. Bokeh \<3.
> > 
> > Having datetime axes formatted without missing data linearly interpolated (i.e. with missing entries omitted from the chart completely) is [not](https://stackoverflow.com/questions/23585545/how-do-i-make-bokeh-omit-missing-dates-when-using-datetime-as-x-axis/) [an](https://stackoverflow.com/questions/45893822/when-plotting-a-graph-using-bokeh-how-to-remove-missing-date-while-x-axis-type) [uncommon](https://stackoverflow.com/questions/37373975/example-how-do-i-make-bokeh-omit-missing-dates-when-using-datetime-as-x-axis) [request](https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/D-tk12C0peE).
> > 
> > Finding a solution that:
> > 
> > - works for me,
> > - doesn’t use extension models end-users would need NodeJS for, and
> > - has the labels formatted in a resolution-adapted manner instead of a fixed string manner as with solutions involving p.xaxis.major\_label\_overrides,  
> > I’d like to share it with the community. The code works in evergreen browsers by using a FuncTickFormatter with JavaScript code to switch out label formatting function for one that first maps indexes to their corresponding dates, then uses DatetimeTickFormatter to format the labels  
> > concisely, regardless of their datetime resolution. Please find comments interspersed.
> 
> > import pandas as pd
> > 
> > from [bokeh.io](http://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++] : '');
> > };
> > 
> > // Before the second redrawing when above doFormat will be 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)
> 
> > ``
> 
> > The code exhibits some drawbacks:
> > 
> > - ticks are not positioned as informedly rounded as would be if DatetimeTicker were used,
> > - ticks outside of defined range are not labeled, and
> > - when a single major tick is visible, it is formatted as %Y, because there is no range span for DatetimeTickFormatter to compute.  
> > The approach would benefit somewhat if FuncTickFormatter could be configured to accept the whole array of ticks at once instead of a single tick at a time. In particular, this would let us avoid the use of non-public-API axis.formatter.doFormat() and axis.tick\_coords.major.
> 
> > 
> 
> > I’d like some input:
> 
> > - Could the above code or the approach be improved?
> > - Might a PR extending FuncTickFormatter with something like returns\_array = Bool(False, help=…) be reviewed favorably?
> > - How do we proceed to such datetime axes stop being of workarounds and becoming API?  
> > Thanks!

---

<div class="post-metadata">

**Author:** ![Kernc](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/59ef9b/32.png) [@Kernc](https://discourse.bokeh.org/u/Kernc)\
**Post date:** [February 2, 2018, 11:35pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/3 "2018-02-02T23:35:59Z")

</div>

df[‘inc’] = ( **df.close \> df.open** ).astype(int).astype(str)

``

Aye, right. That should have been:

`  
Didn’t even notice. x)

Thanks.  
`

---

<div class="post-metadata">

**Author:** ![kkketzal](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/e274bd/32.png) [@kkketzal](https://discourse.bokeh.org/u/kkketzal)\
**Post date:** [February 5, 2018, 9:16pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/4 "2018-02-05T21:16:18Z")

</div>

mmmm…I want to plot some glyphs (small lines) only in a certain candlestick every day (maximun volume cluster for every day in only one candlestick), but with this approach, the plot is wrong because the traditional datetime xaxis is missing…can you give me any ideas??

Thanks!

> **···**
>
> El jueves, 1 de febrero de 2018, 12:44:25 (UTC+1), Kernc escribió:
> 
> > First post. Bokeh \<3.
> > 
> > Having datetime axes formatted without missing data linearly interpolated (i.e. with missing entries omitted from the chart completely) is [not](https://stackoverflow.com/questions/23585545/how-do-i-make-bokeh-omit-missing-dates-when-using-datetime-as-x-axis/) [an](https://stackoverflow.com/questions/45893822/when-plotting-a-graph-using-bokeh-how-to-remove-missing-date-while-x-axis-type) [uncommon](https://stackoverflow.com/questions/37373975/example-how-do-i-make-bokeh-omit-missing-dates-when-using-datetime-as-x-axis) [request](https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/D-tk12C0peE).
> > 
> > Finding a solution that:
> > 
> > - works for me,
> > - doesn’t use extension models end-users would need NodeJS for, and
> > - has the labels formatted in a resolution-adapted manner instead of a fixed string manner as with solutions involving p.xaxis.major\_label\_overrides,  
> > I’d like to share it with the community. The code works in evergreen browsers by using a FuncTickFormatter with JavaScript code to switch out label formatting function for one that first maps indexes to their corresponding dates, then uses DatetimeTickFormatter to format the labels  
> > concisely, regardless of their datetime resolution. Please find comments interspersed.
> 
> > import pandas as pd
> > 
> > from [bokeh.io](http://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++] : '');
> > };
> > 
> > // Before the second redrawing when above doFormat will be 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)
> 
> > ``
> 
> > The code exhibits some drawbacks:
> > 
> > - ticks are not positioned as informedly rounded as would be if DatetimeTicker were used,
> > - ticks outside of defined range are not labeled, and
> > - when a single major tick is visible, it is formatted as %Y, because there is no range span for DatetimeTickFormatter to compute.  
> > The approach would benefit somewhat if FuncTickFormatter could be configured to accept the whole array of ticks at once instead of a single tick at a time. In particular, this would let us avoid the use of non-public-API axis.formatter.doFormat() and axis.tick\_coords.major.
> 
> > 
> 
> > I’d like some input:
> 
> > - Could the above code or the approach be improved?
> > - Might a PR extending FuncTickFormatter with something like returns\_array = Bool(False, help=…) be reviewed favorably?
> > - How do we proceed to such datetime axes stop being of workarounds and becoming API?  
> > Thanks!

---

<div class="post-metadata">

**Author:** ![Kernc](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/59ef9b/32.png) [@Kernc](https://discourse.bokeh.org/u/Kernc)\
**Post date:** [February 5, 2018, 9:22pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/5 "2018-02-05T21:22:09Z")

</div>

Can you share a [minimal (non-)working example](https://en.wikipedia.org/wiki/Minimal_Working_Example)?​

---

<div class="post-metadata">

**Author:** ![kkketzal](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/e274bd/32.png) [@kkketzal](https://discourse.bokeh.org/u/kkketzal)\
**Post date:** [February 6, 2018, 8:45pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/6 "2018-02-06T20:45:27Z")

</div>

Sure…  
The CSV files are attached.

[![](https://discourse-uploads.bokeh.org/original/2X/3/37ee50b909258b4e10e7cb8f6bc0c04d10091833.png)](https://lh3.googleusercontent.com/-Q_-n7yM5Xds/WnoTnMiFOII/AAAAAAAAU_U/HpJU0PfoqT8ScGgyrVIu6xC696KT9HkyQCLcBGAs/s1600/plot2.png)

import pandas as pd

from [bokeh.io](http://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

# read a CSV file

def read\_csv(filename):

# dataframes have an datetime index: “DateTimeSecs”

date\_column = [“DateTimeSecs”]

index\_column = “DateTimeSecs”

df = pd.read\_csv(filename, parse\_dates = date\_column).set\_index(index\_column)

return df

# plot candlestick chart

def plot\_ohlcv(df):

df[‘inc’] = (df.open \< df.close).astype(int).astype(str)

# df[‘date’] = pd.to\_datetime(df[‘date’]) # Have real dates in ‘date’ column

df[‘date’] = df.index # only assing the index to new column: “date”

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’, .7, ‘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++] : ‘’);

};

// Before the second redrawing when above doFormat will be 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)];

“”")

return p

# Plot a glyph (small vbar) in EVERY CANDLE.

# It marks the price where the maximum volume is traded.

def plot\_candle\_vpoc(df, p):

df[‘date’] = df.index # Have real dates in ‘date’ column

df.reset\_index(drop=True, inplace=True) # And a simple range(0,n) index

# create a small vbar

size = 0.00001

df[“vpoc\_max\_top”] = df[“vpoc\_max”] + size

df[“vpoc\_max\_bottom”] = df[“vpoc\_max”] - size

source = ColumnDataSource(df)

p.vbar(‘index’, .7, ‘vpoc\_max\_bottom’, ‘vpoc\_max\_top’, source=source, line\_color=‘black’,fill\_color=“black”)

return p

# Plot a small glyph (small vbar, but MORE GREATER THAN glyphs in previous function)

# Similar to candle VPOC, but only marks ONE CANDLE EVERY DAY:

# the price where the maximum volume is traded in that day.

def plot\_cluster(df, p):

df[‘date’] = df.index # Have real dates in ‘date’ column

df.reset\_index(drop=True, inplace=True) # And a simple range(0,n) index

# create a small vbar

size = 0.00001

df[“vpoc\_max\_top”] = df[“vpoc\_max”] + size

df[“vpoc\_max\_bottom”] = df[“vpoc\_max”] - size

source = ColumnDataSource(df)

p.vbar(‘index’, 2, ‘vpoc\_max\_bottom’, ‘vpoc\_max\_top’, source=source, fill\_color=“red”, line\_color=“green”)

return p

# read the CSV’s

df\_ohlcv = read\_csv(“df\_ohlcv.csv”)

df\_candle\_vpoc = read\_csv(“df\_candle\_vpoc.csv”)

df\_max\_cluster\_volume = read\_csv(“df\_max\_cluster\_volume.csv”)

p = plot\_ohlcv(df\_ohlcv) # OK, no problem

p = plot\_candle\_vpoc(df\_candle\_vpoc, p) # OK, no problem, EVERY CANDLE has a VPOC

p = plot\_cluster(df\_max\_cluster\_volume, p) # PROBLEM: every day only ONE CANDLE has the maximum volume

show(p)

``

The OHLC plot is OK.

The Candle VPOC plot is OK (every candle has a small glyph, a black vbar).

The maximum cluster of volume plot is wrong. Only one candle a day needs to be marked. The glyph is plotted in the left side of the chart

[![](https://discourse-uploads.bokeh.org/original/2X/e/e070c30a635ba27c9f17a66aaaa4ddd57393aa51.png)](https://lh3.googleusercontent.com/-snUUCsHdJDg/WnoTjdzncHI/AAAAAAAAU_Q/j2b0G6Cz49YEtwWQNHQHiq-WNvzi4qLTwCLcBGAs/s1600/plot1.png)

[![](https://discourse-uploads.bokeh.org/original/2X/3/37ee50b909258b4e10e7cb8f6bc0c04d10091833.png)](https://lh3.googleusercontent.com/-r1B6COJQkkI/WnoTuqpN9wI/AAAAAAAAU_Y/FQcUEH9Jg4syVrTMyGwTEvbMiY6WJUhwQCLcBGAs/s1600/plot2.png)

Thanks in advance.

[df\_candle\_vpoc.csv](https://discourse-uploads.bokeh.org/original/1X/1b8738611ab225e94872865804764aadb97fc1fe.csv) (78.1 KB)

[df\_max\_cluster\_volume.csv](https://discourse-uploads.bokeh.org/original/1X/dbf116ef6375ba96ce3fe946658c6339342ea424.csv) (1.16 KB)

[df\_ohlcv.csv](https://discourse-uploads.bokeh.org/original/1X/c6800f5158b73c785c7fd5061422006ffc5472bc.csv) (141 KB)

> **···**
>
> El lunes, 5 de febrero de 2018, 22:22:51 (UTC+1), Kernc escribió:
> 
> > Can you share a [minimal (non-)working example](https://en.wikipedia.org/wiki/Minimal_Working_Example)?​

---

<div class="post-metadata">

**Author:** ![Kernc](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/59ef9b/32.png) [@Kernc](https://discourse.bokeh.org/u/Kernc)\
**Post date:** [February 7, 2018, 1:55am UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/7 "2018-02-07T01:55:45Z")

</div>

In plot\_cluster(), your cluster df gets its own separate range 0…N index. To plot correctly on the same x axis, all data frames need to share the _same_ index (range or otherwise). You should restructure your code to merge cluster data into ohlcv data before converting it into ColumnDataSource, like so:

ohlcv = pd.read\_csv(’/tmp/df\_ohlcv.csv’)  
cluster = pd.read\_csv(’/tmp/df\_max\_cluster\_volume.csv’)

merged = ohlcv.merge(cluster, on=‘DateTimeSecs’, how=‘outer’) # SQL-like “outer” join  
merged.head() # See image

``

Now reset the index and plot vbars for ‘vpoc\_max’ and it should work. 👍

[![](https://discourse-uploads.bokeh.org/original/2X/0/0681dbebd897d441a4e2b3603883d0200051a35f.png)](https://lh3.googleusercontent.com/--ggJx2pcO7U/WnpbSH4kBJI/AAAAAAAAB1M/_Iqf8ko1AWcCfDyOGRJ2MIWOXWp9_mb6QCLcBGAs/s1600/Screenshot_2018-02-07_02-44-10.png)

---

<div class="post-metadata">

**Author:** ![kkketzal](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/e274bd/32.png) [@kkketzal](https://discourse.bokeh.org/u/kkketzal)\
**Post date:** [February 7, 2018, 9:19pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/8 "2018-02-07T21:19:04Z")

</div>

Thanks. Your solution works fine.

Other issue is when you zooming the chart, the candle time changes depending on the zoom level you are visualizing… the more you zoom, the more accurate the candle time is.

[![](https://discourse-uploads.bokeh.org/original/2X/a/a384f7b4e01308a6fb2fbcbe6d1f45019f2b442d.png)](https://lh3.googleusercontent.com/-xZUC4fPLN20/WntsNvxSoqI/AAAAAAAAU_0/XQHxTcU7XnY-TtQ0sMEPdqo8Jxt0PpbxgCLcBGAs/s1600/plot3.png)

[![](https://discourse-uploads.bokeh.org/original/2X/c/c975e5fc33e90d0ca66ef01c4f79f08b2ee059e7.png)](https://lh3.googleusercontent.com/-aY75BYHo2Wc/WntsTDYRIEI/AAAAAAAAU_4/pKTjNqaCpksZcWVS50G-dpI-MHdRvMfRQCLcBGAs/s1600/plot4.png)

Any ideas??

Thanks again.

> **···**
>
> El miércoles, 7 de febrero de 2018, 2:55:46 (UTC+1), Kernc escribió:
> 
> > In plot\_cluster(), your cluster df gets its own separate range 0…N index. To plot correctly on the same x axis, all data frames need to share the _same_ index (range or otherwise). You should restructure your code to merge cluster data into ohlcv data before converting it into ColumnDataSource, like so:
> > 
> > ohlcv = pd.read\_csv(‘/tmp/df\_ohlcv.csv’)  
> > cluster = pd.read\_csv(‘/tmp/df\_max\_cluster\_volume.csv’)
> > 
> > merged = ohlcv.merge(cluster, on=‘DateTimeSecs’, how=‘outer’) # SQL-like “outer” join  
> > merged.head() # See image
> 
> > ``
> 
> > Now reset the index and plot vbars for ‘vpoc\_max’ and it should work. 👍
> > 
> > [![](https://discourse-uploads.bokeh.org/original/2X/0/0681dbebd897d441a4e2b3603883d0200051a35f.png)](https://lh3.googleusercontent.com/--ggJx2pcO7U/WnpbSH4kBJI/AAAAAAAAB1M/_Iqf8ko1AWcCfDyOGRJ2MIWOXWp9_mb6QCLcBGAs/s1600/Screenshot_2018-02-07_02-44-10.png)

---

<div class="post-metadata">

**Author:** ![Kernc](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/59ef9b/32.png) [@Kernc](https://discourse.bokeh.org/u/Kernc)\
**Post date:** [February 7, 2018, 10:17pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/9 "2018-02-07T22:17:47Z")

</div>

> the more you zoom in, the more accurate the label is.

I don’t understand the question. Is there a problem? 😉

You can adapt the format of labels at different zoom levels by passing custom format specifiers to [DatetimeTickFormatter](https://bokeh.pydata.org/en/latest/docs/reference/models/formatters.html#bokeh.models.formatters.DatetimeTickFormatter) formatter passed into FuncTickFormatter’s args=.

---

<div class="post-metadata">

**Author:** ![kkketzal](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/e274bd/32.png) [@kkketzal](https://discourse.bokeh.org/u/kkketzal)\
**Post date:** [February 8, 2018, 9:29am UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/10 "2018-02-08T09:29:28Z")

</div>

Sorry, I have not explained well.  
What I want to tell you is that in the first picture, the candlestick at 17:00h is different than the candlestick in the second picture at 17:00h, at the same time in xaxis.

Both pictures shows the same chart at different zoom level. The second picture has more zoom and it is more accurated…

I mean that if I want to get the candlestick “exact time”, I need to zoom the chart too much.

Do you think I can solve the problem with DatetimeTickFormatter??

Thanks so much.

> **···**
>
> El miércoles, 7 de febrero de 2018, 23:18:51 (UTC+1), Kernc escribió:
> 
> > > the more you zoom in, the more accurate the label is.
> 
> > I don’t understand the question. Is there a problem? 😉
> 
> > You can adapt the format of labels at different zoom levels by passing custom format specifiers to [DatetimeTickFormatter](https://bokeh.pydata.org/en/latest/docs/reference/models/formatters.html#bokeh.models.formatters.DatetimeTickFormatter) formatter passed into FuncTickFormatter’s args=.

---

<div class="post-metadata">

**Author:** ![Kernc](https://discourse.bokeh.org/letter_avatar_proxy/v4/letter/k/59ef9b/32.png) [@Kernc](https://discourse.bokeh.org/u/Kernc)\
**Post date:** [February 8, 2018, 5:04pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/11 "2018-02-08T17:04:23Z")

</div>

Ah, I see. In the first figure, the time shows 17h whereas it is in fact 17h-_ish_ (17h15), as observed in the second figure.  
You could remedy it by passing a formatter with a different hourly format: formatter=DatetimeTickFormatter(hours=[**‘%Hh’** , ‘%H:%M’], …) E.g. if you changed it to “%H:%M”, the label (in your first figure) would say correctly “17:15”.

The fact that ticks are not positioned at round o’clock interval has got to do with the first [mentioned drawback](https://groups.google.com/a/continuum.io/d/msg/bokeh/t3HkalO4TGA/bv-ZDF1aBAAJ):

> ticks are not positioned as informedly rounded as would be if DatetimeTicker were used.

To overcome that, you’d have to override p.xaxis.ticker to something other than the linear [BasicTicker](https://bokeh.pydata.org/en/latest/docs/reference/models/tickers.html#bokeh.models.tickers.BasicTicker) [LinearAxis sets by default](https://bokeh.pydata.org/en/latest/_modules/bokeh/models/axes.html#LinearAxis). You’re on your own there, though, but if you figure something out, please do tell. 🙂

---

<div class="post-metadata">

**Author:** ![system](https://discourse-uploads.bokeh.org/original/2X/1/14eb24f9e10e4e9b78e3f10f66c04ffa96446e5a.svg) [@system](https://discourse.bokeh.org/u/system)\
**Post date:** [July 21, 2021, 9:14pm UTC](https://discourse.bokeh.org/t/solved-datetime-axis-missing-values-skipped-adaptive-formatting/2766/12 "2021-07-21T21:14:02Z")

</div>


