Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for date format in addition to integers (day number, etc) #9

Merged
merged 3 commits into from
Nov 15, 2020
Merged
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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ The package only contains one module called `barplot`. This module takes the fol
* `top_entries`: (type: numeric) Number of top entries to display (e.g. 5 for top 5 for any given time period...)

The `barplot` object contains one main method:
* `plot(title, orientation, item_label, value_label)`:
* `plot(title, orientation, item_label, value_label, time_label, frame_duration, date_format)`:
* `title`: (type: string) Main title of the plot (static by default)
* `orientation`: (type: string -> 'horizontal' or 'vertical') whether bars grow upwards ('vertical') or rightwards ('horizontal')
* `initial_frame`: (type: numeric or string) Should either match one of the values from the `time_column` or be provided as `min` or `max`, in which case the initial frame would correspond to the minimum or maximum value of the `time_column`.
* `item_label`: (type: string) Title of the axis corresponding to the item values
* `value_label`: (type: string) Title of the axis corresponding to the value
* `time_label`: (type: string) Title for the time axis which appears in each frame next to the formmated date/time variable
* `frame_duration`: (type: int -> default 500) Frame and transition duration time in milliseconds

* `date_format`: (type: str) Format for the displayed date/time, should be compatible with strftime format, [see strftime reference](https://strftime.org/).

### Example plot: Top 10 crops from 1961 to 2018

See example notebooks under `example/`.

```python
import pandas as pd
from raceplotly.plots import barplot
Expand Down
44 changes: 31 additions & 13 deletions raceplotly/plots.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import plotly.graph_objects as go
import pandas as pd
from random import sample
import numpy as np
import datetime

class barplot(object):
'''
Expand All @@ -21,8 +23,9 @@ def __init__(self, df: pd.DataFrame = None, item_column: str = None, value_colum
self.title = ''
self.fig = None
self.orientation = None
self.date_format = None

def plot(self, title: str = None, orientation: str = 'horizontal', initial_frame = 'min', value_label: str = None, item_label: str = None, frame_duration: int = 500):
def plot(self, title: str = None, orientation: str = 'horizontal', initial_frame = 'min', value_label: str = None, item_label: str = None, time_label: str = 'Date: ', frame_duration: int = 500, date_format: str = None):
'''
By default the time variable is appended to the title string

Expand All @@ -31,7 +34,7 @@ def plot(self, title: str = None, orientation: str = 'horizontal', initial_frame
'''

self.orientation = orientation # record last self.orientation used

self.date_format = date_format # record last date_format
self.title = title

#get colors
Expand All @@ -41,7 +44,7 @@ def plot(self, title: str = None, orientation: str = 'horizontal', initial_frame
self.__make_frame1(initial_frame)

# define ui: adds Play,Pause buttons and defines empty slider
self.__define_ui()
self.__define_ui(time_label)

# make frames, also updates sliders at each frame
self.fig['frames'] = self.__make_frames(title, frame_duration)
Expand Down Expand Up @@ -73,15 +76,28 @@ def __make_frames(self, title: str, frame_duration: int):

x, y = self.__check_orientation()
frames = []
for year in range(self.df[self.time_column].min(), self.df[self.time_column].max()+1):
## sorted date to iterate through them
dates = np.sort(self.df[self.time_column].unique())
for date in dates:

# specified date string for plotly frame id and for printing in plot
if isinstance(date, np.datetime64):
date = pd.to_datetime(str(date))
try:
date_str = date.strftime(format = self.date_format) if self.date_format is not None else str(date)
except:
raise Exception("Something was wrong setting the date_format, please check the strftime (https://strftime.org/) documentation for date formatting and try again")
else:
date_str = str(date)

# filter out by year
snap_data = self.df[self.df['Year'] == year]
snap_data = self.df[self.df[self.time_column] == date]

# get_top 10
snap_data = snap_data.sort_values('Value', ascending=False).iloc[:self.top_entries,:]
snap_data = snap_data.sort_values(self.value_column, ascending=False).iloc[:self.top_entries,:]

# get top enttry at top of chart
snap_data = snap_data.sort_values('Value', ascending=True)
snap_data = snap_data.sort_values(self.value_column, ascending=True)

# make frame
frames.append(
Expand All @@ -93,6 +109,8 @@ def __make_frames(self, title: str, frame_duration: int):
marker_color=snap_data['color'],
cliponaxis=False,
hoverinfo='all',
hovertemplate = '<extra></extra>', #annoying and obscure, see docs
## (https://community.plotly.com/t/remove-trace-0-next-to-hover/33731)
textposition='outside',
texttemplate='%{x}<br>%{y:.4s}' if self.orientation == 'vertical' else '%{y}<br>%{x:.4s}',
textangle = 0,
Expand Down Expand Up @@ -123,17 +141,17 @@ def __make_frames(self, title: str, frame_duration: int):
bargap=0.15,
title= title
),
name = year
name = date_str
)
)

slider_step = {"args": [
[year],
[date_str],
{"frame": {"duration": frame_duration, "redraw": False},
"mode": "immediate",
"transition": {"duration": frame_duration}}
],
"label": year,
"label": date_str,
"method": "animate"}
self.sliders_dict["steps"].append(slider_step)

Expand All @@ -160,7 +178,7 @@ def __make_frame1(self, time_frame1 = 'min'):
frame1 = frame1.sort_values(self.value_column, ascending=False).iloc[:self.top_entries,:]

# return in ascending order so that top bar corresponds to largest value
frame1 = frame1.sort_values('Value', ascending=True)
frame1 = frame1.sort_values(self.value_column, ascending=True)

x, y = self.__check_orientation()

Expand Down Expand Up @@ -220,7 +238,7 @@ def __check_orientation(self):

return x, y

def __define_ui(self):
def __define_ui(self, time_label: str = None):

self.fig["layout"]["updatemenus"] = [
{
Expand Down Expand Up @@ -257,7 +275,7 @@ def __define_ui(self):
"xanchor": "left",
"currentvalue": {
"font": {"size": 20},
"prefix": "Year:",
"prefix": time_label,
"visible": True,
"xanchor": "right"
},
Expand Down
3 changes: 1 addition & 2 deletions tests/test_module.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/usr/bin/env python

import pandas as pd
from raceplotly.plots import barplot
import pytest
Expand All @@ -21,4 +19,5 @@ def test_plot_vertical():
bar_race.plot(title = 'Top 10 Crops from 1961 to 2018',
orientation='vertical',
item_label='Crops')
assert True