Skip to content
All work

Case studyData Products2022

Energy Analytics Dashboard

An application analysts use to explore energy consumption and generation across sites, meters and long time ranges, through charts and tables that stay in step with each other.

energy · consumption analysis
RegionSiteMeterLast 12 monthsLogLinear

Consumption · logarithmic

MeasuredBaseline
1k100101MeasuredBaseline

Sites

Meters

Interval

Filters, charts and tables reading one stateThe filter bar at the top is not a control panel sitting beside the charts. It is the page address. Everything below derives from it, which is why the charts, the table and any export of them cannot disagree.

Interface recreated for portfolio purposes.

01

Problem to outcome

The complete case study in seven parts. The sections below provide the engineering detail behind this summary.

Problem

Every screen wants to show more series, more rows and more history than a browser renders comfortably, and every honest answer involves a trade-off the reader should be able to see. Analysts also collaborate by sharing views, so a dashboard that could not reproduce its own state from a link was failing at the main thing it was for.

Context

An application analysts use to explore energy consumption and generation across sites, meters and long time ranges, through charts and tables that stay in step with each other.

Constraints

  • Values spanning several orders of magnitude
  • Charts, tables and exports must never disagree
  • Exports have to be citable

My role

Senior Frontend Engineer on the dashboard application, working with data and design teams. The contribution covered the charting layer, shared filter model, large tables, and export pipeline.

Approach

Filters live in the page address rather than in memory, so any view can be shared as a link and survives a reload. Everything downstream (the data request, the charts, the tables and the exports) is derived from that one source, which is why they cannot disagree with each other.

Solution

Make the page address the source of truth. Filters, date range and chart scale live in the URL, and the charts, tables and exports are all derived from that one state, so they cannot drift apart, and any view is a link.

Outcome

Analysts can move through a large dataset without waiting on it, share an exact view as a link, and export something faithful enough to drop straight into a report.
as the source of truth, so any view is a shareable link
outputs from one state: charts, tables and exports
scales, chosen visibly rather than silently
deployment, so there is no server to hide slow work behind
02

What is the energy dashboard?

An analytics product for exploring energy data (consumption and generation across sites, meters and long time ranges) through linked charts and tables.

The people using it are analysts. They compare series over time, drill into anomalies, and export what they find into reports. They will notice if a chart quietly rounds something away.

How an analyst works

  1. 01

    Set filters

    Sites, meters, date range

  2. 02

    The address updates

    The view becomes a link

  3. 03

    Request

    Cached, and never sent twice

  4. 04

    Summarise

    Bounded points, however wide the range

  5. 05

    Draw

    Charts and tables from the same data

  6. 06

    Drill in

    Anomalies, and the rows behind them

  7. 07

    Export

    CSV, PDF or image of exactly this

  8. 08

    Share the link

    Someone else opens the same view

The last step is the one that shaped the architecture: the view has to survive being sent to somebody else.

What it does

Chart layer
Built on Nivo, with logarithmic and linear scales and axis formatting derived from whichever is active.
Shared filter system
One filter model stored in the page address, so every chart and table on screen is answering the same question.
Large tables
Responsive with far more rows than a browser will render at once, by rendering only what is visible.
Export pipeline
CSV, PDF and image, generated from the same prepared data the charts render rather than a second fetch.
Caching layer
Returning to a view you have already seen is instant rather than another round trip.
Static delivery
Built and served from S3 behind CloudFront, with long-lived caching on assets.
03

Why the problem was difficult

Drawing a line chart is a solved problem. Drawing one that stays honest when its values span several orders of magnitude, stays fast across years of readings, and can be reproduced exactly by someone who was sent a link, is not.

Analysts do not work alone. They compare series, find something odd, and send it to a colleague. A dashboard whose state lives only in memory looks completely fine to the person who built it and fails the moment it is shared. 'Here is the link, now set the filters like this' is not a feature.

And every screen wants more series, more rows and more history than a browser renders comfortably. Every honest answer to that involves a trade-off (summarising, virtualising, choosing a scale), and the reader has to be able to see which one was made.

How do you keep a dense dashboard fast and honest at the same time, when every honest answer involves a trade-off the reader should be able to see?

The question the rest of this page answers
04

Contribution and ownership

Charting

How is the data drawn?

  • The chart layer built on Nivo
  • Logarithmic and linear scales, with matching axis formatting
  • Rendering performance for dense charts and long date ranges

State and filters

What is the dashboard actually showing?

  • The shared filter model, stored in the page address
  • Parsing and validating what a link is asking for
  • The caching layer keyed on the parsed filter state

Data surfaces

What can an analyst take away?

  • Tables that stay responsive far past what a browser will render
  • The CSV, PDF and image export pipeline
  • Exports generated from the same data the charts render

Delivery

How does it reach people?

  • The static build
  • Delivery through S3 and CloudFront with long-lived asset caching

What isn’t mine

