使用自定义键的数据连接无法按预期工作

时间:2015-06-17 11:27:30

标签: d3.js

我正在使用d3绘制一些点。我想根据某些条件改变所有点的形状。联接看起来有点像这样:

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;})
;

d3 enter()和exit()选项似乎不反映“shape”更改造成的任何更改。

小提琴在这里:http://jsfiddle.net/schmoo2k/jcpctbty/

1 个答案:

答案 0 :(得分:2)

您需要注意,关键函数是根据选择计算的,this作为SVG元素,然后是数据数组为this的数据。

我想也许这就是你想要做的......



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>
&#13;
&#13;
&#13;

抽象版

在这里整理一下是一个更具可读性和惯用性的版本(包括解决文本元素的问题)......

&#13;
&#13;
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)];
  };
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
&#13;
&#13;
&#13;

通用的数据驱动方法

&#13;
&#13;
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;
  };
}
&#13;
text {
  font: bold 12px monospace;
  fill: black;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
&#13;
&#13;
&#13;