Skip to content

Commit 80c4218

Browse files
committed
Null/nodata feature handling logic
- new option `noDataColor`: fill color to use for features with null/nodata attribute values. In polygon, point-color and line-color modes. (default: '#606060'). Also appears in legend as a separate class/category. Currently, null/nodata features are not assigned a distinctive symbol when in point-size and line-width modes. - new option `noDataIgnore`: if true, features with null attribute values are not shown on the map. This also means the legend will not have a nodata classs (default: false) - new option `nodata` in `legendTemplate`: you can now customize the nodata text in Legend - modified classification code to filter out null attribute values when generating classes - more thorough check for non-existing attribute field vs. null as attribute value (with more concise error messages) - legend data rows (symbol + key) now have a css class (`legendDataRow`), set in leaflet-dataclassification.css, resulting in a cleaner legend-generation code - updated examples (`combined.html` dataset: density value for North Dakota has been nulled to showcase nodata handling; all show an info panel about the plugin; overriding Chrome's built-in CSS rules to avoid focus rectangle on clicking polygons on a Leaflet map) - updated documentation - updated main screenshot
1 parent d8965d6 commit 80c4218

11 files changed

+337
-106
lines changed

README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Aims to simplify data visualization and creation of elegant thematic web maps wi
1818
- Supports ColorBrewer2 color ramps and custom color ramps (thanks to [chroma.js](https://github.com/gka/chroma.js))
1919
- Various SVG shapes/symbols for Point features
2020
- For size/width based symbology, min and max values can be adjusted to create a telling visualization with distinguishable classes
21+
- Handling of null/nodata feature attributes
2122
- Legend generation with options for:
2223
- class order (ascending/descending)
2324
- legend header (title)
@@ -62,6 +63,8 @@ const layer = L.dataClassification(data, {
6263
lineWidth: {min: 1, max: 15},
6364
colorRamp: 'OrRd',
6465
colorCustom: ['rgba(210,255,178,1)', '#fec44fff', 'f95f0eff'], // if specified, overrides colorRamp!
66+
noDataColor: '#101010',
67+
noDataIgnore: false,
6568
reverseColorRamp: false,
6669
middlePointValue: 0,
6770
classRounding: 2,
@@ -71,7 +74,8 @@ const layer = L.dataClassification(data, {
7174
legendTemplate: {
7275
highest: '{low} and above',
7376
middle: '{low} – {high}',
74-
lowest: 'below {high}'
77+
lowest: 'below {high}',
78+
nodata: 'No data'
7579
},
7680
unitModifier: {action: 'divide', by: 1000},
7781
style: {
@@ -107,6 +111,8 @@ const layer = L.dataClassification(data, {
107111
#### General options
108112
- `colorRamp <string>`: color ramp to use for symbology. Based on ColorBrewer2 color ramps (https://colorbrewer2.org/), included in Chroma.js. Custom colors (`colorCustom`) override this. (default: 'PuRd')
109113
- `colorCustom <array<string>>`: custom color ramp defined as an array, colors in formats supported by Chroma.js, with opacity support. A minimum of two colors are required. If defined, custom colors override `colorRamp`. Example: ['rgba(210,255,178,1)', '#fec44fff', 'f95f0eff']. Examples for yellow in different color formats: 'ffff00', '#ff0', 'yellow', '#ffff0055', 'rgba(255,255,0,0.35)', 'hsla(58,100%,50%,0.6)', chroma('yellow').alpha(0.5). For more formats, see: https://gka.github.io/chroma.js/. For an interactive color palette helper, see: https://gka.github.io/palettes/.
114+
- `noDataColor <string>`: fill color to use for features with null/nodata attribute values. In polygon, point-color and line-color modes. (default: '#606060')
115+
- `noDataIgnore <boolean>`: if true, features with null attribute values are not shown on the map. This also means the legend will not have a nodata classs (default: false)
110116
- `reverseColorRamp <boolean>`: if true, reverses the chosen color ramp, both in symbology on map and legend colors. Useful when you found a great looking colorramp (green to red), but would prefer reversed colors to match visual implications about colors: green implies positive, red implies negative phenomena. (default: false)
111117
- `middlePointValue <number>`: adjust boundary value of middle classes (only when classifying into even classes). Useful for symmetric classification of diverging data around 0. Only use a value within the range of the two middle classes.
112118
- `classRounding <integer>`: class boundary value rounding. When positive numbers are used for this option, class boundary values are rounded to x decimals, zero will round to whole numbers, while negative numbers will round values to the nearest 10, 100, 1000, etc. Example: with a setting of "1", a value of 254777.253 will get rounded up to 254777.3, with "0" it will be 254777, with "-2" it will become 254800. (default: null - no rounding happens, values are used as-is)
@@ -117,6 +123,7 @@ const layer = L.dataClassification(data, {
117123
- `highest <string>`: template for the upper end of classes, "highest value and above" (default: '{low} <')
118124
- `middle <string>`: template for rows in the middle, "low to high" (default: '{low} – {high}')
119125
- `lowest <string>`: template for the lower end of classes, "lowest value and below" (default: '< {high}')
126+
- `nodata <string>`: text to show for null/nodata class (default: 'No data')
120127
- `unitModifier <object>`: modifies the final class boundary values in order to multiply/divide them by a number. Useful for example when a dataset attribute is in metres, but kilometres would fit the legend better (786000 metres shown as 786 km). Purely visual, only affects legend. Happens after classRounding.
121128
- `action <string>`: ['divide'|'multiply'] action to take on the number specified by `by`. Required for `unitModifier`.
122129
- `by <number>`: a number to divide/multiply class boundary values with. Required for `unitModifier`.

examples/combined.html

+36-6
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131
bottom: 0;
3232
left: 0;
3333
right: 0;
34-
}
34+
}
35+
36+
/* Overriding Chrome's built-in CSS rules to avoid focus rectangle on clicking polygons on a Leaflet map */
37+
path.leaflet-interactive:focus {
38+
outline: none;
39+
}
3540
</style>
3641
</head>
3742
<body>
@@ -92,20 +97,23 @@
9297
layer.on('mousemove',e=>{
9398
e.target.getTooltip().setLatLng(e.latlng);
9499
});
100+
} else {
101+
layer.bindTooltip('<b>' + feature.properties.name + '</b><br> No data');
102+
layer.on('mousemove',e=>{
103+
e.target.getTooltip().setLatLng(e.latlng);
104+
});
95105
}
96106
}
97-
function onEachFeature(feature, layer) {
98-
layer.bindPopup(feature.properties.name);
99-
}
100107
window.testdata = L.dataClassification(d, {
101108
mode: 'quantile',
102109
classes: 5,
103110
field: 'density',
104111
colorRamp: 'YlGnBu',
112+
noDataColor: '#505050',
105113
legendAscending: false,
106114
reverseColorRamp: false,
107115
legendTitle: 'Density (pop/mi²)',
108-
classRounding: 1,
116+
classRounding: 1,
109117
onEachFeature: tooltip,
110118
// polygon outline styling example (standard L.Path styling options):
111119
style: {
@@ -149,7 +157,29 @@
149157
});
150158
//testdata3.bringToFront();
151159

152-
map.fitBounds([[17, -170],[68, -65]]);
160+
map.fitBounds([[17, -150],[68, -65]]);
161+
162+
var infopanel = L.control({position: 'bottomright'});
163+
infopanel.onAdd = function (map) {
164+
var div = L.DomUtil.create('div', 'info');
165+
div.innerHTML +=
166+
'<div style="display: flex; flex-direction: column; max-width: 500px; text-align: center;">' +
167+
'<div style="font-weight: bold; margin-bottom: 5px;">' +
168+
'Leaflet-dataclassification plugin demo page: "combined"' +
169+
'</div>'+
170+
'<div style="justify-content: center; ">' +
171+
'This is an example page showcasing some of the features of Leaflet plugin <i>leaflet-dataclassification</i> for three layers simultaneously. '+
172+
'Feature tooltips on hover (native feature of Leaflet) were added to provide an easy check of attribute values used. '+
173+
'<br><i>Note: population density for North Dakota has been manually removed to showcase handling of Null data (nodata) in feature attributes.</i>'+
174+
'<br><br>'+
175+
'Single-step data classification, symbology and legend creation for GeoJSON data powered thematic maps.'+
176+
'<br><br>'+
177+
'Project page: <a href="https://github.com/balladaniel/leaflet-dataclassification">https://github.com/balladaniel/leaflet-dataclassification</a>'
178+
'</div>'+
179+
'</div>';
180+
return div;
181+
}
182+
infopanel.addTo(map);
153183

154184
</script>
155185
</body>

examples/data/us-states.geojson

+58-54
Large diffs are not rendered by default.

examples/lines_c.html

+21
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,27 @@
7777
map.fitBounds(testdata.getBounds());
7878
});
7979

80+
var infopanel = L.control({position: 'bottomright'});
81+
infopanel.onAdd = function (map) {
82+
var div = L.DomUtil.create('div', 'info');
83+
div.innerHTML +=
84+
'<div style="display: flex; flex-direction: column; max-width: 500px; text-align: center;">' +
85+
'<div style="font-weight: bold; margin-bottom: 5px;">' +
86+
'Leaflet-dataclassification plugin demo page: "lines_color"' +
87+
'</div>'+
88+
'<div style="justify-content: center; ">' +
89+
'This is an example page showcasing some of the features of Leaflet plugin <i>leaflet-dataclassification</i>. '+
90+
'Feature tooltips on hover (native feature of Leaflet) were added to provide an easy check of attribute values used. '+
91+
'<br><br>'+
92+
'Single-step data classification, symbology and legend creation for GeoJSON data powered thematic maps.'+
93+
'<br><br>'+
94+
'Project page: <a href="https://github.com/balladaniel/leaflet-dataclassification">https://github.com/balladaniel/leaflet-dataclassification</a>'
95+
'</div>'+
96+
'</div>';
97+
return div;
98+
}
99+
infopanel.addTo(map);
100+
80101
</script>
81102
</body>
82103
</html>

examples/lines_w.html

+21
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@
7979
map.fitBounds(testdata.getBounds());
8080
});
8181

82+
var infopanel = L.control({position: 'bottomright'});
83+
infopanel.onAdd = function (map) {
84+
var div = L.DomUtil.create('div', 'info');
85+
div.innerHTML +=
86+
'<div style="display: flex; flex-direction: column; max-width: 500px; text-align: center;">' +
87+
'<div style="font-weight: bold; margin-bottom: 5px;">' +
88+
'Leaflet-dataclassification plugin demo page: "lines_width"' +
89+
'</div>'+
90+
'<div style="justify-content: center; ">' +
91+
'This is an example page showcasing some of the features of Leaflet plugin <i>leaflet-dataclassification</i>. '+
92+
'Feature tooltips on hover (native feature of Leaflet) were added to provide an easy check of attribute values used. '+
93+
'<br><br>'+
94+
'Single-step data classification, symbology and legend creation for GeoJSON data powered thematic maps.'+
95+
'<br><br>'+
96+
'Project page: <a href="https://github.com/balladaniel/leaflet-dataclassification">https://github.com/balladaniel/leaflet-dataclassification</a>'
97+
'</div>'+
98+
'</div>';
99+
return div;
100+
}
101+
infopanel.addTo(map);
102+
82103
</script>
83104
</body>
84105
</html>

examples/points_c.html

+21
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@
7979
map.fitBounds(testdata.getBounds());
8080
});
8181

82+
var infopanel = L.control({position: 'bottomleft'});
83+
infopanel.onAdd = function (map) {
84+
var div = L.DomUtil.create('div', 'info');
85+
div.innerHTML +=
86+
'<div style="display: flex; flex-direction: column; max-width: 500px; text-align: center;">' +
87+
'<div style="font-weight: bold; margin-bottom: 5px;">' +
88+
'Leaflet-dataclassification plugin demo page: "points_color"' +
89+
'</div>'+
90+
'<div style="justify-content: center; ">' +
91+
'This is an example page showcasing some of the features of Leaflet plugin <i>leaflet-dataclassification</i>. '+
92+
'Feature tooltips on hover (native feature of Leaflet) were added to provide an easy check of attribute values used. '+
93+
'<br><br>'+
94+
'Single-step data classification, symbology and legend creation for GeoJSON data powered thematic maps.'+
95+
'<br><br>'+
96+
'Project page: <a href="https://github.com/balladaniel/leaflet-dataclassification">https://github.com/balladaniel/leaflet-dataclassification</a>'
97+
'</div>'+
98+
'</div>';
99+
return div;
100+
}
101+
infopanel.addTo(map);
102+
82103
</script>
83104
</body>
84105
</html>

examples/points_s.html

+20
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@
7777
map.fitBounds(testdata.getBounds());
7878
});
7979

80+
var infopanel = L.control({position: 'bottomright'});
81+
infopanel.onAdd = function (map) {
82+
var div = L.DomUtil.create('div', 'info');
83+
div.innerHTML +=
84+
'<div style="display: flex; flex-direction: column; max-width: 500px; text-align: center;">' +
85+
'<div style="font-weight: bold; margin-bottom: 5px;">' +
86+
'Leaflet-dataclassification plugin demo page: "points_size"' +
87+
'</div>'+
88+
'<div style="justify-content: center; ">' +
89+
'This is an example page showcasing some of the features of Leaflet plugin <i>leaflet-dataclassification</i>. '+
90+
'Feature tooltips on hover (native feature of Leaflet) were added to provide an easy check of attribute values used. '+
91+
'<br><br>'+
92+
'Single-step data classification, symbology and legend creation for GeoJSON data powered thematic maps.'+
93+
'<br><br>'+
94+
'Project page: <a href="https://github.com/balladaniel/leaflet-dataclassification">https://github.com/balladaniel/leaflet-dataclassification</a>'
95+
'</div>'+
96+
'</div>';
97+
return div;
98+
}
99+
infopanel.addTo(map);
80100
</script>
81101
</body>
82102
</html>

examples/polygons.html

+27-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131
bottom: 0;
3232
left: 0;
3333
right: 0;
34-
}
34+
}
35+
36+
/* Overriding Chrome's built-in CSS rules to avoid focus rectangle on clicking polygons on a Leaflet map */
37+
path.leaflet-interactive:focus {
38+
outline: none;
39+
}
3540
</style>
3641
</head>
3742
<body>
@@ -81,6 +86,27 @@
8186
map.fitBounds([[-48, 165], [-33, 179]]);
8287
});
8388

