Setting ticks on bar chart

Trying to set xticks on a bar chart, but the ticks that show up seem to be sequential index numbers rather than the values of the appropriate variable.

For example, adapting from an example found in the docs:

import holoviews as hv
from hvplot.sample_data import us_crime
hv.extension('bokeh','matplotlib')
crime = us_crime.read()
data = crime[["Year", 'Violent crime total', 'Property crime total']].groupby("Year").sum().stack().reset_index().rename(columns={"level_1": "type", 0: "total"}).set_index("Year")
hv.Bars(data, ["Year", "type"], ["total"]).opts(width=800, tools=["hover"], stacked=True, xrotation=90, toolbar=None, xticks=10)

The above code produces:

Another attempt:

hv.Bars(data, ["Year", "type"], ["total"]).opts(width=800, tools=["hover"], stacked=True, xrotation=90, toolbar=None, xticks=[(0, 1960), (55, 2014)])

Output:

Is this the correct approach? Is this a bug?
I would appreciate any input, insight, or guidance.

EDIT:
Here is another thing I tried that resulted in a blank xaxis:

from bokeh.io import show
from bokeh.models import FixedTicker

plot = hv.Bars(data, ["Year", "type"], ["total"]).opts(width=800, tools=["hover"], stacked=True, xrotation=90, toolbar=None)
bokeh_plot = hv.render(plot)
ticks = data.index.unique()[::4].tolist()
ticker = FixedTicker(ticks=ticks)
bokeh_plot.xaxis.ticker = ticker
show(bokeh_plot)

I think you will need to use a custom extension but may be there is an other solution simpler

import holoviews as hv
from hvplot.sample_data import us_crime

from bokeh.models import CategoricalTicker
from bokeh.util.compiler import TypeScript

TS_CODE = """
import {FactorRange, Factor} from "models/ranges/factor_range"
import {CategoricalTicker, FactorTickSpec} from "models/tickers/categorical_ticker"

export class CustomTicker extends CategoricalTicker {
   get_ticks(start: number, end: number, range: FactorRange, _cross_loc: number): FactorTickSpec {
      
    const majors = this._collect2(range.factors, range, start, end)

    const tops = this._collect2(range.tops ?? [], range, start, end)
    const mids = this._collect2(range.mids ?? [], range, start, end)
    const decimate = Math.max(Math.ceil(majors.length/10),1)
    return {
      major: majors.filter((_element, index) => index % decimate == 0),
      minor: [],
      tops,
      mids,
    }
  }
  
  private _collect2(factors: Factor[], range: FactorRange, start: number, end: number): Factor[] {
    const result: Factor[] = []

    for (const factor of factors) {
      const coord = range.synthetic(factor)
      if (coord > start && coord < end)
        result.push(factor)
    }

    return result
  }
}

"""

class CustomTicker(CategoricalTicker):
    __implementation__ = TypeScript(TS_CODE)

hv.extension('bokeh')

1 Like

@xavArtley, Thanks so much, I really appreciate your assistance! This helped me not just solve the problem at hand, but also introduced me to hooks, which I found helpful for making other changes as well.

Based on your example, I made some changes to make it flexible to zooming and panning. Here is what I ended up with, hopefully it will help somebody some day. :slight_smile:

import holoviews as hv
from hvplot.sample_data import us_crime

from bokeh.models import CategoricalTicker
from bokeh.models.formatters import NumeralTickFormatter
from bokeh.util.compiler import TypeScript

TS_CODE = """
import {FactorRange, Factor} from "models/ranges/factor_range"
import {CategoricalTicker, FactorTickSpec} from "models/tickers/categorical_ticker"

export class CustomTicker extends CategoricalTicker {
  get_ticks(start: number, end: number, range: FactorRange, _cross_loc: number): FactorTickSpec {

    const desired_num_ticks = 8

    const majors = this._collect2(range.factors, range, start, end)

    const tops = this._collect2(range.tops ?? [], range, start, end)
    const mids = this._collect2(range.mids ?? [], range, start, end)

    const mod = Math.max(1, Math.round(majors.length / desired_num_ticks))
    const ticks = majors.filter((_element, index) => (index % mod == 0))

    // if last tick is close to end, just replace with end
    if (majors.length - majors.indexOf(ticks[ticks.length - 1]) - 1 <= mod / 2)
      ticks[ticks.length - 1] = majors[majors.length - 1]
    else // otherwise include end
      ticks.push(majors[majors.length - 1])

    return {
      major: ticks,
      minor: [],
      tops,
      mids,
    }
  }

  private _collect2(factors: Factor[], range: FactorRange, start: number, end: number): Factor[] {
    const result: Factor[] = []

    for (const factor of factors) {
      const coord = range.synthetic(factor)
      if (coord > start && coord < end)
        result.push(factor)
    }

    return result
  }
}

"""

class CustomTicker(CategoricalTicker):
    __implementation__ = TypeScript(TS_CODE)

hv.extension('bokeh')

crime = us_crime.read()
data = crime[["Year", 'Violent crime total', 'Property crime total']].groupby("Year").sum().stack().reset_index().rename(columns={"level_1": "type", 0: "total"}).set_index("Year")

def custom_ticker_hook(plot, element):
    plot.handles["xaxis"].ticker = CustomTicker()

def range_padding_hook(plot, element):
    plot.handles["x_range"].range_padding = 0.02

hv.Bars(data, ["Year", "type"], ["total"]).opts(width=800, tools=["hover"], stacked=True, xrotation=90, yformatter=NumeralTickFormatter(format="0,0"), hooks=[custom_ticker_hook, range_padding_hook])