Merge pull request #1136 from umap-project/choropleth

Add experimental choropleth datalayer type
This commit is contained in:
Yohan Boniface 2023-10-12 08:47:30 +02:00 committed by GitHub
commit 6aa6eb4c11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 531 additions and 17 deletions

View file

@ -34,6 +34,7 @@
"homepage": "http://wiki.openstreetmap.org/wiki/UMap",
"dependencies": {
"@tmcw/togeojson": "^5.8.0",
"colorbrewer": "^1.5.6",
"csv2geojson": "5.1.1",
"dompurify": "^3.0.3",
"georsstogeojson": "^0.1.0",
@ -55,6 +56,7 @@
"leaflet.path.drag": "0.0.6",
"leaflet.photon": "0.8.0",
"osmtogeojson": "^3.0.0-beta.3",
"simple-statistics": "^7.8.3",
"togpx": "^0.5.4",
"tokml": "0.4.0"
}

View file

@ -26,5 +26,7 @@ mkdir -p umap/static/umap/vendors/tokml && cp -r node_modules/tokml/tokml.js uma
mkdir -p umap/static/umap/vendors/locatecontrol/ && cp -r node_modules/leaflet.locatecontrol/dist/L.Control.Locate.css umap/static/umap/vendors/locatecontrol/
mkdir -p umap/static/umap/vendors/locatecontrol/ && cp -r node_modules/leaflet.locatecontrol/src/L.Control.Locate.js umap/static/umap/vendors/locatecontrol/
mkdir -p umap/static/umap/vendors/dompurify/ && cp -r node_modules/dompurify/dist/purify.js umap/static/umap/vendors/dompurify/
mkdir -p umap/static/umap/vendors/colorbrewer/ && cp node_modules/colorbrewer/index.js umap/static/umap/vendors/colorbrewer/colorbrewer.js
mkdir -p umap/static/umap/vendors/simple-statistics/ && cp node_modules/simple-statistics/dist/simple-statistics.min.js umap/static/umap/vendors/simple-statistics/
echo 'Done!'

View file

