Skip to content
← back to projects
AI & Data

The Night Shift: Visualizing Sleep, Stress, and Health.

University of TorontoOct 2025 - Nov 2025

An interactive D3.js data story that pushes back on a simple assumption: that your job is what determines your sleep. Three visualizations, one dataset of 374 people across 11 occupations, and a guided narrative that argues individual behavior and health context matter more than profession. A bubble landscape exposes stress variance within the same job. A Sankey diagram traces the path from physical activity to sleep quality. The Dream Lab animates BMI, heart rate, and sleep disorders with dots that pulse at each individual's resting heart rate.

The Night Shift: Visualizing Sleep, Stress, and Health — hero image

374

people in the dataset

11

occupations compared

3

visualization scenes

32.2pp

high vs low activity sleep gap

Skills used

D3.jsJavaScriptHTML5Interaction DesignData VisualizationStatistical Analysis

chapter 01 —

Reframing a familiar question.

Every night, millions of us close our eyes hoping for rest, and a lot of us miss it. The easy story is that some jobs sleep well and other jobs do not. The data tells a more interesting one. The Night Shift walks through three perspectives, profession and stress, activity and sleep quality, and physiological health, and lets the user see that the strongest signals are not job titles at all. They are physical activity level, BMI category, resting heart rate, and sleep disorder status. The piece is built to be read like a magazine essay and explored like a tool.

chapter 02 —

From ranking jobs to reading individuals.

A typical sleep dashboard ranks occupations and stops there. That framing is comforting (your job is the problem) but it is also wrong, or at least incomplete. The same profession can stretch across two hours of sleep duration and five full stress points. The redesign of the narrative was the most important design decision: instead of asking "which job sleeps best," the story asks "what actually moves your sleep," and then shows that movement, body composition, and heart rate explain more than the label on a business card. Every chart and every annotation is in service of that reframe.

chapter 03 —

Product decisions.

The strategic calls behind the prototype and the reasoning each one rests on.

Lead with the reframe, not a ranking.

The default move with an occupation-coded dataset is to sort professions by average sleep and call it a story. That framing flatters the dataset but lies to the user, because within-profession variance is enormous. The Night Shift opens with the question "does your job define your sleep?" and answers no on the very first scene. The whole reading experience is shaped by that bet: the bubble chart is structured so the user notices spread first and averages second.

Three chart types for three different arguments.

Most data stories pick one chart family and stretch it. Each scene here makes a different kind of claim and gets a chart suited to that claim. The bubble landscape argues about variance, so it shows distribution. The Sankey argues about flow, so it shows where activity levels actually land in sleep buckets. The Dream Lab argues about physiology, so it animates individuals as living, pulsing dots. Using one chart for all three would have collapsed three arguments into one shape.

Pulse-sync the Dream Lab dots to resting heart rate.

A scatter plot of BMI vs heart rate vs sleep disorder is informative but cold. Pulsing each dot at the individual's resting heart rate (64-86 bpm) reframes the chart from a statistical surface to a room full of bodies. It is a small piece of motion design that does outsized narrative work: the user feels that these are people, not points, and they remember the chart hours later. The animation also encodes data, faster pulse means higher resting heart rate, so it is not just decoration.

Drill from occupation to individual on click.

The bubble landscape shows averages by profession, which is the storytelling problem we are trying to fight. Clicking a bubble breaks it open into the individual records inside, so the user sees with their own eyes that Nurse contains both 5.9 hour and 8.2 hour sleepers. The interaction makes the variance argument unavoidable. Removing the click would have left the chart vulnerable to the same misread we are trying to correct.

Compute the narration from the dataset, not hardcode it.

Numbers in the captions (54.9% high activity to good sleep, 22.7% for low, 32.2pp gap, Nurse spans 2.3h) are computed at load time from the CSV. If a teammate later swaps the dataset or tweaks the bucket thresholds, the prose stays honest. It is a small engineering choice with a large editorial benefit, the story can never quietly diverge from the data.

