Some checks failed
CI / lint-and-test (pull_request) Has been cancelled
- Update dbt model references to use new schema naming (stg_toronto, int_toronto, mart_toronto) - Refactor figure factories to use consistent column naming from new schema - Update callbacks to work with refactored data structures - Add centralized design tokens module for consistent styling - Streamline CLAUDE.md documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
195 lines
5.1 KiB
Python
195 lines
5.1 KiB
Python
"""Scatter plot figure factory for correlation views."""
|
|
|
|
from typing import Any
|
|
|
|
import pandas as pd
|
|
import plotly.express as px
|
|
import plotly.graph_objects as go
|
|
|
|
from portfolio_app.design import (
|
|
CHART_PALETTE,
|
|
GRID_COLOR,
|
|
PAPER_BG,
|
|
PLOT_BG,
|
|
TEXT_PRIMARY,
|
|
TEXT_SECONDARY,
|
|
)
|
|
|
|
|
|
def create_scatter_figure(
|
|
data: list[dict[str, Any]],
|
|
x_column: str,
|
|
y_column: str,
|
|
name_column: str | None = None,
|
|
size_column: str | None = None,
|
|
color_column: str | None = None,
|
|
title: str | None = None,
|
|
x_title: str | None = None,
|
|
y_title: str | None = None,
|
|
trendline: bool = False,
|
|
color_scale: str = "Blues",
|
|
) -> go.Figure:
|
|
"""Create scatter plot for correlation visualization.
|
|
|
|
Args:
|
|
data: List of data records.
|
|
x_column: Column name for x-axis values.
|
|
y_column: Column name for y-axis values.
|
|
name_column: Column name for point labels (hover).
|
|
size_column: Column name for point sizes.
|
|
color_column: Column name for color encoding.
|
|
title: Optional chart title.
|
|
x_title: X-axis title.
|
|
y_title: Y-axis title.
|
|
trendline: Whether to add OLS trendline.
|
|
color_scale: Plotly color scale for continuous colors.
|
|
|
|
Returns:
|
|
Plotly Figure object.
|
|
"""
|
|
if not data:
|
|
return _create_empty_figure(title or "Scatter Plot")
|
|
|
|
df = pd.DataFrame(data)
|
|
|
|
# Build hover_data
|
|
hover_data = {}
|
|
if name_column and name_column in df.columns:
|
|
hover_data[name_column] = True
|
|
|
|
# Create scatter plot
|
|
fig = px.scatter(
|
|
df,
|
|
x=x_column,
|
|
y=y_column,
|
|
size=size_column if size_column and size_column in df.columns else None,
|
|
color=color_column if color_column and color_column in df.columns else None,
|
|
color_continuous_scale=color_scale,
|
|
hover_name=name_column,
|
|
trendline="ols" if trendline else None,
|
|
opacity=0.7,
|
|
)
|
|
|
|
# Style the markers
|
|
fig.update_traces(
|
|
marker={
|
|
"line": {"width": 1, "color": "rgba(255,255,255,0.3)"},
|
|
},
|
|
)
|
|
|
|
# Trendline styling
|
|
if trendline:
|
|
fig.update_traces(
|
|
selector={"mode": "lines"},
|
|
line={"color": CHART_PALETTE[1], "dash": "dash", "width": 2},
|
|
)
|
|
|
|
fig.update_layout(
|
|
title=title,
|
|
paper_bgcolor=PAPER_BG,
|
|
plot_bgcolor=PLOT_BG,
|
|
font_color=TEXT_PRIMARY,
|
|
xaxis={
|
|
"gridcolor": GRID_COLOR,
|
|
"title": x_title or x_column.replace("_", " ").title(),
|
|
"zeroline": False,
|
|
},
|
|
yaxis={
|
|
"gridcolor": GRID_COLOR,
|
|
"title": y_title or y_column.replace("_", " ").title(),
|
|
"zeroline": False,
|
|
},
|
|
margin={"l": 10, "r": 10, "t": 40, "b": 10},
|
|
showlegend=color_column is not None,
|
|
)
|
|
|
|
return fig
|
|
|
|
|
|
def create_bubble_chart(
|
|
data: list[dict[str, Any]],
|
|
x_column: str,
|
|
y_column: str,
|
|
size_column: str,
|
|
name_column: str | None = None,
|
|
color_column: str | None = None,
|
|
title: str | None = None,
|
|
x_title: str | None = None,
|
|
y_title: str | None = None,
|
|
size_max: int = 50,
|
|
) -> go.Figure:
|
|
"""Create bubble chart with sized markers.
|
|
|
|
Args:
|
|
data: List of data records.
|
|
x_column: Column name for x-axis values.
|
|
y_column: Column name for y-axis values.
|
|
size_column: Column name for bubble sizes.
|
|
name_column: Column name for labels.
|
|
color_column: Column name for colors.
|
|
title: Optional chart title.
|
|
x_title: X-axis title.
|
|
y_title: Y-axis title.
|
|
size_max: Maximum marker size in pixels.
|
|
|
|
Returns:
|
|
Plotly Figure object.
|
|
"""
|
|
if not data:
|
|
return _create_empty_figure(title or "Bubble Chart")
|
|
|
|
df = pd.DataFrame(data)
|
|
|
|
fig = px.scatter(
|
|
df,
|
|
x=x_column,
|
|
y=y_column,
|
|
size=size_column,
|
|
color=color_column,
|
|
hover_name=name_column,
|
|
size_max=size_max,
|
|
opacity=0.7,
|
|
color_discrete_sequence=CHART_PALETTE,
|
|
)
|
|
|
|
fig.update_layout(
|
|
title=title,
|
|
paper_bgcolor=PAPER_BG,
|
|
plot_bgcolor=PLOT_BG,
|
|
font_color=TEXT_PRIMARY,
|
|
xaxis={
|
|
"gridcolor": GRID_COLOR,
|
|
"title": x_title or x_column.replace("_", " ").title(),
|
|
},
|
|
yaxis={
|
|
"gridcolor": GRID_COLOR,
|
|
"title": y_title or y_column.replace("_", " ").title(),
|
|
},
|
|
margin={"l": 10, "r": 10, "t": 40, "b": 10},
|
|
)
|
|
|
|
return fig
|
|
|
|
|
|
def _create_empty_figure(title: str) -> go.Figure:
|
|
"""Create an empty figure with a message."""
|
|
fig = go.Figure()
|
|
fig.add_annotation(
|
|
text="No data available",
|
|
xref="paper",
|
|
yref="paper",
|
|
x=0.5,
|
|
y=0.5,
|
|
showarrow=False,
|
|
font={"size": 14, "color": TEXT_SECONDARY},
|
|
)
|
|
fig.update_layout(
|
|
title=title,
|
|
paper_bgcolor=PAPER_BG,
|
|
plot_bgcolor=PLOT_BG,
|
|
font_color=TEXT_PRIMARY,
|
|
xaxis={"visible": False},
|
|
yaxis={"visible": False},
|
|
)
|
|
return fig
|