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
Functions
@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}]
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
Normalises the style options into the map the JavaScript runtime reads.
Options
:radius— point radius in pixels. Defaults to8.:blur— blur radius in pixels. Defaults to15.:opacity— layer opacity, 0 to 1. Defaults to1.: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"]}