Ferrari bloodlines

An experimental view of Ferrari models, production periods and proposed relationships.
motor-sport
highcharter
data-visualization
Author

Joshua Kunst

Published

August 30, 2026

Modified

August 30, 2026

This post is part of motorsport-legends. It uses Ferrari models to explore families, production periods and relationships across time.

Through this project, I try to organize and store data about vehicles, race results and other motorsport topics that interest me.

About the data

The main source is Ferrari’s Past Models catalogue. The process used to collect the model pages, images, descriptions and available specifications is documented in the 01-ferrari-past-models.R script.

Part of the resulting dataset is enriched through AI-assisted research, deterministic rules and manual review. This is used particularly to estimate each model’s production end year and to propose families and relationships. These values remain under review, and the process is documented in 99-ferrari-past-model-enrichment.R.

Model names, images, descriptions, specifications and other source material belong to their respective owners. This post only organizes and displays the information as part of the project.

library(tidyverse)
library(highcharter)

ferrari_data_url <- paste0(
  "https://api.github.com/repos/jbkunst/motorsport-legends/contents/",
  "data/cars/ferrari_past_models_enriched.csv"
)

ferrari_response <- httr2::request(ferrari_data_url) |>
  httr2::req_headers(
    Accept = "application/vnd.github.raw+json",
    Authorization = paste("Bearer", gh::gh_token()),
    `X-GitHub-Api-Version` = "2022-11-28"
  ) |>
  httr2::req_perform()

ferrari <- ferrari_response |>
  httr2::resp_body_string() |>
  I() |>
  readr::read_csv(show_col_types = FALSE)

ferrari
# A tibble: 243 × 76
   model_id                model start_year end_year source_year source_category
   <chr>                   <chr>      <dbl>    <dbl>       <dbl> <chr>          
 1 ferrari_1947_125_s      125 S       1947       NA        1947 Sports Prototy…
 2 ferrari_1947_159_s      159 S       1947       NA        1947 Sports Prototy…
 3 ferrari_1948_166_inter  166 …       1948     1950        1948 Grand Touring  
 4 ferrari_1948_166_inter… 166 …       1948       NA        1948 Sports Prototy…
 5 ferrari_1948_166_mm     166 …       1948       NA        1948 Sports Prototy…
 6 ferrari_1948_166_s      166 S       1948       NA        1948 Sports Prototy…
 7 ferrari_1950_195_inter  195 …       1950     1951        1950 Grand Touring  
 8 ferrari_1950_195_s      195 S       1950       NA        1950 Sports Prototy…
 9 ferrari_1950_275_s      275 S       1950       NA        1950 Sports Prototy…
10 ferrari_1950_340_ameri… 340 …       1950       NA        1950 Sports Prototy…
# ℹ 233 more rows
# ℹ 70 more variables: production_status <chr>, period_source_name <chr>,
#   period_source_url <chr>, period_source_type <chr>, period_confidence <chr>,
#   period_review_status <chr>, period_notes <chr>, period_reviewed_at <date>,
#   history_year <dbl>, history_year_type <chr>, history_url <chr>,
#   history_image_url <lgl>, history_source_name <chr>,
#   history_review_status <chr>, history_reviewed_at <date>, …

The current snapshot contains 243 models grouped into 11 proposed families.

coverage <- ferrari |>
  summarise(
    models = n(),
    production_period = sum(!is.na(end_year)),
    start_year_only = sum(is.na(end_year)),
    lineage = sum(!is.na(lineage)),
    predecessor = sum(!is.na(predecessor))
  ) |>
  pivot_longer(everything(), names_to = "field", values_to = "models") |>
  mutate(
    field = recode(
      field,
      production_period = "Known production period",
      start_year_only = "Start year only",
      lineage = "Assigned lineage",
      predecessor = "Known predecessor",
      .default = "All models"
    )
  )

coverage
# A tibble: 5 × 2
  field                   models
  <chr>                    <int>
1 All models                 243
2 Known production period    148
3 Start year only             95
4 Assigned lineage           181
5 Known predecessor          152

Bars represent the production periods currently recorded in the project. Models without a reviewed end year remain points.

Families before chronology

family_summary <- ferrari |>
  count(main_family, sort = TRUE) |>
  mutate(main_family = fct_reorder(main_family, n))

ggplot(family_summary, aes(n, main_family)) +
  geom_segment(
    aes(x = 0, xend = n, yend = main_family),
    linewidth = 1.4,
    colour = "#d9d2c3"
  ) +
  geom_point(size = 3.2, colour = "#c41230") +
  labs(
    title = "Eleven ways through Ferrari history",
    subtitle = "The current taxonomy is useful, but some families still contain very different stories.",
    x = "Models",
    y = NULL
  )

These family groupings are exploratory and may change as the project reviews more models and relationships.

A first timeline

The timeline is an experimental view of the chronology and family structure available in the current dataset.

Code
tooltip_html <- JS(
  "function () {",
  "  const c = this.point.custom || {};",
  "  const end = c.end ? '–' + c.end : ' · end year pending';",
  "  const image = c.image ? '<img src=\"' + c.image + '\" alt=\"\" style=\"display:block;width:260px;height:146px;object-fit:cover;margin:-12px -14px 12px\">' : '';",
  "  return '<div style=\"padding:12px 14px;min-width:220px\">' +",
  "    image +",
  "    '<div style=\"font-size:16px;font-weight:600;color:#28120f\">' + this.point.name + '</div>' +",
  "    '<div style=\"margin:2px 0 10px;color:#8f1d23\">' + c.start + end + '</div>' +",
  "    '<div><b>Family</b> · ' + c.family + '</div>' +",
  "    '<div><b>Lineage</b> · ' + c.lineage + '</div>' +",
  "    '<div><b>Engine</b> · ' + (c.engine || 'pending') + ' · ' + (c.layout || 'pending') + '</div>' +",
  "    '<div><b>Previous</b> · ' + c.predecessor + '</div>' +",
  "    '<div><b>Relation</b> · ' + c.relationship.replaceAll('_', ' ') + '</div>' +",
  "  '</div>';",
  "}"
)

highchart() |>
  highcharter::hc_size(height = 900) |>
  hc_add_dependency("modules/xrange.js") |>
  hc_chart(
    backgroundColor = "#f5f1e8",
    zoomType = "x",
    spacing = c(28, 24, 22, 12),
    style = list(fontFamily = "IBM Plex Sans, sans-serif")
  ) |>
  hc_title(
    text = "Ferrari bloodlines",
    align = "left",
    style = list(color = "#28120f", fontSize = "28px", fontWeight = "300")
  ) |>
  hc_subtitle(
    text = "Production periods are bars. Unfinished research remains visible as points.",
    align = "left",
    style = list(color = "#765f56", fontSize = "14px")
  ) |>
  hc_xAxis(
    type = "datetime",
    min = datetime_to_timestamp(as.Date("1947-01-01")),
    gridLineWidth = 1,
    gridLineColor = "#ded7ca",
    lineColor = "#9b8e80",
    tickColor = "#9b8e80"
  ) |>
  hc_yAxis(
    categories = family_levels,
    title = list(text = ""),
    gridLineColor = "#ded7ca",
    labels = list(style = list(color = "#4a3731", fontSize = "12px"))
  ) |>
  hc_plotOptions(
    series = list(
      states = list(
        inactive = list(opacity = 1)
      )
    )
  ) |>
  hc_add_series(
    type = "xrange",
    name = "Known production period",
    data = period_points,
    borderColor = "#f5f1e8",
    borderWidth = 1.2,
    borderRadius = 1,
    pointWidth = 10,
    states = list(
      hover = list(
        brightness = 0.08,
        borderColor = "#ffffff",
        borderWidth = 2
      )
    )
  ) |>
  hc_add_series(
    type = "scatter",
    name = "End year pending",
    data = date_points,
    color = "#c41230",
    marker = list(
      radius = 4,
      symbol = "circle",
      lineWidth = 1.5,
      lineColor = "#c41230",
      fillColor = "#f5f1e8"
    )
  ) |>
  hc_tooltip(
    useHTML = TRUE,
    outside = TRUE,
    shared = FALSE,
    followPointer = TRUE,
    backgroundColor = "#fffdf8",
    borderColor = "#c9bdae",
    borderRadius = 8,
    shadow = TRUE,
    style = list(zIndex = 9999),
    formatter = tooltip_html
  ) |>
  hc_legend(
    align = "center",
    verticalAlign = "bottom",
    itemStyle = list(color = "#4a3731", fontWeight = "400")
  ) |>
  hc_credits(
    enabled = TRUE,
    text = "Data: motorsport-legends · work in progress",
    href = "https://github.com/jbkunst/motorsport-legends"
  )

This is an experimental representation of the data currently available in motorsport-legends, including estimated production periods and provisional relationships.

References