diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index dbc6f8e..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "code-runner.runInTerminal": true,
- "code-runner.saveFileBeforeRun": true,
- "code-runner.clearPreviousOutput": true,
-}
\ No newline at end of file
diff --git a/app.py b/app.py
index 29de324..43e4af5 100644
--- a/app.py
+++ b/app.py
@@ -1,12 +1,15 @@
+"""Main Dash App for Rainfall Analysis"""
+
+from itertools import product
+from pathlib import Path
+from dash import dcc, html, Input, Output, State
+import pandas as pd
import dash
import dash_bootstrap_components as dbc
-import pandas as pd
import plotly.io as pio
-import pyfigure, pyfunc, pylayout, pylayoutfunc
-from dash import dcc, html, Input, Output, State
-from pathlib import Path
from pyconfig import appConfig
from pytemplate import hktemplate
+import pyfigure, pyfunc, pylayout, pylayoutfunc # pylint: disable=multiple-imports
pio.templates.default = hktemplate
@@ -17,14 +20,14 @@
# BOOTSRAP THEME
THEME = appConfig.DASH_THEME.THEME
-dbc_css = (
- "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates@V1.0.4/dbc.min.css"
+DBC_CSS = (
+ "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates@V1.1.2/dbc.min.css"
)
# APP
app = dash.Dash(
APP_TITLE,
- external_stylesheets=[getattr(dbc.themes, THEME), dbc_css],
+ external_stylesheets=[getattr(dbc.themes, THEME), DBC_CSS],
title=APP_TITLE,
update_title=UPDATE_TITLE,
meta_tags=[
@@ -39,6 +42,7 @@
pylayout.HTML_TITLE,
pylayout.HTML_ALERT_README,
pylayout.HTML_ROW_BUTTON_UPLOAD,
+ pylayout.HTML_ROW_BUTTON_EXAMPLE,
pylayout.HTML_ROW_TABLE,
pylayout.HTML_ROW_BUTTON_VIZ,
pylayout.HTML_ROW_OPTIONS_GRAPH_RAINFALL,
@@ -49,11 +53,11 @@
pylayout.HTML_ROW_GRAPH_ANALYSIS,
pylayout.HTML_ROW_GRAPH_CUMSUM,
pylayout.HTML_ROW_GRAPH_CONSISTENCY,
- # pylayout.HTML_MADEBY,
+ html.Hr(),
pylayout.HTML_SUBTITLE,
pylayout.HTML_FOOTER,
],
- fluid=True,
+ fluid=False,
className="dbc",
)
@@ -69,19 +73,32 @@
Input("dcc-upload", "contents"),
State("dcc-upload", "filename"),
State("dcc-upload", "last_modified"),
- Input("button-skip", "n_clicks"),
+ Input("button-example-1", "n_clicks"),
+ Input("button-example-2", "n_clicks"),
+ Input("button-example-3", "n_clicks"),
+ Input("button-example-4", "n_clicks"),
prevent_initial_call=True,
)
-def callback_upload(content, filename, filedate, _):
+def callback_upload(content, filename, filedate, _b1, _b2, _b3, _b4):
+ """Callback for uploading data and displaying the table."""
+
ctx = dash.callback_context
if content is not None:
children, dataframe = pyfunc.parse_upload_data(content, filename, filedate)
- if ctx.triggered[0]["prop_id"] == "button-skip.n_clicks":
- dataframe = pd.read_csv(
- Path(r"./example_7Y5S.csv"), index_col=0, parse_dates=True
- )
+ example_data = {
+ "button-example-1.n_clicks": r"./example_7Y5S.csv",
+ "button-example-2.n_clicks": r"./example_2Y4S_named.csv",
+ "button-example-3.n_clicks": r"./example_9Y1S_named.csv",
+ "button-example-4.n_clicks": r"./example_1Y7S_named.csv",
+ }
+
+ context_trigger_prop_id = ctx.triggered[0]["prop_id"]
+
+ if context_trigger_prop_id in example_data:
+ example_file = example_data[context_trigger_prop_id]
+ dataframe = pd.read_csv(Path(example_file), index_col=0, parse_dates=True)
filename = None
filedate = None
@@ -130,6 +147,8 @@ def callback_upload(content, filename, filedate, _):
prevent_initial_call=True,
)
def callback_visualize(_, table_data, table_columns, graphbar_opt):
+ """Callback for visualizing the rainfall data."""
+
dataframe = pyfunc.transform_to_dataframe(table_data, table_columns)
row_download_table_style = {"visibility": "visible"}
@@ -139,13 +158,13 @@ def callback_visualize(_, table_data, table_columns, graphbar_opt):
button_analyze_outline = False
if dataframe.size > (366 * 8):
- fig = pyfigure.figure_scatter(dataframe)
+ fig = pyfigure.generate_scatter_figure(dataframe)
else:
row_graphbar_options_style = {"visibility": "visible"}
if graphbar_opt in ["group", "stack"]:
- fig = pyfigure.figure_bar(dataframe, graphbar_opt)
+ fig = pyfigure.generate_bar_figure(dataframe, graphbar_opt)
else:
- fig = pyfigure.figure_scatter(dataframe)
+ fig = pyfigure.generate_scatter_figure(dataframe)
return [
fig,
@@ -165,6 +184,7 @@ def callback_visualize(_, table_data, table_columns, graphbar_opt):
prevent_initial_call=True,
)
def callback_download_table(_, table_data, table_columns):
+ """Callback for downloading the table data."""
dataframe = pyfunc.transform_to_dataframe(table_data, table_columns)
return dcc.send_data_frame(dataframe.to_csv, "derived_table.csv")
@@ -182,6 +202,7 @@ def callback_download_table(_, table_data, table_columns):
prevent_initial_call=True,
)
def callback_analyze(_, table_data, table_columns):
+ """Callback for analyzing the rainfall data."""
button_viz_analysis_disabled = True
button_viz_analysis_outline = True
@@ -200,7 +221,7 @@ def callback_analyze(_, table_data, table_columns):
]
# CUMUMLATIVE SUM
- cumsum = pyfunc.calc_cumsum(dataframe)
+ cumsum = pyfunc.calculate_cumulative_sum(dataframe)
_, table_cumsum = pylayoutfunc.create_table_layout(
cumsum, "table-cumsum", deletable=False
@@ -219,8 +240,12 @@ def callback_analyze(_, table_data, table_columns):
button_viz_analysis_disabled = False
button_viz_analysis_outline = False
row_button_download_analysis_style = {"visibility": "visible"}
- except Exception as e:
- children = html.Div(f"SOMETHING ERROR {e}")
+ except (TypeError, ValueError) as e:
+ children = html.Div(
+ f"Input data or columns are not in the expected format: {e}"
+ )
+ except KeyError as e:
+ children = html.Div(f"Dataframe does not have the expected columns: {e}")
return [
children,
@@ -254,6 +279,7 @@ def callback_download_results(
cumsum_data,
cumsum_columns,
):
+ """Callback for downloading the analysis results."""
biweekly = (biweekly_data, biweekly_columns)
monthly = (monthly_data, monthly_columns)
@@ -310,7 +336,7 @@ def callback_graph_analysis(
cumsum_data,
cumsum_columns,
):
- from itertools import product
+ """Callback for generating the analysis graphs."""
label_periods = ["Biweekly", "Monthly", "Yearly"]
label_maxsum = ["Max + Sum"]
@@ -334,7 +360,7 @@ def callback_graph_analysis(
summary_all.append(dataframe)
graphs_maxsum = [
- pyfigure.figure_summary_maxsum(
+ pyfigure.generate_summary_maximum_sum(
summary,
title=f"{period}: {title}",
period=period,
@@ -343,12 +369,12 @@ def callback_graph_analysis(
for summary, title, period in zip(summary_all, label_maxsum * 3, label_periods)
]
graphs_raindry = [
- pyfigure.figure_summary_raindry(
+ pyfigure.generate_summary_rain_dry(
summary, title=f"{period}: {title}", period=period
)
for summary, title, period in zip(summary_all, label_raindry * 3, label_periods)
]
- graph_maxdate = [pyfigure.figure_summary_maxdate(summary_all)]
+ graph_maxdate = [pyfigure.generate_summary_maximum_date(summary_all)]
all_graphs = graphs_maxsum + graphs_raindry + graph_maxdate
labels = [": ".join(i) for i in product(label_ufunc, label_periods)]
@@ -363,7 +389,8 @@ def callback_graph_analysis(
cumsum = pyfunc.transform_to_dataframe(cumsum_data, cumsum_columns)
graph_cumsum = [
- pyfigure.figure_cumsum_single(cumsum, col=station) for station in cumsum.columns
+ pyfigure.generate_cumulative_sum(cumsum, data_column=station)
+ for station in cumsum.columns
]
children_cumsum = pylayoutfunc.create_tabcard_graph_layout(
@@ -375,13 +402,15 @@ def callback_graph_analysis(
if cumsum.columns.size == 1:
children_consistency = (
dcc.Graph(
- figure=pyfigure.figure_empty(text="Not Available for Single Station"),
+ figure=pyfigure.generate_empty_figure(
+ text="Not Available for Single Station"
+ ),
config={"staticPlot": True},
),
)
else:
graph_consistency = [
- pyfigure.figure_consistency(cumsum, col=station)
+ pyfigure.generate_scatter_with_trendline(cumsum, data_column=station)
for station in cumsum.columns
]
@@ -392,14 +421,5 @@ def callback_graph_analysis(
return children_analysis, children_cumsum, children_consistency
-@app.callback(
- Output("row-troubleshoot", "children"),
- Input("button-troubleshoot", "n_clicks"),
- prevent_initial_call=True,
-)
-def _callback_troubleshoot(_):
- return html.Div("troubleshoot")
-
-
if __name__ == "__main__":
app.run_server(debug=DEBUG)
diff --git a/app_config.yml b/app_config.yml
index a1cc01a..386170c 100644
--- a/app_config.yml
+++ b/app_config.yml
@@ -1,10 +1,10 @@
DASH_APP:
APP_TITLE: Rainfall Data Explorer
UPDATE_TITLE: Updating...
- DEBUG: False
+ DEBUG: FALSE
DASH_THEME:
- THEME: COSMO
+ THEME: ZEPHYR
TEMPLATE:
LOGO_SOURCE:
@@ -12,6 +12,6 @@ TEMPLATE:
SHOW_LEGEND_INSIDE: False
SHOW_RANGESELECTOR: False
-VERSION: v1.3.0
-GITHUB_LINK: https://github.com/fiakoenjiniring/rainfall
-GITHUB_REPO: fiakoenjiniring/rainfall
+VERSION: v1.4.0
+GITHUB_LINK: https://github.com/taruma/rainfall
+GITHUB_REPO: taruma/rainfall
diff --git a/pyconfig.py b/pyconfig.py
index cd879e7..849eb22 100644
--- a/pyconfig.py
+++ b/pyconfig.py
@@ -1,3 +1,5 @@
+"""This module is used to load the configuration file and make it available to the application."""
+
from box import Box
_CONFIG_PATH = "app_config.yml"
diff --git a/pyfigure.py b/pyfigure.py
index f27f961..1029cbc 100644
--- a/pyfigure.py
+++ b/pyfigure.py
@@ -1,47 +1,76 @@
-from dash import dcc
-from plotly.subplots import make_subplots
-from pyconfig import appConfig
+"""
+This module contains functions for generating different types of figures
+ related to rainfall data analysis.
+"""
+
+from collections import defaultdict, OrderedDict
+from itertools import cycle, islice
+import re
import numpy as np
+import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
+from dash import dcc
+from plotly.subplots import make_subplots
+from pyconfig import appConfig
import pytemplate
-import pandas as pd
-from collections import defaultdict, OrderedDict
-from itertools import cycle, islice
THRESHOLD_SUMMARY = (367 * 8) // 2
THRESHOLD_GRAPH_RAINFALL = 365 * 8
THRESHOLD_XAXES = 12 * 2 * 5
THRESHOLD_STATIONS = 8
+LABEL_GRAPH_RAINFALL = {
+ "title": "Rainfall Each Station",
+ "yaxis": {"title": "Rainfall (mm)"},
+ "xaxis": {"title": "Date"},
+ "legend": {"title": "Stations"},
+}
-def _generate_dict_watermark(n: int = 1, source=appConfig.TEMPLATE.WATERMARK_SOURCE):
- n = "" if n == 1 else n
- return dict(
- source=source,
- xref=f"x{n} domain",
- yref=f"y{n} domain",
- x=0.5,
- y=0.5,
- sizex=0.5,
- sizey=0.5,
- xanchor="center",
- yanchor="middle",
- name="watermark-hidrokit",
- layer="below",
- opacity=0.1,
- )
+current_font_color = pytemplate.FONT_COLOR_RGB_ALPHA
-LABEL_GRAPH_RAINFALL = dict(
- title="Rainfall Each Station",
- yaxis={"title": "Rainfall (mm)"},
- xaxis={"title": "Date"},
- legend={"title": "Stations"},
-)
+def generate_watermark(
+ subplot_number: int = 1, watermark_source=appConfig.TEMPLATE.WATERMARK_SOURCE
+):
+ """Generate a watermark for a subplot.
+
+ Args:
+ subplot_number (int, optional): The number of the subplot.
+ Defaults to 1.
+ watermark_source (str, optional): The source of the watermark.
+ Defaults to appConfig.TEMPLATE.WATERMARK_SOURCE.
+
+ Returns:
+ dict: A dictionary containing the watermark properties.
+ """
+ subplot_number = "" if subplot_number == 1 else subplot_number
+ return {
+ "source": watermark_source,
+ "xref": f"x{subplot_number} domain",
+ "yref": f"y{subplot_number} domain",
+ "x": 0.5,
+ "y": 0.5,
+ "sizex": 0.5,
+ "sizey": 0.5,
+ "xanchor": "center",
+ "yanchor": "middle",
+ "name": "watermark-hidrokit",
+ "layer": "below",
+ "opacity": 0.1,
+ }
-def figure_scatter(dataframe):
+def generate_scatter_figure(dataframe):
+ """
+ Generate a scatter plot figure based on the provided dataframe.
+
+ Parameters:
+ dataframe (pandas.DataFrame): The dataframe containing the data to be plotted.
+
+ Returns:
+ plotly.graph_objs._figure.Figure: The scatter plot figure.
+ """
data = [
go.Scatter(x=dataframe.index, y=dataframe[col], mode="lines", name=col)
@@ -54,7 +83,21 @@ def figure_scatter(dataframe):
return fig
-def figure_bar(dataframe, barmode="stack"):
+def generate_bar_figure(dataframe, barmode="stack"):
+ """
+ Generate a bar figure based on the given dataframe.
+
+ Parameters:
+ - dataframe: pandas DataFrame
+ The input dataframe containing the data for the bar figure.
+ - barmode: str, optional
+ The mode for displaying the bars. Default is "stack".
+
+ Returns:
+ - fig: plotly Figure
+ The generated bar figure.
+
+ """
if barmode == "stack":
col_df = dataframe.columns[::-1]
@@ -80,7 +123,17 @@ def figure_bar(dataframe, barmode="stack"):
return fig
-def figure_empty(text: str = "", size: int = 40):
+def generate_empty_figure(text: str = "", size: int = 40):
+ """
+ Generates an empty figure with optional text annotation.
+
+ Args:
+ text (str, optional): Text to be displayed as an annotation. Defaults to "".
+ size (int, optional): Font size of the annotation. Defaults to 40.
+
+ Returns:
+ go.Figure: An empty figure with the specified text annotation.
+ """
data = [{"x": [], "y": []}]
layout = go.Layout(
title={"text": "", "x": 0.5},
@@ -96,19 +149,19 @@ def figure_empty(text: str = "", size: int = 40):
"showticklabels": False,
"zeroline": False,
},
- margin=dict(t=55, l=55, r=55, b=55),
+ margin={"t": 55, "l": 55, "r": 55, "b": 55},
annotations=[
- dict(
- name="text",
- text=f"{text}",
- opacity=0.3,
- font_size=size,
- xref="x domain",
- yref="y domain",
- x=0.5,
- y=0.05,
- showarrow=False,
- )
+ {
+ "name": "text",
+ "text": f"{text}",
+ "opacity": 0.3,
+ "font_size": size,
+ "xref": "x domain",
+ "yref": "y domain",
+ "x": 0.5,
+ "y": 0.05,
+ "showarrow": False,
+ }
],
height=450,
)
@@ -116,7 +169,7 @@ def figure_empty(text: str = "", size: int = 40):
return go.Figure(data, layout)
-def figure_summary_maxsum(
+def generate_summary_maximum_sum(
summary,
ufunc_cols: list[str] = None,
rows: int = 2,
@@ -125,6 +178,25 @@ def figure_summary_maxsum(
title: str = "Summary Rainfall",
period: str = None,
) -> dcc.Graph:
+ """
+ Generates a summary graph of maximum and sum values for rainfall data.
+
+ Args:
+ summary: The summary data containing rainfall information.
+ ufunc_cols (optional): A list of column names to include in the graph.
+ Defaults to ["max", "sum"].
+ rows (optional): The number of rows in the subplot grid. Defaults to 2.
+ cols (optional): The number of columns in the subplot grid. Defaults to 1.
+ subplot_titles (optional): A list of titles for each subplot.
+ Defaults to the values in ufunc_cols.
+ title (optional): The title of the graph. Defaults to "Summary Rainfall".
+ period (optional): The period of the data. Can be "monthly", "yearly", or None.
+ Defaults to None.
+
+ Returns:
+ A dcc.Graph object representing the summary graph.
+
+ """
ufunc_cols = ["max", "sum"] if ufunc_cols is None else ufunc_cols
subplot_titles = ufunc_cols if subplot_titles is None else subplot_titles
@@ -133,7 +205,8 @@ def figure_summary_maxsum(
(summary.size > THRESHOLD_SUMMARY) or (summary.index.size > THRESHOLD_XAXES)
) and (period.lower() != "yearly"):
return dcc.Graph(
- figure=figure_empty("dataset above threshold"), config={"staticPlot": True}
+ figure=generate_empty_figure("dataset above threshold"),
+ config={"staticPlot": True},
)
fig = make_subplots(
@@ -144,11 +217,12 @@ def figure_summary_maxsum(
subplot_titles=subplot_titles,
)
- fig.layout.images = [_generate_dict_watermark(n) for n in range(2, rows + 1)]
+ fig.layout.images = [generate_watermark(n) for n in range(2, rows + 1)]
data_dict = defaultdict(list)
stations = [station_name for station_name, _ in summary.columns.to_list()]
stations = list(OrderedDict.fromkeys(stations))
+ last_series = None
for station in stations:
for ufcol, series in summary[station].items():
if ufcol in ufunc_cols:
@@ -160,6 +234,7 @@ def figure_summary_maxsum(
legendgrouptitle_text=station,
)
data_dict[ufcol].append(_bar)
+ last_series = series
for counter, (ufcol, data) in enumerate(data_dict.items(), 1):
fig.add_traces(data, rows=counter, cols=cols)
@@ -175,30 +250,30 @@ def figure_summary_maxsum(
legend={"title": "Stations"},
)
- ticktext = series.index.strftime("%d %b %Y")
+ ticktext = last_series.index.strftime("%d %b %Y")
if period.lower() in ["monthly", "yearly"]:
if period.lower() == "monthly":
- ticktext = series.index.strftime("%B %Y")
+ ticktext = last_series.index.strftime("%B %Y")
if period.lower() == "yearly":
- ticktext = series.index.strftime("%Y")
+ ticktext = last_series.index.strftime("%Y")
- if series.index.size <= THRESHOLD_XAXES:
+ if last_series.index.size <= THRESHOLD_XAXES:
xticktext = ticktext
- xtickvals = np.arange(series.index.size)
+ xtickvals = np.arange(last_series.index.size)
else:
xticktext = ticktext[::2]
- xtickvals = np.arange(series.index.size)[::2]
+ xtickvals = np.arange(last_series.index.size)[::2]
- UPDATE_XAXES = {
+ update_x_axes = {
"ticktext": xticktext,
"tickvals": xtickvals,
- "gridcolor": pytemplate._FONT_COLOR_RGB_ALPHA.replace("0.4", "0.2"),
+ "gridcolor": current_font_color.replace("0.4", "0.2"),
"gridwidth": 2,
}
- UPDATE_YAXES = {
- "gridcolor": pytemplate._FONT_COLOR_RGB_ALPHA.replace("0.4", "0.2"),
+ update_y_axes = {
+ "gridcolor": current_font_color.replace("0.4", "0.2"),
"gridwidth": 2,
"fixedrange": True,
"title": "Rainfall (mm)",
@@ -209,7 +284,7 @@ def update_axis(fig, update, n, axis: str = "x"):
fig.update(layout={f"{axis}axis{n}": update})
for n_row in range(1, rows + 1):
- for axis, update in zip(["x", "y"], [UPDATE_XAXES, UPDATE_YAXES]):
+ for axis, update in zip(["x", "y"], [update_x_axes, update_y_axes]):
update_axis(fig, update, n_row, axis)
# ref: https://stackoverflow.com/questions/39863250
@@ -229,7 +304,7 @@ def update_axis(fig, update, n, axis: str = "x"):
return dcc.Graph(figure=fig)
-def figure_summary_raindry(
+def generate_summary_rain_dry(
summary: pd.DataFrame,
ufunc_cols: list[str] = None,
rows: int = None,
@@ -238,7 +313,23 @@ def figure_summary_raindry(
title: str = "Summary Rainfall",
period: str = None,
) -> dcc.Graph:
-
+ """
+ Generates a summary graph of rainfall and dry days.
+
+ Args:
+ summary (pd.DataFrame): The summary data containing rainfall and dry day information.
+ ufunc_cols (list[str], optional): The columns to include in the graph.
+ Defaults to ["n_rain", "n_dry"].
+ rows (int, optional): The number of rows in the graph. Defaults to None.
+ cols (int, optional): The number of columns in the graph. Defaults to 1.
+ subplot_titles (list[str], optional): The titles for each subplot. Defaults to None.
+ title (str, optional): The title of the graph. Defaults to "Summary Rainfall".
+ period (str, optional): The period of the data (e.g., "monthly", "yearly").
+ Defaults to None.
+
+ Returns:
+ dcc.Graph: The generated graph.
+ """
rows = summary.columns.levels[0].size if rows is None else rows
ufunc_cols = ["n_rain", "n_dry"] if ufunc_cols is None else ufunc_cols
@@ -250,7 +341,8 @@ def figure_summary_raindry(
(summary.size > THRESHOLD_SUMMARY) or (summary.index.size > THRESHOLD_XAXES)
) and (period.lower() != "yearly"):
return dcc.Graph(
- figure=figure_empty("dataset above threshold"), config={"staticPlot": True}
+ figure=generate_empty_figure("dataset above threshold"),
+ config={"staticPlot": True},
)
vertical_spacing = 0.2 / rows
@@ -263,7 +355,7 @@ def figure_summary_raindry(
subplot_titles=subplot_titles,
)
- fig.layout.images = [_generate_dict_watermark(n) for n in range(2, rows + 1)]
+ fig.layout.images = [generate_watermark(n) for n in range(2, rows + 1)]
for station in summary.columns.levels[0]:
summary[(station, "n_left")] = (
@@ -275,6 +367,7 @@ def figure_summary_raindry(
data_dict = defaultdict(list)
stations = [station_name for station_name, _ in summary.columns.to_list()]
stations = list(OrderedDict.fromkeys(stations))
+ last_series = None
for station in stations:
for ufcol, series in summary[station].items():
if ufcol in ufunc_cols + ["n_left"]:
@@ -304,6 +397,7 @@ def figure_summary_raindry(
legendrank=500,
)
data_dict[station].append(_bar)
+ last_series = series
for counter, (ufcol, data) in enumerate(data_dict.items(), 1):
fig.add_traces(data, rows=counter, cols=cols)
@@ -318,32 +412,32 @@ def figure_summary_raindry(
legend={"title": "Stations"},
)
- ticktext = series.index.strftime("%d %b %Y")
+ ticktext = last_series.index.strftime("%d %b %Y")
if period.lower() in ["monthly", "yearly"]:
if period.lower() == "monthly":
- ticktext = series.index.strftime("%B %Y")
+ ticktext = last_series.index.strftime("%B %Y")
if period.lower() == "yearly":
- ticktext = series.index.strftime("%Y")
+ ticktext = last_series.index.strftime("%Y")
- if series.index.size <= THRESHOLD_XAXES:
+ if last_series.index.size <= THRESHOLD_XAXES:
xticktext = ticktext
- xtickvals = np.arange(series.index.size)
+ xtickvals = np.arange(last_series.index.size)
else:
xticktext = ticktext[::2]
- xtickvals = np.arange(series.index.size)[::2]
+ xtickvals = np.arange(last_series.index.size)[::2]
- UPDATE_XAXES = {
+ update_x_axes = {
"ticktext": xticktext,
"tickvals": xtickvals,
- "gridcolor": pytemplate._FONT_COLOR_RGB_ALPHA.replace("0.4", "0.1"),
+ "gridcolor": current_font_color.replace("0.4", "0.1"),
"gridwidth": 2,
# "nticks": 2,
"ticklabelstep": 2,
}
- UPDATE_YAXES = {
- "gridcolor": pytemplate._FONT_COLOR_RGB_ALPHA.replace("0.4", "0.1"),
+ update_y_axes = {
+ "gridcolor": current_font_color.replace("0.4", "0.1"),
"gridwidth": 2,
"fixedrange": True,
"title": "Days",
@@ -357,7 +451,7 @@ def update_axis(fig, update, n, axis: str = "x"):
fig.update(layout={f"xaxis{rows}": {"title": "Date"}})
for n_row in range(1, rows + 1):
- for axis, update in zip(["x", "y"], [UPDATE_XAXES, UPDATE_YAXES]):
+ for axis, update in zip(["x", "y"], [update_x_axes, update_y_axes]):
update_axis(fig, update, n_row, axis)
color_list = list(pytemplate.hktemplate.layout.colorway[:2]) + ["DarkGray"]
@@ -368,7 +462,7 @@ def update_axis(fig, update, n, axis: str = "x"):
return dcc.Graph(figure=fig)
-def figure_summary_maxdate(
+def generate_summary_maximum_date(
summary_all: pd.DataFrame,
ufunc_col: list[str] = None,
rows: int = 3,
@@ -378,7 +472,25 @@ def figure_summary_maxdate(
periods: list[str] = None,
bubble_sizes: list[int] = None,
):
-
+ """
+ Generates a summary graph of maximum rainfall events.
+
+ Args:
+ summary_all (pd.DataFrame): The summary data containing rainfall information.
+ ufunc_col (list[str], optional): The columns to use for calculations.
+ Defaults to None.
+ rows (int, optional): The number of rows in the subplot grid. Defaults to 3.
+ cols (int, optional): The number of columns in the subplot grid. Defaults to 1.
+ subplot_titles (list[str], optional): The titles for each subplot. Defaults to None.
+ title (str, optional): The title of the graph. Defaults to "Maximum Rainfall Events".
+ periods (list[str], optional): The periods to consider for the analysis.
+ Defaults to None.
+ bubble_sizes (list[int], optional): The sizes of the bubbles in the graph.
+ Defaults to None.
+
+ Returns:
+ dcc.Graph: The generated graph.
+ """
ufunc_col = ["max_date"] if ufunc_col is None else ufunc_col
subplot_titles = (
["Biweekly", "Monthly", "Yearly"] if subplot_titles is None else subplot_titles
@@ -393,7 +505,7 @@ def figure_summary_maxdate(
subplot_titles=subplot_titles,
)
- fig.layout.images = [_generate_dict_watermark(n) for n in range(2, rows + 1)]
+ fig.layout.images = [generate_watermark(n) for n in range(2, rows + 1)]
# Create new DF
@@ -461,23 +573,23 @@ def update_axis(fig, update, n, axis: str = "x"):
fig.update(layout={f"xaxis{rows}": {"title": "Date"}})
# GENERAL UPDATE
- UPDATE_XAXES = {
- "gridcolor": pytemplate._FONT_COLOR_RGB_ALPHA.replace("0.4", "0.1"),
+ update_x_axes = {
+ "gridcolor": current_font_color.replace("0.4", "0.1"),
"gridwidth": 2,
"showspikes": True,
"spikesnap": "cursor",
"spikemode": "across",
"spikethickness": 1,
}
- UPDATE_YAXES = {
- "gridcolor": pytemplate._FONT_COLOR_RGB_ALPHA.replace("0.4", "0.1"),
+ update_y_axes = {
+ "gridcolor": current_font_color.replace("0.4", "0.1"),
"gridwidth": 2,
"fixedrange": True,
"title": "Station",
}
for n_row in range(1, rows + 1):
- for axis, update in zip(["x", "y"], [UPDATE_XAXES, UPDATE_YAXES]):
+ for axis, update in zip(["x", "y"], [update_x_axes, update_y_axes]):
update_axis(fig, update, n_row, axis)
n_data = len(fig.data)
@@ -495,17 +607,30 @@ def update_axis(fig, update, n, axis: str = "x"):
return dcc.Graph(figure=fig)
-def figure_cumsum_single(cumsum: pd.DataFrame, col: str = None) -> go.Figure:
- import re
+def generate_cumulative_sum(
+ cumulative_sum_df: pd.DataFrame, data_column: str = None
+) -> go.Figure:
+ """
+ Generates a cumulative sum plot using the provided DataFrame.
+
+ Args:
+ cumulative_sum_df (pd.DataFrame): The DataFrame containing the cumulative sum data.
+ data_column (str, optional): The column name to use for the y-axis data.
+ If not provided, the first column of the DataFrame will be used.
+
+ Returns:
+ go.Figure: The generated cumulative sum plot as a Plotly Figure.
- col = cumsum.columns[0] if col is None else col
+ """
- new_dataframe = cumsum.copy()
+ data_column = cumulative_sum_df.columns[0] if data_column is None else data_column
+
+ new_dataframe = cumulative_sum_df.copy()
new_dataframe["number"] = np.arange(1, len(new_dataframe) + 1)
fig = px.scatter(
x=new_dataframe.number,
- y=new_dataframe[col],
+ y=new_dataframe[data_column],
trendline="ols",
trendline_color_override=pytemplate.hktemplate.layout.colorway[1],
)
@@ -518,9 +643,9 @@ def figure_cumsum_single(cumsum: pd.DataFrame, col: str = None) -> go.Figure:
_scatter.line.width = 1
_scatter.marker.size = 12
_scatter.marker.symbol = "circle"
- _scatter.name = col
+ _scatter.name = data_column
_scatter.hovertemplate = (
- f"{col}
%{{y}} mm
%{{x}}"
+ f"{data_column}
%{{y}} mm
%{{x}}"
)
# MODIFIED TRENDLINE
@@ -556,15 +681,26 @@ def figure_cumsum_single(cumsum: pd.DataFrame, col: str = None) -> go.Figure:
return dcc.Graph(figure=fig)
-def figure_consistency(cumsum: pd.DataFrame, col: str) -> go.Figure:
- import re
+def generate_scatter_with_trendline(
+ cumulative_sum_df: pd.DataFrame, data_column: str
+) -> go.Figure:
+ """
+ Generate a scatter plot with a trendline.
+
+ Args:
+ cumulative_sum_df (pd.DataFrame): The cumulative sum dataframe.
+ data_column (str): The column name for the data.
+
+ Returns:
+ go.Figure: The scatter plot figure with a trendline.
+ """
- cumsum = cumsum.copy()
+ cumulative_sum_df = cumulative_sum_df.copy()
# Create Mean Cumulative Other Stations
- cumsum_x = cumsum[col]
- other_stations = cumsum.columns.drop(col)
- cumsum_y = cumsum[other_stations].mean(axis=1)
+ cumsum_x = cumulative_sum_df[data_column]
+ other_stations = cumulative_sum_df.columns.drop(data_column)
+ cumsum_y = cumulative_sum_df[other_stations].mean(axis=1)
fig = px.scatter(
x=cumsum_x,
@@ -581,9 +717,9 @@ def figure_consistency(cumsum: pd.DataFrame, col: str) -> go.Figure:
_scatter.line.width = 1
_scatter.marker.size = 12
_scatter.marker.symbol = "circle"
- _scatter.name = col
+ _scatter.name = data_column
_scatter.hovertemplate = (
- f"{col}
y: %{{y}} mm
x: %{{x}} mm"
+ f"{data_column}
y: %{{y}} mm
x: %{{x}} mm"
)
# MODIFIED TRENDLINE
@@ -608,7 +744,7 @@ def figure_consistency(cumsum: pd.DataFrame, col: str) -> go.Figure:
_trendline.name = "trendline"
fig.update_layout(
- xaxis_title=f"Cumulative Annual {col} (mm)",
+ xaxis_title=f"Cumulative Annual {data_column} (mm)",
yaxis_title="Cumulative Average Annual References (mm)",
margin=dict(l=0, t=35, b=0, r=0),
yaxis_tickformat=".0f",
diff --git a/pyfunc.py b/pyfunc.py
index f22ac49..b5796fd 100644
--- a/pyfunc.py
+++ b/pyfunc.py
@@ -1,12 +1,37 @@
+"""
+This module contains functions for parsing and processing uploaded data,
+ generating summary statistics for rainfall data,
+ transforming table data into a pandas DataFrame,
+ and calculating the cumulative sum of a DataFrame.
+"""
+
import base64
import io
import pandas as pd
from dash import html
import numpy as np
-from hidrokit.contrib.taruma import hk98
+from hidrokit.contrib.taruma import statistic_summary
def parse_upload_data(content, filename, filedate):
+ """
+ Parse and process uploaded data.
+
+ Args:
+ content (str): The content of the uploaded file.
+ filename (str): The name of the uploaded file.
+ filedate (str): The date of the uploaded file.
+
+ Returns:
+ tuple: A tuple containing the processed data and an HTML element.
+ The processed data is a pandas DataFrame if the file is in CSV format.
+ If the file is in XLSX or XLS format, an HTML element with a warning message
+ is returned.
+ If the file is in any other format, an HTML element with an error message
+ is returned.
+ """
+
+ _ = filedate # unused variable
_, content_string = content.split(",")
decoded = base64.b64decode(content_string)
@@ -34,18 +59,37 @@ def parse_upload_data(content, filename, filedate):
),
None,
)
- except Exception as e:
+ except UnicodeDecodeError as e:
+ print(e)
+ return html.Div([f"File is not valid UTF-8. {e}"]), None
+ except pd.errors.ParserError as e:
+ print(e)
+ return html.Div([f"CSV file is not well-formed. {e}"]), None
+ except ValueError as e:
print(e)
- return html.Div([f"There was an error processing this file. {e}"]), None
+ return html.Div([f"Content string is not valid base64. {e}"]), None
return html.Div(["File Diterima"]), dataframe
def generate_summary_single(dataframe, n_days="1MS"):
+ """
+ Generate a summary of rainfall data for a single location.
+
+ Args:
+ dataframe (pandas.DataFrame): The input dataframe containing rainfall data.
+ n_days (str, optional): The number of days to consider for the summary.
+ Defaults to "1MS".
+
+ Returns:
+ pandas.DataFrame: The summary dataframe containing various statistics of
+ the rainfall data.
+ """
+
def days(vector):
return len(vector)
- def sum(vector):
+ def vector_sum(vector):
return vector.sum().round(3)
def n_rain(vector):
@@ -57,16 +101,15 @@ def n_dry(vector):
def max_date(vector):
if vector.any():
return vector.idxmax().date()
- else:
- return pd.NaT
+ return pd.NaT
- def max(vector):
+ def vector_max(vector):
return vector.max()
- ufunc = [days, max, sum, n_rain, n_dry, max_date]
+ ufunc = [days, vector_max, vector_sum, n_rain, n_dry, max_date]
ufunc_col = ["days", "max", "sum", "n_rain", "n_dry", "max_date"]
- summary = hk98.summary_all(
+ summary = statistic_summary.summary_all(
dataframe, ufunc=ufunc, ufunc_col=ufunc_col, n_days=n_days
)
@@ -74,6 +117,19 @@ def max(vector):
def generate_summary_all(dataframe, n_days: list = None):
+ """
+ Generate summary statistics for multiple time periods.
+
+ Args:
+ dataframe (pandas.DataFrame): The input dataframe containing the data.
+ n_days (list, optional): A list of time periods to calculate
+ the summary statistics for.
+ If not provided, the default time periods ["16D", "1MS", "1YS"] will be used.
+
+ Returns:
+ list: A list of summary statistics for each time period.
+
+ """
n_days = ["16D", "1MS", "1YS"] if n_days is None else n_days
summary_all = []
@@ -90,6 +146,22 @@ def transform_to_dataframe(
apply_numeric: bool = True,
parse_dates: list = None,
):
+ """
+ Transform table data into a pandas DataFrame.
+
+ Args:
+ table_data (list): The data to be transformed into a DataFrame.
+ table_columns (list): The column names of the table data.
+ multiindex (bool, optional): Whether to create a multi-index DataFrame.
+ Defaults to False.
+ apply_numeric (bool, optional): Whether to apply numeric conversion to the DataFrame.
+ Defaults to True.
+ parse_dates (list, optional): The column names to parse as dates.
+ Defaults to None.
+
+ Returns:
+ pandas.DataFrame: The transformed DataFrame.
+ """
if multiindex is True:
dataframe = pd.DataFrame(table_data)
@@ -133,8 +205,16 @@ def transform_to_dataframe(
return dataframe
-def calc_cumsum(dataframe):
+def calculate_cumulative_sum(dataframe):
+ """
+ Calculate the cumulative sum of a DataFrame by resampling it on a yearly basis.
+
+ Parameters:
+ dataframe (pandas.DataFrame): The input DataFrame containing the data.
+ Returns:
+ pandas.DataFrame: The DataFrame with the cumulative sum rounded to the nearest integer.
+ """
consistency = dataframe.resample("YS").sum().cumsum()
return consistency.round()
diff --git a/pylayout.py b/pylayout.py
index a067e8a..957a841 100644
--- a/pylayout.py
+++ b/pylayout.py
@@ -1,7 +1,11 @@
+"""
+This module defines the layout components for a Dash application used for rainfall analysis.
+"""
+
from dash import html, dcc
import dash_bootstrap_components as dbc
-from pyconfig import appConfig
import plotly.io as pio
+from pyconfig import appConfig
from pytemplate import hktemplate
import pyfigure
import pylayoutfunc
@@ -15,7 +19,11 @@
className="float fw-bold text-center mt-3 fs-1 fw-bold",
),
html.Span(
- [appConfig.GITHUB_REPO, "@", appConfig.VERSION],
+ html.A(
+ [appConfig.GITHUB_REPO, "@", appConfig.VERSION],
+ href="https://github.com/taruma/rainfall",
+ target="_blank",
+ ),
className="text-muted",
),
],
@@ -32,60 +40,19 @@
className="text-center fs-5",
)
-HTML_SPONSORED = html.Div(
- [
- "sponsored by ",
- html.A("FIAKO Engineering", href="https://fiako.engineering"),
- ],
- className="text-center fs-5 mb-3",
-)
-
-ALERT_CONTRIBUTION = dbc.Alert(
- [
- "Tertarik untuk berkontribusi? Ingin terlibat proyek hidrokit seperti ini? hubungi saya di ",
- html.A("hi@taruma.info", href="mailto:hi@taruma.info", className="text-bold"),
- ". Langsung buat isu di ",
- html.A("Github", href=appConfig.GITHUB_LINK),
- " jika memiliki pertanyaan/komentar/kritik/saran atau menemui kesalahan di proyek ini.",
- ]
-)
-
-HTML_ALERT_CONTRIBUTION = pylayoutfunc.create_HTML_alert(ALERT_CONTRIBUTION)
-
ALERT_README = dbc.Alert(
[
- "Informasi aplikasi ini dapat dilihat di ",
- html.A(
- "GitHub README",
- href="https://github.com/fiakoenjiniring/rainfall#readme",
- ),
+ "Rainfall Data Explorer (hidrokit-rainfall) is a web application for "
+ "analyzing daily rainfall data, providing maximum rainfall, total rainfall, "
+ "rainy days, dry days, and maximum rainfall events visualization, "
+ "with added features like annual cumulative graph and consistency (mass curve)",
".",
],
color="warning",
className="m-4",
)
-HTML_ALERT_README = pylayoutfunc.create_HTML_alert(ALERT_README, className=None)
-
-ALERT_SPONSOR = dbc.Alert(
- [
- "Terima kasih untuk ",
- html.A(
- "FIAKO Engineering",
- href="https://fiako.engineering",
- ),
- " yang telah mensponsori versi v1.1.0. Untuk catatan pembaruan bisa dilihat melalui ",
- html.A(
- "halaman rilis di github",
- href="https://github.com/fiakoenjiniring/rainfall/releases/tag/v1.1.0",
- ),
- ".",
- ],
- color="info",
-)
-
-HTML_ALERT_SPONSOR = pylayoutfunc.create_HTML_alert(ALERT_SPONSOR, className=None)
-
+HTML_ALERT_README = pylayoutfunc.create_html_alert(ALERT_README, class_name=None)
DCC_UPLOAD = html.Div(
dcc.Upload(
@@ -93,11 +60,12 @@
children=html.Div(
[
dbc.Button(
- "Drag and Drop or Select Files",
+ "Upload File (.csv)",
color="primary",
outline=False,
class_name="fs-4 text-center",
id="button-upload",
+ size="lg",
)
]
),
@@ -115,24 +83,78 @@
[DCC_UPLOAD],
width="auto",
),
+ ],
+ justify="center",
+ ),
+ ],
+ fluid=True,
+ ),
+)
+
+HTML_ROW_BUTTON_EXAMPLE = html.Div(
+ dbc.Container(
+ [
+ dbc.Row(
+ [
+ dbc.Col(
+ [
+ dbc.Button(
+ "Example 1 (5 Stations, 7 Years)",
+ color="success",
+ id="button-example-1",
+ class_name="text-center",
+ size="sm",
+ ),
+ ],
+ class_name="text-center",
+ width="auto",
+ ),
+ dbc.Col(
+ [
+ dbc.Button(
+ "Example 2 (4 Stations, 2 Years)",
+ color="success",
+ id="button-example-2",
+ class_name="text-center",
+ size="sm",
+ ),
+ ],
+ class_name="text-center",
+ width="auto",
+ ),
+ dbc.Col(
+ [
+ dbc.Button(
+ "Example 3 (1 Station, 9 Years)",
+ color="success",
+ id="button-example-3",
+ class_name="text-center",
+ size="sm",
+ ),
+ ],
+ class_name="text-center",
+ width="auto",
+ ),
dbc.Col(
[
dbc.Button(
- "Use Example Data",
- color="info",
- id="button-skip",
- class_name="fs-4 text-center",
+ "Example 4 (7 Station, 1 Years)",
+ color="success",
+ id="button-example-4",
+ class_name="text-center",
+ size="sm",
),
],
- class_name="fs-4 text-center",
+ class_name="text-center",
width="auto",
),
],
justify="center",
+ class_name="my-3",
),
],
fluid=True,
- ),
+ )
)
HTML_ROW_TABLE = html.Div(
@@ -142,7 +164,7 @@
dbc.CardBody(
id="row-table-uploaded",
children=dcc.Graph(
- figure=pyfigure.figure_empty(),
+ figure=pyfigure.generate_empty_figure(),
config={"staticPlot": True},
),
),
@@ -228,7 +250,7 @@
dcc.Loading(
dcc.Graph(
id="graph-rainfall",
- figure=pyfigure.figure_empty(),
+ figure=pyfigure.generate_empty_figure(),
config={"staticPlot": True},
)
)
@@ -271,7 +293,7 @@
dbc.Container(
dcc.Loading(
children=dcc.Graph(
- figure=pyfigure.figure_empty(),
+ figure=pyfigure.generate_empty_figure(),
config={"staticPlot": True},
),
id="tab-analysis",
@@ -327,7 +349,7 @@
dbc.Container(
dcc.Loading(
children=dcc.Graph(
- figure=pyfigure.figure_empty(),
+ figure=pyfigure.generate_empty_figure(),
config={"staticPlot": True},
),
id="tab-graph-analysis",
@@ -345,7 +367,7 @@
dbc.Col(
dcc.Loading(
children=dcc.Graph(
- figure=pyfigure.figure_empty(),
+ figure=pyfigure.generate_empty_figure(),
config={"staticPlot": True},
),
id="tab-graph-cumsum",
@@ -367,7 +389,7 @@
dbc.Col(
dcc.Loading(
children=dcc.Graph(
- figure=pyfigure.figure_empty(),
+ figure=pyfigure.generate_empty_figure(),
config={"staticPlot": True},
),
id="tab-graph-consistency",
@@ -381,53 +403,15 @@
className="my-3",
)
-_HTML_TROUBLESHOOT = html.Div(
- dbc.Container(
- [
- dbc.Row([html.Div("HEELLOOOO")]),
- dbc.Button("Hello", id="button-troubleshoot"),
- html.Div(id="row-troubleshoot"),
- ],
- fluid=True,
- )
-)
-
-HTML_OTHER_PROJECTS = html.Div(
- [
- html.Span("other dashboard:"),
- html.A(
- [
- html.Del("BMKG", style={"text-decoration-style": "double"}),
- " 🛖 Explorer",
- ],
- href="https://github.com/taruma/dash-data-explorer",
- style={"text-decoration": "none"},
- ),
- ],
- className="d-flex gap-2 justify-content-center my-2",
-)
-
-HTML_MADEBY = html.Div(
- dcc.Markdown(
- "Made with [Dash+Plotly](https://plotly.com).",
- className="fs-4 text-center mt-2",
- ),
-)
-
HTML_FOOTER = html.Div(
html.Footer(
[
html.Span("\u00A9"),
- " 2022 ",
- # html.A(
- # "Taruma Sakti Megariansyah",
- # href="https://github.com/taruma",
- # ),
- # ", ",
+ " 2022-2024 ",
html.A(
- "PT. FIAKO ENJINIRING INDONESIA",
- href="https://fiako.engineering",
- target="_blank"
+ "Taruma Sakti Megariansyah",
+ href="https://dev.taruma.info",
+ target="_blank",
),
". MIT License. Visit on ",
html.A(
diff --git a/pylayoutfunc.py b/pylayoutfunc.py
index 78a220e..96f79d8 100644
--- a/pylayoutfunc.py
+++ b/pylayoutfunc.py
@@ -1,9 +1,13 @@
-from __future__ import annotations
+"""
+This module contains functions for creating table and graph layouts
+ using Dash and Bootstrap components.
+"""
+
+from collections.abc import Iterable
+from datetime import datetime
from dash import html, dash_table, dcc
import dash_bootstrap_components as dbc
from pytemplate import hktemplate
-from datetime import datetime
-from pyconfig import appConfig
def create_table_layout(
@@ -15,7 +19,23 @@ def create_table_layout(
deletable=True,
renamable=False,
):
- from collections.abc import Iterable
+ """
+ Create a table layout using the given dataframe.
+
+ Args:
+ dataframe (pandas.DataFrame): The input dataframe.
+ idtable (str): The ID of the DataTable component.
+ filename (str, optional): The name of the file. Defaults to None.
+ filedate (int, optional): The timestamp of the file. Defaults to None.
+ editable (list or bool, optional): A list of booleans indicating
+ the editability of each column,
+ or a single boolean value to be applied to all columns. Defaults to False.
+ deletable (bool, optional): Whether the columns are deletable. Defaults to True.
+ renamable (bool, optional): Whether the columns are renamable. Defaults to False.
+
+ Returns:
+ tuple: A tuple containing the title element and the DataTable component.
+ """
new_dataframe = dataframe.rename_axis("DATE").reset_index()
new_dataframe.DATE = new_dataframe.DATE.dt.date
@@ -52,7 +72,7 @@ def create_table_layout(
if (filename is not None) and (filedate is not None)
else ""
)
- title_table = f"DATA TABLE" + add_title
+ title_table = "DATA TABLE" + add_title
return html.H2(title_table, className="text-center"), table
@@ -63,6 +83,23 @@ def create_table_summary(
deletable=True,
renamable=False,
):
+ """
+ Creates a table summary using the provided summary data.
+
+ Args:
+ summary (pandas.DataFrame): The summary data to be displayed in the table.
+ idtable (str): The ID of the DataTable component.
+ editable (bool, optional): Specifies whether the table cells are editable.
+ Defaults to False.
+ deletable (bool, optional): Specifies whether the table columns are deletable.
+ Defaults to True.
+ renamable (bool, optional): Specifies whether the table columns are renamable.
+ Defaults to False.
+
+ Returns:
+ dash_table.DataTable: The created DataTable component.
+ """
+
new_summary = summary.rename_axis("DATE").reset_index()
new_summary.DATE = new_summary.DATE.dt.date
@@ -96,6 +133,20 @@ def create_tabcard_table_layout(
disabled: list = None,
active_tab: str = None,
):
+ """
+ Create a tabbed card layout with tables.
+
+ Args:
+ tables (list): A list of tables to be displayed in each tab.
+ tab_names (list, optional): A list of tab names.
+ Defaults to ["Biweekly", "Monthly", "Yearly"].
+ disabled (list, optional): A list of booleans indicating whether each tab is disabled.
+ Defaults to None.
+ active_tab (str, optional): The active tab. Defaults to None.
+
+ Returns:
+ dbc.Tabs: A tabbed card layout with the specified tables and settings.
+ """
disabled = [False] * len(tables) if disabled is None else disabled
tab_names = ["Biweekly", "Monthly", "Yearly"] if tab_names is None else tab_names
@@ -121,6 +172,20 @@ def create_tabcard_graph_layout(
disabled: list = None,
active_tab: str = None,
):
+ """
+ Create a layout with tab cards containing graphs.
+
+ Args:
+ graphs (list[dcc.Graph]): A list of Dash Core Component Graph objects.
+ tab_names (list, optional): A list of tab names.
+ Defaults to ["Biweekly", "Monthly", "Yearly"].
+ disabled (list, optional): A list of boolean values indicating whether
+ each tab is disabled. Defaults to None.
+ active_tab (str, optional): The ID of the active tab. Defaults to None.
+
+ Returns:
+ dbc.Tabs: A Dash Bootstrap Components Tabs object containing tab cards with graphs.
+ """
disabled = [False] * len(graphs) if disabled is None else disabled
tab_names = ["Biweekly", "Monthly", "Yearly"] if tab_names is None else tab_names
@@ -140,11 +205,23 @@ def create_tabcard_graph_layout(
return dbc.Tabs(tab, active_tab=active_tab)
-def create_HTML_alert(alert: dbc.Alert, className: str = "my-2"):
+def create_html_alert(alert: dbc.Alert, class_name: str = "my-2"):
+ """
+ Creates an HTML alert container with the specified alert component and class name.
+
+ Parameters:
+ alert (dbc.Alert): The alert component to be displayed.
+ className (str, optional): The class name to be applied to the alert container.
+ Defaults to "my-2".
+
+ Returns:
+ html.Div: The HTML alert container.
+
+ """
return html.Div(
dbc.Container(
dbc.Row([dbc.Col(alert, width="auto")], justify="center"),
fluid=True,
),
- className=className,
+ className=class_name,
)
diff --git a/pytemplate.py b/pytemplate.py
index 541902b..ca51820 100644
--- a/pytemplate.py
+++ b/pytemplate.py
@@ -1,8 +1,9 @@
"""TEMPLATE PLOTLY BASED ON THEME"""
+
import plotly.io as pio
from dash_bootstrap_templates import load_figure_template
-from pyconfig import appConfig
from plotly import colors
+from pyconfig import appConfig
load_figure_template(appConfig.DASH_THEME.THEME.lower())
hktemplate = pio.templates[pio.templates.default]
@@ -10,27 +11,27 @@
# VARS
_TEMPLATE = appConfig.TEMPLATE
_FONT_FAMILY = hktemplate.layout.font.family
-_FONT_COLOR_TUPLE = colors.hex_to_rgb(hktemplate.layout.font.color)
-_FONT_COLOR_RGB_ALPHA = "rgba({},{},{},0.4)".format(*_FONT_COLOR_TUPLE)
+_RED, _GREEN, _BLUE = colors.hex_to_rgb(hktemplate.layout.font.color)
+FONT_COLOR_RGB_ALPHA = f"rgba({_RED},{_GREEN},{_BLUE},0.4)"
## LAYOUT
# WATERMARK
_SOURCE_WATERMARK = _TEMPLATE.WATERMARK_SOURCE
hktemplate.layout.images = [
- dict(
- source=_SOURCE_WATERMARK,
- xref="x domain",
- yref="y domain",
- x=0.5,
- y=0.5,
- sizex=0.5,
- sizey=0.5,
- xanchor="center",
- yanchor="middle",
- name="watermark-hidrokit",
- layer="below",
- opacity=0.1,
- ),
+ {
+ "source": _SOURCE_WATERMARK,
+ "xref": "x domain",
+ "yref": "y domain",
+ "x": 0.5,
+ "y": 0.5,
+ "sizex": 0.5,
+ "sizey": 0.5,
+ "xanchor": "center",
+ "yanchor": "middle",
+ "name": "watermark-hidrokit",
+ "layer": "below",
+ "opacity": 0.1,
+ },
]
## GENERAL
@@ -51,7 +52,7 @@
# hktemplate.layout.legend.title.text = "placeholder"
-def apply_legend_inside():
+def _apply_legend_inside():
hktemplate.layout.legend.xanchor = "left"
hktemplate.layout.legend.yanchor = "top"
hktemplate.layout.legend.x = 0.005
@@ -63,7 +64,7 @@ def apply_legend_inside():
if _TEMPLATE.SHOW_LEGEND_INSIDE:
- apply_legend_inside()
+ _apply_legend_inside()
# MODEBAR
hktemplate.layout.modebar.activecolor = "blue"
@@ -101,53 +102,57 @@ def apply_legend_inside():
hktemplate.layout.xaxis.linewidth = _XAXIS_LINEWIDTH
hktemplate.layout.xaxis.linecolor = _XAXIS_GRIDCOLOR
hktemplate.layout.xaxis.spikecolor = _XAXIS_GRIDCOLOR
-hktemplate.layout.xaxis.gridcolor = _FONT_COLOR_RGB_ALPHA
+hktemplate.layout.xaxis.gridcolor = FONT_COLOR_RGB_ALPHA
hktemplate.layout.xaxis.gridwidth = _XAXIS_LINEWIDTH
# hktemplate.layout.xaxis.title.text = "PLACEHOLDER XAXIS"
hktemplate.layout.xaxis.title.font.size = _XAXIS_TITLE_FONT_SIZE
hktemplate.layout.xaxis.title.standoff = _XAXIS_TITLE_STANDOFF
+
# RANGESELECTOR XAXIS
-def apply_rangeselector():
+def _apply_rangeselector():
hktemplate.layout.xaxis.rangeselector.buttons = [
- dict(
- count=1,
- label="1m",
- step="month",
- stepmode="backward",
- visible=True,
- name="button1",
- ),
- dict(
- count=6,
- label="6m",
- step="month",
- stepmode="backward",
- visible=True,
- name="button2",
- ),
- dict(
- count=1,
- label="YTD",
- step="year",
- stepmode="todate",
- visible=True,
- name="button3",
- ),
- dict(
- count=1,
- label="1y",
- step="year",
- stepmode="backward",
- visible=True,
- name="button4",
- ),
- dict(step="all", name="button5"),
+ {
+ "count": 1,
+ "label": "1m",
+ "step": "month",
+ "stepmode": "backward",
+ "visible": True,
+ "name": "button1",
+ },
+ {
+ "count": 6,
+ "label": "6m",
+ "step": "month",
+ "stepmode": "backward",
+ "visible": True,
+ "name": "button2",
+ },
+ {
+ "count": 1,
+ "label": "YTD",
+ "step": "year",
+ "stepmode": "todate",
+ "visible": True,
+ "name": "button3",
+ },
+ {
+ "count": 1,
+ "label": "1y",
+ "step": "year",
+ "stepmode": "backward",
+ "visible": True,
+ "name": "button4",
+ },
+ {
+ "step": "all",
+ "name": "button5",
+ },
]
if _TEMPLATE.SHOW_RANGESELECTOR:
- apply_rangeselector()
+ _apply_rangeselector()
# YAXIS
_YAXIS_GRIDCOLOR = "black" # hktemplate.layout.yaxis.gridcolor
@@ -160,7 +165,7 @@ def apply_rangeselector():
hktemplate.layout.yaxis.linecolor = _YAXIS_GRIDCOLOR
hktemplate.layout.yaxis.spikecolor = _YAXIS_GRIDCOLOR
hktemplate.layout.yaxis.rangemode = "tozero"
-hktemplate.layout.yaxis.gridcolor = _FONT_COLOR_RGB_ALPHA
+hktemplate.layout.yaxis.gridcolor = FONT_COLOR_RGB_ALPHA
hktemplate.layout.yaxis.gridwidth = _YAXIS_LINEWIDTH
# hktemplate.layout.yaxis.title.text = "PLACEHOLDER XAXIS"
hktemplate.layout.yaxis.title.font.size = _YAXIS_TITLE_FONT_SIZE