Skip to content

Commit

Permalink
bar base and timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolaskruchten committed Jul 13, 2020
1 parent f37fd3c commit fc64079
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 141 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).


## [4.9.0] - unreleased

### Added

- `px.NO_COLOR` constant to override wide-form color assignment in Plotly Express ([#2614](https://github.com/plotly/plotly.py/pull/2614))
- `facet_row_spacing` and `facet_col_spacing` added to Plotly Express cartesian 2d functions ([#2614](https://github.com/plotly/plotly.py/pull/2614))
- `base` added to Plotly Express `bar` and `bar_polar` functions
- `plotly.express.timeline()` added as an official alternative to `plotly.figure_factories.create_gantt()`

### Fixed

Expand All @@ -21,6 +22,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).




## [4.8.2] - 2020-06-26

### Updated
Expand Down
142 changes: 74 additions & 68 deletions doc/python/gantt.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ jupyter:
text_representation:
extension: .md
format_name: markdown
format_version: '1.1'
jupytext_version: 1.1.1
format_version: '1.2'
jupytext_version: 1.4.2
kernelspec:
display_name: Python 3
language: python
Expand All @@ -20,7 +20,7 @@ jupyter:
name: python
nbconvert_exporter: python
pygments_lexer: ipython3
version: 3.6.7
version: 3.7.7
plotly:
description: How to make Gantt Charts in Python with Plotly. Gantt Charts use
horizontal bars to represent the start and end times of tasks.
Expand All @@ -37,109 +37,100 @@ jupyter:
A [Gantt chart](https://en.wikipedia.org/wiki/Gantt_chart) is a type of bar chart that illustrates a project schedule. The chart lists the tasks to be performed on the vertical axis, and time intervals on the horizontal axis. The width of the horizontal bars in the graph shows the duration of each activity.


Gantt charts can be made using a [figure factory](/python/figure-factories/) as detailed in this page. See also the [bar charts examples](https://plotly.com/python/bar-charts/).
### Gantt Charts and Timelines with plotly.express

[Plotly Express](/python/plotly-express/) is the easy-to-use, high-level interface to Plotly, which [operates on a variety of types of data](/python/px-arguments/) and produces [easy-to-style figures](/python/styling-plotly-express/). With `px.timeline` (*introduced in version 4.9*) each data point is represented as a horizontal bar with a start and end point specified as dates.

#### Simple Gantt Chart
The `px.timeline` function by default sets the X-axis to be of `type=date`, so it can be configured like any [time-series chart](/python/time-series/).

Plotly Express also supports a [general-purpose `px.bar` function for bar charts](/python/bar-charts/).

```python
import plotly.figure_factory as ff
import plotly.express as px
import pandas as pd

df = [dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28'),
dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15'),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30')]
df = pd.DataFrame([
dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28'),
dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15'),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30')
])

fig = ff.create_gantt(df)
fig = px.timeline(df, x_start="Start", x_end="Finish", y="Task")
fig.update_yaxes(autorange="reversed") # otherwise tasks are listed from the bottom up
fig.show()
```

#### Index by Numeric Variable
`px.timeline` supports [discrete color](/python/discrete-color/) as above, or [continuous color](/python/colorscales/) as follows.

```python
import plotly.figure_factory as ff
import plotly.express as px
import pandas as pd

df = [dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28', Complete=10),
dict(Task="Job B", Start='2008-12-05', Finish='2009-04-15', Complete=60),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30', Complete=95)]
df = pd.DataFrame([
dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28', Resource="Alex"),
dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15', Resource="Alex"),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30', Resource="Max")
])

fig = ff.create_gantt(df, colors='Viridis', index_col='Complete', show_colorbar=True)
fig = px.timeline(df, x_start="Start", x_end="Finish", y="Task", color="Resource")
fig.update_yaxes(autorange="reversed")
fig.show()
```

#### Index by String Variable

```python
import plotly.figure_factory as ff

df = [dict(Task="Job A", Start='2009-01-01', Finish='2009-02-01', Resource='Apple'),
dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15', Resource='Grape'),
dict(Task="Job C", Start='2009-04-20', Finish='2009-09-30', Resource='Banana')]
import plotly.express as px
import pandas as pd

colors = ['#7a0504', (0.2, 0.7, 0.3), 'rgb(210, 60, 180)']
df = pd.DataFrame([
dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28', Completion_pct=50),
dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15', Completion_pct=25),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30', Completion_pct=75)
])

fig = ff.create_gantt(df, colors=colors, index_col='Resource', reverse_colors=True,
show_colorbar=True)
fig = px.timeline(df, x_start="Start", x_end="Finish", y="Task", color="Completion_pct")
fig.update_yaxes(autorange="reversed")
fig.show()
```

#### Use a Dictionary for Colors
It is also possible to have multiple bars on the same horizontal line, say by resource:

```python
import plotly.figure_factory as ff
*Note*: When setting `color` to the same value as `y`, `autorange` should not be set to `reverse`, so as to list the value of the Y axis in the same order as the legend entries.

df = [dict(Task="Job A", Start='2016-01-01', Finish='2016-01-02', Resource='Apple'),
dict(Task="Job B", Start='2016-01-02', Finish='2016-01-04', Resource='Grape'),
dict(Task="Job C", Start='2016-01-02', Finish='2016-01-03', Resource='Banana')]
```python
import plotly.express as px
import pandas as pd

colors = dict(Apple='rgb(220, 0, 0)', Grape='rgb(170, 14, 200)', Banana=(1, 0.9, 0.16))
df = pd.DataFrame([
dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28', Resource="Alex"),
dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15', Resource="Alex"),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30', Resource="Max")
])

fig = ff.create_gantt(df, colors=colors, index_col='Resource', show_colorbar=True)
fig = px.timeline(df, x_start="Start", x_end="Finish", y="Resource", color="Resource")
fig.show()
```

#### Use a Pandas Dataframe
#### Deprecated Figure Factory

Prior to the introduction of `plotly.express.timeline()` in version 4.9, the recommended way to make Gantt charts was to use the now-deprecated `create_gantt()` [figure factory](/python/figure-factories/), as follows:

```python
import plotly.figure_factory as ff

import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gantt_example.csv')
df = [dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28'),
dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15'),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30')]

fig = ff.create_gantt(df, colors=['#333F44', '#93e4c1'], index_col='Complete',
show_colorbar=True, bar_width=0.2, showgrid_x=True, showgrid_y=True)
fig = ff.create_gantt(df)
fig.show()
```

#### Using Hours and Minutes in Times

```python
import plotly.figure_factory as ff
<!-- #region -->
#### Group Tasks Together

df = [
dict(Task='Morning Sleep', Start='2016-01-01', Finish='2016-01-01 6:00:00', Resource='Sleep'),
dict(Task='Breakfast', Start='2016-01-01 7:00:00', Finish='2016-01-01 7:30:00', Resource='Food'),
dict(Task='Work', Start='2016-01-01 9:00:00', Finish='2016-01-01 11:25:00', Resource='Brain'),
dict(Task='Break', Start='2016-01-01 11:30:00', Finish='2016-01-01 12:00:00', Resource='Rest'),
dict(Task='Lunch', Start='2016-01-01 12:00:00', Finish='2016-01-01 13:00:00', Resource='Food'),
dict(Task='Work', Start='2016-01-01 13:00:00', Finish='2016-01-01 17:00:00', Resource='Brain'),
dict(Task='Exercise', Start='2016-01-01 17:30:00', Finish='2016-01-01 18:30:00', Resource='Cardio'),
dict(Task='Post Workout Rest', Start='2016-01-01 18:30:00', Finish='2016-01-01 19:00:00', Resource='Rest'),
dict(Task='Dinner', Start='2016-01-01 19:00:00', Finish='2016-01-01 20:00:00', Resource='Food'),
dict(Task='Evening Sleep', Start='2016-01-01 21:00:00', Finish='2016-01-01 23:59:00', Resource='Sleep')
]

colors = dict(Cardio = 'rgb(46, 137, 205)',
Food = 'rgb(114, 44, 121)',
Sleep = 'rgb(198, 47, 105)',
Brain = 'rgb(58, 149, 136)',
Rest = 'rgb(107, 127, 135)')

fig = ff.create_gantt(df, colors=colors, index_col='Resource', title='Daily Schedule',
show_colorbar=True, bar_width=0.8, showgrid_x=True, showgrid_y=True)
fig.show()
```

#### Group Tasks Together
The following example shows how to use the now-deprecated `create_gantt()` [figure factory](/python/figure-factories/) to color tasks by a numeric variable.
<!-- #endregion -->

```python
import plotly.figure_factory as ff
Expand All @@ -162,7 +153,22 @@ fig = ff.create_gantt(df, colors=colors, index_col='Resource', show_colorbar=Tru
fig.show()
```

#### Color by Numeric Variable

The following example shows how to use the now-deprecated `create_gantt()` [figure factory](/python/figure-factories/) to color tasks by a numeric variable.

```python
import plotly.figure_factory as ff

df = [dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28', Complete=10),
dict(Task="Job B", Start='2008-12-05', Finish='2009-04-15', Complete=60),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30', Complete=95)]

fig = ff.create_gantt(df, colors='Viridis', index_col='Complete', show_colorbar=True)
fig.show()
```

#### Reference


For more info on `ff.create_gantt()`, see the [full function reference](https://plotly.com/python-api-reference/generated/plotly.figure_factory.create_gantt.html)
For more info on `ff.create_gantt()`, see the [full function reference](https://plotly.com/python-api-reference/generated/plotly.figure_factory.create_gantt.html)
2 changes: 2 additions & 0 deletions packages/python/plotly/plotly/express/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
line_geo,
area,
bar,
timeline,
bar_polar,
violin,
box,
Expand Down Expand Up @@ -81,6 +82,7 @@
"parallel_categories",
"area",
"bar",
"timeline",
"bar_polar",
"violin",
"box",
Expand Down
49 changes: 49 additions & 0 deletions packages/python/plotly/plotly/express/_chart_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ def bar(
hover_data=None,
custom_data=None,
text=None,
base=None,
error_x=None,
error_x_minus=None,
error_y=None,
Expand Down Expand Up @@ -357,6 +358,53 @@ def bar(
bar.__doc__ = make_docstring(bar, append_dict=_cartesian_append_dict)


def timeline(
data_frame=None,
x_start=None,
x_end=None,
y=None,
color=None,
facet_row=None,
facet_col=None,
facet_col_wrap=0,
facet_row_spacing=None,
facet_col_spacing=None,
hover_name=None,
hover_data=None,
custom_data=None,
text=None,
animation_frame=None,
animation_group=None,
category_orders={},
labels={},
color_discrete_sequence=None,
color_discrete_map={},
color_continuous_scale=None,
range_color=None,
color_continuous_midpoint=None,
opacity=None,
range_x=None,
range_y=None,
title=None,
template=None,
width=None,
height=None,
):
"""
In a timeline plot, each row of `data_frame` is represented as a rectangular
mark on an x axis of type `date`, spanning from `x_start` to `x_end`.
"""
return make_figure(
args=locals(),
constructor="timeline",
trace_patch=dict(textposition="auto", orientation="h"),
layout_patch=dict(barmode="overlay"),
)


timeline.__doc__ = make_docstring(timeline)


def histogram(
data_frame=None,
x=None,
Expand Down Expand Up @@ -847,6 +895,7 @@ def bar_polar(
hover_name=None,
hover_data=None,
custom_data=None,
base=None,
animation_frame=None,
animation_group=None,
category_orders={},
Expand Down
36 changes: 34 additions & 2 deletions packages/python/plotly/plotly/express/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

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

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

if "is_timeline" in args:
fig.update_xaxes(type="date")

return fig.layout


Expand Down Expand Up @@ -1599,6 +1603,31 @@ def aggfunc_continuous(x):
return args


def process_dataframe_timeline(args):
"""
Massage input for bar traces for px.timeline()
"""
args["is_timeline"] = True
if args["x_start"] is None or args["x_end"] is None:
raise ValueError("Both x_start and x_end are required")

try:
x_start = pd.to_datetime(args["data_frame"][args["x_start"]])
x_end = pd.to_datetime(args["data_frame"][args["x_end"]])
except (ValueError, TypeError):
raise TypeError(
"Both x_start and x_end must refer to data convertible to datetimes."
)

# note that we are not adding any columns to the data frame here, so no risk of overwrite
args["data_frame"][args["x_end"]] = (x_end - x_start).astype("timedelta64[ms]")
args["x"] = args["x_end"]
del args["x_end"]
args["base"] = args["x_start"]
del args["x_start"]
return args


def infer_config(args, constructor, trace_patch, layout_patch):
attrs = [k for k in direct_attrables + array_attrables if k in args]
grouped_attrs = []
Expand Down Expand Up @@ -1801,6 +1830,9 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None):
args = build_dataframe(args, constructor)
if constructor in [go.Treemap, go.Sunburst] and args["path"] is not None:
args = process_dataframe_hierarchy(args)
if constructor == "timeline":
constructor = go.Bar
args = process_dataframe_timeline(args)

trace_specs, grouped_mappings, sizeref, show_colorbar = infer_config(
args, constructor, trace_patch, layout_patch
Expand Down
Loading

0 comments on commit fc64079

Please sign in to comment.