http://mathling.com/shape/density library module
http://mathling.com/shape/density
Density (or other measures) based maps.
Copyright© Mary Holstege 2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Bleeding: edge
Imports
http://mathling.com/geometric/intersectionimport module namespace inter="http://mathling.com/geometric/intersection" at "../geo/intersection.xqy"http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"http://mathling.com/type/slot
import module namespace slot="http://mathling.com/type/slot" at "../types/slot.xqy"http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"http://mathling.com/core/random
import module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"http://mathling.com/geometric/path
import module namespace path="http://mathling.com/geometric/path" at "../geo/path.xqy"http://mathling.com/geometric/edge
import module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/shape/grid
import module namespace grid="http://mathling.com/shape/grid" at "../shapes/grids.xqy"http://mathling.com/geometric/ellipse
import module namespace ellipse="http://mathling.com/geometric/ellipse" at "../geo/ellipse.xqy"http://mathling.com/core/config
import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"
Functions
Function: dots
declare function dots($regions as map(xs:string,item()*)*,
$n as xs:integer,
$grid-kind as xs:string) as map(xs:string,item()*)*
declare function dots($regions as map(xs:string,item()*)*, $n as xs:integer, $grid-kind as xs:string) as map(xs:string,item()*)*
dots()
Given a sequence of regions, return a sequence of slots, where
each slot contains a sequence of grid points from the corresponding region.
We can then render the region using the dots with appropriate sizing to
create a dot density diagram instead of a chloropleth. The slots will adopt
the properties of the regions, so you can attach rendering properties to
them that way.
Params
- regions as map(xs:string,item()*)*: the set of regions
- n as xs:integer: number of grid points per bounding box side
- grid-kind as xs:string: "quad" for a rectangular grid or "hex" for a hexagonal grid
Returns
- map(xs:string,item()*)*: sequence of slots
declare function this:dots( $regions as map(xs:string,item()*)*, $n as xs:integer, $grid-kind as xs:string ) as map(xs:string,item()*)* { let $bb := geom:bounding-box($regions) let $grid := if ($grid-kind="quad") then ( grid:array($n, $n, 0, $bb) ) else if ($grid-kind="hex") then ( grid:hex-grid((box:diagonal($bb) div 2) div $n, $bb) ) else ( errors:error("ML-BADARGS", ("grid-kind", $grid-kind)) ) return ( fold-left($regions, array{ 1 to count($grid) }, function($partitions as item()*, $region as map(xs:string,item()*)) { let $available := head($partitions)?* let $pbb := geom:bounding-box($region) return ( let $in-region := filter($available, function($ix as xs:integer) { box:contains-point($pbb, $grid[$ix]) and inter:region-contains($region, $grid[$ix]) } ) return ( array {$available[not(. = $in-region)]}, slot:slot( $grid[position() = $in-region] )=>geom:with-properties( geom:property-map($region) ) ) ), tail($partitions) } ) )=>tail() }
Function: dot-diagram
declare function dot-diagram($slots as map(xs:string,item()*)*,
$density-fn as function(xs:integer) as xs:double,
$min-dot as xs:double,
$max-dot as xs:double) as map(xs:string,item()*)*
declare function dot-diagram($slots as map(xs:string,item()*)*, $density-fn as function(xs:integer) as xs:double, $min-dot as xs:double, $max-dot as xs:double) as map(xs:string,item()*)*
dot-diagram()
Given a set of slots containing points as produced by density:dots(),
map the density function over the slots to the given dot size to produce
the dots. Properties are transferred from the slot to the individual dots,
so you can attach things like colour to the slots to have the dots coloured
accordingly. TBD: consolidation for large numbers of dots
Params
- slots as map(xs:string,item()*)*: slots containing grid points
- density-fn as function(xs:integer)asxs:double: function mapping slot index to measure to be mapped
- min-dot as xs:double: minimum dot size
- max-dot as xs:double: maximum dot size
Returns
- map(xs:string,item()*)*: series of dots
declare function this:dot-diagram( $slots as map(xs:string,item()*)*, $density-fn as function(xs:integer) as xs:double, $min-dot as xs:double, $max-dot as xs:double ) as map(xs:string,item()*)* { let $densities := for $i in 1 to count($slots) return $density-fn($i) let $min-density := min($densities) let $max-density := max($densities) let $delta := $max-density - $min-density + 1 for $slot at $i in $slots let $d := $densities[$i] let $properties := $slot=>slot:property-map() let $r := util:mix($min-dot, $max-dot, ($d - $min-density + 1) div $delta)=>util:decimal(1) return ( $slot=>slot:body( for $dot in $slot=>slot:body() return ellipse:circle($dot, $r) )=>geom:with-properties($properties) ) }
Function: chloropleth
declare function chloropleth($regions as map(xs:string,item()*)*,
$density-fn as function(map(xs:string,item()*), xs:integer) as xs:double,
$colours as xs:string*) as map(xs:string,item()*)*
declare function chloropleth($regions as map(xs:string,item()*)*, $density-fn as function(map(xs:string,item()*), xs:integer) as xs:double, $colours as xs:string*) as map(xs:string,item()*)*
chloropleth()
Render out a sequence of regions by mapping the density function to
a set of colours.
Params
- regions as map(xs:string,item()*)* sequence of regions
- density-fn as function(map(xs:string,item()*),xs:integer)asxs:double: function from region and region index to a measurement
- colours as xs:string*: colours to map the density to
Returns
- map(xs:string,item()*)*: sequence of regions coloured according to the density function
declare function this:chloropleth( $regions as map(xs:string,item()*)*, $density-fn as function(map(xs:string,item()*), xs:integer) as xs:double, $colours as xs:string* ) as map(xs:string,item()*)* { let $densities := for $i in 1 to count($regions) return $density-fn($regions[$i], $i) let $min-density := min($densities) let $max-density := max($densities) let $delta := $max-density - $min-density + 1 let $n-colours := count($colours) for $region at $i in $regions let $density := $densities[$i] return ( $region=>geom:with-properties( map { "colour": $colours[util:intmix(1, $n-colours, ($density - $min-density + 1) div $delta)] } ) ) }
Original Source Code
xquery version "3.1"; (:~ : Density (or other measures) based maps. : : Copyright© Mary Holstege 2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since February 2023 : @custom:Bleeding edge :) module namespace this="http://mathling.com/shape/density"; import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"; import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"; import module namespace slot="http://mathling.com/type/slot" at "../types/slot.xqy"; import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"; import module namespace inter="http://mathling.com/geometric/intersection" at "../geo/intersection.xqy"; import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"; import module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"; import module namespace ellipse="http://mathling.com/geometric/ellipse" at "../geo/ellipse.xqy"; import module namespace path="http://mathling.com/geometric/path" at "../geo/path.xqy"; import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"; import module namespace grid="http://mathling.com/shape/grid" at "../shapes/grids.xqy"; declare namespace svg="http://www.w3.org/2000/svg"; declare namespace map="http://www.w3.org/2005/xpath-functions/map"; declare namespace array="http://www.w3.org/2005/xpath-functions/array"; declare namespace math="http://www.w3.org/2005/xpath-functions/math"; (:~ : dots() : Given a sequence of regions, return a sequence of slots, where : each slot contains a sequence of grid points from the corresponding region. : We can then render the region using the dots with appropriate sizing to : create a dot density diagram instead of a chloropleth. The slots will adopt : the properties of the regions, so you can attach rendering properties to : them that way. : : @param $regions: the set of regions : @param $n: number of grid points per bounding box side : @param $grid-kind: "quad" for a rectangular grid or "hex" for a hexagonal grid : @return sequence of slots :) declare function this:dots( $regions as map(xs:string,item()*)*, $n as xs:integer, $grid-kind as xs:string ) as map(xs:string,item()*)* { let $bb := geom:bounding-box($regions) let $grid := if ($grid-kind="quad") then ( grid:array($n, $n, 0, $bb) ) else if ($grid-kind="hex") then ( grid:hex-grid((box:diagonal($bb) div 2) div $n, $bb) ) else ( errors:error("ML-BADARGS", ("grid-kind", $grid-kind)) ) return ( fold-left($regions, array{ 1 to count($grid) }, function($partitions as item()*, $region as map(xs:string,item()*)) { let $available := head($partitions)?* let $pbb := geom:bounding-box($region) return ( let $in-region := filter($available, function($ix as xs:integer) { box:contains-point($pbb, $grid[$ix]) and inter:region-contains($region, $grid[$ix]) } ) return ( array {$available[not(. = $in-region)]}, slot:slot( $grid[position() = $in-region] )=>geom:with-properties( geom:property-map($region) ) ) ), tail($partitions) } ) )=>tail() }; (:~ : dot-diagram() : Given a set of slots containing points as produced by density:dots(), : map the density function over the slots to the given dot size to produce : the dots. Properties are transferred from the slot to the individual dots, : so you can attach things like colour to the slots to have the dots coloured : accordingly. TBD: consolidation for large numbers of dots : : @param $slots: slots containing grid points : @param $density-fn: function mapping slot index to measure to be mapped : @param $min-dot: minimum dot size : @param $max-dot: maximum dot size : @return series of dots :) declare function this:dot-diagram( $slots as map(xs:string,item()*)*, $density-fn as function(xs:integer) as xs:double, $min-dot as xs:double, $max-dot as xs:double ) as map(xs:string,item()*)* { let $densities := for $i in 1 to count($slots) return $density-fn($i) let $min-density := min($densities) let $max-density := max($densities) let $delta := $max-density - $min-density + 1 for $slot at $i in $slots let $d := $densities[$i] let $properties := $slot=>slot:property-map() let $r := util:mix($min-dot, $max-dot, ($d - $min-density + 1) div $delta)=>util:decimal(1) return ( $slot=>slot:body( for $dot in $slot=>slot:body() return ellipse:circle($dot, $r) )=>geom:with-properties($properties) ) }; (:~ : chloropleth() : Render out a sequence of regions by mapping the density function to : a set of colours. : : @param $regions sequence of regions : @param $density-fn: function from region and region index to a measurement : @param $colours: colours to map the density to : @return sequence of regions coloured according to the density function :) declare function this:chloropleth( $regions as map(xs:string,item()*)*, $density-fn as function(map(xs:string,item()*), xs:integer) as xs:double, $colours as xs:string* ) as map(xs:string,item()*)* { let $densities := for $i in 1 to count($regions) return $density-fn($regions[$i], $i) let $min-density := min($densities) let $max-density := max($densities) let $delta := $max-density - $min-density + 1 let $n-colours := count($colours) for $region at $i in $regions let $density := $densities[$i] return ( $region=>geom:with-properties( map { "colour": $colours[util:intmix(1, $n-colours, ($density - $min-density + 1) div $delta)] } ) ) };