From 8e12e6cf2413dc1bec77d290020b23ebd56491ed Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Sat, 10 Jun 2023 19:57:54 +0200 Subject: [PATCH 01/22] POC of a choropleth layer --- package.json | 1 + scripts/vendorsjs.sh | 1 + umap/static/umap/js/umap.features.js | 2 +- umap/static/umap/js/umap.forms.js | 1 + umap/static/umap/js/umap.layer.js | 96 +++++++++++++++++++++++++++- umap/templates/umap/js.html | 1 + 6 files changed, 98 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b2995e06..aae95f12 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "homepage": "http://wiki.openstreetmap.org/wiki/UMap", "dependencies": { "@tmcw/togeojson": "^5.8.0", + "chroma-js": "^2.4.2", "csv2geojson": "5.1.1", "dompurify": "^3.0.3", "georsstogeojson": "^0.1.0", diff --git a/scripts/vendorsjs.sh b/scripts/vendorsjs.sh index 751132fc..b12bd27b 100755 --- a/scripts/vendorsjs.sh +++ b/scripts/vendorsjs.sh @@ -26,5 +26,6 @@ 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/chroma/ && cp -r node_modules/chroma-js/chroma.min.js umap/static/umap/vendors/chroma/ echo 'Done!' diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 7de50be8..7e2428d9 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -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) } diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index 0d983e67..f3f022b2 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -379,6 +379,7 @@ L.FormBuilder.LayerTypeChooser = L.FormBuilder.Select.extend({ ['Default', L._('Default')], ['Cluster', L._('Clustered')], ['Heat', L._('Heatmap')], + ['Choropleth', L._('Choropleth')], ], }) diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 21d41b52..16a3d75b 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -106,6 +106,94 @@ L.U.Layer.Cluster = L.MarkerClusterGroup.extend({ }, }) +L.U.Layer.Choropleth = L.FeatureGroup.extend({ + _type: 'Choropleth', + includes: [L.U.Layer], + canBrowse: true, + + 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 + ) + }, + + computeLimits: function () { + const values = [] + this.datalayer.eachLayer((layer) => values.push(layer.properties.density)) + this.options.limits = chroma.limits( + values, + this.datalayer.options.choropleth.mode || 'q', + this.datalayer.options.choropleth.steps || 5 + ) + const color = this.datalayer.getOption('color') + this.options.colors = chroma + .scale(['white', color]) + .colors(this.options.limits.length) + }, + + getColor: function (feature) { + if (!feature) return // FIXME shold not happen + const featureValue = feature.properties.density + // Find the bucket/step/limit that this value is less than and give it that color + for (let i = 0; i < this.options.limits.length; i++) { + if (featureValue <= this.options.limits[i]) { + return this.options.colors[i] + } + } + }, + + getOption: function (option, feature) { + if (option === 'fillColor' || option === 'color') return this.getColor(feature) + }, + + addLayer: function (layer) { + this.computeLimits() + L.FeatureGroup.prototype.addLayer.call(this, layer) + }, + + removeLayer: function (layer) { + this.computeLimits() + L.FeatureGroup.prototype.removeLayer.call(this, layer) + }, + + onAdd: function (map) { + this.computeLimits() + L.FeatureGroup.prototype.onAdd.call(this, map) + }, + + getEditableOptions: function () { + return [ + [ + 'options.choropleth.steps', + { + handler: 'IntInput', + placeholder: L._('Choropleth steps'), + helpText: L._('Choropleth steps (default 5)'), + }, + ], + [ + 'options.choropleth.mode', + { + handler: 'Select', + selectOptions: [ + ['q', L._('quantile')], + ['e', L._('equidistant')], + ['l', L._('logarithmic')], + ['k', L._('k-mean')], + ], + helpText: L._('Choropleth mode'), + }, + ], + ] + }, +}) + L.U.Layer.Heat = L.HeatLayer.extend({ _type: 'Heat', includes: [L.U.Layer], @@ -897,8 +985,6 @@ L.U.DataLayer = L.Evented.extend({ 'options.fillOpacity', ] - shapeOptions = shapeOptions.concat(this.layer.getEditableOptions()) - const redrawCallback = function (field) { this.hide() this.layer.postUpdate(field) @@ -1050,7 +1136,11 @@ L.U.DataLayer = L.Evented.extend({ this.map.ui.openPanel({ data: { html: container }, className: 'dark' }) }, - getOption: function (option) { + getOption: function (option, feature) { + if (this.layer && this.layer.getOption) { + const value = this.layer.getOption(option, feature) + if (value) return value + } if (L.Util.usableOption(this.options, option)) return this.options[option] else return this.map.getOption(option) }, diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html index e2da8669..609eff2d 100644 --- a/umap/templates/umap/js.html +++ b/umap/templates/umap/js.html @@ -24,6 +24,7 @@ + {% endcompress %} {% if locale %}{% endif %} {% compress js %} From 5d350a7cc987dd5ba370f0fd5482e671df6f2c56 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Sat, 10 Jun 2023 21:17:28 +0200 Subject: [PATCH 02/22] Control property used in choropleth --- umap/static/umap/js/umap.layer.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 16a3d75b..418dccfd 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -123,9 +123,14 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ ) }, + _getValue: function (feature) { + const key = this.datalayer.options.choropleth.property || 'value' + return feature.properties[key] + }, + computeLimits: function () { const values = [] - this.datalayer.eachLayer((layer) => values.push(layer.properties.density)) + this.datalayer.eachLayer((layer) => values.push(this._getValue(layer))) this.options.limits = chroma.limits( values, this.datalayer.options.choropleth.mode || 'q', @@ -139,7 +144,7 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ getColor: function (feature) { if (!feature) return // FIXME shold not happen - const featureValue = feature.properties.density + const featureValue = this._getValue(feature) // Find the bucket/step/limit that this value is less than and give it that color for (let i = 0; i < this.options.limits.length; i++) { if (featureValue <= this.options.limits[i]) { @@ -169,6 +174,14 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ getEditableOptions: function () { return [ + [ + 'options.choropleth.property', + { + handler: 'BlurInput', + placeholder: L._('Choropleth property value'), + helpText: L._('Choropleth property value'), + }, + ], [ 'options.choropleth.steps', { From bf116e8d93d3e57815f2c9697ca59c31e283ee66 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Sat, 10 Jun 2023 22:21:41 +0200 Subject: [PATCH 03/22] Use brewer palettes for choropleth colors cf https://gka.github.io/chroma.js/#chroma-brewer --- umap/static/umap/js/umap.layer.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 418dccfd..24e005c2 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -136,9 +136,8 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ this.datalayer.options.choropleth.mode || 'q', this.datalayer.options.choropleth.steps || 5 ) - const color = this.datalayer.getOption('color') this.options.colors = chroma - .scale(['white', color]) + .scale(this.datalayer.options.choropleth.brewer || 'Blues') .colors(this.options.limits.length) }, @@ -173,6 +172,12 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ }, getEditableOptions: function () { + // chroma expose each palette both in title mode and in lowercase + // TODO: PR to chroma to get a accessor to the palettes names list + const brewerPalettes = Object.keys(chroma.brewer) + .filter((s) => s[0] == s[0].toUpperCase()) + .sort() + .map((k) => [k, k]) return [ [ 'options.choropleth.property', @@ -182,6 +187,14 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ helpText: L._('Choropleth property value'), }, ], + [ + 'options.choropleth.brewer', + { + handler: 'Select', + label: L._('Choropleth color palette'), + selectOptions: brewerPalettes, + }, + ], [ 'options.choropleth.steps', { From 7a0dbd014a5e88aeeea58db26910779e28d7e111 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 12 Jun 2023 15:53:33 +0200 Subject: [PATCH 04/22] There is one more limit than the number of steps Limits are steps boundaries, and first limit is always the lower value, and latest limit always the bigger. --- umap/static/umap/js/umap.layer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 24e005c2..54cdbe23 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -138,16 +138,16 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ ) this.options.colors = chroma .scale(this.datalayer.options.choropleth.brewer || 'Blues') - .colors(this.options.limits.length) + .colors(this.options.limits.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 = 0; i < this.options.limits.length; i++) { + for (let i = 1; i < this.options.limits.length; i++) { if (featureValue <= this.options.limits[i]) { - return this.options.colors[i] + return this.options.colors[i-1] } } }, From 451eb8c0bfd0eba534807544a2c7a51f3cba6720 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 20 Jun 2023 20:32:40 +0200 Subject: [PATCH 05/22] Naive Choropleth legend, WIP --- umap/static/umap/js/umap.controls.js | 1 + umap/static/umap/js/umap.layer.js | 13 +++++++++++++ umap/static/umap/map.css | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 66c8849d..ec351921 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -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() }, diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 54cdbe23..66f3acfb 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -218,6 +218,19 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ ], ] }, + + renderLegend: function (container) { + const parent = L.DomUtil.create('ul', '', container) + let li, color, label + + this.options.limits.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.limits[index].toFixed(1)} - ${+this.options.limits[index+1].toFixed(1)}` + }) + }, }) L.U.Layer.Heat = L.HeatLayer.extend({ diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index cef43e39..a35008d1 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -1080,6 +1080,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 */ From 6c502c54b4108c3096d9befe89cd96ba1eec227e Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 19 Jun 2023 10:02:48 +0200 Subject: [PATCH 06/22] Better defaults for choropleth layer --- umap/static/umap/js/umap.features.js | 5 +++- umap/static/umap/js/umap.layer.js | 35 +++++++++++++++++++++------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 7e2428d9..427a8756 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -1,5 +1,5 @@ L.U.FeatureMixin = { - staticOptions: {}, + staticOptions: {mainColor: 'color'}, initialize: function (map, latlng, options) { this.map = map @@ -1084,6 +1084,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 diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 66f3acfb..1adae247 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -110,6 +110,13 @@ 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, + }, initialize: function (datalayer) { this.datalayer = datalayer @@ -136,24 +143,25 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ this.datalayer.options.choropleth.mode || 'q', this.datalayer.options.choropleth.steps || 5 ) + const fillColor = this.datalayer.getOption('fillColor') || this.defaults.fillColor this.options.colors = chroma - .scale(this.datalayer.options.choropleth.brewer || 'Blues') - .colors(this.options.limits.length - 1) + .scale(this.datalayer.options.choropleth.brewer || ['white', fillColor]) + .colors(this.options.limits.length) }, 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.limits.length; i++) { + for (let i = 0; i < this.options.limits.length; i++) { if (featureValue <= this.options.limits[i]) { - return this.options.colors[i-1] + return this.options.colors[i] } } }, getOption: function (option, feature) { - if (option === 'fillColor' || option === 'color') return this.getColor(feature) + if (feature && option === feature.staticOptions.mainColor) return this.getColor(feature) }, addLayer: function (layer) { @@ -228,7 +236,9 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ 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.limits[index].toFixed(1)} - ${+this.options.limits[index+1].toFixed(1)}` + label.textContent = `${+this.options.limits[index].toFixed( + 1 + )} - ${+this.options.limits[index + 1].toFixed(1)}` }) }, }) @@ -1175,13 +1185,22 @@ L.U.DataLayer = L.Evented.extend({ this.map.ui.openPanel({ data: { html: container }, className: 'dark' }) }, + getOwnOption: function (option) { + if (L.Util.usableOption(this.options, option)) return this.options[option] + }, + getOption: function (option, feature) { if (this.layer && this.layer.getOption) { const value = this.layer.getOption(option, feature) if (value) return value } - if (L.Util.usableOption(this.options, option)) return this.options[option] - else return this.map.getOption(option) + if (this.getOwnOption(option)) { + 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) { From 7b68c52a156942ee0903bf0ab42a42bd7c7ae850 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 19 Jun 2023 13:20:12 +0200 Subject: [PATCH 07/22] Allow to select choropleth property from a list instead of gessing it --- umap/static/umap/js/umap.layer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 1adae247..eda199a6 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -190,7 +190,8 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ [ 'options.choropleth.property', { - handler: 'BlurInput', + handler: 'Select', + selectOptions: this.datalayer._propertiesIndex, placeholder: L._('Choropleth property value'), helpText: L._('Choropleth property value'), }, From 125aa72785457a79f7d84c7cd8e9e4fdc92861a1 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 19 Jun 2023 19:03:51 +0200 Subject: [PATCH 08/22] Make choropleth mode work with lines --- umap/static/umap/js/umap.features.js | 3 ++- umap/static/umap/js/umap.layer.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 427a8756..5b3b8f7b 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -1,5 +1,5 @@ L.U.FeatureMixin = { - staticOptions: {mainColor: 'color'}, + staticOptions: { mainColor: 'color' }, initialize: function (map, latlng, options) { this.map = map @@ -948,6 +948,7 @@ L.U.Polyline = L.Polyline.extend({ staticOptions: { stroke: true, fill: false, + mainColor: 'color', }, isSameClass: function (other) { diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index eda199a6..dd7e569a 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -132,7 +132,7 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ _getValue: function (feature) { const key = this.datalayer.options.choropleth.property || 'value' - return feature.properties[key] + return +feature.properties[key] // TODO: should we catch values non castable to int ? }, computeLimits: function () { From 82bb017b2352081310eb700f69681b186827323e Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 3 Jul 2023 18:26:22 +0200 Subject: [PATCH 09/22] Basic Choropleth tests --- umap/static/umap/js/umap.layer.js | 6 +- umap/static/umap/test/Choropleth.js | 242 ++++++++++++++++++++++++++++ umap/static/umap/test/index.html | 2 + 3 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 umap/static/umap/test/Choropleth.js diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index dd7e569a..3b207142 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -146,16 +146,16 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({ const fillColor = this.datalayer.getOption('fillColor') || this.defaults.fillColor this.options.colors = chroma .scale(this.datalayer.options.choropleth.brewer || ['white', fillColor]) - .colors(this.options.limits.length) + .colors(this.options.limits.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 = 0; i < this.options.limits.length; i++) { + for (let i = 1; i < this.options.limits.length; i++) { if (featureValue <= this.options.limits[i]) { - return this.options.colors[i] + return this.options.colors[i - 1] } } }, diff --git a/umap/static/umap/test/Choropleth.js b/umap/static/umap/test/Choropleth.js new file mode 100644 index 00000000..3682b4ad --- /dev/null +++ b/umap/static/umap/test/Choropleth.js @@ -0,0 +1,242 @@ +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() + 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.options.type = 'Choropleth' + this.datalayer.options.choropleth = { + property: 'value', + } + this.datalayer.resetLayer() + DATALAYER = this.datalayer + // Does not pass because chroma-js seems to have rounding issues + //assert.deepEqual(this.datalayer.layer.options.limits, [45, 438.6, 707.0, 3231.0, 4935.2, 9898]) + assert.equal(poly1._path.attributes.fill.value, '#ffffff') + assert.equal(poly4._path.attributes.fill.value, '#ffbfbf') + assert.equal(poly9._path.attributes.fill.value, '#ff0000') + }) + it('choropleth should compute brewer colors', function () { + this.datalayer.options.choropleth.brewer = 'Blues' + this.datalayer.resetLayer(true) + DATALAYER = this.datalayer + assert.equal(poly1._path.attributes.fill.value, '#f7fbff') + assert.equal(poly4._path.attributes.fill.value, '#c6dbef') + assert.equal(poly9._path.attributes.fill.value, '#08306b') + }) + it('choropleth should allow to change steps', function () { + this.datalayer.options.choropleth.steps = 6 + this.datalayer.resetLayer(true) + assert.equal(poly1._path.attributes.fill.value, '#f7fbff') + assert.equal(poly4._path.attributes.fill.value, '#94c4df') + assert.equal(poly9._path.attributes.fill.value, '#08306b') + }) + }) +}) diff --git a/umap/static/umap/test/index.html b/umap/static/umap/test/index.html index 620d0274..5d922418 100644 --- a/umap/static/umap/test/index.html +++ b/umap/static/umap/test/index.html @@ -25,6 +25,7 @@ + @@ -82,6 +83,7 @@ +