Rover.Heatmap (Rover v0.3.0)

Copy Markdown View Source

Density, as a heat field rather than as pins.

Where five hundred markers are a wall of overlapping icons, a heatmap answers a different question: where is there a lot of this? Deliveries per neighbourhood, clients per area, incidents over a season.

<.map id="deliveries" heatmap={@deliveries} />

A point needs only a coordinate:

%{lat: 45.75, lon: 4.85}
%{lat: 45.75, lon: 4.85, weight: 0.4}

No identity, and why

Rover.Marker and Rover.Shape both insist on a stable :id, because both are reconciled one feature at a time. A heatmap is not: it is an aggregate, and no individual point is visible in the result. Requiring an id for every row of a density query would be ceremony that buys nothing.

So heatmaps are diffed the way shapes are — by a revision computed once per render on the server. Same list, same rev, no work on the client. A changed list rebuilds the field, which is what changing a density field means anyway.

Weights

:weight is relative, from 0 to 1, and defaults to 1. OpenLayers saturates anything above 1, so raw counts do not work as-is — divide by your maximum:

max = Enum.max_by(rows, & &1.orders).orders

<.map
  id="deliveries"
  heatmap={rows}
  heatmap_fields={[weight: fn row -> row.orders / max end]}
/>

Summary

Functions

Normalises a list of points. Nil entries, and entries without a usable coordinate, are dropped.

The revision of a normalised point list: what the client compares to decide whether to rebuild the field.

Normalises the style options into the map the JavaScript runtime reads.

Types

point()

@type point() :: %{lat: float(), lon: float(), weight: float()}

Functions

new_all!(points, opts \\ [])

@spec new_all!(
  Enumerable.t(),
  keyword()
) :: [point()]

Normalises a list of points. Nil entries, and entries without a usable coordinate, are dropped.

Unlike markers, an unusable point is skipped rather than raised on: a density query returning one row with a null coordinate should thin the map, not take the page down.

Examples

iex> Rover.Heatmap.new_all!([%{lat: 45.75, lon: 4.85}])
[%{lat: 45.75, lon: 4.85, weight: 1.0}]

iex> Rover.Heatmap.new_all!([%{lat: 45.75, lon: 4.85, weight: 0.25}])
[%{lat: 45.75, lon: 4.85, weight: 0.25}]

iex> Rover.Heatmap.new_all!([%{lat: nil, lon: nil}, %{lat: 45.75, lon: 4.85}])
[%{lat: 45.75, lon: 4.85, weight: 1.0}]

rev(points)

@spec rev([point()]) :: integer()

The revision of a normalised point list: what the client compares to decide whether to rebuild the field.

Examples

iex> points = Rover.Heatmap.new_all!([%{lat: 45.75, lon: 4.85}])
iex> Rover.Heatmap.rev(points) == Rover.Heatmap.rev(points)
true

style!(opts)

@spec style!(keyword()) :: map()

Normalises the style options into the map the JavaScript runtime reads.

Options

  • :radius — point radius in pixels. Defaults to 8.
  • :blur — blur radius in pixels. Defaults to 15.
  • :opacity — layer opacity, 0 to 1. Defaults to 1.
  • :gradient — a list of CSS colours, cold to hot. Defaults to OpenLayers'.

Examples

iex> Rover.Heatmap.style!([])
%{radius: 8, blur: 15, opacity: 1}

iex> Rover.Heatmap.style!(radius: 12, gradient: ["#fff", "#f00"])
%{radius: 12, blur: 15, opacity: 1, gradient: ["#fff", "#f00"]}