89+
var infopanel = L.control({position: 'bottomright'});
90+
infopanel.onAdd = function (map) {
91+
var div = L.DomUtil.create('div', 'info');
92+
div.innerHTML +=
93+
'<div style="display: flex; flex-direction: column; max-width: 500px; text-align: center;">' +
94+
'<div style="font-weight: bold; margin-bottom: 5px;">' +
95+
'Leaflet-dataclassification plugin demo page: "polygons"' +
96+
'</div>'+
97+
'<div style="justify-content: center; ">' +
98+
'This is an example page showcasing some of the features of Leaflet plugin <i>leaflet-dataclassification</i>. '+
99+
'Feature tooltips on hover (native feature of Leaflet) were added to provide an easy check of attribute values used. '+
100+
'<br><br>'+
101+
'Single-step data classification, symbology and legend creation for GeoJSON data powered thematic maps.'+
102+
'<br><br>'+
103+
'Project page: <a href="https://github.com/balladaniel/leaflet-dataclassification">https://github.com/balladaniel/leaflet-dataclassification</a>'
104+
'</div>'+
105+
'</div>';
106+
return div;
107+
}
108+
infopanel.addTo(map);
109+
84110
</script>
85111
</body>
86112
</html>

leaflet-dataclassification.css

+5
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,9 @@
1616
float: left;
1717
margin-right: 8px;
1818
opacity: 0.7;
19+
}
20+
.legendDataRow {
21+
display: flex;
22+
flex-direction: row;
23+
align-items: center
1924
}

0 commit comments

Comments
 (0)