@ -354,7 +354,8 @@ input.switch:checked ~ label:after {
.button-bar.half {
grid-template-columns: 1fr 1fr;
}
.umap-multiplechoice.by3 {
.umap-multiplechoice.by3,
.umap-multiplechoice.by5 {
grid-template-columns: 1fr 1fr 1fr;
}
.umap-multiplechoice.by4 {

View file

@ -598,6 +598,7 @@ L.U.DataLayersControl = L.Control.extend({
L.U.DataLayer.include({
renderLegend: function (container) {
if (this.layer.renderLegend) return this.layer.renderLegend(container)
const color = L.DomUtil.create('span', 'datalayer-color', container)
color.style.backgroundColor = this.getColor()
},

View file

@ -1,5 +1,5 @@
L.U.FeatureMixin = {
staticOptions: {},
staticOptions: { mainColor: 'color' },
initialize: function (map, latlng, options) {
this.map = map
@ -283,7 +283,7 @@ L.U.FeatureMixin = {
} else if (L.Util.usableOption(this.properties._umap_options, option)) {
value = this.properties._umap_options[option]
} else if (this.datalayer) {
value = this.datalayer.getOption(option)
value = this.datalayer.getOption(option, this)
} else {
value = this.map.getOption(option)
}
@ -948,6 +948,7 @@ L.U.Polyline = L.Polyline.extend({
staticOptions: {
stroke: true,
fill: false,
mainColor: 'color',
},
isSameClass: function (other) {
@ -1084,6 +1085,9 @@ L.U.Polyline = L.Polyline.extend({
L.U.Polygon = L.Polygon.extend({
parentClass: L.Polygon,
includes: [L.U.FeatureMixin, L.U.PathMixin],
staticOptions: {
mainColor: 'fillColor',
},
isSameClass: function (other) {
return other instanceof L.U.Polygon

View file

@ -379,6 +379,7 @@ L.FormBuilder.LayerTypeChooser = L.FormBuilder.Select.extend({
['Default', L._('Default')],
['Cluster', L._('Clustered')],
['Heat', L._('Heatmap')],
['Choropleth', L._('Choropleth')],
],
})
@ -732,7 +733,7 @@ L.FormBuilder.MultiChoice = L.FormBuilder.Element.extend({
fetch: function () {
let value = (this.backup = this.toHTML())
if (!this.container.querySelector(`input[type="radio"][value="${value}"]`))
value = this.default
value = typeof(this.options.default) !== 'undefined' ? this.options.default : this.default
this.container.querySelector(`input[type="radio"][value="${value}"]`).checked = true
},

View file

@ -106,6 +106,194 @@ L.U.Layer.Cluster = L.MarkerClusterGroup.extend({
},
})
L.U.Layer.Choropleth = L.FeatureGroup.extend({
_type: 'Choropleth',
includes: [L.U.Layer],
canBrowse: true,
// Have defaults that better suit the choropleth mode.
defaults: {
color: 'white',
fillColor: 'red',
fillOpacity: 0.7,
weight: 2,
},
MODES: {
kmeans: L._('K-means'),
equidistant: L._('Equidistant'),
jenks: L._('Jenks-Fisher'),
quantiles: L._('Quantiles'),
manual: L._('Manual'),
},
initialize: function (datalayer) {
this.datalayer = datalayer
if (!L.Util.isObject(this.datalayer.options.choropleth)) {
this.datalayer.options.choropleth = {}
}
L.FeatureGroup.prototype.initialize.call(
this,
[],
this.datalayer.options.choropleth
)
this.datalayer.on('datachanged', this.redraw, this)
},
redraw: function () {
this.computeBreaks()
if (this._map) this.eachLayer(this._map.addLayer, this._map)
},
_getValue: function (feature) {
const key = this.datalayer.options.choropleth.property || 'value'
return +feature.properties[key] // TODO: should we catch values non castable to int ?
},
computeBreaks: function () {
const values = []
this.datalayer.eachLayer((layer) => {
let value = this._getValue(layer)
if (!isNaN(value)) values.push(value)
})
if (!values.length) {
this.options.breaks = []
this.options.colors = []
return
}
let mode = this.datalayer.options.choropleth.mode,
classes = +this.datalayer.options.choropleth.classes || 5,
breaks = []
if (mode === 'manual') {
const manualBreaks = this.datalayer.options.choropleth.breaks
if (manualBreaks) {
breaks = manualBreaks.split(",").map(b => +b).filter(b => !isNaN(b))
}
} else if (mode === 'equidistant') {
breaks = ss.equalIntervalBreaks(values, classes)
} else if (mode === 'jenks') {
breaks = ss.jenks(values, classes)
} else if (mode === 'quantiles') {
const quantiles = [...Array(classes)].map((e, i) => i/classes).concat(1)
breaks = ss.quantile(values, quantiles)
} else {
breaks = ss.ckmeans(values, classes).map((cluster) => cluster[0])
breaks.push(ss.max(values)) // Needed for computing the legend
}
this.options.breaks = breaks
this.datalayer.options.choropleth.breaks = this.options.breaks.map(b => +b.toFixed(2)).join(',')
const fillColor = this.datalayer.getOption('fillColor') || this.defaults.fillColor
let colorScheme = this.datalayer.options.choropleth.brewer
if (!colorbrewer[colorScheme]) colorScheme = 'Blues'
this.options.colors = colorbrewer[colorScheme][breaks.length - 1] || []
},
getColor: function (feature) {
if (!feature) return // FIXME shold not happen
const featureValue = this._getValue(feature)
// Find the bucket/step/limit that this value is less than and give it that color
for (let i = 1; i < this.options.breaks.length; i++) {
if (featureValue <= this.options.breaks[i]) {
return this.options.colors[i - 1]
}
}
},
getOption: function (option, feature) {
if (feature && option === feature.staticOptions.mainColor) return this.getColor(feature)
},
addLayer: function (layer) {
// Do not add yet the layer to the map
// wait for datachanged event, so we want compute breaks once
var id = this.getLayerId(layer)
this._layers[id] = layer
return this
},
onAdd: function (map) {
this.computeBreaks()
L.FeatureGroup.prototype.onAdd.call(this, map)
},
postUpdate: function (e) {
if (e.helper.field === 'options.choropleth.breaks') {
this.datalayer.options.choropleth.mode = 'manual'
e.helper.builder.helpers["options.choropleth.mode"].fetch()
}
this.computeBreaks()
if (e.helper.field !== 'options.choropleth.breaks') {
e.helper.builder.helpers["options.choropleth.breaks"].fetch()
}
},
getEditableOptions: function () {
const brewerSchemes = Object.keys(colorbrewer)
.filter((k) => k !== 'schemeGroups')
.sort()
return [
[
'options.choropleth.property',
{
handler: 'Select',
selectOptions: this.datalayer._propertiesIndex,
label: L._('Choropleth property value'),
},
],
[
'options.choropleth.brewer',
{
handler: 'Select',
label: L._('Choropleth color palette'),
selectOptions: brewerSchemes,
},
],
[
'options.choropleth.classes',
{
handler: 'Range',
min: 3,
max: 9,
step: 1,
label: L._('Choropleth classes'),
helpText: L._('Number of desired classes (default 5)'),
},
],
[
'options.choropleth.breaks',
{
handler: 'BlurInput',
label: L._('Choropleth breakpoints'),
helpText: L._('Comma separated list of numbers, including min and max values.'),
},
],
[
'options.choropleth.mode',
{
handler: 'MultiChoice',
default: 'kmeans',
choices: Object.entries(this.MODES),
label: L._('Choropleth mode'),
},
],
]
},
renderLegend: function (container) {
const parent = L.DomUtil.create('ul', '', container)
let li, color, label
this.options.breaks.slice(0, -1).forEach((limit, index) => {
li = L.DomUtil.create('li', '', parent)
color = L.DomUtil.create('span', 'datalayer-color', li)
color.style.backgroundColor = this.options.colors[index]
label = L.DomUtil.create('span', '', li)
label.textContent = `${+this.options.breaks[index].toFixed(
1
)} - ${+this.options.breaks[index + 1].toFixed(1)}`
})
},
})
L.U.Layer.Heat = L.HeatLayer.extend({
_type: 'Heat',
includes: [L.U.Layer],
@ -399,7 +587,7 @@ L.U.DataLayer = L.Evented.extend({
if (visible) this.map.removeLayer(this.layer)
const Class = L.U.Layer[this.options.type] || L.U.Layer.Default
this.layer = new Class(this)
this.eachLayer((feature) => this.showFeature(feature))
this.eachLayer(this.showFeature)
if (visible) this.show()
this.propagateRemote()
},
@ -970,11 +1158,9 @@ L.U.DataLayer = L.Evented.extend({
'options.fillOpacity',
]
shapeOptions = shapeOptions.concat(this.layer.getEditableOptions())
const redrawCallback = function (field) {
const redrawCallback = function (e) {
this.hide()
this.layer.postUpdate(field)
this.layer.postUpdate(e)
this.show()
}
@ -1123,9 +1309,22 @@ L.U.DataLayer = L.Evented.extend({
this.map.ui.openPanel({ data: { html: container }, className: 'dark' })
},
getOption: function (option) {
getOwnOption: function (option) {
if (L.Util.usableOption(this.options, option)) return this.options[option]
else return this.map.getOption(option)
},
getOption: function (option, feature) {
if (this.layer && this.layer.getOption) {
const value = this.layer.getOption(option, feature)
if (typeof value !== 'undefined') return value
}
if (typeof this.getOwnOption(option) !== 'undefined') {
return this.getOwnOption(option)
} else if (this.layer && this.layer.defaults && this.layer.defaults[option]) {
return this.layer.defaults[option]
} else {
return this.map.getOption(option)
}
},
buildVersionsFieldset: function (container) {

View file

@ -1087,6 +1087,17 @@ a.add-datalayer:hover,
vertical-align: middle;
}
.datalayer-legend {
color: #555;
padding: 6px 8px;
box-shadow: 0 0 3px rgba(0,0,0,0.2);
border-radius: 1px;
}
.datalayer-legend ul {
list-style-type: none;
padding: 0;
margin: 0;
}
/* ********************************* */
/* Popup */

View file

@ -0,0 +1,243 @@
const POLYGONS = {
_umap_options: defaultDatalayerData(),
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
name: 'number 1',
value: 45,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[0, 49],
[-2, 47],
[1, 46],
[3, 47],
[0, 49],
],
],
},
},
{
type: 'Feature',
properties: {
name: 'number 2',
value: 87,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[0, 49],
[2, 50],
[6, 49],
[4, 47],
[0, 49],
],
],
},
},
{
type: 'Feature',
properties: {
name: 'number 3',
value: 673,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[4, 47],
[6, 49],
[11, 47],
[9, 45],
[4, 47],
],
],
},
},
{
type: 'Feature',
properties: {
name: 'number 4',
value: 674,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[2, 46],
[4, 47],
[8, 45],
[6, 43],
[2, 46],
],
],
},
},
{
type: 'Feature',
properties: {
name: 'number 5',
value: 839,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[-2, 47],
[1, 46],
[0, 44],
[-4, 45],
[-2, 47],
],
],
},
},
{
type: 'Feature',
properties: {
name: 'number 6',
value: 3829,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[1, 45],
[5, 43],
[4, 42],
[0, 44],
[1, 45],
],
],
},
},
{
type: 'Feature',
properties: {
name: 'number 7',
value: 4900,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[9, 45],
[12, 47],
[15, 45],
[13, 43],
[9, 45],
],
],
},
},
{
type: 'Feature',
properties: {
name: 'number 8',
value: 4988,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[7, 43],
[9, 45],
[12, 43],
[10, 42],
[7, 43],
],
],
},
},
{
type: 'Feature',
properties: {
name: 'number 9',
value: 9898,
},
geometry: {
type: 'Polygon',
coordinates: [
[
[4, 42],
[6, 43],
[9, 41],
[7, 40],
[4, 42],
],
],
},
},
],
}
describe('L.U.Choropleth', function () {
let path = '/map/99/datalayer/edit/62/',
poly1,
poly4,
poly9
before(function () {
this.server = sinon.fakeServer.create()
this.server.respondWith(/\/datalayer\/62\/\?.*/, JSON.stringify(POLYGONS))
this.map = initMap({ umap_id: 99 })
this.datalayer = this.map.getDataLayerByUmapId(62)
this.server.respond()
this.datalayer.options.type = 'Choropleth'
this.datalayer.options.choropleth = {
property: 'value',
}
enableEdit()
this.datalayer.eachLayer(function (layer) {
if (layer.properties.name === 'number 1') {
poly1 = layer
} else if (layer.properties.name === 'number 4') {
poly4 = layer
} else if (layer.properties.name === 'number 9') {
poly9 = layer
}
})
})
after(function () {
this.server.restore()
//resetMap()
})
describe('#init()', function () {
it('datalayer should have 9 features', function () {
assert.equal(this.datalayer._index.length, 9)
})
})
describe('#compute()', function () {
it('choropleth should compute default colors', function () {
this.datalayer.resetLayer(true)
assert.deepEqual(
this.datalayer.layer.options.breaks,
[45, 673, 3829, 4900, 9898, 9898]
)
assert.equal(poly1._path.attributes.fill.value, '#eff3ff')
assert.equal(poly4._path.attributes.fill.value, '#bdd7e7')
assert.equal(poly9._path.attributes.fill.value, '#3182bd')
})
it('can change brewer scheme', function () {
this.datalayer.options.choropleth.brewer = 'Reds'
this.datalayer.resetLayer(true)
assert.equal(poly1._path.attributes.fill.value, '#fee5d9')
assert.equal(poly4._path.attributes.fill.value, '#fcae91')
assert.equal(poly9._path.attributes.fill.value, '#de2d26')
})
it('choropleth should allow to change steps', function () {
this.datalayer.options.choropleth.brewer = 'Blues'
this.datalayer.options.choropleth.classes = 6
this.datalayer.resetLayer(true)
assert.equal(poly1._path.attributes.fill.value, '#eff3ff')
assert.equal(poly4._path.attributes.fill.value, '#c6dbef')
assert.equal(poly9._path.attributes.fill.value, '#3182bd')
})
})
})

View file

@ -25,6 +25,8 @@
<script src="../vendors/dompurify/purify.js"></script>
<script src="../vendors/togpx/togpx.js"></script>
<script src="../vendors/tokml/tokml.js"></script>
<script src="../vendors/simple-statistics/simple-statistics.min.js"></script>
<script src="../vendors/colorbrewer/colorbrewer.js"></script>
<script src="../js/umap.core.js"></script>
<script src="../js/umap.autocomplete.js"></script>
<script src="../js/umap.popup.js"></script>
@ -82,6 +84,7 @@
<script src="./Util.js"></script>
<script src="./Controls.js"></script>
<script src="./Permissions.js"></script>
<script src="./Choropleth.js"></script>
<style type="text/css">
#mocha {
position: absolute;

View file

@ -24,6 +24,8 @@
<script src="{{ STATIC_URL }}umap/vendors/tokml/tokml.js"></script>
<script src="{{ STATIC_URL }}umap/vendors/locatecontrol/L.Control.Locate.js"></script>
<script src="{{ STATIC_URL }}umap/vendors/dompurify/purify.js"></script>
<script src="{{ STATIC_URL }}umap/vendors/colorbrewer/colorbrewer.js"></script>
<script src="{{ STATIC_URL }}umap/vendors/simple-statistics/simple-statistics.min.js"></script>
{% endcompress %}
{% if locale %}<script src="{{ STATIC_URL }}umap/locale/{{ locale }}.js"></script>{% endif %}
{% compress js %}

View file

@ -107,12 +107,17 @@ class DataLayerFactory(factory.django.DjangoModelFactory):
@classmethod
def _adjust_kwargs(cls, **kwargs):
data = kwargs.pop("data", copy.deepcopy(DATALAYER_DATA))
kwargs["settings"]["name"] = kwargs["name"]
if "data" in kwargs:
data = copy.deepcopy(kwargs.pop("data"))
if "settings" not in kwargs:
kwargs["settings"] = data.get("_umap_options", {})
else:
data = DATALAYER_DATA.copy()
data["_umap_options"] = {
**DataLayerFactory.settings._defaults,
**kwargs["settings"],
}
data["_umap_options"]["name"] = kwargs["name"]
kwargs["geojson"] = ContentFile(json.dumps(data), "foo.json")
return kwargs

View file

@ -0,0 +1,15 @@
{"type":"FeatureCollection", "features": [
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[1.501527,48.941052],[1.591206,48.814867],[1.57954,48.701812],[1.801451,48.466086],[1.942897,48.441084],[2.007238,48.284689],[2.181355,48.313746],[2.42076,48.299253],[2.506186,48.238532],[2.538115,48.140651],[2.706543,48.124819],[2.798946,48.168273],[2.936316,48.163392],[3.043633,48.272021],[3.049523,48.360117],[3.414789,48.390269],[3.460432,48.653009],[3.43581,48.753572],[3.485183,48.85191],[3.403221,48.86469],[3.171679,49.014126],[3.16523,49.099654],[2.73501,49.060453],[2.590528,49.079654],[2.440904,49.145804],[2.252481,49.152881],[2.080885,49.209774],[1.885235,49.162636],[1.742142,49.180152],[1.704359,49.232197],[1.623323,49.086078],[1.457757,49.026295],[1.501527,48.941052]]]},"properties":{"code":"11","nom":"Île-de-France","taux":"6.7"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[1.501527,48.941052],[1.362182,48.83418],[1.319544,48.760961],[1.184736,48.77263],[0.876711,48.715496],[0.812949,48.66051],[0.850635,48.582626],[0.966551,48.522742],[0.942246,48.399005],[0.762194,48.306877],[0.797658,48.194455],[0.852575,48.133602],[0.774574,47.839684],[0.456628,47.638826],[0.378954,47.569105],[0.23,47.608397],[0.185279,47.424736],[0.078979,47.282822],[0.05383,47.163734],[0.174218,47.071274],[0.29823,47.053922],[0.300739,46.97383],[0.438705,46.929578],[0.692567,46.974304],[0.704318,46.903296],[0.924749,46.69279],[0.894303,46.625732],[1.145929,46.506401],[1.177279,46.383948],[1.415185,46.347215],[1.522307,46.426528],[1.644846,46.386817],[1.819504,46.430034],[2.088946,46.4089],[2.281044,46.420404],[2.352004,46.512207],[2.614961,46.553276],[2.596648,46.637215],[2.70497,46.73939],[2.774489,46.718903],[2.959919,46.803872],[3.032063,46.794909],[3.075744,47.019148],[2.9834,47.259766],[2.873492,47.348397],[2.928897,47.444565],[2.848901,47.537541],[2.954229,47.645774],[2.85667,47.760929],[3.023799,47.78655],[3.010289,47.904717],[3.128449,47.970977],[3.029468,48.133204],[2.936316,48.163392],[2.798946,48.168273],[2.706543,48.124819],[2.538115,48.140651],[2.506186,48.238532],[2.42076,48.299253],[2.181355,48.313746],[2.007238,48.284689],[1.942897,48.441084],[1.801451,48.466086],[1.57954,48.701812],[1.591206,48.814867],[1.501527,48.941052]]]},"properties":{"code":"24","nom":"Centre-Val de Loire","taux":"6.7"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[6.064006,46.416223],[6.156442,46.545472],[6.110744,46.576314],[6.438105,46.761751],[6.432675,46.928606],[6.640342,47.002761],[6.739914,47.108052],[6.858344,47.164385],[7.062201,47.34416],[6.917306,47.356174],[6.983116,47.493302],[7.130341,47.503029],[7.015962,47.74323],[6.645537,47.904023],[6.478606,47.885522],[6.366153,47.961944],[6.237983,47.93284],[6.156058,48.006943],[5.947528,47.979712],[5.821437,47.868178],[5.690285,47.818602],[5.690072,47.684834],[5.40634,47.673403],[5.354765,47.59136],[4.97911,47.687764],[4.9541,47.866767],[4.789078,48.007829],[4.582673,48.029463],[4.449004,47.957152],[4.199154,47.969941],[4.111782,47.926998],[3.902092,47.939168],[3.80497,48.102547],[3.667867,48.139211],[3.616802,48.271344],[3.414789,48.390269],[3.049523,48.360117],[3.043633,48.272021],[2.936316,48.163392],[3.029468,48.133204],[3.128449,47.970977],[3.010289,47.904717],[3.023799,47.78655],[2.85667,47.760929],[2.954229,47.645774],[2.848901,47.537541],[2.928897,47.444565],[2.873492,47.348397],[2.9834,47.259766],[3.075744,47.019148],[3.032063,46.794909],[3.215545,46.682893],[3.598001,46.723983],[3.696958,46.660583],[3.743289,46.567565],[3.890467,46.481246],[3.99804,46.465464],[3.986627,46.319196],[3.891239,46.285246],[3.890131,46.214487],[3.988788,46.169805],[4.104087,46.198391],[4.261025,46.178754],[4.389398,46.213601],[4.405814,46.296058],[4.618558,46.264794],[4.69311,46.302197],[4.780208,46.176676],[4.940022,46.517199],[5.052372,46.484874],[5.20114,46.508211],[5.310563,46.44677],[5.473052,46.265067],[5.649345,46.339495],[5.725182,46.260732],[5.908936,46.283951],[6.064006,46.416223]]]},"properties":{"code":"27","nom":"Bourgogne-Franche-Comté","taux":"6.3"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[1.379698,50.065012],[1.194376,49.968033],[0.769727,49.872092],[0.572939,49.849717],[0.211669,49.714464],[0.065609,49.512575],[0.110385,49.394266],[-0.01337,49.321204],[-0.225695,49.28182],[-0.41485,49.335581],[-0.722245,49.347004],[-0.939382,49.395041],[-1.163947,49.36666],[-1.30636,49.538928],[-1.229597,49.624654],[-1.287844,49.692596],[-1.421165,49.703706],[-1.541576,49.654112],[-1.860296,49.650194],[-1.885691,49.54039],[-1.819827,49.390482],[-1.711172,49.325095],[-1.598847,49.1692],[-1.56071,49.001481],[-1.59415,48.834715],[-1.574403,48.751851],[-1.449159,48.623417],[-1.571089,48.626441],[-1.489942,48.489372],[-1.377246,48.458283],[-1.272248,48.53392],[-1.070164,48.508492],[-0.86036,48.501459],[-0.757277,48.436552],[-0.505062,48.505799],[-0.367623,48.492944],[-0.169379,48.536973],[-0.148718,48.458069],[0.116248,48.435556],[0.295856,48.480175],[0.363956,48.451632],[0.38261,48.333828],[0.53597,48.249845],[0.68322,48.248588],[0.797658,48.194455],[0.762194,48.306877],[0.942246,48.399005],[0.966551,48.522742],[0.850635,48.582626],[0.812949,48.66051],[0.876711,48.715496],[1.184736,48.77263],[1.319544,48.760961],[1.362182,48.83418],[1.501527,48.941052],[1.457757,49.026295],[1.623323,49.086078],[1.704359,49.232197],[1.775625,49.299694],[1.722799,49.433058],[1.766341,49.466149],[1.689574,49.694787],[1.757928,49.780795],[1.678451,49.918131],[1.379698,50.065012]]]},"properties":{"code":"28","nom":"Normandie","taux":"6.7"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[4.233063,49.957825],[4.227996,50.066369],[4.126921,50.135009],[4.221852,50.256954],[4.037617,50.342951],[3.658267,50.371344],[3.664218,50.453174],[3.473964,50.533564],[3.286524,50.527578],[3.258377,50.700647],[3.152001,50.782343],[2.813274,50.716946],[2.717475,50.813607],[2.59959,50.853333],[2.63268,50.946009],[2.543035,51.088544],[1.728856,50.937893],[1.580633,50.867344],[1.560707,50.699673],[1.585409,50.537352],[1.537942,50.282668],[1.590273,50.255949],[1.379698,50.065012],[1.678451,49.918131],[1.757928,49.780795],[1.689574,49.694787],[1.766341,49.466149],[1.722799,49.433058],[1.775625,49.299694],[1.704359,49.232197],[1.742142,49.180152],[1.885235,49.162636],[2.080885,49.209774],[2.252481,49.152881],[2.440904,49.145804],[2.590528,49.079654],[2.73501,49.060453],[3.16523,49.099654],[3.171679,49.014126],[3.403221,48.86469],[3.485183,48.85191],[3.601596,48.944074],[3.646312,49.04059],[3.600011,49.12069],[3.704389,49.181369],[3.646957,49.315153],[3.924439,49.406179],[4.035498,49.359906],[4.040706,49.508532],[4.127027,49.677918],[4.250094,49.757099],[4.233063,49.957825]]]},"properties":{"code":"32","nom":"Hauts-de-France","taux":"8.9"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[3.414789,48.390269],[3.616802,48.271344],[3.667867,48.139211],[3.80497,48.102547],[3.902092,47.939168],[4.111782,47.926998],[4.199154,47.969941],[4.449004,47.957152],[4.582673,48.029463],[4.789078,48.007829],[4.9541,47.866767],[4.97911,47.687764],[5.354765,47.59136],[5.40634,47.673403],[5.690072,47.684834],[5.690285,47.818602],[5.821437,47.868178],[5.947528,47.979712],[6.156058,48.006943],[6.237983,47.93284],[6.366153,47.961944],[6.478606,47.885522],[6.645537,47.904023],[7.015962,47.74323],[7.130341,47.503029],[7.246298,47.422198],[7.40341,47.435524],[7.506757,47.495629],[7.592796,47.60178],[7.52234,47.662322],[7.556707,47.879941],[7.622086,47.972272],[7.577316,48.120371],[7.739875,48.321749],[7.735972,48.404162],[7.839822,48.641377],[7.971752,48.757632],[8.096323,48.810384],[8.214342,48.975096],[8.09138,48.98926],[7.934623,49.057811],[7.635282,49.054164],[7.43995,49.180796],[7.298647,49.117456],[7.066095,49.114582],[6.935419,49.222156],[6.738508,49.163661],[6.669186,49.280602],[6.565843,49.347353],[6.552235,49.423348],[6.25641,49.510019],[5.988982,49.45333],[5.836959,49.542475],[5.659774,49.552869],[5.480126,49.504205],[5.426913,49.597345],[5.269185,49.696072],[5.166201,49.692915],[5.063178,49.761924],[4.873879,49.820796],[4.883308,49.896181],[4.79091,49.958398],[4.879559,50.152031],[4.762161,50.136395],[4.688873,49.9955],[4.446938,49.937508],[4.233063,49.957825],[4.250094,49.757099],[4.127027,49.677918],[4.040706,49.508532],[4.035498,49.359906],[3.924439,49.406179],[3.646957,49.315153],[3.704389,49.181369],[3.600011,49.12069],[3.646312,49.04059],[3.601596,48.944074],[3.485183,48.85191],[3.43581,48.753572],[3.460432,48.653009],[3.414789,48.390269]]]},"properties":{"code":"44","nom":"Grand Est","taux":"7.1"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-1.129406,46.310272],[-1.01381,46.355623],[-0.750471,46.304254],[-0.557647,46.363451],[-0.640647,46.416225],[-0.602132,46.53328],[-0.656193,46.700775],[-0.829149,46.933362],[-0.773878,47.004248],[-0.619979,46.993321],[-0.559532,47.061883],[-0.184838,47.108333],[-0.128379,47.054429],[0.05383,47.163734],[0.078979,47.282822],[0.185279,47.424736],[0.23,47.608397],[0.378954,47.569105],[0.456628,47.638826],[0.774574,47.839684],[0.852575,48.133602],[0.797658,48.194455],[0.68322,48.248588],[0.53597,48.249845],[0.38261,48.333828],[0.363956,48.451632],[0.295856,48.480175],[0.116248,48.435556],[-0.148718,48.458069],[-0.169379,48.536973],[-0.367623,48.492944],[-0.505062,48.505799],[-0.757277,48.436552],[-0.86036,48.501459],[-1.070164,48.508492],[-1.04502,48.327729],[-1.100055,48.259278],[-1.02126,47.994939],[-1.15399,47.965817],[-1.245885,47.776717],[-1.390429,47.828276],[-1.593405,47.776049],[-1.654817,47.712589],[-1.864016,47.706981],[-2.074733,47.651663],[-2.107329,47.531054],[-2.312864,47.464471],[-2.458493,47.448123],[-2.534106,47.382961],[-2.513808,47.298376],[-2.30164,47.2364],[-2.187361,47.280622],[-2.167063,47.166181],[-1.983624,47.029505],[-2.153824,46.890149],[-2.144506,46.826399],[-1.942876,46.692706],[-1.856015,46.608581],[-1.812344,46.493417],[-1.627062,46.414221],[-1.50214,46.397345],[-1.207398,46.266567],[-1.129406,46.310272]]]},"properties":{"code":"52","nom":"Pays de la Loire","taux":"5.6"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-1.571089,48.626441],[-1.845069,48.616365],[-1.856132,48.704855],[-2.223953,48.595382],[-2.312861,48.680925],[-2.68156,48.531129],[-2.819603,48.593528],[-2.82693,48.650565],[-3.029395,48.775914],[-3.113974,48.86674],[-3.231756,48.867412],[-3.430731,48.797224],[-3.585013,48.775497],[-3.581252,48.670015],[-3.832374,48.711936],[-4.351403,48.676113],[-4.730581,48.556346],[-4.793343,48.416236],[-4.608724,48.337888],[-4.566609,48.28634],[-4.301125,48.191447],[-4.271733,48.133054],[-4.668228,48.070201],[-4.453577,47.981778],[-4.362572,47.889267],[-4.362414,47.795739],[-4.192305,47.797376],[-4.163556,47.849183],[-3.902479,47.835941],[-3.800595,47.787634],[-3.532306,47.768279],[-3.452758,47.695339],[-3.344224,47.70836],[-3.208134,47.663424],[-3.123256,47.569508],[-2.975179,47.576069],[-2.851152,47.619021],[-2.701951,47.589923],[-2.664333,47.518637],[-2.537128,47.525677],[-2.458493,47.448123],[-2.312864,47.464471],[-2.107329,47.531054],[-2.074733,47.651663],[-1.864016,47.706981],[-1.654817,47.712589],[-1.593405,47.776049],[-1.390429,47.828276],[-1.245885,47.776717],[-1.15399,47.965817],[-1.02126,47.994939],[-1.100055,48.259278],[-1.04502,48.327729],[-1.070164,48.508492],[-1.272248,48.53392],[-1.377246,48.458283],[-1.489942,48.489372],[-1.571089,48.626441]]]},"properties":{"code":"53","nom":"Bretagne","taux":"5.8"}},
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[-1.129406,46.310272],[-1.111166,46.261343],[-1.22387,46.165946],[-1.126407,46.12426],[-1.05269,46.011183],[-1.137158,45.819899],[-1.242569,45.781575],[-1.237223,45.705896],[-1.009635,45.611437],[-1.157037,45.470366],[-1.162337,45.297869],[-1.222903,44.864823],[-1.160528,44.774667],[-1.036362,44.693562],[-1.191538,44.660726],[-1.251405,44.488888],[-1.312326,44.144798],[-1.448208,43.642275],[-1.597388,43.437493],[-1.762022,43.375887],[-1.608932,43.252129],[-1.505326,43.292788],[-1.382786,43.253297],[-1.413947,43.129178],[-1.006449,42.988992],[-0.946455,42.954058],[-0.751634,42.966937],[-0.729757,42.896558],[-0.54381,42.793157],[-0.392597,42.799559],[-0.313444,42.849375],[-0.327082,42.915785],[-0.190981,43.111203],[-0.017009,43.270449],[0.009592,43.422106],[-0.096788,43.582405],[-0.242833,43.584979],[-0.194138,43.737015],[-0.17911,43.937915],[-0.073597,43.945046],[0.032552,43.900192],[0.076043,43.983139],[0.189571,44.014642],[0.304091,43.993061],[0.597978,44.078225],[0.666597,44.025144],[0.752879,44.102268],[0.868779,44.126327],[0.949924,44.276443],[0.887336,44.366374],[1.049797,44.362639],[1.013165,44.53613],[1.287777,44.714785],[1.441926,44.877576],[1.413304,44.999382],[1.535723,45.046276],[1.650977,45.025013],[1.823915,44.927683],[1.908158,44.978423],[2.062908,44.976505],[2.171759,45.081497],[2.195364,45.220851],[2.350481,45.327561],[2.37825,45.414302],[2.487472,45.418842],[2.516327,45.553428],[2.465345,45.60082],[2.52836,45.681924],[2.388014,45.827373],[2.492228,45.86403],[2.59442,45.989441],[2.5598,46.173367],[2.478945,46.281146],[2.323023,46.329277],[2.281044,46.420404],[2.088946,46.4089],[1.819504,46.430034],[1.644846,46.386817],[1.522307,46.426528],[1.415185,46.347215],[1.177279,46.383948],[1.145929,46.506401],[0.894303,46.625732],[0.924749,46.69279],[0.704318,46.903296],[0.692567,46.974304],[0.438705,46.929578],[0.300739,46.97383],[0.29823,47.053922],[0.174218,47.071274],[0.05383,47.163734],[-0.128379,47.054429],[-0.184838,47.108333],[-0.559532,47.061883],[-0.619979,46.993321],[-0.773878,47.004248],[-0.829149,46.933362],[-0.656193,46.700775],[-0.602132,46.53328],[-0.640647,46.416225],[-0.557647,46.363451],[-0.750471,46.304254],[-1.01381,46.355623],[-1.129406,46.310272]]],[[[-1.25026,45.845963],[-1.24792,45.990418],[-1.383917,45.951445],[-1.25026,45.845963]]]]},"properties":{"code":"75","nom":"Nouvelle-Aquitaine","taux":"6.2"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-0.313444,42.849375],[-0.01064,42.684384],[0.175726,42.73648],[0.359628,42.723391],[0.429277,42.690748],[0.682258,42.708965],[0.644538,42.783076],[0.708375,42.861403],[0.927327,42.789256],[1.075109,42.785348],[1.161851,42.711044],[1.325023,42.723901],[1.435057,42.606074],[1.549251,42.655773],[1.737968,42.611311],[1.729889,42.495327],[1.958281,42.424048],[2.083593,42.362697],[2.257804,42.438354],[2.435485,42.38888],[2.482957,42.339647],[2.841416,42.458522],[3.040758,42.473142],[3.035424,42.678252],[3.065587,43.021675],[3.168348,43.157328],[3.343858,43.270394],[3.510959,43.273329],[3.662162,43.39228],[3.907138,43.516846],[4.10104,43.554371],[4.13767,43.481949],[4.230281,43.460184],[4.409353,43.561127],[4.487234,43.699241],[4.593035,43.68746],[4.690546,43.883899],[4.8421,43.986474],[4.70746,44.10367],[4.722071,44.187421],[4.649227,44.27036],[4.503539,44.340188],[4.336071,44.339519],[4.25885,44.264784],[4.051457,44.317322],[4.068445,44.405112],[3.905171,44.592709],[3.875462,44.740627],[3.740649,44.838697],[3.475771,44.815371],[3.412832,44.944842],[3.337942,44.955907],[3.182317,44.863735],[3.105495,44.886775],[2.998574,44.674443],[2.923267,44.728643],[2.849652,44.87149],[2.738258,44.941219],[2.602682,44.843163],[2.556123,44.721284],[2.435001,44.638875],[2.326791,44.669693],[2.169416,44.63807],[2.171637,44.790027],[2.09421,44.872012],[2.062908,44.976505],[1.908158,44.978423],[1.823915,44.927683],[1.650977,45.025013],[1.535723,45.046276],[1.413304,44.999382],[1.441926,44.877576],[1.287777,44.714785],[1.013165,44.53613],[1.049797,44.362639],[0.887336,44.366374],[0.949924,44.276443],[0.868779,44.126327],[0.752879,44.102268],[0.666597,44.025144],[0.597978,44.078225],[0.304091,43.993061],[0.189571,44.014642],[0.076043,43.983139],[0.032552,43.900192],[-0.073597,43.945046],[-0.17911,43.937915],[-0.194138,43.737015],[-0.242833,43.584979],[-0.096788,43.582405],[0.009592,43.422106],[-0.017009,43.270449],[-0.190981,43.111203],[-0.327082,42.915785],[-0.313444,42.849375]]]},"properties":{"code":"76","nom":"Occitanie","taux":"8.5"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[2.062908,44.976505],[2.09421,44.872012],[2.171637,44.790027],[2.169416,44.63807],[2.326791,44.669693],[2.435001,44.638875],[2.556123,44.721284],[2.602682,44.843163],[2.738258,44.941219],[2.849652,44.87149],[2.923267,44.728643],[2.998574,44.674443],[3.105495,44.886775],[3.182317,44.863735],[3.337942,44.955907],[3.412832,44.944842],[3.475771,44.815371],[3.740649,44.838697],[3.875462,44.740627],[3.905171,44.592709],[4.068445,44.405112],[4.051457,44.317322],[4.25885,44.264784],[4.336071,44.339519],[4.503539,44.340188],[4.649227,44.27036],[4.762255,44.325382],[4.81409,44.232315],[5.060561,44.308137],[5.1549,44.230941],[5.384527,44.201049],[5.454715,44.119226],[5.576192,44.188037],[5.686443,44.197158],[5.631598,44.328306],[5.49307,44.337174],[5.418533,44.424945],[5.597253,44.543274],[5.649631,44.617885],[5.790624,44.653293],[5.850394,44.750747],[6.136227,44.864072],[6.355363,44.854775],[6.318202,45.003859],[6.203923,45.012471],[6.229392,45.10875],[6.331295,45.118124],[6.393911,45.061818],[6.629992,45.109325],[6.767941,45.15974],[6.849855,45.127165],[6.968762,45.208058],[7.137593,45.255693],[7.110693,45.326509],[7.184271,45.407484],[7.000332,45.504414],[7.000692,45.6399],[6.829113,45.702831],[6.818078,45.834974],[6.939609,45.846733],[7.043891,45.922087],[7.018252,45.984185],[6.81473,46.129696],[6.864511,46.282986],[6.722865,46.40755],[6.545176,46.394725],[6.390033,46.340163],[6.279914,46.351093],[6.295651,46.226055],[6.126621,46.14046],[5.985317,46.143309],[5.971781,46.211519],[6.124246,46.251016],[6.169736,46.367935],[6.064006,46.416223],[5.908936,46.283951],[5.725182,46.260732],[5.649345,46.339495],[5.473052,46.265067],[5.310563,46.44677],[5.20114,46.508211],[5.052372,46.484874],[4.940022,46.517199],[4.780208,46.176676],[4.69311,46.302197],[4.618558,46.264794],[4.405814,46.296058],[4.389398,46.213601],[4.261025,46.178754],[4.104087,46.198391],[3.988788,46.169805],[3.890131,46.214487],[3.891239,46.285246],[3.986627,46.319196],[3.99804,46.465464],[3.890467,46.481246],[3.743289,46.567565],[3.696958,46.660583],[3.598001,46.723983],[3.215545,46.682893],[3.032063,46.794909],[2.959919,46.803872],[2.774489,46.718903],[2.70497,46.73939],[2.596648,46.637215],[2.614961,46.553276],[2.352004,46.512207],[2.281044,46.420404],[2.323023,46.329277],[2.478945,46.281146],[2.5598,46.173367],[2.59442,45.989441],[2.492228,45.86403],[2.388014,45.827373],[2.52836,45.681924],[2.465345,45.60082],[2.516327,45.553428],[2.487472,45.418842],[2.37825,45.414302],[2.350481,45.327561],[2.195364,45.220851],[2.171759,45.081497],[2.062908,44.976505]]]},"properties":{"code":"84","nom":"Auvergne-Rhône-Alpes","taux":"6.1"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[4.230281,43.460184],[4.554917,43.446213],[4.562798,43.372135],[4.855045,43.332619],[4.86685,43.404678],[4.96771,43.4261],[5.04104,43.327285],[5.36205,43.32196],[5.363649,43.207122],[5.53693,43.21449],[5.600895,43.162546],[5.812732,43.109367],[6.124052,43.079307],[6.387568,43.1449],[6.635535,43.172509],[6.665956,43.31822],[6.739809,43.412882],[6.917972,43.447739],[6.971833,43.545451],[7.040444,43.541583],[7.167666,43.657402],[7.320289,43.691329],[7.528519,43.790518],[7.495441,43.864356],[7.648598,43.97411],[7.716938,44.081763],[7.670853,44.153737],[7.426953,44.112875],[7.188913,44.197801],[7.008059,44.236435],[6.896505,44.374301],[6.854014,44.529125],[6.933509,44.575953],[6.987061,44.690138],[7.006773,44.839316],[6.859866,44.852903],[6.749751,44.907359],[6.740812,45.016733],[6.629992,45.109325],[6.393911,45.061818],[6.331295,45.118124],[6.229392,45.10875],[6.203923,45.012471],[6.318202,45.003859],[6.355363,44.854775],[6.136227,44.864072],[5.850394,44.750747],[5.790624,44.653293],[5.649631,44.617885],[5.597253,44.543274],[5.418533,44.424945],[5.49307,44.337174],[5.631598,44.328306],[5.686443,44.197158],[5.576192,44.188037],[5.454715,44.119226],[5.384527,44.201049],[5.1549,44.230941],[5.060561,44.308137],[4.81409,44.232315],[4.762255,44.325382],[4.649227,44.27036],[4.722071,44.187421],[4.70746,44.10367],[4.8421,43.986474],[4.690546,43.883899],[4.593035,43.68746],[4.487234,43.699241],[4.409353,43.561127],[4.230281,43.460184]]]},"properties":{"code":"93","nom":"Provence-Alpes-Côte d'Azur","taux":"7.8"}},
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[9.402271,41.858702],[9.412569,41.952476],[9.54998,42.104166],[9.558829,42.285265],[9.533196,42.545947],[9.449194,42.66224],[9.492385,42.8051],[9.463558,42.986401],[9.340873,42.994465],[9.311015,42.834679],[9.344478,42.73781],[9.085764,42.714609],[9.020694,42.644273],[8.886527,42.628966],[8.666509,42.515224],[8.674792,42.476243],[8.555885,42.36475],[8.689105,42.263528],[8.570341,42.230301],[8.590174,42.163885],[8.741329,42.040912],[8.5977,41.953238],[8.64145,41.909889],[8.803133,41.891381],[8.717242,41.722775],[8.914508,41.689724],[8.793077,41.629554],[8.788535,41.557736],[9.082201,41.441974],[9.219679,41.368212],[9.327205,41.616357],[9.387491,41.657359],[9.402271,41.858702]]]},"properties":{"code":"94","nom":"Corse","taux":"6.2"}}
],"_umap_options": {"displayOnLoad": true,"browsable": true,"name": "Taux de chômage","labelKey": "{nom} ({taux})","type": "Choropleth","choropleth": {"property": "taux"}}}