The data team owned collection, storage, summarisation, and the server-side aggregation behind the charts. The design team owned the visual direction. Frontend ownership covered the dashboard requirements, chart layer, filter model, tables, exports, and delivery.

05

The constraints shaped the product

Values spanning several orders of magnitude

A single chart can hold a site drawing megawatts alongside one drawing kilowatts. A linear scale flattens the small series into the axis; a logarithmic one misleads anyone who does not notice it.

Scale became an explicit, labelled control rather than something the chart decided quietly on the reader's behalf.

Charts, tables and exports must never disagree

The same numbers appear in three places. Any of them re-deriving or re-fetching independently is a disagreement waiting to be discovered by the analyst rather than by us.

All three are derived from one prepared dataset, and exports are generated from what is already on screen.

Exports have to be citable

An export that needs editing before it can go in a report is an export nobody uses. It has to carry what it was filtered to, and match the view it came from.

The active filters and scale are stamped onto every export, and image and PDF output is produced from the live page.

Designed wide, used on a laptop

The dashboards were designed for a large screen and are routinely opened on a 13-inch laptop in a meeting.

Layouts reflow rather than scale down, and dense charts stay legible at the width they are actually read at.

A static deployment

The application is built and served as static assets. There is no server-side rendering step and no backend of our own to move slow work into.

Anything expensive is either bounded before it arrives, deferred until it is needed, or cached. There is nowhere else to put it.

06

How it fits together

Filters live in the page address rather than in memory, so any view can be shared as a link and survives a reload. Everything downstream (the data request, the charts, the tables and the exports) is derived from that one source, which is why they cannot disagree with each other.

  1. Analyst

    Comparing, drilling in, exporting

  2. Dashboard

    Static build on S3 + CloudFront

  3. Page address

    FiltersDate rangeScale

  4. Filter layer

    Parses and validates what the URL asks for

  5. Data request

    Cached, deduplicated, never sent twice

  6. Shaping

    Server-side summarisingScale applied

  7. Outputs

    ChartsTablesExports

A filter change travels one path. The page address is the only thing upstream of the request, which is what makes a view reproducible and a cache hit possible.
07

Decisions that shaped the product

Each of these solved a real problem and cost something in return. The trade-off is stated alongside the decision, because a decision without one was not really a decision.

Decision 01

Putting the dashboard state in the URL

Problem

Analysts work by sending each other views. A dashboard whose state lives only in memory looks fine and cannot be shared, and 'here's the link, now set the filters like this' is not a feature.

Decision

Treat the page address as the source of truth for filters, date range and chart scale. Changing a filter changes the URL; everything else reads from there, including the cache key for the data request.

Trade-off

Filter changes go through parsing and validation rather than a simple state update, so there is more machinery, and the format has to be versioned as filters evolve. Shareable, reloadable, back-button-correct views were worth it.

Result

Every view is a link. Charts, tables and exports read from one place, so they cannot show different things.

Decision 02

Making the chart scale visible instead of automatic

Problem

Energy data spans several orders of magnitude. A linear scale flattens the small series into the axis; a logarithmic one misleads anyone who does not notice it. Both are wrong if chosen silently.

Decision

Make the scale an explicit, labelled part of the dashboard (shown on the chart, carried in the URL and stamped onto every export), with axis formatting derived from it.

Trade-off

More state to manage and one more thing on screen, where a quiet toggle would have been simpler to build. A chart that can silently change its own meaning is not simpler for the person reading it.

Result

Readers can always tell which scale they are looking at, including in a screenshot pasted into a deck.

Decision 03

Exporting from exactly what is on screen

Problem

Exports are where dashboards quietly lie: the export re-fetches or re-derives the data, and ends up disagreeing with the view the user was looking at.

Decision

Generate exports from the same prepared data the charts render, and produce image and PDF output from the live page. The heavy export code loads only when someone actually exports.

Trade-off

Page-based export is sensitive to layout, fonts and theming. The alternative (a second rendering path) is a second thing to keep in sync, and it drifts.

Result

What you export is what you were looking at, and the export machinery costs nothing until it is used.

Decision 04

Treating charts and tables as different performance problems

Problem

The obvious move was to throttle everything. But charts and tables fail differently at volume: charts struggle with the number of points drawn, tables with the number of rows in the page.

Decision

Summarise the data on the server before charting, so the number of points stays bounded however wide the range, and render only the table rows currently visible.

Trade-off

Summarising means the chart is not showing every raw reading, which has to be stated in the interface rather than assumed. Virtualised rows also complicate find-in-page.

Result

The dashboard stays responsive across the full date range instead of degrading as the range grows.
08

Making a chart tell the truth about its own scale

A chart is an argument about data, and the reader cannot check it unless they can see which choices were made. In energy data those choices are unavoidable: the values genuinely span orders of magnitude, and something has to give.

The temptation is to pick a scale automatically and say nothing. It produces a cleaner interface and a chart that can silently change its own meaning between one filter and the next.

