基于多个属性d3来选择数据



我有一个用d3和svg表示的网格。我正在尝试选择网格上任何特定瓦片的相邻(相邻)瓦片。瓦片是通过它们在网格上的x和y坐标来访问的。我所拥有的感觉相当混乱,并没有完全按照我的意愿进行,我不希望点击的瓷砖被选中,或者瓷砖与之成对角线。

var w = 960,
    h = 500,
    z = 20,
    x = w / z,
    y = h / z;
var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h);
svg.selectAll("rect")
    .data(d3.range(x * y))
  .enter().append("rect")
    .attr("width", z)
    .attr("height", z)
    .attr("clicked", false)
    .attr('x', horizontalpos)
    .attr('y', verticalpos)
    .on("click", test)
    .style("stroke", "rgb(6,120,155)")
    .style("stroke-width", 2)
    .style("fill", "rgb(255, 255, 255)")
function translate(d) {
  return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}

function verticalpos(d) {
  return ((d % x) * z);
}
function horizontalpos(d) {
  return (Math.floor(d / x) * z );
 }
function test(){
  var d = d3.selectAll("[x='40']").filter("[y='40']");
  d3.selectAll("[x=" + "'"+ (parseInt(d.attr("x")) +20).toString() +"'" +"],[x=" + "'"+ (parseInt(d.attr("x")) -20).toString() +"'" +"],"+ "[x=" + "'"+ (parseInt(d.attr("x"))).toString() +"'" +"]")
    .filter("[y=" + "'"+ (parseInt(d.attr("y"))).toString() +"'" +"],[y=" + "'"+ (parseInt(d.attr("y")) +20).toString() +"'" +"]"+",[y=" + "'"+ (parseInt(d.attr("y")) -20).toString() +"'" +"]")
    .transition()
    .style("fill", "black"); 
}

jsfiddle-https://jsfiddle.net/wkencq2w/15/

我想知道的是-有没有办法通过两个属性来选择数据,比如:

d3.select("[x='40'], [y='40']") 

这对我来说不起作用,但它背后的逻辑是我希望如何选择数据。

因为它是D3,所以我不会基于位置计算,而是基于数据绑定。这将大大简化事务,并降低代码的数量和复杂性。一种可能的方法是定义一个具有x和y属性的二维对象阵列,然后将其绑定到D3选择:

var grid = d3.range(y).map(function(dy) {
  return d3.range(x).map(function(dx) {
    return {x: dx, y: dy};
  });
});
var g = svg.selectAll("g")
    .data(grid)
  .enter().append("g")                 // Group each row's rects in a svg:g
    .selectAll("rect")                 // Do a nested selection
    .data(function(d) { return d; })   // Bind the sub-array for this row

从这种方法中受益最多的部分是test()函数,它现在可以对绑定到每个rect的数据进行操作,而不必获取属性值并使用它们进行计算。

function test(d) {
  var clicked = d3.select(this).datum();   // Object bound to the rect.
  d3.selectAll("rect").filter(function(d) {
    // Do the filtering based on data rather than on positions.
    return d.x === clicked.x && Math.abs(d.y - clicked.y) === 1 ||
           d.y === clicked.y && Math.abs(d.x - clicked.x) === 1;
  })
  .transition()
  .style("fill", "black"); 
}

请查看此JSFiddle以获取完整的示例。

您可以通过两个属性来选择数据,只需将它们放在一起即可,例如[x='40'][y='40']。这与,css运算符一起允许生成一个css选择字符串,为您提供所需内容。

var w = 960,
    h = 500,
    z = 20,
    x = w / z,
    y = h / z;
var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h);
svg.selectAll("rect")
    .data(d3.range(x * y))
  .enter().append("rect")
    .attr("width", z)
    .attr("height", z)
    .attr("clicked", false)
    .attr('x', horizontalpos)
    .attr('y', verticalpos)
    .on("click", test)
    .style("stroke", "rgb(6,120,155)")
    .style("stroke-width", 2)
    .style("fill", "rgb(255, 255, 255)")
    
function translate(d) {
  return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}
function verticalpos(d) {
  return ((d % x) * z);
}
function horizontalpos(d) {
  return (Math.floor(d / x) * z );
 }
 
function test(d) {
  x = parseInt(d3.select(this).attr("x"));
  y = parseInt(d3.select(this).attr("y"));
  var selector = ""
  for (var dx=-20;dx<=20;dx+=20) {
  	for (var dy=-20;dy<=20;dy+=20) {
      selector += "[x='"+ (x + dx) +"'][y='"+ (y + dy) +"'],"
    }
  }
  // cut off the final extraneous comma
  selector = selector.substring(0, selector.length - 1);
  d3.selectAll(selector)
    .transition()
    .style("fill", "black"); 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

或者,如果你只是想要一个没有中心的十字架,就像你在问题中描述的那样,你可以这样做。。。

var w = 960,
    h = 500,
    z = 20,
    x = w / z,
    y = h / z;
var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h);
svg.selectAll("rect")
    .data(d3.range(x * y))
  .enter().append("rect")
    .attr("width", z)
    .attr("height", z)
    .attr("clicked", false)
    .attr('x', horizontalpos)
    .attr('y', verticalpos)
    .on("click", test)
    .style("stroke", "rgb(6,120,155)")
    .style("stroke-width", 2)
    .style("fill", "rgb(255, 255, 255)")
    
function translate(d) {
  return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}
function verticalpos(d) {
  return ((d % x) * z);
}
function horizontalpos(d) {
  return (Math.floor(d / x) * z );
 }
 
function test(d) {
  x = parseInt(d3.select(this).attr("x"));
  y = parseInt(d3.select(this).attr("y"));
  var selector = ""
  var deltas = [[-20, 0], [20, 0], [0, 20], [0, -20]];
  for (var i=0;i < deltas.length;i++) {
    selector += "[x='"+ (x + deltas[i][0]) +"'][y='"+ (y + deltas[i][1]) +"'],"
  }
  
  // cut off the final extraneous comma
  selector = selector.substring(0, selector.length - 1);
  d3.selectAll(selector)
    .transition()
    .style("fill", "black"); 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

我试图使用过滤器来解决这个问题

function test(d){
  var x = d3.select(this);
  var x1 = (parseInt(x.attr("x")) +20);
  var x2 = (parseInt(x.attr("x")) -20);
  var y1 = (parseInt(x.attr("y")) +20);
  var y2 = (parseInt(x.attr("y")) -20);
var f = d3.selectAll("rect")[0].filter(function(d){
  //left rect
    if (d3.select(d).attr("x") == x1 && d3.select(d).attr("y") == parseInt(x.attr("y")))
    return true;
  //right rect  
    if (d3.select(d).attr("x") == x2 && d3.select(d).attr("y") == parseInt(x.attr("y")))
    return true;
  //bottom rect  
    if (d3.select(d).attr("y") == y1 && d3.select(d).attr("x") == parseInt(x.attr("x")))
    return true;
  //top rect  
    if (d3.select(d).attr("y") == y2 && d3.select(d).attr("x") == parseInt(x.attr("x")))
    return true;
  return false;
});
//select all filtered and make their fill black
d3.selectAll(f).transition().delay(100).style("fill", "black");
}

此处的工作代码

希望这能有所帮助!

最新更新