d3.js onclick 和 touch 在 topojson 地图上工作不稳定

Posted

技术标签:

【中文标题】d3.js onclick 和 touch 在 topojson 地图上工作不稳定【英文标题】:d3.js onclick and touch works erratically on topojson map 【发布时间】:2020-06-09 09:58:57 【问题描述】:

我一直在玩 d3.js,我一直在尝试创建一个 d3 地图,其中单击州地图上的不同区/县会显示地图旁边的区/县的详细信息。

我最初使用mouseovermouseout 来显示相同​​的内容,但它不适合移动设备。所以现在我正在尝试对onclick 做同样的事情,但它的工作方式不同。

该地区应在点击时改变颜色(它与mouseover 一起使用)。然而,它只有在区域内多次重复随机点击后才会改变颜色。

这就是我所做的。

    var width = 345,
    height = 450;
  var projection = d3.geoMercator()
    .center([88.36, 27.58])
    .translate([width / 2, height / 2])
    .scale(6000);
  var path = d3.geoPath()
    .projection(projection);

  var svg = d3.select('#Sk_Map').append('svg')
    .attr('width', width)
    .attr('height', height);

  var g = svg.append('g');

  d3.json('https://raw.githubusercontent.com/shklnrj/IndiaStateTopojsonFiles/master/Sikkim.topojson')
    .then(state => 

      g.append('path')
        .datum(topojson.merge(state, state.objects.Sikkim.geometries))
        .attr('class', 'land')
        .attr('d', path);

      g.append('path')
        .datum(topojson.mesh(state, state.objects.Sikkim, (a, b) => a !== b))
        .attr('class', 'boundary')
        .attr('d', path);

      g.append("g")
        .selectAll("path")
        .data(topojson.feature(state, state.objects.Sikkim).features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("class","boundary")

        //.on("mouseover", function(d))

        .on("click", function(d) 
          var prop = d.properties;

          var string = "<p><strong>District Name</strong>: " + prop.Dist_Name;

          d3.select("#Place_Details")
            .html("")
            .append("text")
            .html(string);
       d3.select(this).attr("class","boundary hover");

        )

            //.on("mouseout"), function(d)

        .on("click", function(d) 
        d3.select("h2").text("");
        d3.select(this).attr("class","boundary")
              .attr("fill", "#ff1a75");
        );
    );
.columns 
    float: left;
    width: 50%;


/* Clear floats after the columns */
    .mapcontainer:after 
    content: "";
    display: table;
    clear: both;


svg 
      background: #ffffff;
    

    .land 
      fill: #ff1a75;
    

    .boundary 
      fill: none;
      stroke: #00ffff;
      stroke-linejoin: round;
      stroke-linecap: round;
      stroke-width: 1px;
      vector-effect: non-scaling-stroke;
    

h2 
        top: 50px;
        font-size: 1.6em;
    
    .hover 
        fill: yellow;
        
 <script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
    <script src="https://unpkg.com/topojson@3" charset="utf-8"></script>
    <div id="Sk_Map" style="width: 300px; float:left; height:450px; margin:5px"></div>
    <div id="Place_Details" style="width: 400px; float:right; height:450px; overflow: auto; margin:5px"></div>
  

如何优化此代码?我希望为地图添加缩放功能,但现在我想显示区/县的名称。

【问题讨论】:

【参考方案1】:

您的代码中存在一些问题。

目前您将两个事件侦听器分配给同一个选择:

  g.append("g")
    .selectAll("path")
    .data(topojson.feature(state, state.objects.Sikkim).features)
    .enter()
    .append("path")
    ...
    .on("click", function(d) 
       /* on click code here */
    )
    .on("click", function(d) 
      /* on click code here too */
    );

当像这样分配事件监听器时,第二个会覆盖第一个。所以你的 sn-p 中唯一使用的事件监听器是第二个:

    .on("click", function(d) 
       d3.select("h2").text("");
       d3.select(this).attr("class","boundary")
         .attr("fill", "#ff1a75");
    );

由于您没有 h2 元素(至少在 sn-p 中),因此什么也没有发生。

如果我们删除第二个事件侦听器并只使用第一个,我们仍然不会得到太多的点击事件。在下面,我删除了其他功能(那些没有点击事件的功能,我也删除了不相关的 css,为 sn-p 调整大小,并更改功能描边颜色)。应该清楚为什么单击事件不能很好地工作,功能没有填充。点击只触发边界上的事件:

var width = 345,
    height = 300;
var projection = d3.geoMercator()
  .center([88.36, 27.58])
  .translate([width / 2, height / 2])
  .scale(7000);
var path = d3.geoPath()
  .projection(projection);

var svg = d3.select('#Sk_Map').append('svg')
  .attr('width', width)
  .attr('height', height);

var g = svg.append('g');

d3.json('https://raw.githubusercontent.com/shklnrj/IndiaStateTopojsonFiles/master/Sikkim.topojson')
    .then(state => 

      g.append("g")
        .selectAll("path")
        .data(topojson.feature(state, state.objects.Sikkim).features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("class","boundary")
        .on("click", function(d) 
          alert("click!");
        )
);
    .boundary 
      fill: none;
      stroke: black;
      stroke-linejoin: round;
      stroke-linecap: round;
      stroke-width: 1px;
      vector-effect: non-scaling-stroke;
    
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
    <script src="https://unpkg.com/topojson@3" charset="utf-8"></script>
    <div id="Place_Details"></div>
    <div id="Sk_Map" style="width: 300px; float:left; height:200px; margin:5px"></div>

解决方案是填充特征。这给我们带来了一个优化:我们不需要绘制第一个特征:

  g.append('path')
    .datum(topojson.merge(state, state.objects.Sikkim.geometries))
    .attr('class', 'land')
    .attr('d', path);

因为它将完全被可点击的功能所覆盖。

另外,如果我们希望边界不可点击,我们应该绘制这个特征:

  g.append('path')
    .datum(topojson.mesh(state, state.objects.Sikkim, (a, b) => a !== b))
    .attr('class', 'boundary')
    .attr('d', path);

我们绘制了可点击的特征之后,让它被绘制在上面。 如果我们不关心边框是否可点击,我们可以跳过绘制此功能,因为我们可以只对可点击功能应用笔触。虽然内部边界可能会更厚/更暗。

以上修改如下:

var width = 345,
    height = 300;
var projection = d3.geoMercator()
  .center([88.36, 27.58])
  .translate([width / 2, height / 2])
  .scale(7000);
var path = d3.geoPath()
  .projection(projection);

var svg = d3.select('#Sk_Map').append('svg')
  .attr('width', width)
  .attr('height', height);

var g = svg.append('g');

d3.json('https://raw.githubusercontent.com/shklnrj/IndiaStateTopojsonFiles/master/Sikkim.topojson')
    .then(state => 

      g.append("g")
        .selectAll("path")
        .data(topojson.feature(state, state.objects.Sikkim).features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("class","feature")
        .on("click", function(d) 
             var prop = d.properties;
             var string = "<p><strong>District Name</strong>: " + prop.Dist_Name;
             d3.select("#Place_Details")
               .html(string)
        )
        
      g.append('path')
        .datum(topojson.mesh(state, state.objects.Sikkim, (a, b) => a !== b))
        .attr('class', 'boundary')
        .attr('d', path);        
        
);
.boundary 
   fill: none;
   stroke: #00ffff;
   stroke-linejoin: round;
   stroke-linecap: round;
   stroke-width: 1px;
   vector-effect: non-scaling-stroke;


.feature 
   fill: steelblue;



.hover 
   fill: yellow;
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
    <script src="https://unpkg.com/topojson@3" charset="utf-8"></script>
    <div id="Place_Details"></div>
    <div id="Sk_Map" style="width: 300px; float:left; height:200px; margin:5px"></div>

如果您对如何管理两个点击事件或一个功能上的交替点击事件有疑问,那应该是一个新问题。

【讨论】:

非常感谢。这样可行。我使用两次单击的原因是因为我想在单击时将区域着色为黄色,并在单击其他区域时将其重新绘制为旧颜色,同时将新单击的区域绘制为黄色。 在点击函数中添加了这个,点击事件现在改变颜色。 d3.selectAll('path').style('fill',null); d3.select(this).style("fill","yellow");

以上是关于d3.js onclick 和 touch 在 topojson 地图上工作不稳定的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 d3.js 读取属性错误

使d3.js与ie兼容

如何进行 d3.js 轴的本地化?

使 D3.js 2D 图表具有响应性

移动端JS父层Touch事件用了冒泡,子层onclick事件不生效

React 中touch和click事件冲突