Scale is a decision, shown
Logarithmic or linear is an explicit control, displayed on the chart, carried in the URL and stamped onto every export.
Axis formatting follows the scale
The formatting is derived from whichever scale is active, so the axis cannot describe a chart it is not on.
Summarised, and said so
Charts draw a bounded number of points however wide the range. That the chart is not showing every raw reading is stated in the interface rather than assumed.
A table for every chart
The table view is a real feature, not a fallback. The numbers are reachable by keyboard and screen reader without interpreting a picture.
Not colour alone
Series are identified by direct labels and line style, and the palette is verified against common forms of colour blindness.
Exports match the screen
What gets pasted into a report is generated from the same prepared data the chart drew, not a second query that might disagree.
09

Frontend architecture

One question organised the whole application: what is allowed to know what the dashboard is currently showing? The answer is exactly one thing, and everything else derives from it.

URL state

Filters, date range, chart scale

The source of truth. Everything downstream reads it, which is what makes a view shareable and the back button correct.

Server state

Summarised series and table pages

Keyed on the parsed filter state, so identical views are never fetched twice and returning to one is instant.

Derived data

Prepared chart data

Computed once per filter change and reused by charts, tables and exports, which is the reason the three cannot disagree.

UI state

Hover, tooltips, column visibility

Deliberately local and deliberately not in the URL. A shared link should reproduce a view, not someone's cursor.

10

Deep dive: one state, three outputs

Exports are where dashboards quietly lie. The export re-fetches or re-derives its data and ends up disagreeing with the view the analyst was looking at, and nobody finds out until it is already in a report.

Filters, Date range, Chart scale all resolve into The page address (Parsed and validated once, then used as the cache key), which is then read by Charts + tables and Exports.

UI
Changing a filter changes the URL. The back button works, a reload returns the same view, and a link opens what the sender was looking at.
State
The parsed filter state is the cache key, so two people opening the same link cost one request rather than two.
Failure
A malformed or outdated link degrades to a valid default rather than a blank dashboard, because the URL is validated rather than trusted.
Cost
Filter changes go through parsing and validation rather than a simple state update, and the format has to be versioned as filters evolve.
11

Trade-offs I had to make

Shareable state vs simple state updates

Putting the dashboard in the URL means every filter change is a parse-and-validate step, and the format becomes a compatibility surface the moment someone shares a link.

ChoiceTreat the address as the source of truth anyway. Shareable, reloadable, back-button-correct views were worth the machinery, though I should have versioned the format from the first commit rather than the first breaking change.

Truthful scale vs a quieter interface

A silent automatic scale is simpler to build and puts one less control on screen.

ChoiceMake the scale explicit and labelled. More state to manage and one more thing on screen, but a chart that can silently change its own meaning is not simpler for the person reading it.

Inspectable charts vs raw rendering speed

SVG is accessible, inspectable and easy to style. Past a certain number of marks it is also the slower answer, and canvas is not.

ChoiceStay on SVG and bound the point count by summarising server-side. It held, but I leaned on summarising longer than ideal to avoid giving up what SVG made easy.

12

Where it is now

Analysts can move through a large dataset without waiting on it, share an exact view as a link, and export something faithful enough to drop straight into a report.

Internally, the more useful outcome is that charts, tables and exports became three views of one state. New visualisations inherit filtering, scaling, caching and export instead of reimplementing them.

Built
A charting layer where scale, formatting and summarising are explicit
Designed
A shared filter model that makes any view a link
Implemented
Tables and a CSV, PDF and image export pipeline from one dataset
Delivered
A static build through S3 and CloudFront
Changed
Charts, tables and exports into three views of one state, so new visualisations inherit filtering, scaling, caching and export
13

What this experience changed

Put the state where the sharing happens

Analysts collaborate by sending links. Any state model that cannot survive being pasted into a chat window is solving a different problem from the one the product has.

A chart that chooses silently is not simpler

Automatic scale selection removes a control and adds an unanswerable question. The reader cannot check an argument whose assumptions are hidden.

Charts and tables fail differently

The instinct is to throttle everything. Charts struggle with points drawn and tables with rows in the page. One problem, two fixes, and throttling both would have solved neither well.

Version a shared format from the first commit

The URL filter format became a compatibility surface the moment someone shared a link. Retrofitting that after the first breaking change was avoidable work.

14

What I'd improve next

  • 01Move the densest charts to canvas rendering, which I deferred longer than the point counts justified
  • 02Formalise the shared chart pieces earlier, since two of them drifted before they were named and had to be reconciled
  • 03Version the URL filter format explicitly, rather than the compatibility shim it grew
  • 04State the summarising more prominently on the chart itself, not only in the surrounding interface
  • 05Visual regression tests on chart rendering, which review was catching by eye

05 / Contact

Have a complex product problem worth solving?

Available for senior frontend, product engineering, and AI product engineering opportunities.

connection.openavailable
  1. 01
  2. 02
  3. 03

    Location

    Bangalore, India · Remote