I've created a world map, using DataMaps.
My aim is to asynchronously show and hide arcs on the map, based on some API data.
What have I already tried
I am calling my API
every 5 seconds and push the response data into the map.
(This will be replaced by asynchronous calls in the future)
In my example below, the arcData array
represents my API
response.
I am able to access the arcs via DOM manipulation
.
In my case I am using d3.selectAll('path.datamaps-arc').transition().duration(3500).style("opacity", 0);
to slowly fade out all arcs and delete them afterwards.
var arcData = //Test Data
[
{
origin:
{
latitude: 52.520008,
longitude: 13.404954
},
destination: {
latitude: 37.618889,
longitude: -122.375
}
},
{ origin:
{
latitude: 52.520008,
longitude: 13.404954
},
destination: {
latitude: 25.793333,
longitude:-80.290556
}
},
{
origin:
{
latitude: 52.520008,
longitude: 13.404954
},
destination: {
latitude: 35.877778,
longitude: -78.7875
}
}
];
$(document).ready(function() {
var map = new Datamap({ //create data map
element: document.getElementById('container'),
fills: {
defaultFill: "#343a40",
}
});
//call API every 4 seconds [Workaround for this fiddle]
setInterval(function() {
//add arcs to map
map.arc(arcData, {strokeWidth: 2, animationSpeed: 1000, strokeColor: '#b1dd00'}); // add arc Data
//Remove all arcs [should be replaced by a function that asynchronously hides single arcs after x seconds]
d3.selectAll('path.datamaps-arc').transition().duration(3500).style("opacity", 0);
d3.selectAll('path.datamaps-arc').transition().delay(3500).remove();
}, 4000);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js"></script>
<script src="https://datamaps.github.io/scripts/datamaps.world.min.js"></script>
<div id="container" style="position: relative; width: 500px; height: 300px;"></div>
This solution basically works but:
My Problem
All arcs are hided at the same time. If i asynchronously call the API in the future, there will be a conflict when the arc is currently drawing, and meanwhile the delete process is triggered.
What I want
A solution that I can access every arc by some identifier and separately delete them after they are fully drawed.
All arcs added via DataMaps are actually keyed by their data
in JSON format (datamaps.js#L356):
var arcs = layer.selectAll('path.datamaps-arc').data( data, JSON.stringify );
Notice that DataMaps use JSON.stringify
as key function. It would be nice if DataMaps provide a way to use custom key function here, but alas...
While these keys themselves are not persisted, it is enough to ensure us that there would only be one arc for one identical data. The arc data is the arc identifier itself.
Using these knowledge, we can identify an arc by comparing it's data:
var selectedArcs = d3.selectAll('path.datamaps-arc').filter(function(data) {
// compare data
return data === someValue;
});
To take it further, we can actually tweak the data we pass to DataMaps.arc
so that it will actually holds our compare-friendly identifier. The origin
and destination
field is mandatory, but we're free to use any other field to our liking.
{
id: 'some-unique-identifier',
origin: {
latitude: 52.520008,
longitude: 13.404954
},
destination: {
latitude: 37.618889,
longitude: -122.375
}
}
We could then use this tweaked field(s) for identifying our arc:
var selectedArcs = d3.selectAll('path.datamaps-arc').filter(function(data) {
return data.id === 'some-unique-identifier';
});
Keep in minds that DataMaps keyed each of our arc using its whole
data
value; meaning that two data with sameid
but differentorigin
and ordestination
value would be considered two different arc.
Here's a simple demonstration using a modified version of the original example:
var arcData = //Test Data
[
{
id: 123,
origin:
{
latitude: 52.520008,
longitude: 13.404954
},
destination: {
latitude: 37.618889,
longitude: -122.375
}
},
{
id: 'abc',
origin:
{
latitude: 52.520008,
longitude: 13.404954
},
destination: {
latitude: 25.793333,
longitude:-80.290556
}
},
{
id: 'xyz',
origin:
{
latitude: 52.520008,
longitude: 13.404954
},
destination: {
latitude: 35.877778,
longitude: -78.7875
}
}
];
$(document).ready(function() {
var map = new Datamap({ //create data map
element: document.getElementById('container'),
fills: {
defaultFill: "#343a40",
}
});
function drawMap() {
map.arc(arcData, {strokeWidth: 2, animationSpeed: 1000, strokeColor: '#b1dd00'});
};
function removeArc(id) {
var all = d3.selectAll('path.datamaps-arc');
var sel = all.filter(function(data) {
return data.id === id;
});
sel.transition().duration(1000).style("opacity", 0).remove();
};
$('button').on('click', function(){
var id = $(this).data('arc');
if (id) {
removeArc(id);
} else {
drawMap();
}
});
drawMap();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js"></script>
<script src="https://datamaps.github.io/scripts/datamaps.world.min.js"></script>
<button type="button" data-arc="123">Remove Arc id:123</button>
<button type="button" data-arc="abc">Remove Arc id:abc</button>
<button type="button" data-arc="xyz">Remove Arc id:xyz</button>
<button type="button">Redraw</button>
<div id="container" style="position: relative; width: 500px; height: 300px;"></div>