【问题标题】:Trying to add labels to d3, can not figure out text labels试图将标签添加到 d3,无法找出文本标签
【发布时间】:2021-01-05 03:03:57
【问题描述】:

我正在尝试让我的地图看起来像这样

不幸的是,我的代码看起来是这样的,我不明白为什么我的文本节点如此庞大而不是我想要的样子

这是我要去的代码或检查我的fiddle

这段代码似乎不会产生人类可读的标签

grp
  .append("text")
  .attr("fill", "#000")
  .style("text-anchor", "middle")
  .attr("font-family", "Verdana")
  .attr("x", 0)
  .attr("y", 0)
  .attr("font-size", "10")
  .text(function (d, i) {
    return name;
  });

这是我的完整代码:

     var width = 500,
            height = 275,
            centered;

          var projection = d3.geo
            .conicConformal()
            .rotate([103, 0])
            .center([0, 63])
            .parallels([49, 77])
            .scale(500)
            .translate([width / 2.5, height / 2])
            .precision(0.1);

          var path = d3.geo.path().projection(projection);

          var svg = d3
            .select("#map-selector-app")
            .append("svg")
            .attr("viewBox", `0 0 ${width} ${height}`);
          // .attr("width", width)
          // .attr("height", height);

          svg
            .append("rect")
            .attr("class", "background-svg-map")
            .attr("width", width)
            .attr("height", height)
            .on("click", clicked);

          var g = svg.append("g");
          var json = null;
          var subregions = {
            Western: { centroid: null },
            Prairies: { centroid: null },
            "Northern Territories": { centroid: null },
            Ontario: { centroid: null },
            Québec: { centroid: null },
            Atlantic: { centroid: null },
          };

          d3.json(
            "https://gist.githubusercontent.com/KatFishSnake/7f3dc88b0a2fa0e8c806111f983dfa60/raw/7fff9e40932feb6c0181b8f3f983edbdc80bf748/canadaprovtopo.json",
            function (error, canada) {
              if (error) throw error;

              json = topojson.feature(canada, canada.objects.canadaprov);
              g.append("g")
                .attr("id", "provinces")
                .selectAll("path")
                .data(json.features)
                .enter()
                .append("path")
                .attr("d", path)
                .on("click", clicked);

              g.append("g")
                .attr("id", "province-borders")
                .append("path")
                .datum(
                  topojson.mesh(canada, canada.objects.canadaprov, function (
                    a,
                    b
                  ) {
                    return a !== b;
                  })
                )
                .attr("id", "province-borders-path")
                .attr("d", path);

              // g.select("g")
              //   .selectAll("path")
              //   .each(function (d, i) {
              //     var centroid = path.centroid(d);
              //   });

              Object.keys(subregions).forEach((rkey) => {
                var p = "";
                json.features.forEach(function (f, i) {
                  if (f.properties.subregion === rkey) {
                    p += path(f);
                  }
                });
                var tmp = svg.append("path").attr("d", p);
                subregions[rkey].centroid = getCentroid(tmp.node());
                subregions[rkey].name = rkey;
                tmp.remove();
              });

              Object.values(subregions).forEach(({ centroid, name }) => {
                var w = 80;
                var h = 30;

                var grp = g
                  .append("svg")
                  // .attr("width", w)
                  // .attr("height", h)
                  .attr("viewBox", `0 0 ${w} ${h}`)
                  .attr("x", centroid[0] - w / 2)
                  .attr("y", centroid[1] - h / 2);

                // grp
                //   .append("rect")
                //   .attr("width", 80)
                //   .attr("height", 27)
                //   .attr("rx", 10)
                //   .attr("fill", "rgb(230, 230, 230)")
                //   .attr("stroke-width", "2")
                //   .attr("stroke", "#FFF");

                grp
                  .append("text")
                  .attr("fill", "#000")
                  .style("text-anchor", "middle")
                  .attr("font-family", "Verdana")
                  .attr("x", 0)
                  .attr("y", 0)
                  .attr("font-size", "10")
                  .text(function (d, i) {
                    return name;
                  });

                // var group = g.append("g");
                // group
                //   .append("rect")
                //   .attr("x", centroid[0] - w / 2)
                //   .attr("y", centroid[1] - h / 2)
                //   .attr("width", 80)
                //   .attr("height", 27)
                //   .attr("rx", 10)
                //   .attr("fill", "rgb(230, 230, 230)")
                //   .attr("stroke-width", "2")
                //   .attr("stroke", "#FFF");
                // group
                //   .append("text")
                //   .attr("x", centroid[0] - w / 2)
                //   .attr("y", centroid[1] - h / 2)
                //   .text(function (d, i) {
                //     return name;
                //   });
              });

              // g.append("button")
              //   .attr("class", "wrap")
              //   .text((d) => d.properties.name);
            }
          );

          function getCentroid(element) {
            var bbox = element.getBBox();
            return [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
          }

          function clicked(d) {
            var x, y, k;

            if (d && centered !== d) {
              // CENTROIDS for subregion provinces
              var p = "";
              json.features.forEach(function (f, i) {
                if (f.properties.subregion === d.properties.subregion) {
                  p += path(f);
                }
              });
              var tmp = svg.append("path");
              tmp.attr("d", p);
              var centroid = getCentroid(tmp.node());
              tmp.remove();

              // var centroid = path.centroid(p);
              x = centroid[0];
              y = centroid[1];
              k = 2;

              if (d.properties.subregion === "Northern Territories") {
                k = 1.5;
              }

              centered = d;
            } else {
              x = width / 2;
              y = height / 2;
              k = 1;
              centered = null;
            }

            g.selectAll("path").classed(
              "active",
              centered &&
                function (d) {
                  return (
                    d.properties &&
                    d.properties.subregion === centered.properties.subregion
                  );
                  // return d === centered;
                }
            );

            g.transition()
              .duration(650)
              .attr(
                "transform",
                "translate(" +
                  width / 2 +
                  "," +
                  height / 2 +
                  ")scale(" +
                  k +
                  ")translate(" +
                  -x +
                  "," +
                  -y +
                  ")"
              )
              .style("stroke-width", 1.5 / k + "px");
          }
          .background-svg-map {
            fill: none;
            pointer-events: all;
          }

          #provinces {
            fill: rgb(230, 230, 230);
          }

          #provinces > path:hover {
            fill: #0630a6;
          }

          #provinces .active {
            fill: #0630a6;
          }

          #province-borders-path {
            fill: none;
            stroke: #fff;
            stroke-width: 1.5px;
            stroke-linejoin: round;
            stroke-linecap: round;
            pointer-events: none;
          }
        <script src="https://d3js.org/d3.v3.min.js"></script>
        <script src="https://d3js.org/topojson.v1.min.js"></script>
        <div id="map-selector-app"></div>

