############ Imports ##############
import pandas as pd
import vizro.models as vm
from vizro import Vizro
import vizro.plotly.express as vxe
from vizro.figures import capture
from pathlib import Path
############ Data Load & Prep ############
CSV_PATH = Path("Superstore Dataset.csv") # adjust if your filename differs
df = pd.read_csv(CSV_PATH)
# Dates
for col in ["Order Date", "Ship Date"]:
if col in df.columns:
df[col] = pd.to_datetime(df[col], errors="coerce")
# Clean basic fields
essential_cols = ["Order Date", "Category", "Sub-Category", "Segment", "Sales", "Profit", "Discount"]
df = df.dropna(subset=[c for c in essential_cols if c in df.columns])
# Derive Year/Month
df["Year"] = df["Order Date"].dt.year
df["Month"] = df["Order Date"].dt.to_period("M").dt.to_timestamp()
df = df.sort_values("Order Date")
year_min = int(df["Year"].min())
year_max = int(df["Year"].max())
categories = sorted(df["Category"].dropna().unique().tolist())
segments = sorted(df["Segment"].dropna().unique().tolist())
############ Figures ############
@capture("graph")
def kpi_bars(data_frame):
if data_frame.empty:
safe = pd.DataFrame({"Metric": [], "Value": []})
return vxe.px.bar(safe, x="Metric", y="Value", title="KPIs")
total_sales = data_frame["Sales"].sum()
total_profit = data_frame["Profit"].sum()
orders = len(data_frame)
kpi_df = pd.DataFrame({"Metric": ["Sales", "Profit", "Orders"],
"Value": [total_sales, total_profit, orders]})
fig = vxe.px.bar(kpi_df, x="Metric", y="Value", text="Value", title="KPIs (current filters)")
fig.update_traces(texttemplate="%{text:.0f}", textposition="outside")
fig.update_layout(yaxis_title=None, xaxis_title=None, uniformtext_minsize=10, uniformtext_mode="hide")
return fig
@capture("graph")
def sales_over_time(data_frame):
if data_frame.empty:
safe = pd.DataFrame({"Month": [], "Sales": []})
return vxe.px.line(safe, x="Month", y="Sales", title="Sales by Month")
m = (data_frame.groupby("Month", as_index=False)
.agg(Sales=("Sales", "sum"))
.sort_values("Month"))
m["Sales_Rolling"] = m["Sales"].rolling(3, min_periods=1).mean()
fig = vxe.px.line(m, x="Month", y="Sales", title="Sales by Month (3-mo avg overlay)")
fig.add_traces(vxe.px.line(m, x="Month", y="Sales_Rolling").data)
fig.data[1].name = "3-mo Avg"
fig.data[1].line.update(dash="dash")
fig.update_layout(legend_title=None, yaxis_title="Sales")
return fig
@capture("graph")
def sales_profit_by_category(data_frame):
if data_frame.empty:
safe = pd.DataFrame({"Category": [], "Sales": [], "Profit": []})
return vxe.px.bar(safe, x="Category", y="Sales", title="Sales & Profit by Category")
agg = (data_frame.groupby("Category", as_index=False)
.agg(Sales=("Sales", "sum"), Profit=("Profit", "sum"))
.sort_values("Sales", ascending=False))
fig = vxe.px.bar(agg, x="Category", y=["Sales", "Profit"], barmode="group",
title="Sales & Profit by Category")
fig.update_layout(yaxis_title="Amount")
return fig
@capture("graph")
def profit_vs_discount(data_frame):
if data_frame.empty:
safe = pd.DataFrame({"Discount": [], "Profit": [], "Sub-Category": [], "Sales": []})
return vxe.px.scatter(safe, x="Discount", y="Profit", title="Profit vs Discount")
d = data_frame.copy()
d["Discount"] = d["Discount"].clip(lower=-0.2, upper=0.9)
fig = vxe.px.scatter(
d, x="Discount", y="Profit", color="Sub-Category", size="Sales",
hover_data=["Category", "Segment", "Sales"],
title="Profit vs Discount (bubble size = Sales)"
)
fig.update_layout(legend_title=None)
return fig
@capture("graph")
def top_subcategories(data_frame, top_n: int = 10):
if data_frame.empty:
safe = pd.DataFrame({"Sub-Category": [], "Sales": []})
return vxe.px.bar(safe, x="Sales", y="Sub-Category", orientation="h",
title=f"Top {top_n} Sub-Categories by Sales")
agg = (data_frame.groupby("Sub-Category", as_index=False)
.agg(Sales=("Sales", "sum"), Profit=("Profit", "sum"))
.sort_values("Sales", ascending=False)
.head(top_n).sort_values("Sales"))
fig = vxe.px.bar(agg, x="Sales", y="Sub-Category", orientation="h",
title=f"Top {top_n} Sub-Categories by Sales")
fig.update_layout(xaxis_title="Sales", yaxis_title=None)
return fig
############ PAGE-SCOPED CONTROLS ############
# Page 1 controls (only target page 1 component IDs)
year_filter_p1 = vm.Filter(
id="year_filter_p1",
type="filter",
column="Year",
targets=["kpi_chart", "line_sales", "cat_bars"],
selector={"type": "range_slider", "min": year_min, "max": year_max, "value": [year_max - 2, year_max]},
)
category_filter_p1 = vm.Filter(
id="category_filter_p1",
type="filter",
column="Category",
targets=["kpi_chart", "line_sales", "cat_bars"],
selector=vm.Checklist(options=categories, value=[]),
)
segment_filter_p1 = vm.Filter(
id="segment_filter_p1",
type="filter",
column="Segment",
targets=["kpi_chart", "line_sales", "cat_bars"],
selector=vm.Checklist(options=segments, value=[]),
)
# Page 2 controls (only target page 2 component IDs)
year_filter_p2 = vm.Filter(
id="year_filter_p2",
type="filter",
column="Year",
targets=["scatter_pd", "top_subcats"],
selector={"type": "range_slider", "min": year_min, "max": year_max, "value": [year_max - 2, year_max]},
)
category_filter_p2 = vm.Filter(
id="category_filter_p2",
type="filter",
column="Category",
targets=["scatter_pd", "top_subcats"],
selector=vm.Checklist(options=categories, value=[]),
)
segment_filter_p2 = vm.Filter(
id="segment_filter_p2",
type="filter",
column="Segment",
targets=["scatter_pd", "top_subcats"],
selector=vm.Checklist(options=segments, value=[]),
)
############ Dashboard Model ############
model = vm.Dashboard(
pages=[
vm.Page(
title="Overview",
components=[
vm.Tabs(
type="tabs",
tabs=[
vm.Container(
type="container",
title="KPIs",
components=[vm.Graph(id="kpi_chart", figure=kpi_bars(df))],
),
vm.Container(
type="container",
title="Sales Over Time",
components=[vm.Graph(id="line_sales", figure=sales_over_time(df))]
),
vm.Container(
type="container",
title="Category Performance",
components=[vm.Graph(id="cat_bars", figure=sales_profit_by_category(df))]
),
],
)
],
controls=[year_filter_p1, category_filter_p1, segment_filter_p1],
layout=vm.Flex(),
),
vm.Page(
title="Product & Customer",
components=[
vm.Tabs(
type="tabs",
tabs=[
vm.Container(
type="container",
title="Profit vs Discount",
components=[vm.Graph(id="scatter_pd", figure=profit_vs_discount(df))]
),
vm.Container(
type="container",
title="Top Sub-Categories",
components=[vm.Graph(id="top_subcats", figure=top_subcategories(df))]
),
],
)
],
controls=[year_filter_p2, category_filter_p2, segment_filter_p2],
layout=vm.Flex(),
),
],
theme="vizro_light",
title="Superstore Sales Dashboard",
)
############ Run App ############
app = Vizro().build(model)
if __name__ == "__main__":
app.run(debug=True, port=8050)