Data Join with Custom Key does not work as expected

自作多情 提交于 2019-12-24 14:04:15

问题


I am plotting some points using d3. I want to change the shape off all the points based on some condition. The join looks a bit like this:

var data=[{x:10,y:10}, {x:20, y:30}];
var shape = "rect";
...
var point = svg.selectAll(".point")
  .data(data, function(d, idx) { return "row_" + idx + "_shape_" + shape;})
;

The d3 enter() and exit() selections do not seem to reflect any changes caused by "shape" changing.

Fiddle is here: http://jsfiddle.net/schmoo2k/jcpctbty/


回答1:


You need to be aware that the key function is calculated on the selection with this as the SVG element and then on the data with the data array as this.

I think maybe this is what you are trying to do...

var data = [{
  x: 10,
  y: 10
}, {
  x: 20,
  y: 30
}];

var svg = d3.select("body").append("svg")
  .attr("width", 500)
  .attr("height", 500);

function update(data, shape) {
  var point = svg.selectAll(".point")
    .data(data, function(d, idx) {
      var key = "row_" + idx + "_shape_" + (Array.isArray(this) ? "Data: " + shape :
        d3.select(this).attr("shape"));
      alert(key);
      return key;
    });

  alert("enter selection size: " + point.enter().size());

  point.enter().append(shape)
    .attr("class", "point")
    .style("fill", "red")
    .attr("shape", shape);
  switch (shape) {
    case "rect":
      point.attr("x", function(d) {
          return d.x;
        })
        .attr("y", function(d) {
          return d.y;
        })
        .attr("width", 5)
        .attr("height", 5);
      break;
    case "circle":
      point.attr("cx", function(d) {
          return d.x;
        })
        .attr("cy", function(d) {
          return d.y;
        })
        .attr("r", 5);
      break;
  }
  point.exit().remove();
}

update(data, "rect");

setTimeout(function() {
  update(data, "circle");
}, 5000);
text {
  font: bold 48px monospace;
}
.enter {
  fill: green;
}
.update {
  fill: #333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js"></script>

Abstracted version

Just to tidy things up here is a more readable and idiomatic version (including fixing a problem with the text element)...

var data = [{
  x: 10,
  y: 10,
}, {
  x: 20,
  y: 30,
}];

var svg = d3.select("body").append("svg")
  .attr("width", 500)
  .attr("height", 500),
  marker = Marker();

function update(data, shape) {
  var point = svg.selectAll(".point")
    .data(data, key("shape", shape)),

    enter = point.enter().append("g")
    .attr("class", "point")
    .attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")"
    })
    .attr("shape", shape);

  enter.append(shape)
    .style("fill", "red")
    .attr(marker.width[shape], 5)
    .attr(marker.height[shape], 5);

  enter.append("text")
    .attr({
      "class": "title",
      dx: 10,
      "text-anchor": "start"
    })
    .text(shape);

  point.exit().remove();
}

update(data, "rect");

setTimeout(function() {
  update(data, "circle");
}, 2000);

function Marker() {
  return {
    width: {
      rect: "width",
      circle: "r"
    },
    height: {
      rect: "height",
      circle: "r"
    },
    shape: function(d) {
      return d.shape
    },
  };
}

function key(attr, value) {
  //join data and elements where value of attr is value
  function _phase(that) {
    return Array.isArray(that) ? "data" : "element";
  }

  function _Type(that) {
    return {
      data: value,
      get element() {
        return d3.select(that).attr(attr)
      }
    }
  }
  return function(d, i, j) {
    var _value = _Type(this)
    return i + "_" + _value[_phase(this)];
  };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Generalised, data-driven approach

var data = [{
  x: 10,
  y: 10,
}, {
  x: 20,
  y: 30,
}];

var svg = d3.select("body").append("svg")
  .attr("width", 500)
  .attr("height", 500),
  marker = Marker();

function update(data, shape) {
  //data-driven approach
  data.forEach(function(d, i) {
    d.shape = shape[i]
  });

  var log = [],
    point = svg.selectAll(".point")
    .data(data, key({
      shape: marker.shape,
      transform: marker.transform
    }, log)),

    //UPDATE
    update = point.classed("update", true),
    updateSize = update.size();
  update.selectAll("text").transition().duration(1000).style("fill", "#ccc");
  update.selectAll(".shape").transition().duration(1000).style("fill", "#ccc")

  //ENTER
  var enter = point.enter().append("g")
    .classed("point enter", true)
    .attr("transform", marker.dock)
    .attr("shape", marker.shape),

    //UPDATE+ENTER
    // ... not required on this occasion

    updateAndEnter = point.classed("update-enter", true);

  //EXIT
  var exit = point.exit().classed("exit", true);
  exit.selectAll("text").transition().duration(1000).style("fill", "red");
  exit.selectAll(".shape").transition().duration(1000).style("fill", "red");
  exit.transition().delay(1000.).duration(1000)
    .attr("transform", marker.dock)
    .remove();

  //ADJUSTMENTS
  enter.each(function(d) {
    //append the specified shape for each data element
    //wrap in each so that attr can be a function of the data
    d3.select(this).append(marker.shape(d))
      .style("fill", "green")
      .classed("shape", true)
      .attr(marker.width[marker.shape(d)], 5)
      .attr(marker.height[marker.shape(d)], 5)
  });

  enter.append("text")
    .attr({
      "class": "title",
      dx: 10,
      "text-anchor": "start"
    })
    .text(marker.shape)
    .style("fill", "green")
    .style("opacity", 1);

  enter.transition().delay(1000).duration(2000)
    .attr("transform", marker.transform);
}
data = generateData(40, 10)
update(data, data.map(function(d, i) {
  return ["rect", "circle"][Math.round(Math.random())]
}));

setInterval(function() {
  update(data, data.map(function(d, i) {
    return ["rect", "circle"][Math.round(Math.random())]
  }));
}, 5000);

function generateData(n, p) {
  var values = [];

  for (var i = 0; i < n; i++) {
    values.push({
      x: (i + 1) * p,
      y: (i + 1) * p
    })
  }
  return values;
};

function Marker() {
  return {
    x: {
      rect: "x",
      circle: "cx"
    },
    y: {
      rect: "y",
      circle: "cy"
    },
    width: {
      rect: "width",
      circle: "r"
    },
    height: {
      rect: "height",
      circle: "r"
    },
    shape: function(d) {
      return d.shape
    },
    transform: function(d) {
      return "translate(" + f(d.x) + "," + f(d.y) + ")"
    },
    dock: function(d) {
      return "translate(" + (d.x + 800) + "," + (d.y + 100) + ")"
    }
  };

  function f(x) {
    return d3.format(".0f")(x)
  }
}

function key(attr, value, log) {
  //join data and elements where value of attr is value
  function _phase(that) {
    return Array.isArray(that) ? "data" : "element";
  }

  function _Key(that) {
    if (plural) {
      return {
        data: function(d, i, j) {
          var a, key = "";
          for (a in attr) {
            key += (typeof attr[a] === "function" ? attr[a](d, i, j) : attr[a]);
          }
          return key;
        },
        element: function() {
          var a, key = "";
          for (a in attr) {
            key += d3.select(that).attr(a);
          }
          return key;
        }
      }
    } else {
      return {
        data: function(d, i, j) {
          return typeof value === "function" ? value(d, i, j) : value;
        },
        element: function() {
          return d3.select(that).attr(attr)
        }
      }
    }
  }

  var plural = typeof attr === "object";
  if (plural && arguments.length === 2) log = value;

  return function(d, i, j) {
    var key = _Key(this)[_phase(this)](d, i, j);
    if (log) log.push(i + "_" + _phase(this) + "_" + key);
    return key;
  };
}
text {
  font: bold 12px monospace;
  fill: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>


来源:https://stackoverflow.com/questions/30890212/data-join-with-custom-key-does-not-work-as-expected

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!