Skip to main content
All articles

Charts

Dashboard charts that survive real filters

Building ops and commerce charts with Recharts: shape data on the client, keep queries flexible, and avoid charts that break when users change date, owner, or grouping.

2026-08-20 · 10 min read · Elango P

Charts are products, not decorations

In operations and commerce dashboards, charts answer questions: How did orders move this week? Which owners closed more tickets? What does purchase volume look like by vendor or month? If the chart cannot survive a filter change, it is not ready for production users.

A reliable pattern is: the API returns rows (or a small report payload), and the UI builds chart series with pure functions. Libraries like Recharts then render bars or lines. That split keeps SQL or ORM queries testable and keeps presentation flexible.

Stakeholders often ask for “just a quick graph.” Treat that as a product request: define the question, the grain (day vs month), the filters, and the empty state before you pick colors.

Build chart data as a pure function

Prefer helpers named like buildOrderSummaryChartData(rows, params). Inputs are plain arrays and filter objects; output is the array Recharts expects—labels, values, and maybe stacked series.

Pure builders are easy to unit test. Feed them sample rows, assert the shape, and you catch regressions when someone renames a field. Avoid computing aggregates only inside JSX; that buries bugs in the render tree.

When the chart is wide (many owners or many days), compute a dynamic width from the series length so horizontal scroll is intentional instead of crushing labels into unreadability.

Name fields explicitly in the builder output (for example ownerName, closedCount) so tooltips and legends stay readable. Opaque keys like v1 and v2 force every chart consumer to guess.

If two reports share grouping logic, extract a shared groupByDate helper. Duplicated date-bucketing code is a common source of off-by-one timezone bugs.

Filters users actually use

Real dashboards filter by date range, month, location, shift, payment type, or owner. Design the report request so those filters are first-class query params. The chart builder should accept the same params so titles and axes stay consistent with the table below.

Offer grouping modes users understand: date-wise, month-wise, vendor-wise, hour-wise. Changing grouping should re-run the same API with a different group_by—not invent a second endpoint for every chart type when the underlying fact table is the same.

Empty states matter. When filters return zero rows, show a calm empty message instead of an empty SVG that looks broken.

Default filters should match how managers think: “this month” or “last 7 days,” not an unbounded all-time query that times out on the first visit.

When a filter combination is invalid (end before start), block submit in the UI and return 400 from the API with a clear message. Silent swapping of dates erodes trust.

Pair charts with tables

Ops users distrust charts they cannot verify. Put a table or export beside the graph with the same filtered dataset. Sorting the table should not silently disagree with the chart’s sort mode—share one sort setting when it makes sense.

For team performance views, a bar chart of totals plus a detailed table of components (open, closed, SLA) gives both the glance and the audit trail.

CSV export should use the same filter set as the on-screen chart. Nothing frustrates finance more than a download that does not match the picture they approved in a meeting.

  • API: filtered rows or a compact report DTO
  • UI: pure chart builders + Recharts (or similar)
  • UX: filters, grouping, empty states, matching tables
  • Export: same filters as the visible report

Accessibility and readability

Do not rely on color alone. Patterns, labels, or a table must carry the meaning for color-blind users.

Tooltips should include units and the filter context (“Closed tickets — Mar 2026”). A bare number without context gets screenshotted into Slack and causes arguments.

On small screens, prefer fewer categories or a horizontal bar layout. Forcing a dense 30-column chart onto a phone is a design failure, not a user failure.

Keyboard users should reach filters and export controls without trapping focus inside the SVG. Charts can be decorative for screen readers if the matching table is the accessible source of truth—just say so in aria labels.

Timezones, currencies, and rounding

Most chart bugs are not Recharts bugs—they are timezone and rounding bugs. Decide whether “day” means UTC midnight, the user’s browser zone, or the business location’s zone, and document it next to the date picker.

Money series should pick a currency and a rounding rule once. Mixing cents and whole units, or summing floats without fixed decimal math, creates totals that disagree with the finance export by a few pennies—and managers notice.

Month-wise grouping needs a stable month key (YYYY-MM in a chosen zone). Sorting month labels alphabetically (“Apr”, “Aug”, “Dec”) is a classic mistake; sort by the key, format for display.

When comparing periods (“this month vs last month”), align lengths carefully. February versus March will always look different in raw totals; sometimes a per-day average is the honest comparison.

Choosing the chart type

Bars are for comparing categories. Lines are for trends over time. Stacked bars are for composition—but only when the stack parts add to a meaningful whole. Pie charts struggle past five slices; prefer a bar or a table.

If users need both trend and composition, two small charts beat one overloaded combo chart with dual axes that nobody can explain in a standup.

Interactive legends that hide series are nice once the defaults are correct. Do not require a legend click to understand the first paint.

Performance notes

Do not send unbounded history to the browser. Cap date ranges on the server and paginate tables even if the chart uses an aggregated subset.

Memoize chart data with the filter dependencies so typing in an unrelated field does not rebuild a 500-point series on every keystroke.

Keep chart colors accessible: enough contrast in light UI, and consistent series colors across pages so “orders” does not mean blue on one screen and green on another.

If aggregation is expensive, pre-aggregate daily rollups in a job and let interactive filters hit the rollup table. Live scans of raw events are fine for short ranges only.

Watch payload size: a chart of daily totals for a year is fine as ~365 rows; shipping every raw event “just in case” will melt phones on cellular.

Testing charts without pixel snapshots

Unit-test the builders. Snapshot-test the JSON series, not the SVG pixels. Pixel snapshots flake when fonts or library internals change.

Add one integration test that applies a known filter set and asserts table row count matches the sum implied by the chart series. That single check catches most filter/chart drift.

For visual review, keep a short Storybook (or similar) page with fixture data: empty, sparse, dense, and “many categories.” Designers can review without a production database.

Closing

Good dashboard charts are filterable, testable, and honest. Shape data deliberately, render with a focused chart library, and always give users a way to inspect the numbers behind the bars.

When in doubt, ship a correct table first and add the chart once the filters and totals are trusted. Pretty graphics on wrong numbers are worse than no graphics at all.