Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow users to specify explicit tick labels #1671

Closed
bryevdv opened this issue Jan 11, 2015 · 20 comments · Fixed by #6225
Closed

allow users to specify explicit tick labels #1671

bryevdv opened this issue Jan 11, 2015 · 20 comments · Fixed by #6225

Comments

@bryevdv
Copy link
Member

bryevdv commented Jan 11, 2015

FixedTicker allows users to specify fixed tick locations, but also need to allow users to specify explicit tick labels

@bryevdv bryevdv added this to the short-term milestone Jan 11, 2015
@bryevdv bryevdv mentioned this issue Jan 11, 2015
8 tasks
@bryevdv bryevdv changed the title allow users to specify explicit tick values allow users to specify explicit tick labels Apr 6, 2016
@bryevdv
Copy link
Member Author

bryevdv commented Apr 6, 2016

One idea would be to add mapping (labels? custom?) to any TickFormatter that maps numeric tick values to specific custom labels for that tick location. Before doing its normal formatting and rendering for a tick value, the TickFormatter would first try to look that value up in the mapping. If it exists, that label is used as-is, instead of the normal formatting.

So use might look something like:

plot.xaxis.formatter.custom[50] = "median"

Random observations:

  • this means the customization is attached to a formatter, so if the formatter is changed the customization is lost (potentially confusing in those situations)
  • suggests might want ability to add custom fixed ticks to any ticker (or to be able to combine a fixed ticker with a regular ticker)

@bokeh/dev thoughts?

@pzwang
Copy link

pzwang commented May 23, 2016

I can see multiple angles on this. There is an argument to be made that it should be on the Ticker, as well as the Formatter. However, the most important thing IMO is that the split between ticker vs. formatter is an architectural one in our object model, and, under normal circumstances, it's not a distinction that we should burden the user with understanding.

We need to consider the use cases when an end user is most likely to want to specify these things:

  1. They have a few, specific locations that they want labelled.
  2. They have a custom format that extends an existing built-in formatter
  3. They want to replace a built-in ticker+formatter altogether and only show labels at certain locations, regardless of zoom level

What would the ideal spelling look like for each of these? Are there other common use cases I'm missing here?

@Rubyj
Copy link

Rubyj commented Oct 17, 2016

@bryevdv Is there a way to do this yet?

@bryevdv
Copy link
Member Author

bryevdv commented Oct 17, 2016

Currently you could do this with a custom TickFormatter. Given that there is some path to doing this, even if it is not ideal, adding support into the core library will probably not rise high in the ~750 open issue list. A new interested contributor submitting a PR is probably the quickest route.

@Rubyj
Copy link

Rubyj commented Oct 17, 2016

@bryevdv Thanks. That is currently the way I'm doing it in my code as well.

@canavandl
Copy link
Contributor

Here's an example of doing exactly that (using a FixedTicker and FuncTickFormatter together to set explicit tick labels at specific locations). I think it's the best method for doing this:

from bokeh.plotting import figure, show
from bokeh.models import ColorBar, LinearColorMapper, Plot, Range1d, LinearAxis, FixedTicker, FuncTickFormatter

color_mapper = LinearColorMapper(palette="Spectral6", low=0, high=60)
ticker = FixedTicker(ticks=[5,15,25,35,45,55])
formatter = FuncTickFormatter(code="""
    data = {5: '0-10', 15: '10-20', 25: '20-30', 35: '30-40', 45: '40-50', 55: '50+'}
    return data[tick]
""")

cbar = ColorBar(color_mapper=color_mapper, ticker=ticker, formatter=formatter,
                major_tick_out=0, major_tick_in=0, major_label_text_align='left',
                major_label_text_font_size='10pt', label_standoff=2)

p = Plot(x_range=Range1d(0,1), y_range=Range1d(0,1), width=500, height=500, toolbar_location=None)
p.add_layout(LinearAxis(), 'left')
p.add_layout(LinearAxis(), 'below')

p.add_layout(cbar)

show(p)

@Rubyj
Copy link

Rubyj commented Oct 17, 2016

@canavandl Thank you for the idea. Currently I am using