View file

@ -1,3 +1,6 @@
import json
from pathlib import Path
import pytest
from playwright.sync_api import expect
@ -55,3 +58,25 @@ def test_can_hide_datalayer_from_caption(map, live_server, datalayer, page):
expect(found).to_be_visible()
hidden = page.locator("#umap-ui-container").get_by_text(other.name)
expect(hidden).to_be_hidden()
def test_basic_choropleth_map(map, live_server, page):
path = Path(__file__).parent.parent / "fixtures/choropleth_region_chomage.geojson"
data = json.loads(path.read_text())
DataLayerFactory(data=data, map=map)
page.goto(f"{live_server.url}{map.get_absolute_url()}")
# Hauts-de-France
paths = page.locator("path[fill='#08519c']")
expect(paths).to_have_count(1)
# Occitanie
paths = page.locator("path[fill='#3182bd']")
expect(paths).to_have_count(1)
# Grand-Est, PACA
paths = page.locator("path[fill='#6baed6']")
expect(paths).to_have_count(2)
# Bourgogne-Franche-Comté, Centre-Val-de-Loire, IdF, Normandie, Corse, Nouvelle-Aquitaine
paths = page.locator("path[fill='#bdd7e7']")
expect(paths).to_have_count(6)
# Bretagne, Pays de la Loire, AURA
paths = page.locator("path[fill='#eff3ff']")
expect(paths).to_have_count(3)