# Sankey "Money Flow" Chart — Design ## Summary Add a new "Money Flow" tab to the Analysis view that renders a three-column Sankey diagram showing how income flows through spending categories into needs/wants/savings tags. Rendered entirely with matplotlib using custom bezier path drawing — no new dependencies. ## Data Structure Three columns of nodes connected by curved bands: ``` Income ──► Categories ──► Tags ``` - **Left**: Single "Income" node — sum of all positive-amount transactions in the filtered period. - **Middle**: One node per spending category — sized by total absolute spend. Plus a "Surplus" node if income exceeds total expenses. - **Right**: Needs, Wants, Savings, Untagged — sized by total absolute spend per tag. Bands represent dollar amounts flowing between nodes. Thickness is proportional to amount. All values come from `AnalysisService` queries filtered by the standard `_FilterBar` (date range, account, person). ## Visual Design ### Theme Integration (Cyberpunk "Matrix 2026") - Background: `VOID` (#0a0a0f), matching all other charts - Axes styled via existing `_style_ax()` / `_style_figure()` helpers - Color palette: reuses `CHART_COLORS`, `TAG_COLORS`, `NEON_GREEN`, `NEON_AMBER` ### Nodes - Thin vertical rectangles (width ~0.02 in axes coords) - Fill: node color at 60% opacity - Border: node color at full opacity, 1.5px - Glow: 2-3 progressively wider/more-transparent copies behind for neon bloom Colors: - Income node: `NEON_GREEN` (#00ff41) - Category nodes: `CHART_COLORS` palette (same as existing charts) - Tag nodes: `TAG_COLORS` (needs=green, wants=magenta, savings=cyan) - Surplus node: `NEON_AMBER` (#ffb627) - Untagged node: `TEXT_DIM` (#4a5568) ### Bands (Flows) - Smooth cubic bezier curves (matplotlib `Path` with `CURVE4` codes) - **Gradient fill**: Each band sliced into ~50 vertical strips, color interpolated from source to destination node color - **Glow effect**: A wider, more-transparent copy of each band rendered behind it for neon bloom - Alpha: ~0.35 for main band, ~0.08-0.15 for glow passes - Sorting: Largest bands drawn first (back), smallest on top ### Labels - Node name + dollar amount (e.g., "Groceries $1,234") - Left column labels: right-aligned, to the left of the node - Middle column labels: centered below/above nodes - Right column labels: left-aligned, to the right of the node - Font: `TEXT` color (#b8c4d4), fontsize 8 - Dollar amounts: bold ### Layout - Three columns at x = 0.1, 0.5, 0.9 (in axes normalized coords) - Nodes stacked vertically, sorted largest-on-top - Small gaps between nodes (~2% of total height) - Axes limits: x=[0, 1], y=[0, 1], all axes/spines hidden ## Implementation ### New Class: `_MoneyFlowTab(QWidget)` Follows the same pattern as `_SpendingOverTimeTab`, `_CategoryBreakdownTab`, and `_ForecastingTab`: ```python class _MoneyFlowTab(QWidget): def __init__(self, session, parent=None): # _FilterBar, Figure, FigureCanvasQTAgg, NavigationToolbar2QT # Connect filter signals to refresh() def refresh(self): # 1. Query income total, category totals, tag totals # 2. Build node positions and sizes # 3. Draw nodes, bands, labels via _draw_sankey() ``` ### Helper: `_draw_sankey(ax, income, categories, tags)` Separated rendering logic for testability and clarity: 1. Calculate node heights proportional to amounts 2. Position nodes in three columns with vertical gaps 3. For each flow (income→category, category→tag): draw gradient bezier bands with glow 4. Draw node rectangles with glow 5. Draw labels ### Data Queries Reuses existing `AnalysisService` methods: - `spending_by_category()` — for category totals - `spending_by_tag()` — for tag totals - Direct query for income total (sum of positive amounts in filtered period) - Cross-query: category-to-tag amounts (new query needed — group by category_id + tag) ### Tab Placement 4th tab in `AnalysisView._tabs`: "Money Flow" — inserted after "Category Breakdown", before "Forecasting". ## Files Changed - `src/ui/analysis_view.py` — add `_MoneyFlowTab` class and register in `AnalysisView.__init__` - `src/services/analysis.py` — add `spending_by_category_and_tag()` method for the category→tag cross-query ## No New Dependencies Everything uses matplotlib's existing `PathPatch`, `Path`, `Rectangle`, and color utilities — all already available via the `matplotlib>=3.8` dependency.