Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Fix `mpl_to_plotly` tick marker mirroring, axis positioning, and trace visibility for twinned and overlaid subplots (such as `twinx` and `twiny`) [[#5310](https://github.com/plotly/plotly.py/pull/5310)], with thanks to @robertoffmoura for the contribution!
- Fix `mpl_to_plotly` losing matplotlib axis line colors by exporting the color of each axis's displayed spine to the plotly x and y axis `linecolor` [[#5311](https://github.com/plotly/plotly.py/pull/5311)], with thanks to @robertoffmoura for the contribution!
- Fix `px.timeline` bars ending an hour early or late when timezone-aware dates span a daylight saving time change [[#4611](https://github.com/plotly/plotly.py/issues/4611)]
- Fix concurrent first access to lazily initialized graph object properties, which could raise `ValueError("Invalid value")` [[#5691](https://github.com/plotly/plotly.py/pull/5691)], with thanks to @hb1915 for the contribution!
- Fix `mpl_to_plotly` crashing on touching bars (such as `plt.hist`) due to floating-point noise producing negative `bargap` values by clamping `bargap` to `[0, 1]` [[#5696](https://github.com/plotly/plotly.py/pull/5696)], with thanks to @robertoffmoura for the contribution!
- Fix `mpl_to_plotly` drawing step plots with diagonal lines instead of vertical/horizontal step segments by mapping matplotlib step drawstyles (`steps-pre`, `steps-post`, `steps-mid`) to plotly line shapes [[#5697](https://github.com/plotly/plotly.py/pull/5697)], with thanks to @robertoffmoura for the contribution!
Expand Down
13 changes: 10 additions & 3 deletions plotly/express/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2194,11 +2194,18 @@ def process_dataframe_timeline(args):
"Both x_start and x_end must refer to data convertible to datetimes."
) from exc

# Match the local times displayed on date axes when calculating bar lengths.
schema = df.schema
x_start, x_end = [
nw.col(col).dt.replace_time_zone(None)
if schema[col] == nw.Datetime and schema[col].time_zone is not None
else nw.col(col)
for col in (args["x_start"], args["x_end"])
]

# note that we are not adding any columns to the data frame here, so no risk of overwrite
args["data_frame"] = df.with_columns(
(nw.col(args["x_end"]) - nw.col(args["x_start"]))
.dt.total_milliseconds()
.alias(args["x_end"])
(x_end - x_start).dt.total_milliseconds().alias(args["x_end"])
)
args["x"] = args["x_end"]
args["base"] = args["x_start"]
Expand Down
26 changes: 26 additions & 0 deletions tests/test_optional/test_px/test_px_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,32 @@ def test_timeline_cols_already_temporal(constructor, datetime_columns):
assert fig.layout.xaxis.title.text is None


@pytest.mark.parametrize("time_zone", [None, "UTC", "US/Pacific"])
def test_timeline_daylight_saving(constructor, time_zone):
# Regression for https://github.com/plotly/plotly.py/issues/4611
starts = ["2024-03-09", "2024-03-10", "2024-03-11", "2023-11-05"]
finishes = ["2024-03-10", "2024-03-11", "2024-03-12", "2023-11-06"]
df = nw.from_native(
constructor({"Start": starts, "Finish": finishes, "Task": ["Job A"] * 4})
).with_columns(
nw.col("Start", "Finish")
.str.to_datetime(format="%Y-%m-%d")
.dt.replace_time_zone(time_zone)
)

fig = px.timeline(df.to_native(), x_start="Start", x_end="Finish", y="Task")

# Date axes use local wall-clock coordinates: consecutive midnights are
# 24 hours apart even when the elapsed time is 23 or 25 hours.
assert_array_equal(fig.data[0].x, [24 * 60 * 60 * 1000] * 4)
base = np.asarray(fig.data[0].base, dtype="datetime64[ms]")
assert_array_equal(base, np.array(starts, dtype="datetime64[ms]"))
assert_array_equal(
base + fig.data[0].x.astype("timedelta64[ms]"),
np.array(finishes, dtype="datetime64[ms]"),
)


def test_empty_histogram():
"""Empty px.histogram() should not raise, matching scatter/bar/pie behavior.

Expand Down