Skip to content

Commit

Permalink
v2.7 directory structure adjustments
Browse files Browse the repository at this point in the history
  • Loading branch information
sparkcity committed Jul 21, 2023
1 parent f3c5990 commit a98358b
Show file tree
Hide file tree
Showing 8 changed files with 667 additions and 14 deletions.
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: panel serve --address="0.0.0.0" --port=$PORT src/campaign_dashboard/starbound_dashboard src/campaign_dashboard/protea_dashboard --allow-websocket-origin=campaign-dashboard.herokuapp.com
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

![Python Badge](https://img.shields.io/badge/Python-FFD43B?style=for-the-badge&logo=python&logoColor=blue) ![Jupyter Badge](https://img.shields.io/badge/Jupyter-F37626.svg?&style=for-the-badge&logo=Jupyter&logoColor=white) ![Pandas](https://img.shields.io/badge/pandas-%23150458.svg?style=for-the-badge&logo=pandas&logoColor=white) ![Plotly](https://img.shields.io/badge/Plotly-%233F4F75.svg?style=for-the-badge&logo=plotly&logoColor=white)

Live: https://sparkcity.github.io/campaign_dashboard/app.html
Static Github Pages Deployment: https://sparkcity.github.io/campaign_dashboard/app.html
Full Heroku Deployment: https://campaign-dashboard-5fce31c398a9.herokuapp.com/

A dashboard webapp built to display data visualizations related to the ongoing Dungeons & Dragons campaign I play in with my friends.

Expand Down
12 changes: 0 additions & 12 deletions environment.yml

This file was deleted.

5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
hvplot
jupyterlab
openpyxl
pyparsing
pyparsing
pandas
python
plotly
300 changes: 300 additions & 0 deletions src/campaign_dashboard/atlas_dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
################################# Imports and Initializations

import pandas as pd
import panel as pn
import plotly.express as px

ACCENT = "#A9A9A9"

SUCCESS_SOLID_BUTTON_STYLE = f"""
.bk-btn-success {{
background-color: var(--accent-foreground-rest, {ACCENT});
}}
.bk-active.bk-btn-success {{
background-color: var(--accent-foreground-active, {ACCENT});
}}
.bk-btn-success:hover {{
background-color: var(--accent-foreground-hover, {ACCENT});
}}
"""

pn.extension("plotly", "tabulator", sizing_mode="stretch_width")

combat_df = pd.DataFrame(
pd.read_csv(
"https://raw.githubusercontent.com/sparkcity/campaign_dashboard/main/src/data/starbound/starbound_combat.csv"
)
)
rolls_df = pd.DataFrame(
pd.read_csv(
"https://raw.githubusercontent.com/sparkcity/campaign_dashboard/main/src/data/starbound/starbound_rolls.csv"
)
)
attr_df = pd.DataFrame(
pd.read_csv(
"https://raw.githubusercontent.com/sparkcity/campaign_dashboard/main/src/data/starbound/starbound_attr.csv"
)
)
stats_df = pd.DataFrame(
pd.read_csv(
"https://raw.githubusercontent.com/sparkcity/campaign_dashboard/main/src/data/starbound/starbound_pcs.csv"
)
)

pc_color_map = {
"Sparrow": "#118ab2",
"Madaine": "#073b4c",
"Evelyn": "#06d6a0",
"Pollux": "#ffd166",
"Trey": "#ef476f",
}

sess_min = (rolls_df["session"].min()).item()
sess_max = (rolls_df["session"].max()).item()

################################# Party Visualizations: Context and Overall Rolls

##### Replace the below with the commented section in app.js
party_dist_df = pd.DataFrame(rolls_df["roll_base"].value_counts()).reset_index()

party_dist_fig = px.bar(
party_dist_df,
x="roll_base",
y="count",
color="count",
title="Party Rolls Distribution",
)
party_dist_fig.update_xaxes(tickmode="linear")

##### For whatever reason, the following has to be uncommented manually in app.js
##### to replace the above to work because the dataframe reset_index doesn't work properly
##### instead of making a df with cols ['roll_base','count'],
##### it always creates df with cols ['index' (should be roll _base),'roll_base' (should be count)]

# party_dist_df = pd.DataFrame(rolls_df["roll_base"].value_counts()).reset_index()

# party_dist_fig = px.bar(
# party_dist_df,
# x="index",
# y="roll_base",
# color="roll_base",
# labels={'index':'roll_base','roll_base':'count'},
# title="Party Rolls Distribution",
# )
# party_dist_fig.update_xaxes(tickmode="linear")

party_context_df = (
rolls_df.groupby(["pc", "context"]).size().unstack(fill_value=0).reset_index()
)
party_context_fig = px.bar(
party_context_df,
x="pc",
y=["Combat", "Exploration", "Social"],
title="All Rolls Per Character By Context",
)
party_context_fig.update_layout(autosize=True)

party_box = pn.WidgetBox(
pn.Column(
pn.Row(pn.pane.Markdown(f"# Party Visualizations")),
pn.Row(party_dist_fig),
pn.Row(party_context_fig),
)
)

################################# Party Visualizations: Total Combat Stats Per Character
select_combat_stat = pn.widgets.ToggleGroup(
name="Combat Stat Selection",
options=["dmg_taken", "dmg_dealt", "dmg_mitigated", "dmg_healed"],
behavior="radio",
value="dmg_taken",
)


def total_combat_stat(cntxt):
gb = combat_df.groupby("pc").sum().reset_index()

fig = px.bar(
gb,
x="pc",
y=cntxt,
color="pc",
color_discrete_map=pc_color_map,
title=f"Total {cntxt} Per Character",
)
return fig


def total_combat_stat_per_session(cntxt):
fig = px.line(
combat_df,
x="session",
y=cntxt,
color="pc",
color_discrete_map=pc_color_map,
markers=True,
hover_data=["combat_blurb"],
title=f"{cntxt} Per Character Per Session",
)
return fig


party_combat_box = pn.WidgetBox(
pn.Column(
pn.Row(pn.pane.Markdown(f"# Party Combat Visualizations")),
pn.Row(select_combat_stat),
pn.Row(pn.bind(total_combat_stat, select_combat_stat)),
pn.Row(pn.bind(total_combat_stat_per_session, select_combat_stat)),
align="start",
sizing_mode="stretch_width",
)
)

################################# Individual Visualizations

select_pc = pn.widgets.ToggleGroup(
name="Combat Stat Selection",
options=["Sparrow", "Madaine", "Trey", "Pollux", "Evelyn"],
behavior="radio",
value="Sparrow",
)


def stat_radar(nm):
df = stats_df.loc[stats_df["pc"] == nm]
pc_r = [
df["str"].item(),
df["dex"].item(),
df["con"].item(),
df["int"].item(),
df["wis"].item(),
df["cha"].item(),
]
pc_theta = ["str", "dex", "con", "int", "wis", "cha"]

fig = px.scatter_polar(df, title="Stats", r=pc_r, theta=pc_theta)
fig.update_traces(fill="toself")
return fig


def pc_sum(nm):
df = stats_df.loc[stats_df["pc"] == nm]
c_df = combat_df.loc[combat_df["pc"] == nm]

lvl = df["lvl"].item()
rc = df["race"].item()
cls = df["class"].item()
arctps = df["archetypes"].item()

if type(arctps) == float:
description = f"{nm} is a level {lvl} {rc} {cls} with no Archetypes."
else:
description = f"{nm} is a level {lvl} {rc} {cls} with Archetypes in {arctps}."

mx_dmg_dlt = c_df.loc[c_df["dmg_dealt"].idxmax()]
dmg_dlt = f"{mx_dmg_dlt['dmg_dealt']} damage dealt in Session {mx_dmg_dlt['session']}. </br> Combat encounter summary: {mx_dmg_dlt['combat_blurb']}"
mx_dmg_tkn = c_df.loc[c_df["dmg_taken"].idxmax()]
dmg_tkn = f"{mx_dmg_tkn['dmg_taken']} damage taken in Session {mx_dmg_tkn['session']}. </br> Combat encounter summary: {mx_dmg_tkn['combat_blurb']}"
mx_dmg_mtg = c_df.loc[c_df["dmg_mitigated"].idxmax()]
dmg_mtg = f"{mx_dmg_mtg['dmg_mitigated']} damage mitigated in Session {mx_dmg_mtg['session']}. </br> Combat encounter summary: {mx_dmg_mtg['combat_blurb']}"
mx_dmg_hld = c_df.loc[c_df["dmg_mitigated"].idxmax()]
dmg_hld = f"{mx_dmg_hld['dmg_healed']} damage healed in Session {mx_dmg_hld['session']}. </br> Combat encounter summary: {mx_dmg_hld['combat_blurb']}"
other = f"Total Successful Attacks: {c_df['atks_success'].sum()} </br> Total Failed Attacks: {c_df['atks_fail'].sum()} </br> Total Spells Cast: {c_df['spells_cast'].sum()} </br>Total Final Blows: {c_df['final_blows'].sum()}"

pane = pn.pane.Markdown(
f"""
# Character Highlights: {nm}
| Category | Info |
| ----------- | ----------- |
| **Description**| {description}|
| **Most Damage Dealt** | {dmg_dlt}|
| **Most Damage Taken**| {dmg_tkn}|
| **Most Damage Mitigated**|{dmg_mtg}|
| **Most Damage Healed**|{dmg_hld}|
| **Other**| {other} |
"""
)
return pane


def attr_radar(nm):
df = attr_df.loc[stats_df["pc"] == nm]
pc_r = [
df["acrobatics"].item(),
df["animal_handling"].item(),
df["arcana"].item(),
df["athletics"].item(),
df["deception"].item(),
df["history"].item(),
df["insight"].item(),
df["intimidation"].item(),
df["investigation"].item(),
df["medicine"].item(),
df["nature"].item(),
df["perception"].item(),
df["performance"].item(),
df["persuasion"].item(),
df["religion"].item(),
df["sleight_of_hand"].item(),
df["stealth"].item(),
df["survival"].item(),
]

pc_theta = [
"acrobatics",
"animal_handling",
"arcana",
"athletics",
"deception",
"history",
"insight",
"intimidation",
"investigation",
"medicine",
"nature",
"perception",
"performance",
"persuasion",
"religion",
"sleight_of_hand",
"stealth",
"survival",
]

fig = px.scatter_polar(df, title="Skill Bonuses", r=pc_r, theta=pc_theta)
fig.update_traces(fill="toself")
return fig


ind_box = pn.WidgetBox(
pn.Column(
pn.Row(pn.pane.Markdown(f"# Individual Visualizations")),
pn.Row(select_pc),
pn.Row(pn.bind(pc_sum, select_pc), pn.bind(stat_radar, select_pc)),
pn.Row(pn.bind(attr_radar, select_pc)),
align="start",
sizing_mode="stretch_width",
)
)

################################# Layout Template

template = pn.template.FastListTemplate(
title="Atlassian Campaign Visualizations",
sidebar=[
pn.pane.Markdown("## Dashboard"),
pn.pane.PNG(
"https://raw.githubusercontent.com/sparkcity/campaign_dashboard/main/img/thorns_logo.png",
sizing_mode="scale_both",
),
pn.pane.Markdown(
f"""Earliest Session Data Available: {sess_min}
<br/>Latest Session Data Available: {sess_max}"""
),
],
main=[pn.Row(party_box), pn.Row(party_combat_box), pn.Row(ind_box)],
main_max_width="1000px",
accent=ACCENT,
theme_toggle=False,
)
template.servable()
60 changes: 60 additions & 0 deletions src/campaign_dashboard/dashboard_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import pandas as pd
import panel as pn
import plotly.express as px


def stat_radar(nm, stats_df):
df = stats_df.loc[stats_df["pc"] == nm]
pc_r = [
df["str"].item(),
df["dex"].item(),
df["con"].item(),
df["int"].item(),
df["wis"].item(),
df["cha"].item(),
]
pc_theta = ["str", "dex", "con", "int", "wis", "cha"]

fig = px.scatter_polar(df, title="Stats", r=pc_r, theta=pc_theta)
fig.update_traces(fill="toself")
return fig


def pc_sum(nm, stats_df, combat_df):
df = stats_df.loc[stats_df["pc"] == nm]
c_df = combat_df.loc[combat_df["pc"] == nm]

lvl = df["lvl"].item()
rc = df["race"].item()
cls = df["class"].item()
arctps = df["archetypes"].item()

if type(arctps) == float:
description = f"{nm} is a level {lvl} {rc} {cls} with no Archetypes."
else:
description = f"{nm} is a level {lvl} {rc} {cls} with Archetypes in {arctps}."

mx_dmg_dlt = c_df.loc[c_df["dmg_dealt"].idxmax()]
dmg_dlt = f"{mx_dmg_dlt['dmg_dealt']} damage dealt in Session {mx_dmg_dlt['session']}. </br> Combat encounter summary: {mx_dmg_dlt['combat_blurb']}"
mx_dmg_tkn = c_df.loc[c_df["dmg_taken"].idxmax()]
dmg_tkn = f"{mx_dmg_tkn['dmg_taken']} damage taken in Session {mx_dmg_tkn['session']}. </br> Combat encounter summary: {mx_dmg_tkn['combat_blurb']}"
mx_dmg_mtg = c_df.loc[c_df["dmg_mitigated"].idxmax()]
dmg_mtg = f"{mx_dmg_mtg['dmg_mitigated']} damage mitigated in Session {mx_dmg_mtg['session']}. </br> Combat encounter summary: {mx_dmg_mtg['combat_blurb']}"
mx_dmg_hld = c_df.loc[c_df["dmg_mitigated"].idxmax()]
dmg_hld = f"{mx_dmg_hld['dmg_healed']} damage healed in Session {mx_dmg_hld['session']}. </br> Combat encounter summary: {mx_dmg_hld['combat_blurb']}"
other = f"Total Successful Attacks: {c_df['atks_success'].sum()} </br> Total Failed Attacks: {c_df['atks_fail'].sum()} </br> Total Spells Cast: {c_df['spells_cast'].sum()} </br>Total Final Blows: {c_df['final_blows'].sum()}"

pane = pn.pane.Markdown(
f"""
# Character Highlights: {nm}
| Category | Info |
| ----------- | ----------- |
| **Description**| {description}|
| **Most Damage Dealt** | {dmg_dlt}|
| **Most Damage Taken**| {dmg_tkn}|
| **Most Damage Mitigated**|{dmg_mtg}|
| **Most Damage Healed**|{dmg_hld}|
| **Other**| {other} |
"""
)
return pane
File renamed without changes.
Loading

0 comments on commit a98358b

Please sign in to comment.