Skip to content

Crossfilter Phase 2.1: predicate-dimension spec + repository filter + pilot re-aggregate #44

@magicsunday

Description

@magicsunday

Goal

Make the DashboardBus actually re-filter data, not just propagate selection events. Phase 2.0 (#14) landed the infrastructure — bus, setSelection hook, donut self-highlight; this phase adds the user-visible payoff: clicking a slice somewhere re-renders every subscribed widget with its statistic restricted to that selection's subset.

Background

The Phase 2.0 pilot widget trio (births-per-century donut + marital-status donut + top-surnames tag cloud) carried disjoint dimensions (century / maritalStatus / surname), so visual-highlight on its own could not deliver the "decade-bar filters the migration sankey" UX the original RFC described. The bus is in place; what is still missing is a way for a widget to recompute its aggregate against a filtered subset.

Folds in #33 (concrete century → sankey + stream-graph pilot).

Three sub-tracks

1. Predicate-dimension spec

Define a canonical predicate shape so cross-widget compatibility stops being widget-pair-specific:

{ dimension: "surname" | "century" | "place" | "month" | ...,
  value:     <primitive> }

Update existing emitters in chart-lib so each widget annotates its own dimension instead of leaking widget-internal field names ({slice}, {name}, {label}). The bus payload stays opaque; receivers match on dimension first, then on value. Migrate donut-chart, sankey-flow, stream-graph, bar-chart and stacked-bar in one chart-lib commit.

2. Repository-level filtering

Every statistic that should be filterable accepts an optional filter: ?Predicate argument and routes it to the underlying repository as a WHERE clause. Pilot statistics:

  • EventRepository::getBirthsByCentury(?Predicate $filter = null)
  • EventRepository::getMaritalStatus(?Predicate $filter = null)
  • NameRepository::getTopSurnames(int $n, ?Predicate $filter = null)
  • MigrationRepository::flows(?Predicate $filter = null) — sankey
  • GivenNameTrendsRepository::topByDecade(int $n, ?Predicate $filter = null) — stream-graph

Server-side re-aggregation is fast for census-scale trees and keeps the ≤100 ms acceptance criterion of #14 alive. The repositories validate dimension/value pairs against an allow-list per repository — unknown dimensions fall back to no filter.

3. Pilot integration

Two pilot flows ship together so the cross-dimension story is testable end-to-end:

  • Surname-pilot — Top-surnames tag cloud emits {dimension: "surname", value: "<surname>"} on click. Births-per-century donut + marital-status donut subscribe to the bus, refetch their payload, and re-render.
  • Century-pilot (folded in from Cross-filter pilot: births-century donut → migration sankey + stream graph #33) — Births-by-century donut emits {dimension: "century", value: "<century>"} on click. Migration sankey + given-name stream graph subscribe, refetch, and re-render with the cohort restricted to that century.

Both pilots share a single "Active filter: : " banner at the top of the Statistics chart with a one-click reset.

Tests + fixtures (required for every shipped repo + widget integration)

  • Repository tests: assert the WHERE clause restricts the result; assert unknown dimension falls back to the unfiltered statistic; assert null filter yields the same result as omitting the argument.
  • Jest tests: simulate a selection on the tag cloud / donut, assert sibling widgets call setSelection and request a new payload.
  • Curated GEDCOM fixture (tests/fixtures/cross-filter-pilot.ged) with ≥3 surnames across ≥3 centuries, named in the PR description.

Out of scope (Phase 2.2)

  • Persisting selections across tab switches.
  • URL-encoding the active filter so a filter survives a page reload.
  • Multi-predicate filters (currently one dimension at a time).
  • Filter chaining (clicking "1900s" then "Sonntag" stacks both predicates).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Feature RequestNew feature or improvementneeds designSpec or brainstorm required before implementationphase 4Scope of the fourth feature wavepriority: mediumUseful, planned, not urgent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions