Skip to content

add Flowmaps, layer tuner, time control#205

Open
e-kotov wants to merge 16 commits into
walkerke:mainfrom
e-kotov:flowmap
Open

add Flowmaps, layer tuner, time control#205
e-kotov wants to merge 16 commits into
walkerke:mainfrom
e-kotov:flowmap

Conversation

@e-kotov
Copy link
Copy Markdown

@e-kotov e-kotov commented May 31, 2026

This PR adds

  • Flowmap.gl support
  • Associated custom time scrubber/filter (should work with all layers)
  • Layer tuner (for interactive changes of any layer properties and then copying the final code for the plot)

Videos with demo of what it can do will follow.

Will partially close #37 (e.g. no arcs from deck.gl, lost more work to improve flows experience, but the baseline is solid)

FYI @Robinlovelace @ilyabo @JohMast

e-kotov added 16 commits May 30, 2026 13:18
- Updated flowmap-gl vendor manifest with new SHA256 and byte size.
- Revised documentation for add_flowmap to clarify parameters and added new options for flow_dark_mode and flow_blend.
- Implemented flow_blend parameter to control CSS blending modes, with recommendations for usage based on map styles.
- Added tests to validate flow_blend behavior, including auto-resolution based on map style and error handling for invalid inputs.
- Enhanced is_dark_style utility to classify basemaps correctly and added tests for various styles.
- Introduced lil-gui library for enhanced UI controls in the map visualization.
- Updated mapboxgl.js and maplibregl.js to initialize the layer tuner if enabled.
- Added new add_layer_tuner function to allow real-time customization of map layers.
- Updated documentation for add_flowmap and added new documentation for add_layer_tuner.
- Included lil-gui as a dependency in both mapboxgl.yaml and maplibregl.yaml.
- Implemented time control functionality in mapboxgl and maplibregl widgets.
- Added support for flowmap filter and settings updates, including new parameters for temporal filtering.
- Enhanced documentation for new functions: add_time_control, set_flowmap_filter, and set_flowmap_settings.
- Updated tests to validate new features and ensure proper functionality of flowmap settings.
- Included dependencies for d3 and time-control in YAML configuration files.
- Added support for multiple selected time ranges in time control, allowing users to shift-drag to select additional ranges.
- Updated time control UI with improved collapse functionality and accessibility features.
- Introduced tooltip configuration options for flowmap, enabling customizable tooltips for locations and flows.
- Enhanced flowmap serialization to include tooltip settings and validate new parameters.
- Updated documentation to reflect new parameters and tooltip options.
- Added tests for time control and flowmap tooltip functionalities to ensure proper serialization and behavior.
@e-kotov
Copy link
Copy Markdown
Author

e-kotov commented May 31, 2026

Time filter

001.mp4
maplibre(
  style = carto_style("dark-matter"),
  center = c(-73.58, 45.50),
  zoom = 11,
  projection = "mercator"
) |>
  add_flowmap(
    id = "bixi-rides",
    locations = bixi_locations,
    flows = bixi_flows,
    flow_time_column = "time",
    flow_color_scheme = "Teal",
    flow_dark_mode = TRUE
  ) |>
  add_time_control(
    data = bixi_flows,
    time_column = "time",
    time_interval = "hour",
    title = "BIXI Montréal Rides"
  )

@e-kotov
Copy link
Copy Markdown
Author

e-kotov commented May 31, 2026

Compare view + flows + layer tuner

002.mp4
library(mapgl)
mapbox_token <- Sys.getenv("MAPBOX_API_TOKEN")
if (!requireNamespace("flowmapper", quietly = TRUE)) {
  stop("The flowmapper package is required for flowmap ordering artifacts.")
}

ordering_dir <- "private/flowmap-manual/ordering"
unlink(ordering_dir, recursive = TRUE, force = TRUE)
dir.create(ordering_dir, recursive = TRUE, showWarnings = FALSE)

utils::data("cantons", package = "flowmapper")
utils::data("CH_migration_data", package = "flowmapper")

if (is.na(sf::st_crs(cantons))) {
  sf::st_crs(cantons) <- 3857
}

canton_points <- suppressWarnings(sf::st_point_on_surface(cantons)) |>
  sf::st_transform(4326)

coords <- sf::st_coordinates(canton_points)
locations <- data.frame(
  id = canton_points$NAME_1,
  name = canton_points$NAME_1,
  lat = coords[, "Y"],
  lon = coords[, "X"]
)

flows <- rbind(
  data.frame(
    origin = CH_migration_data$id_a,
    dest = CH_migration_data$id_b,
    count = CH_migration_data$flow_ab
  ),
  data.frame(
    origin = CH_migration_data$id_b,
    dest = CH_migration_data$id_a,
    count = CH_migration_data$flow_ba
  )
)
flows <- flows[flows$count > 0, , drop = FALSE]

set.seed(20260529)
review_point_rows <- sample(seq_len(nrow(locations)), 8)
review_points <- locations[review_point_rows, c("id", "lat", "lon")]
review_points$name <- paste("Review point", seq_len(nrow(review_points)))
review_points$lat <- review_points$lat +
  stats::runif(nrow(review_points), -0.035, 0.035)
review_points$lon <- review_points$lon +
  stats::runif(nrow(review_points), -0.05, 0.05)
review_points <- sf::st_as_sf(
  review_points,
  coords = c("lon", "lat"),
  crs = 4326
)

# MapLibre: flowmap is added last, so it renders above the review points.
# mapboxgl(
#     style = mapbox_style("dark"),
#     center = c(8.25, 46.85),
#     zoom = 7,
#     projection = "mercator",
#     access_token = mapbox_token
#   ) |>

m1 <- maplibre(
  # style = carto_style("positron"),
  style = carto_style("dark-matter"),
  center = c(8.25, 46.85),
  zoom = 7,
  projection = "mercator"
) |>
  add_layer_tuner() |>
  add_fill_layer(
    id = "cantons-fill",
    source = cantons,
    fill_color = "#7d48d4",
    fill_opacity = 0.3,
    fill_outline_color = "#d7e8ec"
  ) |>
  add_flowmap(
    id = "manual-flowmap",
    locations = locations,
    flows = flows
    # flow_color_scheme = "Teal",
    # flow_dark_mode = "auto",
    # flow_blend = "auto"
    # flow_opacity = 0.9
  ) |>
  add_circle_layer(
    id = "review-points",
    source = review_points,
    circle_color = "#ffcf5a",
    circle_radius = 6,
    circle_stroke_color = "#2b2f33",
    circle_stroke_width = 1.5
  ) |>
  add_layers_control(
    position = "top-right",
    layers = list(
      "Canton polygons" = "cantons-fill",
      "Review points" = "review-points",
      "Migration flowmap" = "manual-flowmap"
    )
  )

m2 <- maplibre(
  style = carto_style("positron"),
  # style = carto_style("dark-matter"),
  center = c(8.25, 46.85),
  zoom = 7,
  projection = "mercator"
) |>
  add_layer_tuner() |>
  add_fill_layer(
    id = "cantons-fill",
    source = cantons,
    fill_color = "#7d48d4",
    fill_opacity = 0.3,
    fill_outline_color = "#d7e8ec"
  ) |>
  add_flowmap(
    id = "manual-flowmap",
    locations = locations,
    flows = flows
    # flow_color_scheme = "Teal",
    # flow_dark_mode = "auto",
    # flow_blend = "auto"
    # flow_opacity = 0.9
  ) |>
  add_circle_layer(
    id = "review-points",
    source = review_points,
    circle_color = "#ffcf5a",
    circle_radius = 6,
    circle_stroke_color = "#2b2f33",
    circle_stroke_width = 1.5,
    tooltip = "id"
  ) |>
  add_layers_control(
    position = "top-right",
    layers = list(
      "Canton polygons" = "cantons-fill",
      "Review points" = "review-points",
      "Migration flowmap" = "manual-flowmap"
    )
  )

compare(m1, m2)

@e-kotov
Copy link
Copy Markdown
Author

e-kotov commented May 31, 2026

Layer tuner with flows + copy code of adjusted map when done

003.mp4
maplibre(
  style = carto_style("dark-matter"),
  center = c(-73.58, 45.50),
  zoom = 11,
  projection = "mercator"
) |>
  add_flowmap(
    id = "bixi-rides",
    locations = bixi_locations,
    flows = bixi_flows,
    flow_time_column = "time",
    flow_color_scheme = "Teal",
    flow_dark_mode = TRUE
  ) |>
  add_layer_tuner(position = "top-right", width = 320)

@Robinlovelace
Copy link
Copy Markdown

Awesome just in time for workshop!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

flows visualisation with arcs or flowmaps

2 participants