双击欧洲数据可视化

Double clicking Europe data visualization

我想创建一个可以缩放的欧洲等值线图。 我还想看到当用户双击一个国家时,这个国家被放大并分成多个区域(NUTS 2),每个区域根据第二个度量着色。

举个例子:

假设欧洲由5个国家组成:Country1, ..., Country5。 每个国家根据第一个指标(假设居民人数)着色。 当用户双击 Country4 时,地图会缩放,这样 Country4 就会位于屏幕的中央并可以完整查看。相邻国家可能被裁剪和模糊。

Country4 现在显示为由其区域(R1、...、R6)组成。这些地区根据第二个衡量标准(假设人均收入)着色。 在第二种情况下,我希望未选择的国家(因此国家 1、2、3 和 5)仍根据措施 1 着色。

所以我想要一些东西 like this 但可以双击并更详细地查看每个国家/地区。

我怎样才能做这样的事情? 我还没有找到对我有用的示例。

我发现 these json files and this one 我认为它们很有用(但我不知道如何使用它们)。

谢谢


我发现 this file representing the nuts2 (regions) and this 代表 nuts0(国家/地区)。

如何合并两者?思路是从nuts2.json开始,加上nuts0.json的信息,但是geometriesarcs怎么办呢?我不想造成不一致..

这是一个非常广泛的问题,包含几个问题和目标(地图缩放、等值线创建、两层地图),因此,任何答案都将是广泛的——但不一定没有帮助。我的回答不会解决问题中的所有问题,但应该有助于创建您预期的最终愿景。我将重点关注看似关键的问题:

I would also like to see that when the user double-clicks on a country, the country in question is zoomed and divided into regions, each of which is colored according to a second measure.

虽然你说"also"这表明这是次要的,但你的图片和标题似乎更关心两层效应,并且有很多等值线的例子和问题,但在交互式两层地图上很少。

我认为关键的挑战是细分区域,为此您需要在 parent 和 child 区域之间使用某种通用标识符。如果需要,您可以在您的 geojson 或 topojson 中添加必要的标识符。理想情况下,您的 geojson 可能如下所示:

Parent/Country:

{ 
    "type":"Feature",
    "properties"{ "country":NAME ... },
    "geometry": { ... }
} 

Child/Region:

{ 
    "type":"Feature",
    "properties"{ "country":NAME, "regionName": NAME ... },
    "geometry": { ... }
} 

当点击一个国家(或任何其他事件,如双击)时,您想根据共享标识符绘制 children 区域:

country.on("click", function(d) {
  // remove other regions
  d3.selectAll(".region").remove();

  // filter out relevant regions of a geojson of all regions 
  var countryRegions = geojson.features.filter(function(region) { 
    return region.properties.country == d.properties.country;
  })
  // append the regions
  svg.selectAll(".region")
   .data(countryRegions)
   .enter()
   .append()
   .attr("class",".region")
})

如果您有标准化命名的 geojson 文件,您可以使用文件名作为共享 属性,按照以下方式做一些事情:

country.on("click", function(d) {
  // remove other regions
  d3.selectAll(".region").remove();

  // get appropriate geojson:
  d3.json(d.properties.country+".json", function(error, regions) {

     // draw region features

  }) 

})

您可以通过在 click/other 事件中添加诸如 country.style("opacity",0.4) 之类的内容来提高 click/other 事件中国家/地区的透明度,模糊会增加一些复杂性。两者都应该增强两层效应。在处理国家时不需要裁剪 - 国家很少重叠,并且在任何情况下,新特征通常绘制在旧特征之上(这消除了由于坐标不精确导致的任何视觉重叠)。

这就是两层效果的全部内容,使用相同的原理,您可以轻松创建三层地图,用子区域填充选定区域。


在此基础上,我将简要介绍一下缩放:

使用包含该区域的 geojson/topojson,然后您可以更改投影以反映要素的范围 - 这样您就可以缩放到这些要素:

projection.fitSize([width,height],geojsonObject);

请注意,如果过滤特征数组,fitSize of fitExtent 将不起作用,除非您将特征放在特征中 collection(两者都需要 v4):

var featureCollection = {type:"FeatureCollection",features:features};

要实现平滑缩放,您需要使用 transition.attrTween 转换投影。这有点棘手,因为您需要对投影平移和投影比例进行插值(并且取决于地图投影和类型,可能还有旋转)。

或者,您可以通过操纵 svg 来缩放,有很多关于如何实现这种效果的示例和问题(我在下面的示例中使用了其他方法)。

以上将让您:缩放到区域、绘制相关区域以及在 regions/views.

之间转换

我制作了一个简单的通用示例,使用虚拟地理数据,即 operational here,(使用单击事件),关键部分在下面有更多注释(除了转换功能,请参阅示例以查看它)。此示例需要大量调整才能匹配您的预期数据和可视化效果。

我使用了一些未在下面的代码中声明的变量(请参阅 example 了解完整代码),但它们大多是标准的:geo​​Path (path)、geoProjection (projection)、宽度、高度等,还有 baseProjection,这是起始投影。我也在使用虚拟数据,因此我使用 d3.geoIdentity 而不是更标准的投影 .

// get the parent geojson
d3.json("geojson.json", function(error, geojson) {
  if (error) throw error;

  // get the regions:
  d3.json("geojsonSubdivisions.json", function(error, subdivisions) {
    if (error) throw error;

  // a color scale for the countries:
  var color = d3.scaleLinear().range(["steelblue","darkblue"]).domain([0,4]);
  // a color scale for the regions:
  var subdivisionColor = ["lightsalmon","salmon","coral"];

  // refine the two projections, one for the default/base, and one for the current      
  baseProjection.fitSize([width,height],geojson);
  projection.fitSize([width,height],geojson);

  // append the countries:
  svg.append("g")
    .attr("class", "topLevel")
    .selectAll("path")
    .data(geojson.features)
    .enter()
    .append("path")
    .attr("fill",function(d,i) { return color(i); })
    .attr("opacity",0.7)
    .attr("d", path)
    .style("stroke","black")
    .style("stroke-width",0)
    // style on mouseover:
    .on("mouseover", function() {
        d3.select(this)
          .style("stroke-width",15)
          .raise();
    })
    // undo mouseover styles:
    .on("mouseout", function(d,i) {
        d3.select(this)
          .style("stroke-width", 0 );
    })
    // now zoom in when clicked and show subdivisions:
    .on("click", function(d) {
        // remove all other subdivisions:
        d3.selectAll(".subdivision")
          .remove();

        // get new features:
        var features = subdivisions.features.filter(function(feature) { return feature.id == d.id });

        // draw new features
        svg.selectAll(null)
          .data(features)
          .enter()
          .append("path")
          .attr("class","subdivision")
          .attr("fill", function(d,i) { return subdivisionColor[i] })
          .attr("d", path)
          .style("stroke","black")
          .style("stroke-width",0)
          .on("click", function() {
            zoom(projection,baseProjection); // zoom out when clicked
            d3.selectAll(".subdivision")     
              .remove();    // remove regions when clicked
          })
          // style on mouseover
          .on("mouseover", function() {
                d3.select(this)
                  .style("stroke-width",5)
                  .raise(); // raise it so stroke is not under anything
          })
          // undo style changes on mouseout
          .on("mouseout", function(d,i) {
                d3.select(this)
                  .style("stroke-width", 0 );
          })
          .raise()

        // make a feature collection of the regions:
        var featureCollection = { "type":"FeatureCollection", "features": features }

        // zoom to the selected area:
           // if current projection is default projection:
        if ( projection.translate().toString() === baseProjection.translate().toString() && projection.scale() === baseProjection.scale() ) {
            zoom(baseProjection,projection.fitExtent([[50,50],[width-50,height-50]],featureCollection));
        }
        // current projection != default, zoom out and then in:
        else {
            // provide an end projection point for the transition:
            var endProjection = d3.geoIdentity()
              .reflectY(true)
              .fitExtent([[50,50],[width-50,height-50]],featureCollection)

            zoom(projection,endProjection,baseProjection);
        }
    }); 
  });
});