Skip to content

Commit 5960241

Browse files
initial impl of px.timeline
1 parent 3390b34 commit 5960241

File tree

4 files changed

+93
-2
lines changed

4 files changed

+93
-2
lines changed

Diff for: packages/python/plotly/plotly/express/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
line_geo,
2929
area,
3030
bar,
31+
timeline,
3132
bar_polar,
3233
violin,
3334
box,
@@ -81,6 +82,7 @@
8182
"parallel_categories",
8283
"area",
8384
"bar",
85+
"timeline",
8486
"bar_polar",
8587
"violin",
8688
"box",

Diff for: packages/python/plotly/plotly/express/_chart_types.py

+47
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,53 @@ def bar(
358358
bar.__doc__ = make_docstring(bar, append_dict=_cartesian_append_dict)
359359

360360

361+
def timeline(
362+
data_frame=None,
363+
x_start=None,
364+
x_end=None,
365+
y=None,
366+
color=None,
367+
facet_row=None,
368+
facet_col=None,
369+
facet_col_wrap=0,
370+
facet_row_spacing=None,
371+
facet_col_spacing=None,
372+
hover_name=None,
373+
hover_data=None,
374+
custom_data=None,
375+
text=None,
376+
animation_frame=None,
377+
animation_group=None,
378+
category_orders={},
379+
labels={},
380+
color_discrete_sequence=None,
381+
color_discrete_map={},
382+
color_continuous_scale=None,
383+
range_color=None,
384+
color_continuous_midpoint=None,
385+
opacity=None,
386+
range_x=None,
387+
range_y=None,
388+
title=None,
389+
template=None,
390+
width=None,
391+
height=None,
392+
):
393+
"""
394+
In a timeline plot, each row of `data_frame` is represented as a rectangular
395+
mark on an x axis of type `date`, spanning from `x_start` to `x_end`.
396+
"""
397+
return make_figure(
398+
args=locals(),
399+
constructor="timeline",
400+
trace_patch=dict(textposition="auto", orientation="h"),
401+
layout_patch=dict(barmode="overlay"),
402+
)
403+
404+
405+
timeline.__doc__ = make_docstring(timeline)
406+
407+
361408
def histogram(
362409
data_frame=None,
363410
x=None,

Diff for: packages/python/plotly/plotly/express/_core.py

+34-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
# Declare all supported attributes, across all plot types
2121
direct_attrables = (
22-
["x", "y", "z", "a", "b", "c", "r", "theta", "size", "base"]
22+
["base", "x", "y", "z", "a", "b", "c", "r", "theta", "size", "x_start", "x_end"]
2323
+ ["hover_name", "text", "names", "values", "parents", "wide_cross"]
2424
+ ["ids", "error_x", "error_x_minus", "error_y", "error_y_minus", "error_z"]
2525
+ ["error_z_minus", "lat", "lon", "locations", "animation_group"]
@@ -610,7 +610,8 @@ def configure_cartesian_axes(args, fig, orders):
610610
# Set x-axis titles and axis options in the bottom-most row
611611
x_title = get_decorated_label(args, args["x"], "x")
612612
for xaxis in fig.select_xaxes(row=1):
613-
xaxis.update(title_text=x_title)
613+
if "is_timeline" not in args:
614+
xaxis.update(title_text=x_title)
614615
set_cartesian_axis_opts(args, xaxis, "x", orders)
615616

616617
# Configure axis type across all x-axes
@@ -621,6 +622,9 @@ def configure_cartesian_axes(args, fig, orders):
621622
if "log_y" in args and args["log_y"]:
622623
fig.update_yaxes(type="log")
623624

625+
if "is_timeline" in args:
626+
fig.update_xaxes(type="date")
627+
624628
return fig.layout
625629

626630

@@ -1354,6 +1358,7 @@ def build_dataframe(args, constructor):
13541358
if type(args.get("color", None)) == str and args["color"] == NO_COLOR:
13551359
no_color = True
13561360
args["color"] = None
1361+
13571362
# now that things have been prepped, we do the systematic rewriting of `args`
13581363

13591364
df_output, wide_id_vars = process_args_into_dataframe(
@@ -1599,6 +1604,30 @@ def aggfunc_continuous(x):
15991604
return args
16001605

16011606

1607+
def process_dataframe_timeline(args):
1608+
"""
1609+
Massage input for bar traces for px.timeline()
1610+
"""
1611+
args["is_timeline"] = True
1612+
if args["x_start"] is None or args["x_end"] is None:
1613+
raise ValueError("Both x_start and x_end are required")
1614+
1615+
try:
1616+
x_start = pd.to_datetime(args["data_frame"][args["x_start"]])
1617+
x_end = pd.to_datetime(args["data_frame"][args["x_end"]])
1618+
except TypeError:
1619+
raise TypeError(
1620+
"Both x_start and x_end must refer to data convertible to datetimes."
1621+
)
1622+
1623+
# note that we are not adding any columns to the data frame here, so no risk of overwrite
1624+
args["data_frame"][args["x_end"]] = (x_end - x_start).astype("timedelta64[ms]")
1625+
args["x"] = args["x_end"]
1626+
del args["x_end"]
1627+
args["base"] = args["x_start"]
1628+
del args["x_start"]
1629+
1630+
16021631
def infer_config(args, constructor, trace_patch, layout_patch):
16031632
attrs = [k for k in direct_attrables + array_attrables if k in args]
16041633
grouped_attrs = []
@@ -1801,6 +1830,9 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
18011830
args = build_dataframe(args, constructor)
18021831
if constructor in [go.Treemap, go.Sunburst] and args["path"] is not None:
18031832
args = process_dataframe_hierarchy(args)
1833+
if constructor == "timeline":
1834+
constructor = go.Bar
1835+
args = process_dataframe_timeline(args)
18041836

18051837
trace_specs, grouped_mappings, sizeref, show_colorbar = infer_config(
18061838
args, constructor, trace_patch, layout_patch

Diff for: packages/python/plotly/plotly/express/_doc.py

+10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@
3636
colref_desc,
3737
"Values from this column or array_like are used to position marks along the z axis in cartesian coordinates.",
3838
],
39+
x_start=[
40+
colref_type,
41+
colref_desc,
42+
"Values from this column or array_like are used to position marks along the x axis in cartesian coordinates.",
43+
],
44+
x_end=[
45+
colref_type,
46+
colref_desc,
47+
"Values from this column or array_like are used to position marks along the x axis in cartesian coordinates.",
48+
],
3949
a=[
4050
colref_type,
4151
colref_desc,

0 commit comments

Comments
 (0)