Skip to content

Commit

Permalink
1️⃣ update to v1.0.0
Browse files Browse the repository at this point in the history
update to v1.0.0
  • Loading branch information
taruma authored May 10, 2022
2 parents 9511946 + c7387bd commit fb55c5e
Show file tree
Hide file tree
Showing 14 changed files with 4,569 additions and 287 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.vscode/settings.json
playground.ipynb
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Taruma Sakti
Copyright (c) 2022 Taruma Sakti Megariansyah

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
57 changes: 55 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,55 @@
# dash-hidrokit-rainfall
dashboard for rainfall data exploration
# Dashboard Rainfall Data Explorer

![image](https://user-images.githubusercontent.com/1007910/167583978-b23aedef-bbd7-4b0f-8b3b-94c0d56868b6.png)

__Rainfall Data Explorer__ (`hkrainfall`) adalah _dashboard_ untuk mengeksplorasi data hujan di setiap stasiunnya dan membandingkannya baik secara numerik maupun visual. `hkrainfall` dibuat menggunakan teknologi [Dash + Plotly](https://plotly.com/) dengan bahasa pemrograman Python. Proyek `hkrainfall` bersifat _open-source_ dengan lisensi MIT.

## Cara Menjalankan Dashboard (Lokal)

Sangat dianjurkan untuk menjalankan dashboard ini menggunakan mesin lokal.

- Buat _virtual environment_ menggunakan `environment.yml` (untuk conda) atau `requirements.txt` (untuk venv).
- Jalankan `app.py` di terminal.
- Buka alamat `http://127.0.0.1:8050/` di browser.

## Cara Penggunaan

Dashboard ini bisa membaca berkas berformat '.csv' dengan:

- Kolom pertama merupakan tanggal dengan format yang dapat dibaca oleh fitur `parse_dates` yang tersedia di `pandas`. Direkomendasikan mengubahnya menjadi format `YYYY-MM-DD`.
- Kolom lainnya merupakan data hujan dengan header.
- Baris pertama akan dibaca sebagai header tabel, sehingga dianjurkan menambahkan label untuk setiap kolomnya. Gunakan label `DATE` untuk kolom tanggal yang akan digunakan sebagai index.

Navigasi dashboard ini antara lain:

- _Drag and Drop_ berkas atau pilih berkas pada tombol "Drag and Drop or Select Files". Klik "Use Example Data" jika ingin melihat demonstrasi dashboard ini.
- Jika prosesnya berhasil, akan muncul tabel dengan data yang di-_upload_ atau contoh data. Tabel ini _editable_ sehingga bisa menyesuaikan saat proses pengoreksian saat eksplorasi data. Bisa juga diubah nama tabelnya. Pastikan nama kolom berbeda dan nama kolom pertama (tanggal) selalu "DATE".
- Klik "Visualize Data" untuk melihat visualisasi data. Pada tahap ini, Anda bisa kembali ke tabel jika ingin melakukan pengoreksian data. Anda juga bisa melakukan filter pada setiap kolom untuk eksplorasi selanjutnya.
- Tabel yang telah diubah, bisa di-_download_ kembali dalam bentuk CSV.
- Setelah tabel sudah dikoreksi. Bisa dilanjutkan ke tahapan analisis data. Perlu diingat, data yang dianalisis sesuai dengan tampilan/informasi tabel terkini. Jadi, jika masih ada filter, maka analisis hanya dilakukan pada data yang telah terfilter.
- Klik "Analyze Data" untuk melakukan analisis data. Perlu diingat, proses ini akan memakan waktu jika memiliki dataset yang besar. Jadi, sangat disarankan menggunakan mesin lokal untuk proses ini. Karena yang dapat diakses di web hanya berupa demonstrasi saja dan menggunakan layanan gratis sehingga sangat terbatas kemampuannya.
- Analisis data terbagi menjadi tiga periode yaitu 2 periode (biweekly), setiap bulanan (monthly), dan tahunan (yearly). Sebagai catatan, biweekly itu dibagi berdasarkan 16 hari pertama kemudian sisa harinya pada setiap bulan.
- Analisis data yang dilakukan berupa:
- `days`: Jumlah hari pada setiap periodenya (16 hari untuk biweekly, 1 bulan untuk monthly, dan 1 tahun untuk yearly).
- `max`: Nilai maksimum pada setiap periode.
- `sum`: Total nilai pada setiap periode.
- `n_rain`: Jumlah hari hujan di setiap periode.
- `n_dry`: Jumlah hari kering di setiap periode. Catatan: Ini akan menghitung nilai `np.nan` sebagai hari kering.
- `max_date`: Tanggal kejadian hujan maksimum terjadi pada setiap periode.
- Tabel analisis tidak dapat diubah dan hanya sebagai penyedia informasi saja.
- Melihat tabel analisis rasanya cukup sulit untuk diceritakan atau diinterpretasikan, oleh karena itu disediakan tombol "Visualize it!" untuk melakuakn visualisasi tabel analisis.
- Selain melakukan visualisasi, tabel analisis juga dapat di-_download_ dengan mengklik tombol "Download Results as CSV". Perlu dicatat, `dataframe` tabel analisis menggunakan `MultiIndex` sehingga pada format CSV akan perlu dilakukan pengolahan lagi.
- Visualisasi dari tabel analisis berupa:
- Group Bar Chart untuk setiap periode dengan kolom `max` dan `sum`. Grafik ini bisa melihat secara langsung perbandingan nilai antar stasiun.
- Stack Bar Chart untuk setiap periode dengan kolom `n_rain` dan `n_dry`. Grafik ini bisa memberikan gambaran periode yang memiliki frekuensi hujan/kekeringan tinggi/rendah secara sekilas.
- Bubble Chart (Maximum Rainfall Events) memberikan gambaran besar terkait tanggal kejadian saat hujan maksimum terjadi di setiap stasiun. Ukuran lingkaran menunjukkan seberapa besar hujan maksimum yang terjadi.

Navigasi dengan grafik interaktif plotly:

- Pada setiap grafik plotly, dapat dilakukan interaksi langsung dengan grafik seperti mengatur stasiun/data mana saja yang ditampilkan, pembesaran pada periode tertentu. mengatur ukuran sumbu, dll.
- Sangat dianjurkan untuk mengeksplorasi sendiri mengenai _mode bar_ yang ada di kanan atas setiap grafik plotly.
- Setiap grafik bisa di-_download_ dalam bentuk `.png` untuk kepentingan menaruh di dokumen atau lainnya.

## Catatan

- Dashboard ini seharusnya bergantung pada paket hidrokit terutama pada modul `hidrokit.contrib.taruma.hk98` mengenai rekap data (tabel analisis). Akan tetapi ditemukan isu di hidrokit/hidrokit#219. Sehingga untuk sementara modul tersebut terpisah dengan dashboard ini.
198 changes: 135 additions & 63 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.io as pio
import pyfigure
import pyfunc
import pylayout
from dash import dcc, Input, Output, State
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
Expand All @@ -23,10 +21,8 @@
"https://cdn.jsdelivr.net/gh/AnnMarieW/[email protected]/dbc.min.css"
)

# GLOBAL DATASET
DATAFRAME = None
DF_FILENAME = None
DF_FILEDATE = None
# GLOBAL VARS
SUMMARY_ALL = None

# APP
app = dash.Dash(
Expand All @@ -45,15 +41,19 @@
[
pylayout.HTML_TITLE,
pylayout.HTML_SUBTITLE,
pylayout.HTML_ALERT,
pylayout.HTML_ALERT_README,
pylayout.HTML_ROW_BUTTON_UPLOAD,
pylayout.HTML_ROW_TABLE,
pylayout.HTML_ROW_BUTTON_VIZ,
pylayout.HTML_ROW_OPTIONS_GRAPH_RAINFALL,
pylayout.HTML_ROW_GRAPH_ONE,
pylayout.HTML_ROW_BUTTON_ANALYZE,
pylayout.HTML_ROW_TABLE_ANALYZE,
pylayout.HTML_ROW_BUTTON_VIZ_ANALYSIS,
pylayout.HTML_ROW_GRAPH_ANALYSIS,
pylayout.HTML_ALERT_CONTRIBUTION,
pylayout.HTML_MADEBY,
pylayout.HTML_OTHER_PROJECTS,
pylayout.HTML_FOOTER,
],
fluid=True,
Expand All @@ -63,141 +63,213 @@

@app.callback(
[
Output("output-data-upload", "children"),
Output("upload-data", "disabled"),
Output("row-table-uploaded", "children"),
Output("dcc-upload", "disabled"),
Output("button-upload", "disabled"),
Output("button-visualize", "disabled"),
Output("button-visualize", "outline"),
],
Input("upload-data", "contents"),
State("upload-data", "filename"),
State("upload-data", "last_modified"),
Input("dcc-upload", "contents"),
State("dcc-upload", "filename"),
State("dcc-upload", "last_modified"),
Input("button-skip", "n_clicks"),
prevent_initial_call=True,
)
def callback_upload(content, filename, filedate, _):
global DATAFRAME, DF_FILENAME, DF_FILEDATE

ctx = dash.callback_context

if content is not None:
DF_FILENAME = filename
DF_FILEDATE = filedate
children, DATAFRAME = pyfunc.parse_upload_data(content, filename, filedate)
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_dataset.csv"), index_col=0, parse_dates=True
dataframe = pd.read_csv(
Path(r"./example_1Y7S.csv"), index_col=0, parse_dates=True
)
filename = None
filedate = None

button_viz_disabled = True
button_upload_disabled = False
upload_disabled = False
button_upload_disabled = False
button_viz_disabled = True
button_viz_outline = True

if DATAFRAME is not None:
children = pylayout.create_table_layout(
DATAFRAME,
if dataframe is not None:
children = pylayoutfunc.create_table_layout(
dataframe,
"output-table",
filename=filename,
filedate=filedate,
editable=True,
renamable=True,
)
button_viz_disabled = False
button_upload_disabled = False
upload_disabled = False
button_upload_disabled = False
button_viz_disabled = False
button_viz_outline = False

return [
children,
upload_disabled, # upload_disabled,
button_upload_disabled, # button_upload_disabled,
button_viz_disabled, # button_viz_disabled,
upload_disabled,
button_upload_disabled,
button_viz_disabled,
button_viz_outline,
]


@app.callback(
[
Output("section-graph", "figure"),
Output("visibility-download-button", "style"),
Output("section-graph", "config"),
Output("graph-rainfall", "figure"),
Output("row-button-download-csv", "style"),
Output("graph-rainfall", "config"),
Output("container-graphbar-options", "style"),
Output("button-analyze", "disabled"),
Output('button-analyze', 'outline')
Output("button-analyze", "outline"),
],
Input("button-visualize", "n_clicks"),
State("output-table", "derived_virtual_data"),
State("output-table", "columns"),
State("graph-bar-options", "value"),
State("radio-graphbar-options", "value"),
prevent_initial_call=True,
)
def callback_visualize(_, table_data, table_columns, graphbar_opt):
global DATAFRAME

DATAFRAME = pyfunc.transform_to_dataframe(table_data, table_columns)
dataframe = pyfunc.transform_to_dataframe(table_data, table_columns)

download_row_visible = {"visibility": "visible"}
static_plot_enabled = {"staticPlot": False}
row_graphbar_visibile = {"visibility": "hidden"}
row_download_table_style = {"visibility": "visible"}
row_graph_config = {"staticPlot": False}
row_graphbar_options_style = {"visibility": "hidden"}
button_analyze_disabled = False
button_analyze_outline = False

if DATAFRAME.size > (366 * 8):
fig = pyfigure.figure_scatter(DATAFRAME)
if dataframe.size > (366 * 8):
fig = pyfigure.figure_scatter(dataframe)
else:
row_graphbar_visibile = {"visibility": "visible"}
row_graphbar_options_style = {"visibility": "visible"}
if graphbar_opt in ["group", "stack"]:
fig = pyfigure.figure_bar(DATAFRAME, graphbar_opt)
fig = pyfigure.figure_bar(dataframe, graphbar_opt)
else:
fig = pyfigure.figure_scatter(DATAFRAME)
fig = pyfigure.figure_scatter(dataframe)

return [
fig,
download_row_visible,
static_plot_enabled,
row_graphbar_visibile,
row_download_table_style,
row_graph_config,
row_graphbar_options_style,
button_analyze_disabled,
button_analyze_outline
button_analyze_outline,
]


@app.callback(
Output("download-csv", "data"),
Input("button-download-csv", "n_clicks"),
State("output-table", "derived_virtual_data"),
State("output-table", "columns"),
prevent_initial_call=True,
)
def callback_download(_):
return dcc.send_data_frame(DATAFRAME.to_csv, "derived_table.csv")
def callback_download_table(_, table_data, table_columns):
dataframe = pyfunc.transform_to_dataframe(table_data, table_columns)
return dcc.send_data_frame(dataframe.to_csv, "derived_table.csv")


@app.callback(
Output("col-table-analyze", "children"),
[
Output("tab-analysis", "children"),
Output("button-viz-analysis", "disabled"),
Output("button-viz-analysis", "outline"),
Output("row-button-download-analysis-csv", "style"),
],
Input("button-analyze", "n_clicks"),
State("output-table", "derived_virtual_data"),
State("output-table", "columns"),
prevent_initial_call=True,
)
def callback_analyze(n_clicks, table_data, table_columns):
global DATAFRAME
def callback_analyze(_, table_data, table_columns):
global SUMMARY_ALL

button_viz_analysis_disabled = True
button_viz_analysis_outline = True
row_button_download_analysis_style = {"visibility": "hidden"}

try:
dataframe = pyfunc.transform_to_dataframe(table_data, table_columns)
SUMMARY_ALL = pyfunc.generate_summary_all(dataframe, n_days=["16D", "MS", "YS"])
tables = [
pylayoutfunc.create_table_summary(
summary, f"table-analyze-{counter}", deletable=False
)
for counter, summary in enumerate(SUMMARY_ALL)
]

children = pylayoutfunc.create_tabcard_table_layout(tables)
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}")

return [
children,
button_viz_analysis_disabled,
button_viz_analysis_outline,
row_button_download_analysis_style,
]


@app.callback(
Output("download-analysis-csv", "data"),
Input("button-download-analysis-csv", "n_clicks"),
prevent_initial_call=True,
)
def callback_download_results(_):

dataframe = pd.concat(SUMMARY_ALL, axis=1, keys=["Biweekly", "Monthly", "Yearly"])
return dcc.send_data_frame(dataframe.to_csv, "results.csv")


@app.callback(
Output("tab-graph-analysis", "children"),
Input("button-viz-analysis", "n_clicks"),
prevent_initial_call=True,
)
def callback_troubleshoot(_):
from itertools import product

DATAFRAME = pyfunc.transform_to_dataframe(table_data, table_columns)
summary_all = pyfunc.generate_summary_all(DATAFRAME, n_days=["16D", "MS", "YS"])
label_periods = ["Biweekly", "Monthly", "Yearly"]
label_maxsum = ["Max + Sum"]
label_raindry = ["Dry + Rain"]
label_ufunc = label_maxsum + label_raindry

tables = [
pylayout.create_table_summary(
summary, f"table-analyze-{counter}", deletable=False
graphs_maxsum = [
pyfigure.figure_summary_maxsum(
summary, title=f"<b>{period}: {title}</b>", period=period
)
for counter, summary in enumerate(summary_all)
for summary, title, period in zip(SUMMARY_ALL, label_maxsum * 3, label_periods)
]
graphs_raindry = [
pyfigure.figure_summary_raindry(
summary, title=f"<b>{period}: {title}</b>", period=period
)
for summary, title, period in zip(SUMMARY_ALL, label_raindry * 3, label_periods)
]
graph_maxdate = [pyfigure.figure_summary_maxdate(SUMMARY_ALL)]

all_graphs = graphs_maxsum + graphs_raindry + graph_maxdate
labels = [": ".join(i) for i in product(label_ufunc, label_periods)]
labels += ["Maximum Rainfall Occurrence"]

children = pylayout.create_tabcard_layout(tables)
children = pylayoutfunc.create_tabcard_graph_layout(all_graphs, labels)

return children


@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)
Loading

0 comments on commit fb55c5e

Please sign in to comment.