DataMaps / 按标识符隐藏弧

DataMaps / hide arcs by identifier

我创建了一张世界地图,使用 DataMaps

我的目标是根据一些 API 数据在地图上异步显示和隐藏弧。


我已经尝试过什么

我每 5 秒调用一次 API,并将响应数据推送到地图中。 (以后会被异步调用代替)

在我下面的示例中,arcData array 代表我的 API 响应。

我可以通过 DOM manipulation 访问弧线。 在我的例子中,我使用 d3.selectAll('path.datamaps-arc').transition().duration(3500).style("opacity", 0); 慢慢淡出所有弧线,然后删除它们。

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>


这个解决方案基本有效,但是:

我的问题

同时隐藏所有圆弧。 如果以后异步调用API,当前正在画圆弧时会发生冲突,同时触发删除过程。

我想要的

一个解决方案,我可以通过一些标识符访问每个弧,并在它们完全绘制后单独删除它们。

好吧,我已经尽力解决你的问题了。我所做的是,我为每个数据映射弧分配了唯一的 ID。它会让您轻松访问独立的弧线,并且您可以相应地更改它们的过渡。

出于演示目的,我随机延迟了它并且它可以正常工作。我已将第一弧延迟 1000 毫秒,第二弧延迟 2000 毫秒,第三弧延迟 3000 毫秒。您可以根据需要实施自己的算法来延迟弧的过渡。我在代码中添加了注释,您可以参考。

由于setInterval是运行每4000毫秒后,如果任何弧的延迟超过4000毫秒,那么你将只能看到所有弧同时产生一次,这是第一次.以后弧线的生成会很随机,大家牢记就好了。

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>

希望它能解决您的问题。快乐编码!感谢您让我探索数据地图。

通过 DataMaps are actually keyed by their data in JSON format (datamaps.js#L356 添加的所有弧线):

var arcs = layer.selectAll('path.datamaps-arc').data( data, JSON.stringify ); 

注意 DataMaps 使用 JSON.stringify 作为 关键函数 如果DataMaps在这里提供一种使用自定义键功能的方法就好了,但是唉...

虽然这些key本身并没有被持久化,但是足以保证我们对于一个相同的数据只会有一个arc。弧数据是弧标识符本身。

利用这些知识,我们可以通过比较数据来识别弧线:

var selectedArcs = d3.selectAll('path.datamaps-arc').filter(function(data) {
   // compare data
   return data === someValue;
});

更进一步,我们实际上可以调整我们传递给 DataMaps.arc 的数据,以便它实际上包含我们比较友好的标识符。 origindestination 字段是必填字段,但我们可以根据自己的喜好自由使用任何其他字段。

{
  id: 'some-unique-identifier',
  origin: {
    latitude: 52.520008,
    longitude: 13.404954
  },
  destination: {
    latitude: 37.618889,
    longitude: -122.375
  }
}

然后我们可以使用这个调整后的字段来识别我们的弧线:

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.

下面是使用原始示例的修改版本的简单演示:

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>