Filters and effect toggles on the Dream Lab, not on everything.

Letting the user filter every chart turns a guided story into a generic dashboard. The first two scenes are deliberately fixed so the argument lands. The Dream Lab earns its filters and toggles (sleep disorder type, heart rate range, BMI, pulse animation, motion trails, density aura, deep sleep mode) because by that point the user understands the framing and is ready to test their own hypothesis. Interactivity is a reward for getting through the argument.

chapter 04 —

Datasets.

Sleep Health and Lifestyle Dataset

374 individuals · 11 occupations · 13 columns

Public Kaggle dataset of sleep duration, sleep quality, daily steps, physical activity level, stress level, BMI category, blood pressure, resting heart rate, occupation, and sleep disorder status. Loaded directly in the browser as CSV and parsed with D3.

chapter 05 —

Three chapters, one through-line.

the deep dive —

  1. Chapter I: the dataset

    Set the scope before the argument: 374 people across 11 occupations, with an average of 7.1 hours of sleep, an average stress level of 5.4, sleep ranging 5.8 to 8.5 hours, and stress ranging 3 to 8. Showing the spread up front primes the user for the variance argument that follows.

  2. Chapter II: jobs and stress (bubble landscape)

    Interactive bubble chart with sleep duration on the x-axis and stress on the y-axis. Bubble size encodes the number of people in the occupation, color encodes stress intensity (blue calm, gray neutral, orange tense). Hover surfaces the average, click opens the cluster of individual records so within-profession spread becomes visible.

  3. Chapter III: activity to rest (Sankey diagram)

    Sankey diagram flowing physical activity level (Low, Medium, High) into sleep quality buckets (Poor 5-6h, Average 7h, Good 8-10h). Flow width is the number of people on each path. The headline number, 54.9% of high-activity individuals reach good sleep versus 22.7% of low-activity, falls out of the geometry of the diagram itself.

  4. Chapter IV: the Dream Lab

    Animated scatter plot of BMI category by resting heart rate, colored by sleep disorder status. Each dot pulses at its individual's heart rate. Filters and visual toggles let the user test patterns themselves, motion trails, density aura, constellations, and a deep-sleep mode that dims everything but the cluster being studied.

chapter 06 —

Key findings.

2.3h

Same job, very different sleep

Nurse, with 73 people in the dataset, spans 5.9 to 8.2 hours of sleep and 3.0 to 8.0 on the stress scale. The bubble landscape makes that spread visible by clicking the bubble open. The lesson is structural: occupation averages hide the people inside them, and the design has to actively show the spread or the user will read past it.

32.2pp

Movement creates a measurable path to better sleep

High-activity individuals reach good sleep quality 54.9% of the time (39 of 71). For low-activity individuals the rate is 22.7% (34 of 150). The 32.2 percentage point gap is the largest cross-group effect in the dataset, larger than any single occupation contrast, and the Sankey makes it visible as flow width without needing a callout.

10+ bpm

Heart rate plus BMI flag sleep disorder risk

In the Dream Lab, people with no sleep disorder cluster in Normal BMI at the lowest resting heart rates (68-70 bpm). Insomnia spreads through Normal and Overweight at 3-5 bpm higher. Sleep apnea clusters in Obese at 10+ bpm above the none baseline. Disorder status is a stronger separator than BMI alone, which gives a practical screening read on a noisy chart.

1 reframe

The narrative is the visualization

The strongest finding is not a number, it is that the framing of the question changes what the data appears to say. Ranking professions makes the dataset sound like a workplace story. Asking what actually moves sleep makes it a behavior and physiology story. The visualization choices and the story choices are the same choice.

chapter 07 —

Visualizations.

Nine D3.js scenes wrap a scrollytelling narrative. Each one is built directly from the AI-extracted CSVs.

Bubble landscape, jobs and stress — screenshot

scene 01

01.

Bubble landscape, jobs and stress.

