Open
Description
Symptoms
When using rangebreaks
for a plotly.express.timeline
-figure
with pattern='hour' and bounds=[17.4, 6.4]
(i.e., exceeding midnight for excluding non-buisness-hours)
the resulting plot either misses all data (second plot) or data exceeding a certain span (fourth plot).
When doing the very same using values
and dvalue
kwords in the rangebreaks
(in case of multiple days, one would need to provide a rangebreak for every day), the plots are generated as expected.
Environment:
# Name Version Build Channel
_libgcc_mutex 0.1 conda_forge conda-forge
_openmp_mutex 4.5 2_gnu conda-forge
anyio 3.7.1 pyhd8ed1ab_0 conda-forge
argon2-cffi 21.3.0 pyhd8ed1ab_0 conda-forge
argon2-cffi-bindings 21.2.0 py311hd4cff14_3 conda-forge
arrow 1.2.3 pyhd8ed1ab_0 conda-forge
asttokens 2.2.1 pyhd8ed1ab_0 conda-forge
async-lru 2.0.3 pyhd8ed1ab_0 conda-forge
attrs 23.1.0 pyh71513ae_1 conda-forge
babel 2.12.1 pyhd8ed1ab_1 conda-forge
backcall 0.2.0 pyh9f0ad1d_0 conda-forge
backports 1.0 pyhd8ed1ab_3 conda-forge
backports.functools_lru_cache 1.6.5 pyhd8ed1ab_0 conda-forge
beautifulsoup4 4.12.2 pyha770c72_0 conda-forge
bleach 6.0.0 pyhd8ed1ab_0 conda-forge
brotli-python 1.0.9 py311ha362b79_9 conda-forge
bzip2 1.0.8 h7f98852_4 conda-forge
ca-certificates 2023.7.22 hbcca054_0 conda-forge
cached-property 1.5.2 hd8ed1ab_1 conda-forge
cached_property 1.5.2 pyha770c72_1 conda-forge
certifi 2023.7.22 pyhd8ed1ab_0 conda-forge
cffi 1.15.1 py311h409f033_3 conda-forge
charset-normalizer 3.2.0 pyhd8ed1ab_0 conda-forge
comm 0.1.3 pyhd8ed1ab_0 conda-forge
debugpy 1.6.7 py311hcafe171_0 conda-forge
decorator 5.1.1 pyhd8ed1ab_0 conda-forge
defusedxml 0.7.1 pyhd8ed1ab_0 conda-forge
entrypoints 0.4 pyhd8ed1ab_0 conda-forge
exceptiongroup 1.1.2 pyhd8ed1ab_0 conda-forge
executing 1.2.0 pyhd8ed1ab_0 conda-forge
flit-core 3.9.0 pyhd8ed1ab_0 conda-forge
fqdn 1.5.1 pyhd8ed1ab_0 conda-forge
idna 3.4 pyhd8ed1ab_0 conda-forge
importlib-metadata 6.8.0 pyha770c72_0 conda-forge
importlib_metadata 6.8.0 hd8ed1ab_0 conda-forge
importlib_resources 6.0.0 pyhd8ed1ab_1 conda-forge
ipykernel 6.25.0 pyh71e2992_0 conda-forge
ipython 8.14.0 pyh41d4057_0 conda-forge
isoduration 20.11.0 pyhd8ed1ab_0 conda-forge
jedi 0.18.2 pyhd8ed1ab_0 conda-forge
jinja2 3.1.2 pyhd8ed1ab_1 conda-forge
json5 0.9.14 pyhd8ed1ab_0 conda-forge
jsonpointer 2.0 py_0 conda-forge
jsonschema 4.18.4 pyhd8ed1ab_0 conda-forge
jsonschema-specifications 2023.7.1 pyhd8ed1ab_0 conda-forge
jsonschema-with-format-nongpl 4.18.4 pyhd8ed1ab_0 conda-forge
jupyter-lsp 2.2.0 pyhd8ed1ab_0 conda-forge
jupyter_client 8.3.0 pyhd8ed1ab_0 conda-forge
jupyter_core 5.3.1 py311h38be061_0 conda-forge
jupyter_events 0.6.3 pyhd8ed1ab_1 conda-forge
jupyter_server 2.7.0 pyhd8ed1ab_0 conda-forge
jupyter_server_terminals 0.4.4 pyhd8ed1ab_1 conda-forge
jupyterlab 4.0.3 pyhd8ed1ab_0 conda-forge
jupyterlab_pygments 0.2.2 pyhd8ed1ab_0 conda-forge
jupyterlab_server 2.24.0 pyhd8ed1ab_0 conda-forge
kaleido 0.2.1 pypi_0 pypi
ld_impl_linux-64 2.40 h41732ed_0 conda-forge
libblas 3.9.0 17_linux64_openblas conda-forge
libcblas 3.9.0 17_linux64_openblas conda-forge
libexpat 2.5.0 hcb278e6_1 conda-forge
libffi 3.4.2 h7f98852_5 conda-forge
libgcc-ng 13.1.0 he5830b7_0 conda-forge
libgfortran-ng 13.1.0 h69a702a_0 conda-forge
libgfortran5 13.1.0 h15d22d2_0 conda-forge
libgomp 13.1.0 he5830b7_0 conda-forge
liblapack 3.9.0 17_linux64_openblas conda-forge
libnsl 2.0.0 h7f98852_0 conda-forge
libopenblas 0.3.23 pthreads_h80387f5_0 conda-forge
libsodium 1.0.18 h36c2ea0_1 conda-forge
libsqlite 3.42.0 h2797004_0 conda-forge
libstdcxx-ng 13.1.0 hfd8a6a1_0 conda-forge
libuuid 2.38.1 h0b41bf4_0 conda-forge
libzlib 1.2.13 hd590300_5 conda-forge
markupsafe 2.1.3 py311h459d7ec_0 conda-forge
matplotlib-inline 0.1.6 pyhd8ed1ab_0 conda-forge
mistune 3.0.0 pyhd8ed1ab_0 conda-forge
nbclient 0.8.0 pyhd8ed1ab_0 conda-forge
nbconvert-core 7.7.3 pyhd8ed1ab_0 conda-forge
nbformat 5.9.1 pyhd8ed1ab_0 conda-forge
ncurses 6.4 hcb278e6_0 conda-forge
nest-asyncio 1.5.6 pyhd8ed1ab_0 conda-forge
notebook 7.0.0 pyhd8ed1ab_0 conda-forge
notebook-shim 0.2.3 pyhd8ed1ab_0 conda-forge
numpy 1.25.1 py311h64a7726_0 conda-forge
openssl 3.1.1 hd590300_1 conda-forge
overrides 7.3.1 pyhd8ed1ab_0 conda-forge
packaging 23.1 pyhd8ed1ab_0 conda-forge
pandas 2.0.3 py311h320fe9a_1 conda-forge
pandocfilters 1.5.0 pyhd8ed1ab_0 conda-forge
parso 0.8.3 pyhd8ed1ab_0 conda-forge
pexpect 4.8.0 pyh1a96a4e_2 conda-forge
pickleshare 0.7.5 py_1003 conda-forge
pip 23.2.1 pyhd8ed1ab_0 conda-forge
pkgutil-resolve-name 1.3.10 pyhd8ed1ab_0 conda-forge
platformdirs 3.9.1 pyhd8ed1ab_0 conda-forge
plotly 5.15.0 pyhd8ed1ab_0 conda-forge
prometheus_client 0.17.1 pyhd8ed1ab_0 conda-forge
prompt-toolkit 3.0.39 pyha770c72_0 conda-forge
prompt_toolkit 3.0.39 hd8ed1ab_0 conda-forge
psutil 5.9.5 py311h2582759_0 conda-forge
ptyprocess 0.7.0 pyhd3deb0d_0 conda-forge
pure_eval 0.2.2 pyhd8ed1ab_0 conda-forge
pycparser 2.21 pyhd8ed1ab_0 conda-forge
pygments 2.15.1 pyhd8ed1ab_0 conda-forge
pysocks 1.7.1 pyha2e5f31_6 conda-forge
python 3.11.4 hab00c5b_0_cpython conda-forge
python-dateutil 2.8.2 pyhd8ed1ab_0 conda-forge
python-fastjsonschema 2.18.0 pyhd8ed1ab_0 conda-forge
python-json-logger 2.0.7 pyhd8ed1ab_0 conda-forge
python-tzdata 2023.3 pyhd8ed1ab_0 conda-forge
python_abi 3.11 3_cp311 conda-forge
pytz 2023.3 pyhd8ed1ab_0 conda-forge
pyyaml 6.0 py311hd4cff14_5 conda-forge
pyzmq 25.1.0 py311h75c88c4_0 conda-forge
readline 8.2 h8228510_1 conda-forge
referencing 0.30.0 pyhd8ed1ab_0 conda-forge
requests 2.31.0 pyhd8ed1ab_0 conda-forge
rfc3339-validator 0.1.4 pyhd8ed1ab_0 conda-forge
rfc3986-validator 0.1.1 pyh9f0ad1d_0 conda-forge
rpds-py 0.9.2 py311h46250e7_0 conda-forge
send2trash 1.8.2 pyh41d4057_0 conda-forge
setuptools 68.0.0 pyhd8ed1ab_0 conda-forge
six 1.16.0 pyh6c4a22f_0 conda-forge
sniffio 1.3.0 pyhd8ed1ab_0 conda-forge
soupsieve 2.3.2.post1 pyhd8ed1ab_0 conda-forge
stack_data 0.6.2 pyhd8ed1ab_0 conda-forge
tenacity 8.2.2 pyhd8ed1ab_0 conda-forge
terminado 0.17.1 pyh41d4057_0 conda-forge
tinycss2 1.2.1 pyhd8ed1ab_0 conda-forge
tk 8.6.12 h27826a3_0 conda-forge
tomli 2.0.1 pyhd8ed1ab_0 conda-forge
tornado 6.3.2 py311h459d7ec_0 conda-forge
traitlets 5.9.0 pyhd8ed1ab_0 conda-forge
typing-extensions 4.7.1 hd8ed1ab_0 conda-forge
typing_extensions 4.7.1 pyha770c72_0 conda-forge
typing_utils 0.1.0 pyhd8ed1ab_0 conda-forge
tzdata 2023c h71feb2d_0 conda-forge
uri-template 1.3.0 pyhd8ed1ab_0 conda-forge
urllib3 2.0.4 pyhd8ed1ab_0 conda-forge
wcwidth 0.2.6 pyhd8ed1ab_0 conda-forge
webcolors 1.13 pyhd8ed1ab_0 conda-forge
webencodings 0.5.1 py_1 conda-forge
websocket-client 1.6.1 pyhd8ed1ab_0 conda-forge
wheel 0.41.0 pyhd8ed1ab_0 conda-forge
xz 5.2.6 h166bdaf_0 conda-forge
yaml 0.2.5 h7f98852_2 conda-forge
zeromq 4.3.4 h9c3ff4c_1 conda-forge
zipp 3.16.2 pyhd8ed1ab_0 conda-forge
MWE:
import datetime
import pandas as pd
import plotly.express as px
import plotly.io as pio
pio.renderers.default = "svg"
# build example data
df = pd.read_json(
"""
{
"start":{
"0":"2023-07-27T10:54:28.000Z",
"1":"2023-07-27T11:40:15.000Z",
"2":"2023-07-27T15:00:58.000Z",
"3":"2023-07-27T11:52:28.000Z",
"4":"2023-07-27T12:52:57.000Z",
"5":"2023-07-27T13:20:45.000Z",
"6":"2023-07-27T13:44:24.000Z"
},
"end":{
"0":"2023-07-27T10:54:28.000Z",
"1":"2023-07-27T11:53:44.000Z",
"2":"2023-07-27T15:06:50.000Z",
"3":"2023-07-27T11:55:19.000Z",
"4":"2023-07-27T13:01:35.000Z",
"5":"2023-07-27T13:23:10.000Z",
"6":"2023-07-27T13:47:03.000Z"
},
"device_number":{
"0":"168012",
"1":"168012",
"2":"168012",
"3":"202052",
"4":"202052",
"5":"202052",
"6":"202052"
}
}
""",
dtype=dict(device_number=str)
)
# code for a workaround using value/dvalue rangebreaks for every date in a (known) xrange
df[['start', 'end']] = df[['start', 'end']].apply(lambda x: pd.to_datetime(x).astype('datetime64[ns, UTC]'))
dt_interval = (datetime.datetime(2023, 7, 27, 0, 0, 0, 0), datetime.datetime(2023, 7, 27, 23, 59, 59, 99999))
restrict_timeinterval = (datetime.time(6, 24, 0, 0), datetime.time(17, 24, 0, 0))
no_days_in_range_x = (dt_interval[1].date() - dt_interval[0].date()).days + 1
date_list = [(dt_interval[0].date() + datetime.timedelta(days=k))
for k in range(no_days_in_range_x)] if no_days_in_range_x > 1 else [dt_interval[0].date()]
dvalue0 = datetime.datetime.combine(datetime.date.today(), restrict_timeinterval[0]) \
- datetime.datetime.combine(datetime.date.today(), datetime.time.min)
dvalue1 = datetime.datetime.combine(datetime.date.today(), datetime.time.max) \
- datetime.datetime.combine(datetime.date.today(), restrict_timeinterval[1])
values0 = [datetime.datetime.combine(dt, tm)
for dt, tm in zip(date_list, (datetime.time.min,) * no_days_in_range_x)]
values1 = [datetime.datetime.combine(dt, tm)
for dt, tm in zip(date_list, (restrict_timeinterval[1],) * no_days_in_range_x)]
print(dvalue0, dvalue1)
print(values0, values1)
print(df.values)
# define different rangebreaks to test
l_rangebreaks_default = []
l_rangebreaks_pattern_0 = [
#dict(values=values0, dvalue=dvalue0.total_seconds() * 1e3),
dict(bounds=[17.4, 24], pattern='hour'),
dict(bounds=[0, 6.4], pattern='hour'),
]
l_rangebreaks_pattern_1 = [
#dict(values=values0, dvalue=dvalue0.total_seconds() * 1e3),
dict(bounds=[17.4, 24], pattern='hour'),
dict(bounds=[0.2, 6.4], pattern='hour'),
]
l_rangebreaks_values_0 = [
dict(values=values0, dvalue=dvalue0.total_seconds() * 1e3),
dict(values=values1, dvalue=dvalue1.total_seconds() * 1e3),
]
# build plotly timelines
for l_rangebreaks in [
l_rangebreaks_default,
l_rangebreaks_pattern_0,
l_rangebreaks_pattern_1,
l_rangebreaks_values_0,
]:
print(l_rangebreaks)
fig = px.timeline(
data_frame=df,
x_start='start',
x_end='end',
y='device_number',
range_x=dt_interval,
color_continuous_scale=px.colors.sequential.Rainbow,
color='device_number',
)
fig.update_xaxes(
rangebreaks=l_rangebreaks
)
fig.show(renderer='svg')
Output
6:24:00 6:35:59.999999
[datetime.datetime(2023, 7, 27, 0, 0)] [datetime.datetime(2023, 7, 27, 17, 24)]
[[Timestamp('2023-07-27 10:54:28+0000', tz='UTC')
Timestamp('2023-07-27 10:54:28+0000', tz='UTC') '168012']
[Timestamp('2023-07-27 11:40:15+0000', tz='UTC')
Timestamp('2023-07-27 11:53:44+0000', tz='UTC') '168012']
[Timestamp('2023-07-27 15:00:58+0000', tz='UTC')
Timestamp('2023-07-27 15:06:50+0000', tz='UTC') '168012']
[Timestamp('2023-07-27 11:52:28+0000', tz='UTC')
Timestamp('2023-07-27 11:55:19+0000', tz='UTC') '202052']
[Timestamp('2023-07-27 12:52:57+0000', tz='UTC')
Timestamp('2023-07-27 13:01:35+0000', tz='UTC') '202052']
[Timestamp('2023-07-27 13:20:45+0000', tz='UTC')
Timestamp('2023-07-27 13:23:10+0000', tz='UTC') '202052']
[Timestamp('2023-07-27 13:44:24+0000', tz='UTC')
Timestamp('2023-07-27 13:47:03+0000', tz='UTC') '202052']]
Plots
No rangebreaks
[]
Fails
[{'bounds': [17.4, 24], 'pattern': 'hour'}, {'bounds': [0, 6.4], 'pattern': 'hour'}]
[{'bounds': [17.4, 24], 'pattern': 'hour'}, {'bounds': [0.2, 6.4], 'pattern': 'hour'}]
Fails: note the vanishing timeslot on the upper device!
Workaround works as expected.
[{'values': [datetime.datetime(2023, 7, 27, 0, 0)], 'dvalue': 23040000.0}, {'values': [datetime.datetime(2023, 7, 27, 17, 24)], 'dvalue': 23759999.998999998}]
Other matters
I assume it has something to do how x_start
and x_end
are internally translated to base
and x
for px.bar
/px.timeline
...