Visualising Bolaño’s Savage Detectives

Visualising a classic novel with a confusing timeline and bewildering set of characters
DH
Literature
NLP
Author

Paul Matthews

Published

March 7, 2025

Savage Detectives Cover Splash

Savage Detectives Cover Splash

The Savage Detectives is a wonderful book about a movement of Mexican poets (the “Visceral Realists”) starting in the 1970’s into the late 90’s. It is what is known as a “roman à clef”, having many characters closely based on real people that Bolaño knew as a young man. It is also post-modern in some of it’s characteristics, with an experimental structure and diverse narration. As Gentic puts it:

“by replacing first-person narratives by Cesárea, Lima, and Belano with first-person narratives of other characters talking about them, the novel converts realist literature’s interest in its protagonists’ psychological development into an always moving destabilization, rather than affirmation, of their identity and subjectivity.” (Gentic 2015, 403)

The structure is indeed intricate and seems cleverly crafted, so I wanted to see if some visualisations might make the structure a little clearer.

Several previous approaches have been documented for book and film storylines, particularly those with non-linear timelines, such as StoryFlow (Shixia Liu et al. 2013) and StoryPrint (Watson et al. 2019). But while these both have nice aspects (though no accessible implementations I could find), I chose to try and emulate Story Curves (Kim et al. 2017), which has a logical layout, placing narrative time and story time on two axes. In there example (below), you can see how the story order for Pulp Fiction is displaced across narrative time.

Story Curve for Pulp Fiction - from Kim et al (2018)

Story Curve for Pulp Fiction - from Kim et al (2018)

I used Python and SpaCy together with GLiNER to extract characters, dates and places, and this mostly worked quite well. I then switched over to R for preprocessing and visualisation. I pulled down the Wikipedia character list to validate and match to the extracted ones (a good source of metadata). For the narrators, I used some formatting trickery, as they were in bold in the source text, but it needed a fair amount of manual fixing.

My R code for the viz and initial results are below (click on a viz to zoom in). The locations are not quite right yet as I used some hacky approaches to extracting these. I’d like to turn these into interactives and include more of the character metadata and backstories in due course.

setup

Show R code
options(tidyverse.quiet = TRUE)
library(tidyverse)
library(Polychrome)
# prevent scientific notation
options(scipen = 999)

# Set palette
colourCount = 32 # length(unique(timeline$id))
newPalette = c("#000000", glasbey.colors(colourCount)[-1])
names(newPalette ) <- NULL

# Load Data
load(file = "timeline.RData")
load(file = "narration.RData")

story visualisation

Show R code
# Story Viz
p1 <- timeline |>  filter(label == "person") |> ggplot(aes(x = start + cum_position, y = year_corrected.x)) +
  geom_point(aes( colour = id,  group = character),
             shape = 15, size = 2, position = ggstance::position_dodgev(height = 1)) +
  geom_text(data = chaptersummaries, aes(x = cum_position + chlength/2, y = 1999, 
            label = chapter_corrected, ), check_overlap = TRUE, 
            show.legend = F,  size = 6) +
  geom_text(data = chaptersummaries, aes(x = chlength/2 + cum_position, 
                          y = year_corrected, label = city),
                           position = position_nudge(y = 1),
                          show.legend = F , check_overlap = TRUE,  size = 6) +
  geom_vline(data = chaptersummaries, aes(xintercept = chlength + cum_position), colour = "grey") +
  scale_color_manual(values = newPalette ) +
  scale_x_continuous(name = "position", limits = c(0, 300000)) + 
  scale_y_reverse(name = "year", n.breaks = 6) +
  theme_bw() + 
  theme(legend.position = "none") +
  theme(plot.margin = margin(10, 10, 10, 90))

character timeline

Show R code
# Character Viz
p2 <- timeline |> filter(label == "person") |> group_by(id) |> #, chapter_corrected
  mutate(n = n()) |> 
  mutate(first = min(start), last = max(end)) |>
  ggplot() +
geom_segment(aes(x = start + cum_position, xend = end + 400 + cum_position, y = reorder(character, n, decreasing = F) , yend = character, color = id), linewidth = 5) + 
theme_bw() + 
geom_vline(data = chaptersummaries, aes(xintercept = chlength + cum_position), colour = "grey") +
scale_color_manual(values = newPalette ) +
scale_y_discrete(name = "Character") +
scale_x_continuous(name = "Story Position", limits = c(0,300000)) +
theme(legend.position = "none")

narrators

Show R code
p3 <- narration |> 
  ggplot() +
geom_segment(aes(x = start + cum_position, xend = ifelse(is.na(endNarrate), end + cum_position, endNarrate + cum_position), y =  reorder(character, desc(as.numeric(chapter))) , yend = character, color = id), linewidth = 5) + 
theme_bw() + 
geom_vline(data = chaptersummaries, aes(xintercept = chlength + cum_position), colour = "grey") +
#scale_color_manual(values = newPalette ) +
scale_y_discrete(name = "Character") +
scale_x_continuous(name = "Story Position", limits = c(0,300000)) +
theme(legend.position = "none")
Show R code
p1
p2
p3

Timeline, locations and characters

Timeline, locations and characters

Character occurences

Character occurences

Narrators

Narrators

References

Gentic, Tania. 2015. “Realism, the Avant-Garde, and the Politics of Reading in Roberto Bolaño’s the Savage Detectives.” Critique: Studies in Contemporary Fiction 56 (4): 399–414. https://doi.org/10.1080/00111619.2014.930014.
Kim, Nam, Benjamin Bach, Hyejin Im, Sasha Schriber, Markus Gross, and Hanspeter Pfister. 2017. “Visualizing Nonlinear Narratives with Story Curves.” IEEE Transactions on Visualization and Computer Graphics PP (August): 1–1. https://doi.org/10.1109/TVCG.2017.2744118.
Shixia Liu, Yingcai Wu, Enxun Wei, Mengchen Liu, and Yang Liu. 2013. “StoryFlow: Tracking the Evolution of Stories.” IEEE Transactions on Visualization and Computer Graphics 19 (12): 2436–45. https://doi.org/10.1109/TVCG.2013.196.
Watson, Katie, Samuel S. Sohn, Sasha Schriber, Markus Gross, Carlos Manuel Muniz, and Mubbasir Kapadia. 2019. “IUI ’19: 24th International Conference on Intelligent User Interfaces.” In, 303–11. Marina del Ray California: ACM. https://doi.org/10.1145/3301275.3302302.