Skip to content

Unexpected behavior of rangebreaks with px.timeline and pattern "hour" #4297

Open
@laqua-stack

Description

@laqua-stack

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

[]

output_0_1

Fails

[{'bounds': [17.4, 24], 'pattern': 'hour'}, {'bounds': [0, 6.4], 'pattern': 'hour'}]

output_0_3

[{'bounds': [17.4, 24], 'pattern': 'hour'}, {'bounds': [0.2, 6.4], 'pattern': 'hour'}]

Fails: note the vanishing timeslot on the upper device!

output_0_5

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}]

output_0_7

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...

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3backlogbugsomething broken

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions