3

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

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


我已经尝试过什么

API每 5 秒调用一次,并将响应数据推送到地图中。 (这将在未来被异步调用取代)

在下面的示例中,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,会在当前画弧时发生冲突,同时触发删除过程。

我想要的是

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

4

2 回答 2

2

通过DataMaps添加的所有弧实际上都由它们data的 JSON 格式 ( datamaps.js#L356 ) 键入:

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

请注意,DataMap用作功能如果 DataMaps 提供一种在此处使用自定义键功能的方法会很好,但是唉...JSON.stringify

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

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

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

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

{
  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';
});

请记住,DataMap使用它的整个data值来键入我们的每条弧线;这意味着具有相同id但不同origin和或destination值的两个数据将被视为两个不同的弧。

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

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>

于 2019-04-02T04:30:51.053 回答
1

好吧,我已尝试解决您的问题。我所做的是,我为数据映射的每个弧分配了唯一的 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>

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

于 2019-04-01T11:17:01.280 回答