plotly-calplot
Advanced tools
| from typing import Any, List, Tuple | ||
| import numpy as np | ||
| import pandas as pd | ||
| def get_month_names(data: pd.DataFrame, x: str) -> List[str]: | ||
| return list(data[x].dt.month_name().unique()) | ||
| def get_date_coordinates( | ||
| data: pd.DataFrame, x: str | ||
| ) -> Tuple[Any, List[float], List[int]]: | ||
| month_days = [] | ||
| for m in data[x].dt.month.unique(): | ||
| month_days.append(data.loc[data[x].dt.month == m].max()[x].day) | ||
| month_positions = (np.cumsum(month_days) - 15) / 7 | ||
| weekdays_in_year = [i.weekday() for i in data[x]] | ||
| # sometimes the last week of the current year conflicts with next year's january | ||
| # pandas will give those weeks the number 52 or 53, but this is bad news for this plot | ||
| # therefore we need a correction, for a more in-depth explanation check | ||
| # https://stackoverflow.com/questions/44372048/python-pandas-timestamp-week-returns-52-for-first-day-of-year | ||
| weeknumber_of_dates = ( | ||
| data[x].apply(lambda x: get_weeknumber_of_date(x)).values.tolist() | ||
| ) | ||
| return month_positions, weekdays_in_year, weeknumber_of_dates | ||
| def get_weeknumber_of_date(d: pd.Timestamp) -> int: | ||
| """ | ||
| Pandas week returns ISO week number, this function | ||
| returns gregorian week date | ||
| """ | ||
| return int(d.strftime("%W")) |
| from typing import Any, List | ||
| import pandas as pd | ||
| from plotly import graph_objects as go | ||
| def decide_layout( | ||
| dark_theme: bool, | ||
| title: str, | ||
| month_names: List[str], | ||
| month_positions: Any, | ||
| ) -> go.Layout: | ||
| if dark_theme: | ||
| layout = go.Layout( | ||
| title=title, | ||
| yaxis=dict( | ||
| showline=False, | ||
| showgrid=False, | ||
| zeroline=False, | ||
| tickmode="array", | ||
| ticktext=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], | ||
| tickvals=[0, 1, 2, 3, 4, 5, 6], | ||
| autorange="reversed", | ||
| ), | ||
| xaxis=dict( | ||
| showline=False, | ||
| showgrid=False, | ||
| zeroline=False, | ||
| tickmode="array", | ||
| ticktext=month_names, | ||
| tickvals=month_positions, | ||
| ), | ||
| font={"size": 10, "color": "#fff"}, | ||
| paper_bgcolor=("#333"), | ||
| plot_bgcolor=("#333"), | ||
| margin=dict(t=20, b=20), | ||
| showlegend=False, | ||
| ) | ||
| else: | ||
| layout = go.Layout( | ||
| title=title, | ||
| yaxis=dict( | ||
| showline=False, | ||
| showgrid=False, | ||
| zeroline=False, | ||
| tickmode="array", | ||
| ticktext=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], | ||
| tickvals=[0, 1, 2, 3, 4, 5, 6], | ||
| autorange="reversed", | ||
| ), | ||
| xaxis=dict( | ||
| showline=False, | ||
| showgrid=False, | ||
| zeroline=False, | ||
| tickmode="array", | ||
| ticktext=month_names, | ||
| tickvals=month_positions, | ||
| ), | ||
| font={"size": 10, "color": "#9e9e9e"}, | ||
| plot_bgcolor=("#fff"), | ||
| margin=dict(t=20, b=20), | ||
| showlegend=False, | ||
| ) | ||
| return layout | ||
| def create_month_lines( | ||
| cplt: List[go.Figure], | ||
| month_lines_color: str, | ||
| month_lines_width: int, | ||
| data: pd.DataFrame, | ||
| weekdays_in_year: List[float], | ||
| weeknumber_of_dates: List[int], | ||
| ) -> go.Figure: | ||
| kwargs = dict( | ||
| mode="lines", | ||
| line=dict(color=month_lines_color, width=month_lines_width), | ||
| hoverinfo="skip", | ||
| ) | ||
| for date, dow, wkn in zip(data, weekdays_in_year, weeknumber_of_dates): | ||
| if date.day == 1: | ||
| cplt += [go.Scatter(x=[wkn - 0.5, wkn - 0.5], y=[dow - 0.5, 6.5], **kwargs)] | ||
| if dow: | ||
| cplt += [ | ||
| go.Scatter( | ||
| x=[wkn - 0.5, wkn + 0.5], y=[dow - 0.5, dow - 0.5], **kwargs | ||
| ), | ||
| go.Scatter(x=[wkn + 0.5, wkn + 0.5], y=[dow - 0.5, -0.5], **kwargs), | ||
| ] | ||
| return cplt | ||
| def update_plot_with_current_layout( | ||
| fig: go.Figure, | ||
| cplt: go.Figure, | ||
| row: int, | ||
| layout: go.Layout, | ||
| width: Any, | ||
| total_height: Any, | ||
| ) -> go.Figure: | ||
| fig.update_layout(layout) | ||
| fig.update_xaxes(layout["xaxis"]) | ||
| fig.update_yaxes(layout["yaxis"]) | ||
| fig.update_layout(width=width, height=total_height) | ||
| fig.add_traces(cplt, rows=[(row + 1)] * len(cplt), cols=[1] * len(cplt)) | ||
| return fig |
| from typing import List | ||
| import numpy as np | ||
| import pandas as pd | ||
| from plotly import graph_objects as go | ||
| def create_heatmap_without_formatting( | ||
| data: pd.DataFrame, | ||
| x: str, | ||
| y: str, | ||
| weeknumber_of_dates: List[int], | ||
| weekdays_in_year: List[float], | ||
| gap: int, | ||
| year: int, | ||
| colorscale: str, | ||
| name: str, | ||
| ) -> List[go.Figure]: | ||
| raw_heatmap = [ | ||
| go.Heatmap( | ||
| x=weeknumber_of_dates, | ||
| y=weekdays_in_year, | ||
| z=data[y], | ||
| xgap=gap, # this | ||
| ygap=gap, # and this is used to make the grid-like apperance | ||
| showscale=False, | ||
| colorscale=colorscale, # user can setup their colorscale | ||
| hovertemplate="%{customdata[0]} <br>%{customdata[1]}=%{z} <br>Week=%{x}", | ||
| customdata=np.stack((data[x].astype(str), [name] * data.shape[0]), axis=-1), | ||
| name=str(year), | ||
| ) | ||
| ] | ||
| return raw_heatmap |
| from pandas.core.frame import DataFrame | ||
| from plotly import graph_objects as go | ||
| from plotly_calplot.date_extractors import get_date_coordinates, get_month_names | ||
| from plotly_calplot.layout_formatter import ( | ||
| create_month_lines, | ||
| decide_layout, | ||
| update_plot_with_current_layout, | ||
| ) | ||
| from plotly_calplot.raw_heatmap import create_heatmap_without_formatting | ||
| def year_calplot( | ||
| data: DataFrame, | ||
| x: str, | ||
| y: str, | ||
| fig: go.Figure, | ||
| row: int, | ||
| year: int, | ||
| name: str = "y", | ||
| dark_theme: bool = False, | ||
| month_lines_width: int = 1, | ||
| month_lines_color: str = "#9e9e9e", | ||
| gap: int = 1, | ||
| width: int = 800, | ||
| colorscale: str = "greens", | ||
| title: str = "", | ||
| month_lines: bool = True, | ||
| total_height: int = None, | ||
| ) -> go.Figure: | ||
| """ | ||
| Each year is subplotted separately and added to the main plot | ||
| """ | ||
| month_names = get_month_names(data, x) | ||
| month_positions, weekdays_in_year, weeknumber_of_dates = get_date_coordinates( | ||
| data, x | ||
| ) | ||
| # the calendar is actually a heatmap :) | ||
| cplt = create_heatmap_without_formatting( | ||
| data, x, y, weeknumber_of_dates, weekdays_in_year, gap, year, colorscale, name | ||
| ) | ||
| if month_lines: | ||
| cplt = create_month_lines( | ||
| cplt, | ||
| month_lines_color, | ||
| month_lines_width, | ||
| data[x], | ||
| weekdays_in_year, | ||
| weeknumber_of_dates, | ||
| ) | ||
| layout = decide_layout(dark_theme, title, month_names, month_positions) | ||
| fig = update_plot_with_current_layout(fig, cplt, row, layout, width, total_height) | ||
| return fig |
| from datetime import date | ||
| import pandas as pd | ||
| from pandas.core.frame import DataFrame | ||
| def fill_empty_with_zeros( | ||
| selected_year_data: DataFrame, x: str, dark_theme: bool, year: int | ||
| ) -> pd.DataFrame: | ||
| year_min_date = date(year=year, month=1, day=1) | ||
| year_max_date = date(year=year, month=12, day=31) | ||
| df = pd.DataFrame({x: pd.date_range(year_min_date, year_max_date)}) | ||
| final_df = df.merge(selected_year_data, how="left") | ||
| if not dark_theme: | ||
| final_df = final_df.fillna(0) | ||
| return final_df |
+10
-2
| Metadata-Version: 2.1 | ||
| Name: plotly-calplot | ||
| Version: 0.1.7 | ||
| Version: 0.1.8 | ||
| Summary: Calendar Plot made with Plotly | ||
@@ -9,9 +9,17 @@ Home-page: https://github.com/brunorosilva/plotly-calplot | ||
| Author-email: b.rosilva1@gmail.com | ||
| Requires-Python: >=3.7.1,<4.0.0 | ||
| Requires-Python: >=3.8,<4.0.0 | ||
| Classifier: License :: OSI Approved :: MIT License | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.8 | ||
| Classifier: Programming Language :: Python :: 3.9 | ||
| Requires-Dist: flake8 (>=4.0.1,<5.0.0) | ||
| Requires-Dist: isort (>=5.10.1,<6.0.0) | ||
| Requires-Dist: mypy (>=0.942,<0.943) | ||
| Requires-Dist: numpy (>=1.22.3,<2.0.0) | ||
| Requires-Dist: pandas (>=1.0.5,<2.0.0) | ||
| Requires-Dist: plotly (>=5.4.0,<6.0.0) | ||
| Requires-Dist: pytest (>=7.1.1,<8.0.0) | ||
| Requires-Dist: pytest-cov (>=3.0.0,<4.0.0) | ||
| Requires-Dist: vulture (>=2.3,<3.0) | ||
| Project-URL: Repository, https://github.com/brunorosilva/plotly-calplot | ||
@@ -18,0 +26,0 @@ Description-Content-Type: text/markdown |
@@ -1,1 +0,3 @@ | ||
| from .calplot import calplot | ||
| from .calplot import calplot # noqa: F401 | ||
| __version__ = "0.0.2" |
@@ -1,3 +0,1 @@ | ||
| import numpy as np | ||
| import pandas as pd | ||
| from pandas.core.frame import DataFrame | ||
@@ -7,166 +5,6 @@ from plotly import graph_objects as go | ||
| from plotly_calplot.single_year_calplot import year_calplot | ||
| from plotly_calplot.utils import fill_empty_with_zeros | ||
| def get_weeknumber_of_date(d): | ||
| """ | ||
| Pandas week returns some strange values, this function fixes'em | ||
| """ | ||
| if d.month == 1 and d.week > 50: | ||
| return 0 | ||
| elif d.month == 12 and d.week < 10: | ||
| return 53 | ||
| else: | ||
| return d.week | ||
| def year_calplot( | ||
| data: DataFrame, | ||
| x, | ||
| y, | ||
| name, | ||
| year, | ||
| fig, | ||
| row, | ||
| month_lines, | ||
| month_lines_width, | ||
| month_lines_color, | ||
| colorscale, | ||
| gap, | ||
| title, | ||
| dark_theme, | ||
| width, | ||
| total_height, | ||
| ): | ||
| """ | ||
| Each year is subplotted separately and added to the main plot | ||
| """ | ||
| month_names = list(data[x].dt.month_name().unique()) | ||
| month_days = [] | ||
| for month in data[x].dt.month.unique(): | ||
| month_days.append(data.loc[data[x].dt.month == month].max()[x].day) | ||
| month_positions = (np.cumsum(month_days) - 15) / 7 | ||
| weekdays_in_year = [i.weekday() for i in data[x]] | ||
| # sometimes the last week of the current year conflicts with next year's january | ||
| # pandas will give those weeks the number 52 or 53, but this is bad news for this plot | ||
| # therefore we need a correction, for a more in-depth explanation check | ||
| # https://stackoverflow.com/questions/44372048/python-pandas-timestamp-week-returns-52-for-first-day-of-year | ||
| weeknumber_of_dates = ( | ||
| data[x].apply(lambda x: get_weeknumber_of_date(x)).values.tolist() | ||
| ) | ||
| # the calendar is actually a heatmap :) | ||
| cplt = [ | ||
| go.Heatmap( | ||
| x=weeknumber_of_dates, | ||
| y=weekdays_in_year, | ||
| z=data[y], | ||
| xgap=gap, # this | ||
| ygap=gap, # and this is used to make the grid-like apperance | ||
| showscale=False, | ||
| colorscale=colorscale, # user can setup their colorscale | ||
| hovertemplate="%{customdata[0]} <br>%{customdata[1]}=%{z} <br>Week=%{x}", | ||
| customdata=np.stack((data[x].astype(str), [name] * data.shape[0]), axis=-1), | ||
| name=str(year), | ||
| ) | ||
| ] | ||
| if month_lines: | ||
| kwargs = dict( | ||
| mode="lines", | ||
| line=dict(color=month_lines_color, width=month_lines_width), | ||
| hoverinfo="skip", | ||
| ) | ||
| for date, dow, wkn in zip(data[x], weekdays_in_year, weeknumber_of_dates): | ||
| if date.day == 1: | ||
| cplt += [ | ||
| go.Scatter(x=[wkn - 0.5, wkn - 0.5], y=[dow - 0.5, 6.5], **kwargs) | ||
| ] | ||
| if dow: | ||
| cplt += [ | ||
| go.Scatter( | ||
| x=[wkn - 0.5, wkn + 0.5], y=[dow - 0.5, dow - 0.5], **kwargs | ||
| ), | ||
| go.Scatter( | ||
| x=[wkn + 0.5, wkn + 0.5], y=[dow - 0.5, -0.5], **kwargs | ||
| ), | ||
| ] | ||
| # if row == 0: | ||
| if dark_theme: | ||
| layout = go.Layout( | ||
| title=title, | ||
| yaxis=dict( | ||
| showline=False, | ||
| showgrid=False, | ||
| zeroline=False, | ||
| tickmode="array", | ||
| ticktext=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], | ||
| tickvals=[0, 1, 2, 3, 4, 5, 6], | ||
| autorange="reversed", | ||
| ), | ||
| xaxis=dict( | ||
| showline=False, | ||
| showgrid=False, | ||
| zeroline=False, | ||
| tickmode="array", | ||
| ticktext=month_names, | ||
| tickvals=month_positions, | ||
| ), | ||
| font={"size": 10, "color": "#fff"}, | ||
| paper_bgcolor=("#333"), | ||
| plot_bgcolor=("#333"), | ||
| margin=dict(t=20, b=20), | ||
| showlegend=False, | ||
| ) | ||
| fig.update_layout(layout) | ||
| fig.update_xaxes(layout["xaxis"]) | ||
| fig.update_yaxes(layout["yaxis"]) | ||
| else: | ||
| layout = go.Layout( | ||
| title=title, | ||
| yaxis=dict( | ||
| showline=False, | ||
| showgrid=False, | ||
| zeroline=False, | ||
| tickmode="array", | ||
| ticktext=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], | ||
| tickvals=[0, 1, 2, 3, 4, 5, 6], | ||
| autorange="reversed", | ||
| ), | ||
| xaxis=dict( | ||
| showline=False, | ||
| showgrid=False, | ||
| zeroline=False, | ||
| tickmode="array", | ||
| ticktext=month_names, | ||
| tickvals=month_positions, | ||
| ), | ||
| font={"size": 10, "color": "#9e9e9e"}, | ||
| plot_bgcolor=("#fff"), | ||
| margin=dict(t=20, b=20), | ||
| showlegend=False, | ||
| ) | ||
| fig.update_layout(layout) | ||
| fig.update_xaxes(layout["xaxis"]) | ||
| fig.update_yaxes(layout["yaxis"]) | ||
| fig.update_layout(width=width, height=total_height) | ||
| fig.add_traces(cplt, rows=[(row + 1)] * len(cplt), cols=[1] * len(cplt)) | ||
| return fig | ||
| def fill_empty_with_zeros(selected_year_data: DataFrame, x, dark_theme, year: int): | ||
| year_min_date = "01-01-" + str(year) | ||
| year_max_date = "31-12-" + str(year) | ||
| df = pd.DataFrame({x: pd.date_range(year_min_date, year_max_date)}) | ||
| final_df = df.merge(selected_year_data, how="left") | ||
| if not dark_theme: | ||
| final_df = final_df.fillna(0) | ||
| return final_df | ||
| def calplot( | ||
@@ -188,3 +26,3 @@ data: DataFrame, | ||
| space_between_plots: float = 0.08, | ||
| ): | ||
| ) -> go.Figure: | ||
| """ | ||
@@ -198,3 +36,3 @@ Yearly Calendar Heatmap | ||
| one value column for displaying in the plot | ||
| x : str | ||
@@ -243,3 +81,3 @@ The name of the date like column in data | ||
| according to the amount of years in data | ||
| space_between_plots: float = 0.08 | ||
@@ -246,0 +84,0 @@ controls the vertical space between the plots |
+9
-2
| [tool.poetry] | ||
| name = "plotly_calplot" | ||
| version = "0.1.7" | ||
| version = "0.1.8" | ||
| description = "Calendar Plot made with Plotly" | ||
@@ -11,5 +11,12 @@ authors = ["Bruno Rodrigues Silva <b.rosilva1@gmail.com>"] | ||
| [tool.poetry.dependencies] | ||
| python = "^3.7.1" | ||
| python = ">=3.8,<4.0.0" | ||
| plotly = "^5.4.0" | ||
| pandas = "^1.0.5" | ||
| isort = "^5.10.1" | ||
| flake8 = "^4.0.1" | ||
| vulture = "^2.3" | ||
| mypy = "^0.942" | ||
| numpy = "^1.22.3" | ||
| pytest = "^7.1.1" | ||
| pytest-cov = "^3.0.0" | ||
@@ -16,0 +23,0 @@ [tool.poetry.dev-dependencies] |
+11
-3
@@ -11,7 +11,15 @@ # -*- coding: utf-8 -*- | ||
| install_requires = \ | ||
| ['pandas>=1.0.5,<2.0.0', 'plotly>=5.4.0,<6.0.0'] | ||
| ['flake8>=4.0.1,<5.0.0', | ||
| 'isort>=5.10.1,<6.0.0', | ||
| 'mypy>=0.942,<0.943', | ||
| 'numpy>=1.22.3,<2.0.0', | ||
| 'pandas>=1.0.5,<2.0.0', | ||
| 'plotly>=5.4.0,<6.0.0', | ||
| 'pytest-cov>=3.0.0,<4.0.0', | ||
| 'pytest>=7.1.1,<8.0.0', | ||
| 'vulture>=2.3,<3.0'] | ||
| setup_kwargs = { | ||
| 'name': 'plotly-calplot', | ||
| 'version': '0.1.7', | ||
| 'version': '0.1.8', | ||
| 'description': 'Calendar Plot made with Plotly', | ||
@@ -27,3 +35,3 @@ 'long_description': '# Calendar Heatmap with Plotly\nMaking it easier to visualize and costumize time relevant or time series data with plotly interaction.\n\nThis plot is a very similar to the contribuitions available on Github and Gitlab profile pages and to [Calplot](https://github.com/tomkwok/calplot) - which is a pyplot implementation of the calendar heatmap, thus it is not interactive right off the bat.\n\nThe first mention I could find of this plot being made with plotly was in [this forum post](https://community.plotly.com/t/colored-calendar-heatmap-in-dash/10907/16) and it got my attention as something it should be easily available to anyone.\n\n# Installation\n``` bash\npip install plotly-calplot\n```\n\n# Examples\n\nIn [this Medium article](https://medium.com/@brunorosilva/5fc322125db7) I covered lot\'s of usage methods for this library.\n``` python\nfrom plotly_calplot import calplot\n\nfig = calplot(df, x="date", y="value")\nfig.show()\n# you can also adjust layout and your usual plotly stuff\n```\n\n<img src="https://github.com/brunorosilva/plotly-calplot/blob/main/assets/images/example.png?raw=true">\n', | ||
| 'install_requires': install_requires, | ||
| 'python_requires': '>=3.7.1,<4.0.0', | ||
| 'python_requires': '>=3.8,<4.0.0', | ||
| } | ||
@@ -30,0 +38,0 @@ |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
16790
22.13%11
83.33%358
28.78%