class FixedTickFormatter(TickFormatter):
    """
    Class used to allow custom axis tick labels on a bokeh chart
    Extends bokeh.model.formatters.TickFormatte
    """

    JS_CODE =  """
        _ = require "underscore"
        Model = require "model"
        p = require "core/properties"
        class FixedTickFormatter extends Model
          type: 'FixedTickFormatter'
          doFormat: (ticks) ->
            labels = @get("labels")
            return (labels[tick] ? "" for tick in ticks)
          @define {
            labels: [ p.Any ]
          }
        module.exports =
          Model: FixedTickFormatter
    """

    labels = Dict(Int, String, help="""
    A mapping of integer ticks values to their labels.
    """)

    __implementation__ = JS_CODE

@birdsarah
Copy link
Member

birdsarah commented Oct 17, 2016

@Rubyj I was curious about this, so I put together a complete example.

Please note that you're custom FixedTickFormatter needed to be changed quite a lot for it to work on master (and hence the next release)

from bokeh.plotting import show
from bokeh.properties import Dict, Int, String
from bokeh.models import (
    ColorBar,
    LinearColorMapper,
    Plot,
    Range1d,
    LinearAxis,
    FixedTicker,
    TickFormatter,
)
from bokeh.util.compiler import CoffeeScript

class FixedTickFormatter(TickFormatter):
    """
    Class used to allow custom axis tick labels on a bokeh chart
    Extends bokeh.model.formatters.TickFormatter
    """

    COFFEESCRIPT =  """
        import {Model} from "model"
        import * as p from "core/properties"
        export class FixedTickFormatter extends Model
          type: 'FixedTickFormatter'
          doFormat: (ticks) ->
            labels = @get("labels")
            return (labels[tick] ? "" for tick in ticks)
          @define {
            labels: [ p.Any ]
          }
    """

    labels = Dict(Int, String, help="""
    A mapping of integer ticks values to their labels.
    """)

    __implementation__ = CoffeeScript(COFFEESCRIPT)


color_mapper = LinearColorMapper(palette="Spectral6", low=0, high=60)
ticker = FixedTicker(ticks=[5,15,25,35,45,55])
formatter = FixedTickFormatter(labels={5: '0-10', 15: '10-20', 25: '20-30', 35: '30-40', 45: '40-50', 55: '50+'})

cbar = ColorBar(color_mapper=color_mapper, ticker=ticker, formatter=formatter,
                major_tick_out=0, major_tick_in=0, major_label_text_align='left',
                major_label_text_font_size='10pt', label_standoff=2)

p = Plot(x_range=Range1d(0,1), y_range=Range1d(0,1), width=500, height=500, toolbar_location=None)
p.add_layout(LinearAxis(), 'left')
p.add_layout(LinearAxis(), 'below')
p.add_layout(cbar)
show(p)

@bryevdv - we're going to need some major migration notes about the new export stuff, then new import stuff, and the compilation stuff for folks

@Rubyj
Copy link

Rubyj commented Oct 18, 2016

@birdsarah awesome! Thank you for this :)

@mattpap
Copy link
Contributor

mattpap commented Oct 19, 2016

doFormat: (ticks) ->
    labels = @get("labels")
    return (labels[tick] ? "" for tick in ticks)

Actually labels = @labels, otherwise deprecation warning will appear.

@ghost
Copy link

ghost commented Feb 22, 2017

Hi, I arrived here via Stack Overflow.
The example above breaks somewhere between v 0.12.2 and 0.12.4. I get the following message:

Model `FixedTickFormatter' does not exist. This could be due to a widget or a custom model not being registered before first usage.

The clue seems to be in the comment above :)

we're going to need some major migration notes about the new export stuff, then new import stuff, and the compilation stuff for folks

Has anyone got any suggestion what the best way to go about this is now? I guess there's some different way to register the JS code?

@Rubyj
Copy link

Rubyj commented Feb 22, 2017

@hodgson-neil

I am using the below code which works fine in my application running the latest version of bokeh

    """
    Class used to allow custom axis tick labels on a bokeh chart
    Extends bokeh.model.formatters.TickFormatter
    """

    COFFEESCRIPT =  """
        import {Model} from "model"
        import * as p from "core/properties"
        export class FixedTickFormatter extends Model
          type: 'FixedTickFormatter'
          doFormat: (ticks) ->
            labels = @labels
            return (labels[tick] ? "" for tick in ticks)
          @define {
            labels: [ p.Any ]
          }
    """

    labels = Dict(Int, String, help="""
    A mapping of integer ticks values to their labels.
    """)

    __implementation__ = CoffeeScript(COFFEESCRIPT)```

@ghost
Copy link

ghost commented Feb 22, 2017

@Rubyj Thanks, my mistake. It is indeed working fine on my use case! Thanks.

@tommycarstensen
Copy link

@canavandl How can you do the same for the x-axis in a scatter plot? Thanks.

@tommycarstensen
Copy link

@pzwang I guess my use case is either the first or the third.

I'm generating a Manhattan plot, which plots millions of points with the probability of association between phenotypes and genotypes at millions of base pair locations in the genome:
https://en.wikipedia.org/wiki/Manhattan_plot

Preferably I would also like for the position on a given chromosome to be shown, when I zoom in, but that would probably require a secondary x-axis.

I'm super excited about Bokeh! Right now I have to semi naively down-sample my data to less than 100k data points, because the hover tool doesn't work with datashader. Tomorrow I'll look into doing what I think you refer to as call backs with Bokeh serve, so I can do Manhattan plots for various phenotypic traits. Awesome! Excited!

@bryevdv bryevdv modified the milestones: 0.12.6, short-term Mar 23, 2017
@canavandl
Copy link
Contributor

@tommycarstensen , I haven't tested it, but the below should work:

from bokeh.plotting import show
from bokeh.properties import Dict, Int, String
from bokeh.models import (
    Plot,
    Range1d,
    LinearAxis,
    FixedTicker,
    TickFormatter,
)
from bokeh.util.compiler import CoffeeScript

class FixedTickFormatter(TickFormatter):
    """
    Class used to allow custom axis tick labels on a bokeh chart
    Extends bokeh.model.formatters.TickFormatter
    """

    COFFEESCRIPT =  """
        import {Model} from "model"
        import * as p from "core/properties"
        export class FixedTickFormatter extends Model
          type: 'FixedTickFormatter'
          doFormat: (ticks) ->
            labels = @get("labels")
            return (labels[tick] ? "" for tick in ticks)
          @define {
            labels: [ p.Any ]
          }
    """

    labels = Dict(Int, String, help="""
    A mapping of integer ticks values to their labels.
    """)

    __implementation__ = CoffeeScript(COFFEESCRIPT)

ticker = FixedTicker(ticks=[5,15,25,35,45,55])
formatter = FixedTickFormatter(labels={5: '0-10', 15: '10-20', 25: '20-30', 35: '30-40', 45: '40-50', 55: '50+'})
x_axis = LinearAxis(ticker=ticker, formatter=formatter)

p = Plot(x_range=Range1d(0,60))
p.add_layout(x_axis, 'below')

show(p)

@jbednar
Copy link
Contributor

jbednar commented Mar 23, 2017

Right now I have to semi naively down-sample my data to less than 100k data points, because the hover tool doesn't work with datashader.

With the latest GitHub master version of HoloViews, you can now easily overlay a hover layer on a datashader plot, as shown here: holoviz/holoviews#1223

Not sure if that helps your use case, but if it doesn't, please file an issue about that on the datashader site.

@bryevdv
Copy link
Member Author

bryevdv commented Apr 21, 2017

Ok I'd like to do something for this for 0.12.6. There's alot that's somewhat tangential above, mostly I'd like to return to @pzwang observation that users should not be unduly burdened with considering tickers and formatters unless it's necessary. So I propose the following:

plot.xaxis.major_label_overrides = { 5: "0-10", 15: "10-20", ... } 

Then internally axis models would pass this on to their tick formatters on init (or when a formatter is replaced). All the built in formatters would refer to this list of overrides (and extension formatters could as well). Users would only need to worry about supplying this mapping on an axis, which is a bit more common/approachable than tickers and formatter.

This would let specific values be overridden, however they occur. If only these ticks are desired, use of FixedTickFormatter would still be needed. However we could probably also make it so that

plot.xaxis.ticker = [5, 15, ... ]

works as a shorthand for setting up a fixed ticker.

Does this sound acceptable to everyone? If so I would plan on working on this after the refactor of categorical values.

@pzwang
Copy link

pzwang commented Apr 21, 2017

+1, I think this would be super!

@bryevdv bryevdv mentioned this issue May 2, 2017
3 tasks
@bryevdv
Copy link
Member Author

bryevdv commented May 2, 2017

@hodgson-neil @tommycarstensen @Rubyj PR #6225 should solve this issue in the manner described above, if you have the opportunity to build and test it, it would be helpful and appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants