Ripples into Silence

A scrollytelling visualization using ripples to tell the story of 12 years of recorded migrant deaths and disappearances around Lampedusa.

  • D3.js
  • JavaScript
  • Python
  • SVG
  • Canvas
Timeline
Jan 2025 – Apr 2025, revised Sep 2026
Role
Data preparation, narrative design, and visualization development
Outcome
Published interactive data story
The opening and final state of The Record.
Section

The experience

Ripples into Silence follows deaths and disappearances recorded around Lampedusa, a small Italian island between Tunisia and Sicily. I wanted to explore the human cost of journeys made in the hope of starting a life somewhere new.

I built the piece so a viewer first watches the records appear one after another, then compares the years, and finally sees the people inside the total. The current version covers 95 records from 2014 to 2025, which together count 833 people recorded as dead or missing.

Excerpts in scene order, from the title to the epilogue.

Choosing the frame

My first version, Northbound: A Deadly Voyage, set out to show migration across the whole Mediterranean. It placed countries of origin, transit, and destination in three tiers, with arcs for routes, causes of death, and the number of people lost.

Northbound: A Deadly Voyage. An arc diagram on a charcoal ground, with country silhouettes in three tiers and colored arcs labeled with death counts running from North Africa and Türkiye toward Spain, Italy, and Greece.
Before: 31 countries in three tiers, joined by arcs.
The Why Lampedusa scene: a short paragraph headed Only 50 Kilometers Away beside the yellow silhouette of Lampedusa inside a dashed circle with its 50 km radius marked.
After: one island and a 50 km radius around it.

I found the composition lacked a clear entry point. Keeping the same source, I narrowed the frame to Lampedusa, an island on a central Mediterranean route, so the piece could start from one place.

The 50 km radius is a focus I chose. I applied it by measuring each record’s coordinates against a reference point on the island and keeping the records inside the radius. A coordinate can mark an incident, a rescue, the discovery of remains, or an arrival, so it may not be where a person died.

Data scope and limitations

The source is a CSV export of the IOM Missing Migrants Project, supplied in September 2026. The snapshot covers records through December 2025 and should not be treated as a complete account of all losses.

I kept unique Main IDs dated 2014 to 2025 within 50 km of a reference point on Lampedusa, measured from that single point. The piece’s data and method notes give the coordinates, the distance formula, and the script that rebuilds the sample and records the source’s hash.

A Main ID is a record, not a boat, and records with the same date and coordinates stay separate. The 95 records count 833 people: 199 in the dead field and 634 in the missing field. Blank fields are not counted as confirmed zeros.

The sample differs from the one the 2025 version used. Split rows were merged, some historical records changed in the source, other records are new, and 2025 is added. Four records whose location text disagrees with their coordinates, such as one described as 100 km northwest of Lampedusa that sits at 7.65 km, carry a note and stay in the sample. They are the discrepancies found in review; other coordinates were not checked against their descriptions.

Giving the records a rhythm

I used fading ripples as a metaphor for lives lost at sea. A ripple appears, spreads, and disappears, giving each record a brief presence before the surface grows quiet again. What stays behind is a thin ring at the record’s distance from the island, sized by the number of people it counts. I fixed the distance scale to the frame, so each record’s ring and the 10, 25, and 50 km guides read off the same ruler. Radial position encodes distance; direction and movement do not represent actual routes.

I slowed the opening so the encoding can be learned from a few records. The first five appear one at a time, each labeled with its distance and its count, and the rest follow in date order.

The opening at real speed: five labeled records, then the steady pace.

Playback follows date order without reproducing the gaps between events. After the opening, I used a largely steady pace, allowing more time for years with more records.

Watching one record at a time makes comparing years a matter of memory. So after the animation I drew each year as its own disk, with the same rings and size scale for all 12, and set them side by side.

Twelve disks in three rows of four, one per year from 2014 to 2025, each with the same dashed rings and white marks of different sizes, and its record and dead or missing counts beneath it. The 2025 disk has a faint ring around it.
Twelve annual views with shared distance and size scales.

Any disk opens at full size, and hovering a mark gives its count, date, and distance. I retained the source’s location descriptions in the tooltips so viewers could read the context behind each coordinate.

The 2024 disk opened large beside its totals of 17 records and 211 dead or missing. The other marks dim while one is hovered, and its tooltip reads 44 dead or missing, 9 Dec 2024, 22.5 km from Lampedusa, and gives the recorded location: off the coast of Lampedusa, Italy, departure from Sfax, Tunisia, see coordinates for location of rescue.
The tooltip keeps the source’s location text, which here says the coordinates mark the rescue.

From records to people

In the disks, one mark is one record, sized by the number of people it counts. For the last step I wanted to move attention from the records to the people inside them.

As the reader scrolls, I let the marks leave their disks, draw together, and split into 833 equal circles, each record into as many circles as it counts. The circles pass through an outline of Lampedusa and then settle into groups by the cause reported for their record. The outline is a visual device that holds the 833 together for a moment; it does not show a journey or mean these people reached the island.

One continuous scroll, from the twelve disks to the cause groups.

I kept the transition continuous so viewers could follow the same records into the person-level view. Every circle comes from a mark in an annual disk, and scrolling back returns it to that mark.

Cause groups reflect the source records, not individually confirmed causes for every missing person. I kept multiple causes together rather than assign individual people to causes the record does not distinguish.

Playback and implementation

The record and the disks are SVG, drawn with D3.js. The 833 circles are drawn on a canvas, which takes over each white mark where the SVG drew it; the SVG marks are then hidden so the two never overlap.

The transition has no clock of its own: the scroll position inside the pinned stage sets every circle’s position, so a reader who stops halfway holds halfway. The record runs on its own clock, and only while it is on screen. After the opening, the records in most years take equal slots on the time bar, and a year with only a few records is given at least enough width for its label. The bar’s years are therefore not a calendar scale.

Each circle keeps the Main ID of its record, which decides the mark it leaves and the group it joins. A repository test checks that all 95 records produce the correct number of circles, that the total is 833, and that the seven cause groups have the expected counts.

With reduced motion requested, the disks appear filled, the transition changes straight from the disks to the groups, and the epilogue’s lines appear together. The record’s paths still animate.

Credits

Skills
  • Information design
  • Data visualization
  • Scrollytelling
  • Data preparation
  • Creative development
Tools
  • D3.js
  • JavaScript
  • Python
  • SVG and Canvas
Roles

Design and development

  • Zhuoqi Liu

Faculty guidance

  • Todd Linkner
Note

Claude Code assisted with parts of the implementation and with optimizing the code.