Spaces:
Running
Running
Create utils/_charts.py
Browse files- utils/_charts.py +193 -0
utils/_charts.py
ADDED
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Contains custom components and charts used inside the dashboard."""
|
2 |
+
|
3 |
+
from typing import List, Literal, Optional
|
4 |
+
|
5 |
+
import dash_bootstrap_components as dbc
|
6 |
+
import pandas as pd
|
7 |
+
import vizro.models as vm
|
8 |
+
import vizro.plotly.express as px
|
9 |
+
from dash import html
|
10 |
+
from vizro.models.types import capture
|
11 |
+
|
12 |
+
|
13 |
+
# CUSTOM COMPONENTS -------------------------------------------------------------
|
14 |
+
# Note: This is a static KPI Card only (it will not be reactive to controls). A new dynamic KPI Card component
|
15 |
+
# is currently in development.
|
16 |
+
class KPI(vm.VizroBaseModel):
|
17 |
+
"""Static custom `KPI` Card."""
|
18 |
+
|
19 |
+
type: Literal["kpi"] = "kpi"
|
20 |
+
title: str
|
21 |
+
value: str
|
22 |
+
icon: str
|
23 |
+
sign: Literal["delta-pos", "delta-neg"]
|
24 |
+
ref_value: str
|
25 |
+
|
26 |
+
def build(self):
|
27 |
+
return dbc.Card(
|
28 |
+
[
|
29 |
+
html.H4(self.title),
|
30 |
+
html.P(self.value),
|
31 |
+
html.Span(
|
32 |
+
[
|
33 |
+
html.Span(self.icon, className="material-symbols-outlined"),
|
34 |
+
html.Span(self.ref_value),
|
35 |
+
],
|
36 |
+
className=self.sign,
|
37 |
+
),
|
38 |
+
],
|
39 |
+
className="kpi-card-ref",
|
40 |
+
)
|
41 |
+
|
42 |
+
|
43 |
+
# CUSTOM CHARTS ----------------------------------------------------------------
|
44 |
+
@capture("graph")
|
45 |
+
def bar(
|
46 |
+
x: str,
|
47 |
+
y: str,
|
48 |
+
data_frame: pd.DataFrame,
|
49 |
+
top_n: int = 15,
|
50 |
+
custom_data: Optional[List[str]] = None,
|
51 |
+
):
|
52 |
+
df_agg = data_frame.groupby(y).agg({x: "count"}).sort_values(by=x, ascending=False).reset_index()
|
53 |
+
|
54 |
+
fig = px.bar(
|
55 |
+
data_frame=df_agg.head(top_n),
|
56 |
+
x=x,
|
57 |
+
y=y,
|
58 |
+
orientation="h",
|
59 |
+
text=x,
|
60 |
+
color_discrete_sequence=["#1A85FF"],
|
61 |
+
custom_data=custom_data,
|
62 |
+
)
|
63 |
+
fig.update_layout(xaxis_title="# of Complaints", yaxis=dict(title="", autorange="reversed")) # noqa: C408
|
64 |
+
return fig
|
65 |
+
|
66 |
+
|
67 |
+
@capture("graph")
|
68 |
+
def line(x: str, y: str, data_frame: pd.DataFrame):
|
69 |
+
df_agg = data_frame.groupby(x).agg({y: "count"}).reset_index()
|
70 |
+
fig = px.area(
|
71 |
+
data_frame=df_agg,
|
72 |
+
x=x,
|
73 |
+
y=y,
|
74 |
+
color_discrete_sequence=["#1A85FF"],
|
75 |
+
title="Complaints over time",
|
76 |
+
)
|
77 |
+
fig.update_layout(xaxis_title="Date Received", yaxis_title="# of Complaints", title_pad_t=4)
|
78 |
+
return fig
|
79 |
+
|
80 |
+
|
81 |
+
@capture("graph")
|
82 |
+
def pie(
|
83 |
+
names: str,
|
84 |
+
values: str,
|
85 |
+
data_frame: pd.DataFrame = None,
|
86 |
+
title: Optional[str] = None,
|
87 |
+
custom_order: Optional[List[str]] = None,
|
88 |
+
):
|
89 |
+
df_agg = data_frame.groupby(names).agg({values: "count"}).reset_index()
|
90 |
+
|
91 |
+
# Apply custom order so colors are applied correctly to the pie chart
|
92 |
+
order_mapping = {category: index for index, category in enumerate(custom_order)}
|
93 |
+
df_sorted = df_agg.sort_values(by=names, key=lambda names: names.map(order_mapping))
|
94 |
+
|
95 |
+
fig = px.pie(
|
96 |
+
data_frame=df_sorted,
|
97 |
+
names=names,
|
98 |
+
values=values,
|
99 |
+
color_discrete_sequence=["#1a85ff", "#7ea1ee", "#adbedc", "#df658c", "#d41159"],
|
100 |
+
title=title,
|
101 |
+
hole=0.4,
|
102 |
+
)
|
103 |
+
|
104 |
+
fig.update_layout(legend_x=1, legend_y=1, title_pad_t=2, margin=dict(l=0, r=0, t=60, b=0)) # noqa: C408
|
105 |
+
fig.update_traces(sort=False)
|
106 |
+
return fig
|
107 |
+
|
108 |
+
|
109 |
+
@capture("graph")
|
110 |
+
def choropleth(
|
111 |
+
locations: str,
|
112 |
+
color: str,
|
113 |
+
data_frame: pd.DataFrame = None,
|
114 |
+
title: Optional[str] = None,
|
115 |
+
custom_data: Optional[List[str]] = None,
|
116 |
+
):
|
117 |
+
df_agg = data_frame.groupby(locations).agg({color: "count"}).reset_index()
|
118 |
+
|
119 |
+
fig = px.choropleth(
|
120 |
+
data_frame=df_agg,
|
121 |
+
locations=locations,
|
122 |
+
color=color,
|
123 |
+
color_continuous_scale=[
|
124 |
+
"#ded6d8",
|
125 |
+
"#f3bdcb",
|
126 |
+
"#f7a9be",
|
127 |
+
"#f894b1",
|
128 |
+
"#f780a3",
|
129 |
+
"#f46b94",
|
130 |
+
"#ee517f",
|
131 |
+
"#e94777",
|
132 |
+
"#e43d70",
|
133 |
+
"#df3168",
|
134 |
+
"#d92460",
|
135 |
+
"#d41159",
|
136 |
+
],
|
137 |
+
scope="usa",
|
138 |
+
locationmode="USA-states",
|
139 |
+
title=title,
|
140 |
+
custom_data=custom_data,
|
141 |
+
)
|
142 |
+
|
143 |
+
fig.update_coloraxes(colorbar={"thickness": 10, "title": {"side": "right"}})
|
144 |
+
return fig
|
145 |
+
|
146 |
+
|
147 |
+
# TABLE CONFIGURATIONS ---------------------------------------------------------
|
148 |
+
CELL_STYLE = {
|
149 |
+
"styleConditions": [
|
150 |
+
{
|
151 |
+
"condition": "params.value == 'Closed with explanation'",
|
152 |
+
"style": {"backgroundColor": "#1a85ff"},
|
153 |
+
},
|
154 |
+
{
|
155 |
+
"condition": "params.value == 'Closed with monetary relief'",
|
156 |
+
"style": {"backgroundColor": "#d41159"},
|
157 |
+
},
|
158 |
+
{
|
159 |
+
"condition": "params.value == 'Closed with non-monetary relief'",
|
160 |
+
"style": {"backgroundColor": "#adbedc"},
|
161 |
+
},
|
162 |
+
{
|
163 |
+
"condition": "params.value == 'Closed without relief'",
|
164 |
+
"style": {"backgroundColor": "#7ea1ee"},
|
165 |
+
},
|
166 |
+
{
|
167 |
+
"condition": "params.value == 'Closed with relief'",
|
168 |
+
"style": {"backgroundColor": "#df658c"},
|
169 |
+
},
|
170 |
+
{
|
171 |
+
"condition": "params.value == 'Closed'",
|
172 |
+
"style": {"backgroundColor": "#1a85ff"},
|
173 |
+
},
|
174 |
+
]
|
175 |
+
}
|
176 |
+
|
177 |
+
|
178 |
+
COLUMN_DEFS = [
|
179 |
+
{"field": "Complaint ID", "cellDataType": "text", "headerName": "ID", "flex": 3},
|
180 |
+
{"field": "Date Received", "cellDataType": "text", "headerName": "Date", "flex": 3},
|
181 |
+
{"field": "Channel", "cellDataType": "text", "flex": 3},
|
182 |
+
{"field": "State", "cellDataType": "text", "flex": 2},
|
183 |
+
{"field": "Product", "cellDataType": "text", "flex": 5},
|
184 |
+
{"field": "Issue", "cellDataType": "text", "flex": 5},
|
185 |
+
{
|
186 |
+
"field": "Company response - detailed",
|
187 |
+
"cellDataType": "text",
|
188 |
+
"cellStyle": CELL_STYLE,
|
189 |
+
"headerName": "Company response",
|
190 |
+
"flex": 6,
|
191 |
+
},
|
192 |
+
{"field": "Timely response?", "cellRenderer": "markdown", "headerName": "On time?", "flex": 3},
|
193 |
+
]
|