Sleep duration on the x-axis (5.8 to 8.6h), stress on the y-axis (3.0 to 8.0). Each occupation is a bubble sized by population and colored by stress intensity. Hover surfaces the average, click breaks the bubble into the individual records inside so within-profession spread is visible at a glance.

The drill-down is the argument. The same job stretches across the chart instead of sitting in one zone, which immediately undermines the "your job sleeps well or it doesn't" framing.
Activity to sleep Sankey — screenshot

scene 02

02.

Activity to sleep Sankey.

Three activity bands (Low 150, Medium 153, High 71) flow into three sleep quality buckets (Poor 117, Average 77, Good 180). Flow width is the count on each path, so the relationship between movement and sleep is encoded directly in the geometry, not in a caption.

The 32.2pp gap between high-activity and low-activity routes to good sleep is the strongest cross-group effect in the dataset, and the Sankey lets the user see the size of that gap without reading a number.
The Dream Lab — screenshot

scene 03

03.

The Dream Lab.

BMI category on the x-axis, resting heart rate (64-86 bpm) on the y-axis, color encodes sleep disorder status (None, Insomnia, Sleep Apnea). Each dot pulses at the individual's resting heart rate. Filters and effect toggles (motion trails, density aura, constellations, deep sleep mode) let users test their own hypotheses.

Sleep apnea pulls toward Obese BMI plus high heart rate, 10+ bpm above the no-disorder baseline. The animation reframes the scatter plot from a statistical surface to a room full of bodies, which is the entire point.

chapter 08 —

Tools & technologies.

D3.js

Powered all three scenes: bubble landscape with sized and colored bubbles, Sankey flow with computed link widths, and the animated Dream Lab scatter with per-dot pulse rates synced to resting heart rate.

d3-sankey

Handled the flow geometry for the activity-to-sleep diagram. Lets the layout do the storytelling: flow width is the count, so the 32.2pp gap shows up visually instead of as a caption number.

Vanilla JavaScript + CSV

CSV loaded directly in the browser, no build step. Filter logic, hover tooltips, drill-down clicks, and Dream Lab effect toggles are plain DOM handlers, which keeps the project small and easy to fork.

GitHub Pages

Static hosting at calvin-liew.github.io/a4-sleep-analytics. The whole project is HTML, CSS, JS, and a CSV file, so deploys are git push and done.

chapter 09 —

What the piece actually argues.

The Night Shift is a small data story with one strong opinion: the framing you bring to a dataset decides what the dataset appears to say. Rank professions and you get a workplace narrative. Ask what actually moves sleep and you get a behavior and physiology narrative. The numbers do not change, but the lesson the reader walks away with changes completely.

Technically, the project taught me to treat chart type as part of the argument, not part of the decoration. The bubble landscape is about variance, so it shows distribution. The Sankey is about flow, so it shows where activity levels land. The Dream Lab is about people, so it animates them. Trying to force a single chart family on all three would have weakened every claim.

Narratively, computing the captions from the dataset rather than hardcoding them was the choice I am proudest of. The prose can never silently diverge from the data, which is the failure mode for almost every dashboard-style story I have read.

chapter 10 —

Honest caveats.

the honest caveats —

  • Sample size is 374 individuals from a single Kaggle dataset. Strong enough to argue framing and within-profession variance, not strong enough to make population-level health claims.
  • The data is observational and cross-sectional. Activity, BMI, heart rate, and sleep are correlated, not proven to cause each other; the piece is careful to use language like "creates pathways to" rather than "causes."
  • Sleep quality is bucketed (Poor 5-6h, Average 7h, Good 8-10h) for the Sankey diagram. Cleaner storytelling, lossier than the underlying continuous values.
  • Occupation labels in the dataset are coarse (Doctor, Nurse, Engineer, etc.), which masks specialty differences (ER nurse vs. clinic nurse) that almost certainly matter for sleep and stress.
  • No longitudinal signal. The dataset is a snapshot, so day-to-day variance and seasonal effects are invisible.