【问题讨论】:

    标签: javascript svg d3.js maps


    【解决方案1】:

    带有背景的标签的基本工作流程是:

    • 定位父母g
    • 添加文字
    • 添加矩形并根据父g的边界框设置其大小,然后将其移动到文本后面。

    对于 d3v3,我实际上将在文本之前添加矩形,但在添加文本并且知道所需的大小之前不会对其进行样式设置。如果我们在它之后附加它,它将在文本的前面。在 d3v4 中有一个方便的 .lower() 方法可以为我们向后移动它,在 d3v3 中有一些方法可以做到这一点,但为了简单起见,为了确保矩形在文本后面,我会先添加它

    在我的示例中,我将在更基础的层面偏离您的代码。我不会附加子 SVG,因为这会为您引入一些尺寸问题。此外,我将使用 selectAll()/enter() 循环,而不是使用循环来附加标签。这意味着我最终需要一个数据数组而不是一个对象。不过,为了帮助构建该数组,我将使用一个对象 - 通过浏览您的 json 一次,我们可以构建一个区域列表并为每个区域创建一个 geojson 功能。 geojson 特性很好,因为它允许我们使用 path.centroid() ,它允许我们找到一个特征的质心而无需额外的代码。

    首先,我需要创建数据数组:

      var subregions = {};
      json.features.forEach(function(feature) {
         var subregion = feature.properties.subregion;
         // Have we already encountered this subregion? If not, add it.
         if(!(subregion in subregions)) {
            subregions[subregion] = {"type":"FeatureCollection", features: [] };
         }
         // For every feature, add it to the subregion featureCollection:
         subregions[subregion].features.push(feature);
      })
      // Convert to an array:
      subregions = Object.keys(subregions).map(function(key) {
        return { name: key, geojson: subregions[key] };
      })
    

    现在我们可以用标准的 d3 selectAll/enter 语句附加父 g

      // Create a parent g for each label:
      var subregionsParent  = g.selectAll(null)
        .data(subregions)
        .enter()
        .append("g")
        .attr("transform", function(d) {
           // position the parent, so we don't need to position each child based on geographic location:
           return "translate("+path.centroid(d.geojson)+")";
        })
    

    现在我们可以添加文本和矩形了:

          // add a rectangle to each parent `g`
          var boxes = subregionsParent.append("rect");
            
          // add text to each parent `g`
          subregionsParent.append("text")
            .text(function(d) { return d.name; })
            .attr("text-anchor","middle");
            
          // style the boxes based on the parent `g`'s bbox
          boxes
            .each(function() {
                var bbox = this.parentNode.getBBox();
                d3.select(this)
                  .attr("width", bbox.width + 10)
                  .attr("height", bbox.height +10)
                  .attr("x", bbox.x - 5)
                  .attr("y", bbox.y - 5)
                  .attr("rx", 10)
                  .attr("ry", 10)
                  .attr("fill","#ccc")
            })
    

    您可以看到,考虑到地图上的一些重叠,当涉及到放置时,centroid 方法(无论是使用现有函数还是 path.centroid())可能有点愚蠢。您可以通过多种方式对其进行修改 - 可能会在数据中添加偏移量,或者在添加文本时手动例外。虽然在较大的 SVG 上不应该有重叠。众所周知,注释很难做到。

    这是我上面的结果:

    还有一个用于演示的 sn-p(我已经删除了大量不必要的代码来制作一个更简单的示例,但它应该保留您示例的功能):

    var width = 500,
      height = 275, 
      centered;
    
    var projection = d3.geo.conicConformal()
      .rotate([103, 0])
      .center([0, 63])
      .parallels([49, 77])
      .scale(500)
      .translate([width / 2.5, height / 2])
      
    var path = d3.geo.path().projection(projection);
    
    var svg = d3
      .select("#map-selector-app")
      .append("svg")
      .attr("viewBox", `0 0 ${width} ${height}`);
    
    var g = svg.append("g");
    
    d3.json("https://gist.githubusercontent.com/KatFishSnake/7f3dc88b0a2fa0e8c806111f983dfa60/raw/7fff9e40932feb6c0181b8f3f983edbdc80bf748/canadaprovtopo.json",
                function (error, canada) {
                  if (error) throw error;
    
        json = topojson.feature(canada, canada.objects.canadaprov);
        var provinces = g.append("g")
         .attr("id", "provinces")
         .selectAll("path")
         .data(json.features)
         .enter()
         .append("path")
         .attr("d", path)
         .on("click", clicked);
         
        g.append("g")
          .attr("id", "province-borders")
          .append("path")
          .datum(topojson.mesh(canada, canada.objects.canadaprov, function (a,b) { return a !== b; }))
          .attr("id", "province-borders-path")
          .attr("d", path);
          
        // Add labels:
        // Get the data:
          var subregions = {};
          json.features.forEach(function(feature) {
             var subregion = feature.properties.subregion;
             
             if(!(subregion in subregions)) {
                subregions[subregion] = {"type":"FeatureCollection", features: [] };
             }
             subregions[subregion].features.push(feature);
          })
          // Convert to an array:
          subregions = Object.keys(subregions).map(function(key) {
            return { name: key, geojson: subregions[key] };
          })
          
          // Create a parent g for each label:
          var subregionsParent  = g.selectAll(null)
            .data(subregions)
            .enter()
            .append("g")
            .attr("transform", function(d) {
               return "translate("+path.centroid(d.geojson)+")";
            })
            
          var boxes = subregionsParent.append("rect");
            
          subregionsParent.append("text")
            .text(function(d) { return d.name; })
            .attr("text-anchor","middle");
            
          boxes
            .each(function() {
                var bbox = this.parentNode.getBBox();
                d3.select(this)
                  .attr("width", bbox.width + 10)
                  .attr("height", bbox.height +10)
                  .attr("x", bbox.x - 5)
                  .attr("y", bbox.y - 5)
                  .attr("rx", 10)
                  .attr("ry", 10)
                  .attr("fill","#ccc")
            })
            // End labels.
    
    
          
              function getCentroid(element) {
                var bbox = element.getBBox();
                return [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
              }
    
              function clicked(d) {
                var x, y, k;
    
                if (d && centered !== d) {
                  // CENTROIDS for subregion provinces
                  var p = "";
                  json.features.forEach(function (f, i) {
                    if (f.properties.subregion === d.properties.subregion) {
                      p += path(f);
                    }
                  });
                  var tmp = svg.append("path");
                  tmp.attr("d", p);
                  var centroid = getCentroid(tmp.node());
                  tmp.remove();
    
                  x = centroid[0];
                  y = centroid[1];
                  k = 2;
    
                  if (d.properties.subregion === "Northern Territories") {
                    k = 1.5;
                  }
    
                  centered = d;
                } else {
                  x = width / 2;
                  y = height / 2;
                  k = 1;
                  centered = null;
                }
    
                g.selectAll("path").classed(
                  "active",
                  centered &&
                    function (d) {
                      return (
                        d.properties &&
                        d.properties.subregion === centered.properties.subregion
                      );
                    }
                );
    
                g.transition()
                  .duration(650)
                  .attr("transform","translate(" + width / 2 + "," + height / 2 +")scale(" +            
                      k +")translate(" + -x + "," + -y + ")"
                  )
                  .style("stroke-width", 1.5 / k + "px");
              }
        })
    .background-svg-map {
                fill: none;
                pointer-events: all;
              }
    
              #provinces {
                fill: rgb(230, 230, 230);
              }
    
              #provinces > path:hover {
                fill: #0630a6;
              }
    
              #provinces .active {
                fill: #0630a6;
              }
    
              #province-borders-path {
                fill: none;
                stroke: #fff;
                stroke-width: 1.5px;
                stroke-linejoin: round;
                stroke-linecap: round;
                pointer-events: none;
              }
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <script src="https://d3js.org/topojson.v1.min.js"></script>
    <div id="map-selector-app"></div>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-28
      • 2012-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多