Commit 3331b16a authored by Tejesh's avatar Tejesh 🖖 Committed by S Anand
Browse files

ENH: Integrate d3-legend library with mapviewer, fixes #92, #102

parent fb02b43c
......@@ -1217,6 +1217,52 @@ attribute.
})
</script>
```
This adds legend to the map
```html
<div id="choropleth" class="map"></div>
<script>
var choro_map = g1.mapviewer({
id: 'choropleth',
legend: {
position: 'topright',
format: 'd',
shape: d3.symbolCircle,
size: 100,
scale: d3.scaleLinear().domain([10, 20, 30]).range(['red', 'yellow', 'green']),
orient: 'horizontal',
width: 300,
height: 100
},
layers: {
worldMap2: { type: 'tile', url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' },
indiaGeojson: {
type: 'geojson',
url: 'india-states.geojson',
link: {
url: 'state_score.json',
dataKey: 'name',
mapKey: 'ST_NM'
},
options: {
style: {
fillColor: '#ccc',
fillOpacity: 1
}
},
attrs: {
fillColor: {
metric: 'score',
scale: 'linear',
domain: [10, 20, 30],
range: ['red', 'yellow', 'green'],
}
}
}
}
})
</script>
```
Drilldown feature example:
......@@ -1340,6 +1386,19 @@ Drilldown feature example:
- `levels`: Array of objects that provides layer info
- `layerName`: Can be a string or function. Function takes argument as `properties` of parentLayer feature
- `layerOptions`: Same as layer options in `layers` option. If `url` is function, `url` takes argument as `properties` of parentLayer feature
- `legend`: configuration of legend to be added to layer. It requires [d3-legend](https://cdnjs.com/libraries/d3-legend). This creates a `<div class="map-legend">`.
- `position`: can be `topright`, `topleft`, `bottomleft` or `bottomright`(Defaults to `bottomright`)
- `format`: accepts d3 formats and applies to legend labels. (Defaults to `d`)
- `shape`: can be a d3 symbol or an svg path. Default `d3.symbolSquare`
- `size`: size of legend cell
- `cells`: number of cells in legend. Default `5`
- `width`: width of legend
- `height`: height of legend
- `scale`: accepts d3 scale format (mandatory). For examples, refer [d3-legend](https://d3-legend.susielu.com/#color-examples)
- `orient`: orientation of legend. Can be `vertical` (Default) or `horizontal`
- `shapeWidth`: width of legend cell. Default `20`
- `shapePadding`: padding of legend cell. Default `20`
- `labelOffset`: value to determine distance of label from each legend cell. Default `20`
### g1.mapviewer methods
`fitToLayer(layerName, options)`
......
......@@ -66,7 +66,59 @@ export var MapViewer = class MapViewer {
self.current_level = 0
self.drilldown_stack = []
}
if (self.options.legend) {
self.legend(self.options.legend).addTo(self.map)
}
}
}
MapViewer.prototype.addLegend = function(options) {
var config = {
position: 'bottomright',
height: 200,
width: 200,
shapeWidth: 20,
shapePadding: 20,
labelOffset: 20,
format: 'd',
shape: d3.symbolSquare,
orient: 'vertical',
size: 150,
cells: 5
}
options = Object.assign(config, options)
var map_legend = L.control({position: options.position})
map_legend.onAdd = function (map) {
var div = document.createElement('div')
var svg = d3.select(div)
.attr('class', 'map-legend')
.append('svg')
.attr('height', options.height)
.attr('width', options.width)
var customLegend = d3.legendColor()
.shapeWidth(options.shapeWidth)
.shapePadding(options.shapePadding)
.labelOffset(options.labelOffset)
.labelFormat(options.format)
.cells(options.cells)
.orient(options.orient)
.scale(options.scale)
if (options.shape) {
customLegend.shape("path", d3.symbol().type(options.shape).size(options.size)())
}
svg.append("g")
.attr("class", "legend")
.attr('transform', 'translate(10,10)')
.call(customLegend)
return div
}
return map_legend
}
/*
* @method cacheData(<String> datasetName, 'String' URL || <Object> data): <Object> data
......@@ -299,6 +351,11 @@ MapViewer.prototype._choropleth = function (layerName, layerConfig, filter) {
sublayer.setStyle(style)
})
if (layerConfig.legend) {
if (self.mapDiv.querySelector(".map-legend"))
self.mapDiv.querySelector(".map-legend").remove()
self.addLegend(layerConfig.legend).addTo(self.map)
}
}
/*
* @method _calculateMinMax(layer, <function> metricFormula ): <Array>
......@@ -323,7 +380,7 @@ MapViewer.prototype._calculateMinMax = function (layer, metricFormula) {
MapViewer.prototype.drilldown = function (drilldown) {
var self = this
self.on('layersloaded', function() {
if(self.options.drilldown) {
if (self.options.drilldown) {
self.drilldown_recursive(self.options.drilldown.rootLayer)
}
}, {
......@@ -370,7 +427,7 @@ MapViewer.prototype.drilldown_recursive = function (currentLayer) {
self.map.removeLayer(self.gLayers[currentLayer])
self.current_level += 1
self.on(nextLayer.layerName + 'loaded', function() {
if(nextLayer.layerOptions.attrs && nextLayer.layerOptions.attrs.tooltip)
if (nextLayer.layerOptions.attrs && nextLayer.layerOptions.attrs.tooltip)
self.renderTooltip(nextLayer.layerName, nextLayer.layerOptions)
// attach drilldown events for sublayers
self.drilldown_recursive(nextLayer.layerName)
......
......@@ -13,6 +13,7 @@
<script src="../node_modules/topojson/dist/topojson.min.js"></script>
<script src="../node_modules/d3/build/d3.js"></script>
<script src="../node_modules/d3-scale-chromatic/dist/d3-scale-chromatic.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.6/d3-legend.min.js"></script>
<script src="../dist/mapviewer.min.js"></script>
<style>
.map {
......
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MapViewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="tape.js"></script>
<link rel="stylesheet" href="../node_modules/leaflet/dist/leaflet.css">
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../node_modules/leaflet/dist/leaflet.js"></script>
<script src="../node_modules/d3/build/d3.js"></script>
<script src="../node_modules/d3-scale-chromatic/dist/d3-scale-chromatic.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.6/d3-legend.min.js"></script>
<script src="../dist/mapviewer.min.js"></script>
<style>
.map {
height: 300px;
}
</style>
</head>
<body>
<script>
tape.onFinish(function () { window.renderComplete = true })
</script>
<div id="ml-1" class="map"></div>
<div id="ml-2" class="map"></div>
<script>
var choro_map = g1.mapviewer({
id: 'ml-1',
layers: {
worldMap2: { type: 'tile', url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' },
indiaGeojson: {
type: 'geojson',
url: 'india-states.geojson',
link: {
url: 'state_score.json',
dataKey: 'name',
mapKey: 'ST_NM'
},
options: {
style: {
fillColor: '#ccc',
fillOpacity: 1
}
},
attrs: {
fillColor: {
metric: 'score',
scale: 'linear',
domain: [10, 20, 30],
range: ['red', 'yellow', 'green'],
}
},
legend : {
position: 'topright',
format: 'd',
shape: d3.symbolCircle,
size: 100,
scale: d3.scaleLinear().domain([10, 20, 30]).range(['red', 'yellow', 'green']),
orient: 'horizontal',
width: 300,
height: 100
}
}
}
})
tape("legend is rendered in map container with given options first map #ml-1", function (test) {
choro_map
.on('indiaGeojsonloaded', function () {
test.equals(document.querySelector('#ml-1').contains(document.querySelector('.map-legend')), true)
test.equals(document.querySelector('.map-legend svg').clientHeight, 100)
test.equals(document.querySelector('.leaflet-top.leaflet-right').contains(document.querySelector('.map-legend')), true)
test.end()
})
})
tape("legend is rendered in map container with given options second map #ml-2", function (test) {
var choro_map_second = g1.mapviewer({
id: 'ml-2',
layers: {
worldMap2: { type: 'tile', url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' },
geoIndia: {
type: 'geojson',
url: 'india-states.geojson',
link: {
url: 'state_score.json',
dataKey: 'name',
mapKey: 'ST_NM'
},
options: {
style: {
fillColor: '#ccc',
fillOpacity: 1
}
},
attrs: {
fillColor: {
metric: 'score',
scale: 'linear',
domain: [10, 20, 30],
range: ['red', 'yellow', 'green'],
}
},
legend: {
position: 'topright',
format: 'd',
shape: d3.symbolTriangle,
size: 100,
cells: 3,
scale: d3.scaleLinear().domain([10, 20, 30]).range(['red', 'yellow', 'green']),
orient: 'vertical',
width: 150,
height: 600
}
}
}
})
choro_map_second
.on('geoIndialoaded', function () {
test.notEquals(document.querySelector('#ml-2').querySelector('.map-legend'), null)
// TEST: options of one map legend do not bleed into another
test.equals(document.querySelector('#ml-2').querySelector('.map-legend svg').clientHeight, 600)
test.equals(document.querySelector('.leaflet-top.leaflet-right').contains(document.querySelector('.map-legend')), true)
// TEST: removing a map legend from one map does not remove other map legends
document.querySelector('#ml-2').querySelector('.map-legend').remove()
test.equals(document.querySelector('#ml-2').querySelector('.map-legend'), null)
test.notEquals(document.querySelector('#ml-1').querySelector('.map-legend'), null)
test.end()
})
})
</script>
</body>
</html>
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