"""Time series figure factories for Toronto housing data.""" from typing import Any import plotly.express as px import plotly.graph_objects as go def create_price_time_series( data: list[dict[str, Any]], date_column: str = "full_date", price_column: str = "avg_price", group_column: str | None = None, title: str = "Average Price Over Time", show_yoy: bool = True, ) -> go.Figure: """Create a time series chart for price data. Args: data: List of records with date and price columns. date_column: Column name for dates. price_column: Column name for price values. group_column: Optional column for grouping (e.g., district_code). title: Chart title. show_yoy: Whether to show year-over-year change annotations. Returns: Plotly Figure object. """ import pandas as pd if not data: fig = go.Figure() fig.add_annotation( text="No data available", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, ) fig.update_layout(title=title, height=350) return fig df = pd.DataFrame(data) df[date_column] = pd.to_datetime(df[date_column]) if group_column and group_column in df.columns: fig = px.line( df, x=date_column, y=price_column, color=group_column, title=title, ) else: fig = px.line( df, x=date_column, y=price_column, title=title, ) fig.update_layout( height=350, margin={"l": 40, "r": 20, "t": 50, "b": 40}, xaxis_title="Date", yaxis_title=price_column.replace("_", " ").title(), yaxis_tickprefix="$", yaxis_tickformat=",", hovermode="x unified", ) return fig def create_volume_time_series( data: list[dict[str, Any]], date_column: str = "full_date", volume_column: str = "sales_count", group_column: str | None = None, title: str = "Sales Volume Over Time", chart_type: str = "bar", ) -> go.Figure: """Create a time series chart for volume/count data. Args: data: List of records with date and volume columns. date_column: Column name for dates. volume_column: Column name for volume values. group_column: Optional column for grouping. title: Chart title. chart_type: 'bar' or 'line'. Returns: Plotly Figure object. """ import pandas as pd if not data: fig = go.Figure() fig.add_annotation( text="No data available", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, ) fig.update_layout(title=title, height=350) return fig df = pd.DataFrame(data) df[date_column] = pd.to_datetime(df[date_column]) if chart_type == "bar": if group_column and group_column in df.columns: fig = px.bar( df, x=date_column, y=volume_column, color=group_column, title=title, ) else: fig = px.bar( df, x=date_column, y=volume_column, title=title, ) else: if group_column and group_column in df.columns: fig = px.line( df, x=date_column, y=volume_column, color=group_column, title=title, ) else: fig = px.line( df, x=date_column, y=volume_column, title=title, ) fig.update_layout( height=350, margin={"l": 40, "r": 20, "t": 50, "b": 40}, xaxis_title="Date", yaxis_title=volume_column.replace("_", " ").title(), yaxis_tickformat=",", hovermode="x unified", ) return fig def create_market_comparison_chart( data: list[dict[str, Any]], date_column: str = "full_date", metrics: list[str] | None = None, title: str = "Market Indicators", ) -> go.Figure: """Create a multi-metric comparison chart. Args: data: List of records with date and metric columns. date_column: Column name for dates. metrics: List of metric columns to display. title: Chart title. Returns: Plotly Figure object with secondary y-axis. """ import pandas as pd from plotly.subplots import make_subplots if not data: fig = go.Figure() fig.add_annotation( text="No data available", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, ) fig.update_layout(title=title, height=400) return fig if metrics is None: metrics = ["avg_price", "sales_count"] df = pd.DataFrame(data) df[date_column] = pd.to_datetime(df[date_column]) fig = make_subplots(specs=[[{"secondary_y": True}]]) colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"] for i, metric in enumerate(metrics[:4]): if metric not in df.columns: continue secondary = i > 0 fig.add_trace( go.Scatter( x=df[date_column], y=df[metric], name=metric.replace("_", " ").title(), line={"color": colors[i % len(colors)]}, ), secondary_y=secondary, ) fig.update_layout( title=title, height=400, margin={"l": 40, "r": 40, "t": 50, "b": 40}, hovermode="x unified", legend={ "orientation": "h", "yanchor": "bottom", "y": 1.02, "xanchor": "right", "x": 1, }, ) return fig