Commit 1a892a7d authored by S Anand's avatar S Anand
Browse files

ENH: mapviewer: add scheme: option

parent 36adaa41
Pipeline #51902 passed with stage
in 3 minutes and 1 second
...@@ -1078,13 +1078,10 @@ You can apply styles based on any attribute or function. ...@@ -1078,13 +1078,10 @@ You can apply styles based on any attribute or function.
url: 'cities.json', url: 'cities.json',
latitude: 'lat', latitude: 'lat',
longitude: 'long', longitude: 'long',
options: {
title: 'column-name' // TODO: implement as popup
},
attrs: { attrs: {
fillColor: { fillColor: {
metric: 'pollution', metric: 'pollution',
range: 'RdYlGn' scheme: 'RdYlGn'
} }
} }
} }
...@@ -1112,10 +1109,16 @@ attribute. ...@@ -1112,10 +1109,16 @@ attribute.
dataKey: 'name', // Join this column from the URL (data) dataKey: 'name', // Join this column from the URL (data)
mapKey: 'ST_NM' // with this property in the GeoJSON mapKey: 'ST_NM' // with this property in the GeoJSON
}, },
options: {
style: {
fillColor: '#a00',
fillOpacity: 1
}
},
attrs: { attrs: {
fillColor: { // Fill the regions fillColor: { // Fill the regions
metric: 'score', // with the "score" column state_score.json metric: 'score', // with the "score" column from state_score.json
range: 'RdYlGn' // using a RdYlGn gradient scheme: 'RdYlGn' // using a RdYlGn gradient
}, },
tooltip: function(prop) { // On hover, show this HTML tooltip tooltip: function(prop) { // On hover, show this HTML tooltip
return prop.ST_NM + ': ' + prop.TOT_P return prop.ST_NM + ': ' + prop.TOT_P
...@@ -1206,7 +1209,7 @@ Drilldown feature example: ...@@ -1206,7 +1209,7 @@ Drilldown feature example:
- `url` is url (String) to fetch data - `url` is url (String) to fetch data
- `mapKey` is attribute name in geojson to match - `mapKey` is attribute name in geojson to match
- `dataKey` is column name in input dataset that matches with geojson `mapKey` - `dataKey` is column name in input dataset that matches with geojson `mapKey`
- `attrs` Data driven styles. - `attrs` Data driven styles. same as `options`. (`attrs` take priority over `options`)
- For `color`, `weight`, `opacity`, `fillColor`, `fillOpacity` properties, the options are:. - For `color`, `weight`, `opacity`, `fillColor`, `fillOpacity` properties, the options are:.
- `metric` string / function - `metric` string / function
- If `metric`: is a string, can be any numeric property of geojson - If `metric`: is a string, can be any numeric property of geojson
......
...@@ -136,6 +136,7 @@ MapViewer.prototype.mergeData = function (mapJSON, dataTable, mapKey, dataKey) { ...@@ -136,6 +136,7 @@ MapViewer.prototype.mergeData = function (mapJSON, dataTable, mapKey, dataKey) {
feature.properties[key] = row[key] feature.properties[key] = row[key]
} }
}) })
// returning mapJSON to write unit testcases for mergeData
return mapJSON return mapJSON
break break
default: default:
...@@ -173,17 +174,21 @@ MapViewer.prototype.buildLayer = function (layerName, layerConfig) { ...@@ -173,17 +174,21 @@ MapViewer.prototype.buildLayer = function (layerName, layerConfig) {
case 'topojson': case 'topojson':
case 'geojson': case 'geojson':
self.cacheData(layerName, layerConfig[dataOrURL(layerConfig)]).then(function (mapJSON) { self.cacheData(layerName, layerConfig[dataOrURL(layerConfig)]).then(function (mapJSON) {
gLayer = new L.TopoJSON(mapJSON, layerConfig.options)
self.fitToLayer(gLayer)
self._saveLayer(layerName, gLayer)
if ('link' in layerConfig) { if ('link' in layerConfig) {
self.cacheData(layerName, layerConfig.link[dataOrURL(layerConfig.link)]).then(function (tableData) { self.cacheData(layerName, layerConfig.link[dataOrURL(layerConfig.link)]).then(function (tableData) {
self.mergeData(mapJSON, tableData, layerConfig.link.mapKey, layerConfig.link.dataKey) self.mergeData(mapJSON, tableData, layerConfig.link.mapKey, layerConfig.link.dataKey)
if ('attrs' in layerConfig) self._choropleth(layerName, layerConfig) gLayer = new L.TopoJSON(mapJSON, layerConfig.options)
self.fire(layerName+'loaded') self._saveLayer(layerName, gLayer)
if ('attrs' in layerConfig) self._choropleth(layerName)
self.fitToLayer(layerName)
self.fire(layerName + 'loaded')
}) })
} else { } else {
if ('attrs' in layerConfig) self._choropleth(layerName, layerConfig) gLayer = new L.TopoJSON(mapJSON, layerConfig.options)
self._saveLayer(layerName, gLayer)
if ('attrs' in layerConfig) self._choropleth(layerName)
self.fitToLayer(layerName)
self.fire(layerName + 'loaded') self.fire(layerName + 'loaded')
} }
}) })
...@@ -208,23 +213,27 @@ MapViewer.prototype.buildLayer = function (layerName, layerConfig) { ...@@ -208,23 +213,27 @@ MapViewer.prototype.buildLayer = function (layerName, layerConfig) {
var markerProxy = layerConfig.type.toLowerCase() === 'circle' ? L.circle : L.circleMarker var markerProxy = layerConfig.type.toLowerCase() === 'circle' ? L.circle : L.circleMarker
self.cacheData(layerName, layerConfig[dataOrURL(layerConfig)]).then(function (pointjson) { self.cacheData(layerName, layerConfig[dataOrURL(layerConfig)]).then(function (pointjson) {
var pointLayers = [] var pointLayers = []
pointjson.forEach(function (d) { function create_layer() {
var mark = markerProxy([d[layerConfig.latitude], d[layerConfig.longitude]], layerConfig.options) pointjson.forEach(function (d) {
mark.feature = {} var mark = markerProxy([d[layerConfig.latitude], d[layerConfig.longitude]], layerConfig.options)
mark.feature.properties = d mark.feature = {}
pointLayers.push(mark) mark.feature.properties = d
}) pointLayers.push(mark)
self.fitToLayer(L.featureGroup(pointLayers)) })
self._saveLayer(layerName, L.featureGroup(pointLayers)) self._saveLayer(layerName, L.featureGroup(pointLayers))
}
if ('link' in layerConfig) { if ('link' in layerConfig) {
self.cacheData(layerName, layerConfig.link[dataOrURL(layerConfig.link)]).then(function (tableData) { self.cacheData(layerName, layerConfig.link[dataOrURL(layerConfig.link)]).then(function (tableData) {
self.mergeData(pointjson, tableData, layerConfig.link.mapKey, layerConfig.link.dataKey) self.mergeData(pointjson, tableData, layerConfig.link.mapKey, layerConfig.link.dataKey)
if ('attrs' in layerConfig) self._choropleth(layerName, layerConfig) create_layer()
self.fire(layerName + 'loaded')
if ('attrs' in layerConfig) self._choropleth(layerName)
self.fitToLayer(layerName)
}) })
} else { } else {
if ('attrs' in layerConfig) self._choropleth(layerName, layerConfig) create_layer()
self.fire(layerName + 'loaded') if ('attrs' in layerConfig) self._choropleth(layerName)
self.fitToLayer(layerName)
} }
}) })
break break
...@@ -238,27 +247,36 @@ MapViewer.prototype._choropleth = function (layerName, layerConfig) { ...@@ -238,27 +247,36 @@ MapViewer.prototype._choropleth = function (layerName, layerConfig) {
layer.eachLayer(function (sublayer) { layer.eachLayer(function (sublayer) {
var style = {}, prop, metricFormula, metric, domain var style = {}, prop, metricFormula, metric, domain
for (prop in layerConfig.attrs) {
if(prop.toLowerCase() == 'tooltip') continue for (prop in self.options.layers[layerName].attrs) {
metric = layerConfig.attrs[prop].metric if (prop.toLowerCase() == 'tooltip') continue
if (typeof (self.options.layers[layerName].attrs[prop]) != 'object') {
style[prop] = self.options.layers[layerName].attrs[prop]
continue
}
metric = self.options.layers[layerName].attrs[prop].metric
if (typeof (metric) === 'string') if (typeof (metric) === 'string')
metricFormula = (row) => row[metric] metricFormula = (row) => row[metric]
else else
metricFormula = metric metricFormula = metric
if (layerConfig.attrs[prop].domain) if (self.options.layers[layerName].attrs[prop].domain)
domain = layerConfig.attrs[prop].domain domain = self.options.layers[layerName].attrs[prop].domain
else else
domain = self._calculateMinMax(layer, metricFormula) domain = self._calculateMinMax(layer, metricFormula)
// TODO: ENH: cache _calculateMinMax for each property bcz its same for each sublayer
if (prop === "fillColor" || prop === "color") { // skip scale if sublayer.feature.properties do not have that metric, shows as per settings in `options: {style: ...}`
if (metricFormula(sublayer.feature.properties) !== undefined)
style[prop] = scale([], { style[prop] = scale([], {
metric: metric, metric: metric,
domain: domain, domain: domain,
scheme: layerConfig.attrs[prop].range scheme: self.options.layers[layerName].attrs[prop].scheme,
scale: self.options.layers[layerName].attrs[prop].scale,
range: self.options.layers[layerName].attrs[prop].range
})(sublayer.feature.properties) })(sublayer.feature.properties)
}
} }
sublayer.setStyle(style) sublayer.setStyle(style)
}) })
} }
......
...@@ -51,7 +51,6 @@ function scale (data, config) { ...@@ -51,7 +51,6 @@ function scale (data, config) {
} }
var domain = config.domain || d3.extent(data, metricFormula) var domain = config.domain || d3.extent(data, metricFormula)
if (color) { if (color) {
result = d3['scale' + scale](d3[color]) result = d3['scale' + scale](d3[color])
.domain(domain) .domain(domain)
......
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
attrs: { attrs: {
fillColor: { fillColor: {
metric: 'TOT_P', metric: 'TOT_P',
range: 'RdYlGn' scheme: 'Viridis'
}, },
tooltip: function (properties) { tooltip: function (properties) {
return properties['ST_NM'] return properties['ST_NM']
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
attrs: { attrs: {
fillColor: { fillColor: {
metric: 'DT_CEN_CD', metric: 'DT_CEN_CD',
range: 'RdYlGn' scheme: 'Viridis'
}, },
tooltip: function (properties) { tooltip: function (properties) {
return 'DISTRICT: ' + properties['DISTRICT'] return 'DISTRICT: ' + properties['DISTRICT']
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
attrs: { attrs: {
fillColor: { fillColor: {
metric: 'TOT_P', metric: 'TOT_P',
range: 'RdYlGn' scheme: 'Viridis'
}, },
tooltip: function (properties) { tooltip: function (properties) {
return '<h5 style="background-color: yellow;"> VILLAGE: ' + properties['NAME'] + '</h5>' return '<h5 style="background-color: yellow;"> VILLAGE: ' + properties['NAME'] + '</h5>'
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
attrs: { attrs: {
fillColor: { fillColor: {
metric: function (row) { return row['pollution'] + row['crimes'] }, metric: function (row) { return row['pollution'] + row['crimes'] },
range: 'RdYlGn' scheme: 'Viridis'
}, },
tooltip: 'just some tooltip text test' tooltip: 'just some tooltip text test'
} }
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
attrs: { attrs: {
fillColor: { fillColor: {
metric: 'score', metric: 'score',
range: 'RdYlGn' scheme: 'Viridis'
}, },
tooltip: function (d) { tooltip: function (d) {
return d.ST_NM + ' : ' + d.score return d.ST_NM + ' : ' + d.score
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
attrs: { attrs: {
fillColor: { fillColor: {
metric: function (row) { return row['pollution'] + row['crimes'] }, metric: function (row) { return row['pollution'] + row['crimes'] },
range: 'RdYlGn' scheme: 'Viridis'
}, },
tooltip: function (d) { tooltip: function (d) {
return 'just some tooltip text test' + d.name return 'just some tooltip text test' + d.name
......
...@@ -43,6 +43,8 @@ ...@@ -43,6 +43,8 @@
<div id="marker-map" style="height:300px"></div> <div id="marker-map" style="height:300px"></div>
<script> <script>
// TODO: write test case to see if properties are merged for choropleth?
// TODO: circle markers mergedata functions properly
var marker_map = g1.mapviewer({ var marker_map = g1.mapviewer({
id: 'marker-map', id: 'marker-map',
layers: { layers: {
...@@ -105,13 +107,10 @@ ...@@ -105,13 +107,10 @@
url: 'cities.json', url: 'cities.json',
latitude: 'lat', latitude: 'lat',
longitude: 'long', longitude: 'long',
options: {
title: 'column-name' // TODO: implement as popup,
},
attrs: { attrs: {
fillColor: { fillColor: {
metric: function (row) { return row['pollution'] + row['crimes'] }, metric: function (row) { return row['pollution'] + row['crimes'] },
range: 'RdYlGn' scheme: 'Viridis'
} }
} }
} }
...@@ -124,6 +123,7 @@ ...@@ -124,6 +123,7 @@
circle_svg.each(function () { circle_svg.each(function () {
circle_fill.push($(this).attr('fill')) circle_fill.push($(this).attr('fill'))
}) })
console.log("check", circle_fill, new Set(circle_fill))
// tests if all colors are distinct // tests if all colors are distinct
test.equals(circle_fill.length, (new Set(circle_fill)).size) test.equals(circle_fill.length, (new Set(circle_fill)).size)
test.end() test.end()
...@@ -154,10 +154,18 @@ ...@@ -154,10 +154,18 @@
dataKey: 'name', dataKey: 'name',
mapKey: 'ST_NM' mapKey: 'ST_NM'
}, },
options: {
style: {
fillColor: '#ccc',
fillOpacity: 0.9
}
},
attrs: { attrs: {
fillColor: { fillColor: {
metric: 'TOT_P', metric: 'score', // same as function(d) { return d.age }
range: 'RdYlGn' scale: 'linear',
domain: [10, 15, 30],
range: ['red', 'yellow', 'green'],
} }
} }
} }
...@@ -230,7 +238,7 @@ ...@@ -230,7 +238,7 @@
attrs: { attrs: {
fillColor: { fillColor: {
metric: 'TOT_P', metric: 'TOT_P',
range: 'RdYlGn' scheme: 'Viridis'
}, },
weight: { weight: {
metric: 'P_06', metric: 'P_06',
...@@ -250,7 +258,7 @@ ...@@ -250,7 +258,7 @@
attrs: { attrs: {
fillColor: { fillColor: {
metric: 'pollution', metric: 'pollution',
range: 'RdYlGn', scheme: 'Viridis',
domain: [0, 500] domain: [0, 500]
}, },
weight: { weight: {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment