Source code for metrics.tempo_figures
"""The figures module"""
import logging
from datetime import date
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from metrics.tempo_config import EUR2DKK, EUR2SEK, ROLLING_DATE
# =========================================================
# Figure: Comparing normalized worktime with normalized income
# =========================================================
[docs]def figureEarningsVersusWorkload(user_data):
figure = px.scatter(height=600)
figure.add_trace(
go.Scatter(
x=user_data["%-billable"],
y=user_data["Rolling Weekly Average"],
mode="markers",
line=go.scatter.Line(color="darkgreen"),
name="Weekly",
)
)
figure.add_trace(
go.Scatter(
x=user_data["%-billable30"],
y=user_data["Rolling Monthly Average"],
mode="markers",
line=go.scatter.Line(color="salmon"),
name="Monthly",
)
)
figure.update_layout(xaxis_title="Billable fraction [%]", yaxis_title="Income / Cost")
figure.update_layout(legend=dict(title="", orientation="v", y=0.99, x=0.01, font_size=16))
return figure
# =========================================================
# Figure: Financial plots
# =========================================================
[docs]def figureFinancialTotal(supplementary_data, year=None):
figure = px.scatter(height=600)
monthly_result = supplementary_data.raw_costs[supplementary_data.raw_costs["Real_income"] != 0]
if not year is None:
monthly_result = monthly_result[monthly_result["Year"] == str(year)]
else:
monthly_result = monthly_result.tail(6)
figure.add_trace(
go.Scatter(
x=monthly_result["Month"],
y=monthly_result["Real_income"],
mode="lines",
line=go.scatter.Line(color="#0F5567"),
fill="tozeroy",
fillcolor="rgba(15,85,103,0.1)",
name="Income",
)
)
figure.add_trace(
go.Scatter(
x=monthly_result["Month"],
y=-monthly_result["External_cost"],
mode="lines",
line=go.scatter.Line(color="#F0AA98"),
fill="tozeroy",
fillcolor="rgba(240,170,152,0.1)",
name="Costs",
)
)
figure.add_trace(
go.Scatter(
x=monthly_result["Month"],
y=monthly_result["Real_income"] - monthly_result["External_cost"],
mode="lines",
line=go.scatter.Line(color="#C4C4C4"),
fill="tozeroy",
fillcolor="rgba(196,196,196,0.5)",
name="Result",
)
)
monthly_result = monthly_result[monthly_result["Month"].dt.day == 1]
monthly_result["Month"] = monthly_result["Month"] + pd.DateOffset(months=1) - pd.Timedelta("1 day")
monthly_sum_cost = monthly_result.rolling(12, min_periods=1)["External_cost"].sum()
monthly_sum_in = monthly_result.rolling(12, min_periods=1)["Real_income"].sum()
monthly_result["Result"] = monthly_sum_in - monthly_sum_cost
monthly_result["Name"] = monthly_result["Month"].dt.strftime("%Y-%m-01")
logging.debug("%s", monthly_result)
starting_bank = monthly_result["Starting_amount"].values[:1][0]
if starting_bank != 0:
monthly_result["First"] = pd.to_datetime(monthly_result["Name"])
figure.add_trace(
go.Scatter(
x=monthly_result["First"],
y=monthly_result["Starting_amount"],
mode="lines+markers",
line=go.scatter.Line(color="Green"),
name="Bank",
)
)
figure.add_trace(
go.Scatter(
x=monthly_result["Month"],
y=monthly_result["Result"],
mode="lines+markers",
line=go.scatter.Line(color="black"),
name="Cumulative result",
)
)
if year is None:
title = "last 3 months"
else:
title = year
figure.update_layout(title=f"Financial numbers for {title}", yaxis_title="Income/Cost/Result [ € ]", hovermode="x")
figure.update_layout(
legend=dict(title="Monthly", orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.75)
)
return figure
# =========================================================
# Figure: Normalised time (individual)
# =========================================================
[docs]def figureNormalisedIndividual(user_data):
figure = px.scatter(
user_data[user_data["Date"] > ROLLING_DATE],
x="Date",
y=["%-billable", "%-internal"],
facet_col="User",
facet_col_wrap=2,
facet_row_spacing=0.03,
color_discrete_sequence=["#8FBC8F", "#FF7F50"],
height=1600,
)
figure.update_layout(title="Normalised data, rolling 7 days", yaxis_title="Work time [%]")
return figure
# =========================================================
# Figure: Normalised time (team)
# =========================================================
[docs]def figureNormalisedTeam(team_data, last_date):
figure = px.scatter(
team_data.rename(
columns={
"%-billable": "Weekly Billable",
"%-internal": "Weekly Internal",
"%-billable30": "Monthly Billable",
"%-internal30": "Monthly Internal",
}
),
x="Date",
y=["Weekly Billable", "Weekly Internal", "Monthly Billable", "Monthly Internal"],
color_discrete_sequence=["#8FBC8F", "#FF7F50", "#006400", "#A52A2A"],
height=800,
)
figure.update_layout(title="Normalised team data", yaxis_title="Work time [%]")
figure.update_layout(
xaxis_rangeslider_visible=True,
xaxis_range=[ROLLING_DATE, str(date.today())],
)
figure.add_vrect(
x0=last_date,
x1=max(team_data["Date"]),
annotation_text="Incomplete reported data ᐅ",
annotation_position="bottom left",
fillcolor="darkred",
opacity=0.10,
line_width=0,
)
figure.update_layout(
legend=dict(title="Rolling fractions", orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.75)
)
figure.add_hline(y=50, fillcolor="darkslategrey")
return figure
# =========================================================
# Figure: Rolling income (individual)
# =========================================================
[docs]def figureRollingIncomeIndividual(df_user_income_rolling):
figure = px.scatter(
df_user_income_rolling[df_user_income_rolling["Date"] > ROLLING_DATE],
x="Date",
y="Income",
facet_col="User",
facet_col_wrap=2,
facet_row_spacing=0.03,
height=1600,
)
figure.update_layout(title="Rolling 7 days (income)")
return figure
# =========================================================
# Figure: Rolling income (team)
# =========================================================
[docs]def figureRollingIncomeTeam(df_average_income_rolling_30, last_date):
figure = px.scatter(
df_average_income_rolling_30,
x="Date",
y=["Income", "Income30"],
color_discrete_sequence=["#8FBC8F", "#006400"],
height=600,
)
figure.update_layout(
title="Rolling weekly income (average/person)",
yaxis_title="Income [ € ]",
)
figure.update_layout(
xaxis_rangeslider_visible=True,
xaxis_range=[ROLLING_DATE, str(date.today())],
)
figure.update_layout(legend=dict(title="", orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.75))
figure.add_vrect(
x0=last_date,
x1=max(df_average_income_rolling_30["Date"]),
annotation_text="Incomplete reported data ᐅ",
annotation_position="bottom left",
fillcolor="darkred",
opacity=0.10,
line_width=0,
)
return figure
# =========================================================
# Figure: Rolling total
# =========================================================
[docs]def figureRollingTotal(df_team_rolling_total, supplementary_data):
df_raw_costs = supplementary_data.raw_costs
figure = px.scatter(
df_team_rolling_total,
x="Date",
y=["Income", "Income30"],
color_discrete_sequence=["#B6B6B6", "#81C784"],
height=600,
)
if not df_raw_costs.empty:
figure.add_trace(
go.Scatter(
x=df_raw_costs["Month"],
y=df_raw_costs["WeeklyExtCost"],
mode="lines",
line=go.scatter.Line(color="salmon"),
fill="tozeroy",
fillcolor="rgba(250,128,114,0.1)",
name="Costs",
)
)
figure.update_layout(
title="Rolling weekly income (total)",
yaxis_title="Income [ € ]",
)
figure.update_layout(legend=dict(title="", orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.75))
uncertain_area = supplementary_data.costs[supplementary_data.costs["Real_income"] == 0]
figure.add_vrect(
x0=min(uncertain_area["Date"]),
x1=max(uncertain_area["Date"]) + pd.Timedelta(1, "D"),
annotation_text="Uncertain ᐅ",
annotation_position="bottom left",
fillcolor="grey",
opacity=0.15,
line_width=0,
)
return figure
# =========================================================
# Figure: Spent time on; billable/verifriday/other
# =========================================================
# Requires config: rates
[docs]def figureSpentTimePercentage(tempo_data):
df_by_group = tempo_data.byTimeType().sort_values("Group")
figure = px.histogram(
df_by_group[df_by_group["Date"] > ROLLING_DATE],
x="Date",
y="Time",
color="Timetype",
height=400,
barnorm="percent",
)
figure.update_layout(bargap=0.1, xaxis_title="", yaxis_title="Fractions [%]")
figure.update_layout(
legend=dict(title="Project Key", orientation="h", yanchor="top", y=-0.05, xanchor="center", x=0.5, font_size=16)
)
return figure
# =================================
# Figure: rates to EUR
# =================================
[docs]def figureRatesToEUR():
"""Simple plot to show € for rates in SEK and DKK"""
df_rates = pd.DataFrame()
df_rates["Rates"] = range(750, 1500, 25)
df_rates["SEK"] = df_rates["Rates"] / EUR2SEK
df_rates["DKK"] = df_rates["Rates"] / EUR2DKK
figure = px.scatter(height=400)
figure.add_scatter(
x=df_rates["Rates"],
y=df_rates["SEK"],
name="SEK",
mode="lines+markers",
line=dict(color="DarkBlue", dash="dot"),
)
figure.add_scatter(
x=df_rates["Rates"], y=df_rates["DKK"], name="DKK", mode="lines+markers", line=dict(color="crimson", dash="dot")
)
figure.update_traces(hovertemplate="EUR: %{y:.0f}, RATE: %{x:.0f}")
figure.update_xaxes(showspikes=True)
figure.update_yaxes(showspikes=True)
figure.update_layout(
title="Rate converter",
yaxis_title="Rates [€]",
xaxis_title="Hourly rates",
legend=dict(title="", orientation="v", y=0.99, x=0.01, font_size=16),
)
return figure