简体   繁体   中英

DataMaps / hide arcs by identifier

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 same id but different origin and or destination 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> 

Well I have tried to solve your problem. What I have did is, I have assigned unique ids to each arc of data-map. It will easily give you access to independent arcs and you can change their transitions accordingly.

For demo purpose I have delayed it randomly and it works properly. I have delayed first arc by 1000 ms second arc by 2000 ms and third arc by 3000 ms. You can implement your own algorithm to delay the arcs' transitions as you wish. I have added comments in the code which you can refer.

As setInterval is running after every 4000 ms, if delay of any arc is more than 4000 ms then you will be able to see all the arcs generating at the same time only once which is the first time. After that generation of arcs will be very random, so please just keep it in mind.

 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", } }); //hide arc function which will take x amount of delay and arc-id which you want to delay. function hideArc(delay, arcId) { d3.select('#' + arcId).transition().duration(delay).style("opacity", 0); d3.select('#' + arcId).transition().delay(delay).remove(); } //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 let arcIds = [];// it will hold all the unique arc-ids d3.selectAll('path.datamaps-arc')[0].forEach((ele, index) => { ele.setAttribute('id', 'datamap-arc-' + index); arcIds.push('datamap-arc-' + index);// pushing new generated ids to arcIds array }); //mapping of delay and arc-id, this part is replaceable, you can change it the way you want to change the delay of respective arc. let arcIdAndDelaymapping = arcIds.map((aercId, index) => { return { aercId, delay:1000*(index+1) } }) //Remove all arcs [should be replaced by a function that asynchronously hides single arcs after x seconds] //calling hideArc function with their respective delays. arcIdAndDelaymapping.forEach((arcMapping) => { hideArc(arcMapping.delay, arcMapping.aercId); }) }, 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> 

Hopefully it will solve your problem. Happy Coding!! And thanks for making me explore data-maps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM