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 provide custom units of measurement #13625

Merged
merged 13 commits into from Jan 30, 2024

Conversation

mattpap
Copy link
Contributor

@mattpap mattpap commented Dec 31, 2023

The original implementation of ScaleBar in PR #13319 didn't allow for easy definition of any other units of measurement than metric length. This PR addresses that issue by making Metric model non-abstract and thus allowing e.g. Metric(base_unit="eV") units. MetricLength model is preserved and additional models for generic reciprocal, imperial length and angular units of measurement are added.

Example:

Screencast.from.16.01.2024.17.09.40.webm
import numpy as np
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, RangeTool
from bokeh.plotting import figure, show

n_points = 3000
x_values = np.linspace(0, 100, n_points)
y_values = np.random.randn(n_points).cumsum()

source = ColumnDataSource(data=dict(x=x_values, y=y_values))

p = figure(
    width=800, height=300,
    tools=["xpan", "xzoom_in", "xzoom_out", "reset", "wheel_zoom"],
    active_scroll="wheel_zoom",
    background_fill_color="#efefef",
    x_range=(22, 30),
)

p.line('x', 'y', source=source)
p.yaxis.axis_label = 'Value'
p.x_range.max_interval = 10  # Set max zoom interval

select = figure(
    width=800, height=130,
    y_range=p.y_range,
    tools="",
    toolbar_location=None,
    background_fill_color="#efefef",
)
range_tool = RangeTool(x_range=p.x_range)
range_tool.overlay.fill_color = "navy"
range_tool.overlay.fill_alpha = 0.2
select.line('x', 'y', source=source)
select.ygrid.grid_line_color = None
select.add_tools(range_tool)

from bokeh.models import ScaleBar, Metric
scale_bar = ScaleBar(
    range=p.y_range,
    unit="MeV",
    dimensional=Metric(base_unit="eV"),
    orientation="vertical",
    location="top_left",
    background_fill_color=None,
    border_line_color=None,
)
p.add_layout(scale_bar)

# This is unrelated to this PR, but it's here for the sake of completeness for CZI.
from bokeh.models import CustomAction, CustomJS
toggle = CustomAction(
    # TODO: icon = ...,
    description="Toggle ScaleBar",
    callback=CustomJS(
        args=dict(scale_bar=scale_bar),
        code="""
export default ({scale_bar}) => {
    scale_bar.visible = !scale_bar.visible
}
        """,
    ),
)
p.toolbar.tools.append(toggle)

show(column(p, select))

@mattpap mattpap added this to the 3.4 milestone Dec 31, 2023
@mattpap mattpap force-pushed the mattpap/user_defined_units_of_measurement branch from 1c59f13 to e969f3d Compare December 31, 2023 01:24
Copy link

codecov bot commented Dec 31, 2023

Codecov Report

Attention: 7 lines in your changes are missing coverage. Please review.

Comparison is base (57f77ac) 92.57% compared to head (1cfad03) 92.18%.

Additional details and impacted files
@@              Coverage Diff               @@
##           branch-3.4   #13625      +/-   ##
==============================================
- Coverage       92.57%   92.18%   -0.39%     
==============================================
  Files             323      323              
  Lines           20534    20575      +41     
==============================================
- Hits            19009    18968      -41     
- Misses           1525     1607      +82     

@mattpap
Copy link
Contributor Author

mattpap commented Jan 16, 2024

This is tentatively ready, but there are some unrelated technical issues to resolve, e.g.:

✗ Defaults
 └── bokehjs should implement serializable Angular model and match defaults with bokeh
Angular: defaults are out of sync between Python and bokehjs
    Angular.basis: bokehjs defaults to {type: "map", entries: [["°", [1, "^\circ"]], ["'", [0.016666666666666666, "^\prime"]], ["''", [0.0002777777777777778, "^{\prime\prime}"]]]} but python defaults to {type: "map", entries: [["°", [1, "^circ"]], ["'", [0.016666666666666666, "^prime"]], ["''", [0.0002777777777777778, "^{primeprime}"]]]}

which is an effect of bad encoding of imported files in JS bundles.

src/bokeh/models/annotations/dimensional.py Outdated Show resolved Hide resolved
"fur": ( 660, "fur"),
"mi": ( 5280, "mi" ),
"lea": (15840, "lea"),
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea what "ch" and "lea" are, comments after each case would be helpful

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! Assume ch is the abbrevation for a chain and lea is a Lea.

src/bokeh/models/annotations/dimensional.py Outdated Show resolved Hide resolved
@bryevdv
Copy link
Member

bryevdv commented Jan 16, 2024

Requesting @philippjfr since this is a CZI task

Copy link
Contributor

@philippjfr philippjfr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! My only comment would be a request for adding an example to the docs, the eV example seems fine.

@droumis
Copy link
Member

droumis commented Jan 22, 2024

I second the request for docs with an example/explanation.

Also, is there a way to have the scale bar length stay consistent while zooming and only have the numbers change? Currently, both the length of the bar and the numbers are changing. This might need a way to format the number string to specify the number of decimal places displayed.

Also, does this work for a unit with multiple parts. For example, fT/cm is commonly used as a unit for gradiometers that record spatial derivatives of magnetic fields. In such a case, if a user zooms on the y-axis, we would only want the T part of the unit to scale up or down. Is that configurable? I suppose being able to format the number string to just add '/cm' would suffice.

@mattpap mattpap force-pushed the mattpap/user_defined_units_of_measurement branch from 537d24a to e3528f5 Compare January 23, 2024 14:03
@mattpap
Copy link
Contributor Author

mattpap commented Jan 23, 2024

Also, is there a way to have the scale bar length stay consistent while zooming and only have the numbers change?

Use ScaleBar(length_sizing="exact"), which will maintain the given bar_length (the default 20% of the parent, usually the cartesian frame).

@mattpap
Copy link
Contributor Author

mattpap commented Jan 23, 2024

This might need a way to format the number string to specify the number of decimal places displayed.

Use Scale(label="@{value}{printf-style format}@{unit}"). Currently only printf-style formatter is supported.

@mattpap
Copy link
Contributor Author

mattpap commented Jan 23, 2024

Also, does this work for a unit with multiple parts.

I suppose one could use label to add prefixes and suffixes around @{unit}, though that will work only for the plain text case. Units of measurement support TeX representation, but that isn't implemented in ScaleBar at this point.

@mattpap mattpap force-pushed the mattpap/user_defined_units_of_measurement branch from e3528f5 to 405966b Compare January 23, 2024 14:17
@mattpap mattpap force-pushed the mattpap/user_defined_units_of_measurement branch from 405966b to 1cfad03 Compare January 30, 2024 16:43
@mattpap mattpap merged commit afdab54 into branch-3.4 Jan 30, 2024
29 of 30 checks passed
@mattpap mattpap deleted the mattpap/user_defined_units_of_measurement branch January 30, 2024 21:56
@droumis droumis mentioned this pull request Feb 20, 2024
16 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants