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

Add DatetimeRangeSlider #12034

Merged
merged 3 commits into from
Mar 16, 2022
Merged

Add DatetimeRangeSlider #12034

merged 3 commits into from
Mar 16, 2022

Conversation

ianthomas23
Copy link
Member

This is an early-stage WIP to add a DatetimeRangeSlider.

Example code using a 1 hour step:

from bokeh.layouts import column
from bokeh.models import CustomJS, DatetimeRangeSlider, Div
from bokeh.plotting import show
from datetime import datetime

slider = DatetimeRangeSlider(
    value=(datetime(2022, 3, 1, 12), datetime(2022, 3, 4, 18)),
    start=datetime(2022, 3, 1, 12),
    end=datetime(2022, 3, 4, 18),
    format = "%d %b %Y %H:%M",
)
div = Div(text="...")

slider.js_on_change(
    "value",
    CustomJS(args=dict(div=div), code="""
div.text=
    'From date: ' + this._formatter(this.value[0], '%d %b %Y') +
    '<br/>From time: ' + this._formatter(this.value[0], '%H:%M') +
    '<br/>To date: ' + this._formatter(this.value[1], '%d %b %Y') +
    '<br/>To time: ' + this._formatter(this.value[1], '%H:%M');
"""))

show(column(slider, div))
simplescreenrecorder-2022-03-04_14.28.26.mp4

It is similar to DateRangeSlider. Has the same API except that it accepts datetime objects rather than date:

  1. start and end datetimes.
  2. Tuple of two value datetimes.
  3. format string.
  4. step which is in units of the underlying POSIX milliseconds since epoch UTC.

I am showing this early in case anyone has strong views about how it should proceed. Possible improvements:

  1. Validation of step to restrict it so that an integer multiple of it fits in the next size up date/time unit, e.g. you can't have 27 minutes as your step, but 15, 20, and 30 are OK. Or round it to the nearest acceptable value.
  2. Snapping start, end and the two values to an integer multiple of the step.
  3. Accepting human-readable strings for the step, e.g. "1 hour"?
  4. Could add some logic to guess a sensible step given the start and end limits?

@jbednar
Copy link
Contributor

jbednar commented Mar 4, 2022

That video looks fabulous! Just what I was hoping to see. We could have used this on so many projects over the years.

Validation of step to restrict it so that an integer multiple of it fits in the next size up date/time unit, e.g. you can't have 27 minutes as your step, but 15, 20, and 30 are OK. Or round it to the nearest acceptable value.

I'd be permissive about the step, rounding it to conform with whatever requirements Bokeh or the widget itself needs to enforce, but otherwise accepting whatever the user specifies. If people want nice values, they should choose them, but I don't think we should force the values to be nice.

Snapping start, end and the two values to an integer multiple of the step.

That might be useful in some cases, but I don't think it's desirable in general. Usually the step is just a hint for the GUI, while the start and end can be absolute requirements. E.g. if a dataset covers a certain range of datetimes, it might well be an error to select any range outside those bounds. So I definitely would not want to have the range cover anything other than the precise range the user requested, unless the user explicitly enables such behavior.

Accepting human-readable strings for the step, e.g. "1 hour"?

Whatever the typical Bokeh behavior is in this respect seems appropriate, e.g. for tick spacing. E.g. if you can ask for axis ticks spaced "1 hour" apart, then the same here; otherwise doesn't seem appropriate.

Could add some logic to guess a sensible step given the start and end limits?

I do think there should be a reasonable default step (e.g. (end-start)/200); users don't necessarily want to think about the step unless the default isn't appropriate.

@ianthomas23
Copy link
Member Author

Based on @jbednar's comments, we can take the view that the user needs to specify reasonable values for all of start, end, value and step and we don't need to do anything clever guessing reasonable values that are not specified. So I will continue with adding tests and examples for this.

@jbednar
Copy link
Contributor

jbednar commented Mar 7, 2022

I think step does warrant a default, because users won't necessarily have any opinion about its value until they try it. But I think its default should be whatever other Bokeh sliders default to, presumably some fixed division of the range.

@ianthomas23 ianthomas23 force-pushed the ianthomas23/datetime_range_slider branch from 1fcdbd3 to c6561ac Compare March 11, 2022 18:43
@ianthomas23
Copy link
Member Author

This is ready for review. The Windows test failure doesn't appear to be related?

@philippjfr
Copy link
Contributor

This looks great to me and will be very useful!

@bryevdv
Copy link
Member

bryevdv commented Mar 15, 2022

How does this behave in a bokeh server case where the browser and server are in different timezones?

@ianthomas23
Copy link
Member Author

How does this behave in a bokeh server case where the browser and server are in different timezones?

I will try this out and report back.

@mattpap mattpap added this to the 3.0 milestone Mar 15, 2022
@bryevdv
Copy link
Member

bryevdv commented Mar 15, 2022

OMG after however many years, GitHub has finally, finally added "Re-run failed jobs"

@ianthomas23
Copy link
Member Author

ianthomas23 commented Mar 16, 2022

How does this behave in a bokeh server case where the browser and server are in different timezones?

It makes no difference. Our internal datetime handling (bokeh to bokehjs) is "timezone naive", using Python datetime's terminology (https://docs.python.org/3/library/datetime.html#aware-and-naive-objects). On the Python side the user may specify a timezone or not for the start, end and values. The timezone information is removed as part of the serialization to bokehjs at this line:

return (dt.datetime(*obj.timetuple()[:6], tzinfo=None) - DT_EPOCH).total_seconds() * 1000

No timezone information gets to the web client, if you query it you will be told "UTC +0" but really it is timezone naive.

On the server side if you specify a datetime like

datetime(2022, 3, 16, 0, 0, 0)

i.e. timezone naive, then you get what you expect as the code is timezone naive throughout. If you use

datetime(2022, 3, 16, 0, 0, 0, tzinfo=timezone(offset=timedelta(hours=5)))

then we drop the timezone and you get the same result on the client side as the previous example. If you convert a datetime to a different timezone

>>> start = datetime(2022, 3, 16, 0, 0, 0, tzinfo=timezone(offset=timedelta(hours=5)))
>>> start = start.astimezone(timezone(offset=timedelta(hours=-3)))
>>> print(start)
datetime.datetime(2022, 3, 15, 16, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=75600)))

then our dropping of the timezone gives a different result on the client side (8 hours difference) but the result is the same regardless of the client OS/browser timezone.

So our behaviour is consistent, we are timezone naive throughout, and we make it the user's responsibility to play with timezones if they wish. Mixing timezone aware and timezone naive objects is dangerous, but this is always the case.

For completeness, how difficult would it be to convert to being timezone aware throughout? Functionally this might be quite easy, we need to serialize the timezone, make a decision on what timezone to use if none is specified on the Python side (has to be local timezone?), and make it easy to report timezone on the client side. But it would need a lot of testing to avoid any gotchas like daylight saving time changes.

@bryevdv
Copy link
Member

bryevdv commented Mar 16, 2022

This LGTM

@mattpap mattpap merged commit c975100 into branch-3.0 Mar 16, 2022
@mattpap mattpap deleted the ianthomas23/datetime_range_slider branch March 16, 2022 19:36
ianthomas23 added a commit that referenced this pull request May 11, 2022
* Add DatetimeRangeSlider

* Add tests

* Add docs
@ianthomas23 ianthomas23 removed this from the 3.0 milestone May 12, 2022
@ianthomas23 ianthomas23 added this to the 2.4.3 milestone May 12, 2022
ianthomas23 added a commit that referenced this pull request May 13, 2022
* Use weekly channel for gmap plots (#12113)

* Add DatetimeRangeSlider (#12034)

* Add DatetimeRangeSlider

* Add tests

* Add docs

* Simplify EqHistColorMapper calculations (#12084)

* Simplify EqHistColorMapper calculations

* Add numerical unit tests

* Better numerical unit tests

* Support rescale_discrete_levels in EqHistColorMapper (#12121)

* Drop baseline testing on MacOS and Windows (part of #11906)

* Try more of #11906

* Pin to django 3.x (#11867)

* Fix new typings failures (#11971)

* 12093 Update union syntax (#12094)

* Update union syntax under typings directory

* Update Sequence and Callable imports

* Force webgl backend when requested for testing (#11823)

* Force webgl backend when requested for testing

webgl backend normally falls back to canvas rendering, but that's
undesirable when testing specifically webgl support. For testing
the fallback mechanism, use `settings.force_webgl = false`.

* Install a specific version of chromium on linux

* Python 3.7 specific mypy fixes

* isort fix

* remove unused ignores

* more mypy

* yet more mypy

* re-skip previously skipped SVG test

* Clarify RenderRoot for components (#12036)

* Clarify RenderRoot for components

* unrelated typo fix

* Fix color function call (#11751)

* Fix typo in git.py (#12106)

verison -> version

* Fixed typo: s/server/serve/ (#12051)

* Add docs_toc block (#11989)

* Fixed xwheel and xzoom tools with hard bounds (#11832) (#11834)

* Change DatetimeRangeSlider tests to fit older testing framework

* Fix webgl dashed line joins with butt end caps (#11814)

* Fix webgl dashed line joins with butt end caps

* Update baseline images

* Correctly handle odd-length dash patterns in webgl (#11819)

* Correctly render straight webgl lines with bevel joins (#12065)

* Limit tool-related tap events to the frame (#11938)

* 11965 fixes that null value with unspecified nan_format crashes table rendering (#12098)

* #11965: fixed error when trying to show null values in table columns using ScientificFormatter

* #11965: changed string to double quotes

* Squashed commits:

[4355e9165] 11965: removed whitespaces (+1 squashed commits)

Squashed commits:

[46984940d] #11965: moved nan_format to StringFormatter and made non-nullable

* #11965: changed default nan_format to NaN

* #11965: default nan/null format for all string derived cellformatters set to -

Co-authored-by: Harm Buisman <h.buisman@iknl.nl>

Co-authored-by: Bryan Van de Ven <bryan@bokeh.org>
Co-authored-by: Jada Lilleboe <82007190+jadalilleboe@users.noreply.github.com>
Co-authored-by: Mateusz Paprocki <mattpap@gmail.com>
Co-authored-by: g-parki <61096711+g-parki@users.noreply.github.com>
Co-authored-by: Ikko Ashimine <eltociear@gmail.com>
Co-authored-by: Rick van Hattem <wolph@wol.ph>
Co-authored-by: Timo Cornelius Metzger <39711796+tcmetzger@users.noreply.github.com>
Co-authored-by: Florent <florentbr@gmail.com>
Co-authored-by: harmbuisman <harmbuisman@gmail.com>
Co-authored-by: Harm Buisman <h.buisman@iknl.nl>
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.

5 participants