在D3.js中设置标记结束箭头的颜色

时间:2015-05-28 15:09:48

标签: javascript d3.js

下面的代码按预期在箭头/路径/线上显示标记 - 末端,但标记末端的颜色不会按行变化(即,它始终是相同的橙色,而不是其相应行的颜色)。我认为代码默认为分配给我的数据的第一个字段的颜色(?)。任何建议将不胜感激。



<script src="http://www.protobi.com/javascripts/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script src="http://www.protobi.com/examples/pca/pca.js"></script>

<script type="text/javascript"> 
var margin = {top: 20, right: 20, bottom: 20, left: 20};
var width = 1500 - margin.left - margin.right;
var height = 1500 - margin.top - margin.bottom;
var angle = Math.PI * 0;
var color = d3.scale.category10();

var x = d3.scale.linear().range([width, 0]); // switch to match how R biplot shows it
var y = d3.scale.linear().range([height, 0]);

x.domain([-3.5,3.5]).nice()
y.domain([-3.5,3.5]).nice()

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var svg = d3.select("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");



d3.csv("/brand.csv", function(error, data) {


  var matrix = data.map(function(d){
    return d3.values(d).slice(1,d.length).map(parseFloat);
  });

  var pca = new PCA();
  matrix = pca.scale(matrix,true,true);

  pc = pca.pca(matrix,2)

  var A = pc[0];  // this is the U matrix from SVD
  var B = pc[1];  // this is the dV matrix from SVD

  var brand_names = Object.keys(data[0]);  // first row of data file ["ATTRIBUTE", "BRAND A", "BRAND B", "BRAND C", ...]
  brand_names.shift(); // drop the first column label, e.g. "ATTRIBUTE"

  data.map(function(d,i){
    label: d.ATTRIBUTE,
        d.pc1 = A[i][0];
    d.pc2 = A[i][1];
  });

  var label_offset = {
    "Early/First line": 20,
    "Unfamiliar":10,
    "Convenient": -5
  }

  var brands = brand_names
      .map(function(key, i) {
        return {
          brand: key,
          pc1: B[i][0]*4,
          pc2: B[i][1]*4
        }
      });


  function rotate(x,y, dtheta) {

    var r = Math.sqrt(x*x + y*y);
    var theta = Math.atan(y/x);
    if (x<0) theta += Math.PI;

    return {
      x: r * Math.cos(theta + dtheta),
      y: r * Math.sin(theta + dtheta)
    }
  }


  data.map(function(d) {
    var xy = rotate(d.pc1, d.pc2, angle);
    d.pc1 = xy.x;
    d.pc2 = xy.y;
  });

  brands.map(function(d) {
    var xy = rotate(d.pc1, d.pc2, angle);
    d.pc1 = xy.x;
    d.pc2 = xy.y;
  });


  var showAxis = false;  // normally we don't want to see the axis in PCA, it's meaningless
  if (showAxis) {
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis)
        .append("text")
        .attr("class", "label")
        .attr("x", width)
        .attr("y", -6)
        .style("text-anchor", "end")
        .text("PC1");

    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis)
        .append("text")
        .attr("class", "label")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", ".71em")
        .style("text-anchor", "end")
        .text("PC2");
  }

  var legend = svg.selectAll(".legend")
      .data(color.domain())
      .enter().append("g")
      .attr("class", "legend")
      .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

  legend.append("rect")
      .attr("x", width - 18)
      .attr("width", 18)
      .attr("height", 18)
      .style("fill", color);

  legend.append("text")
      .attr("x", width - 24)
      .attr("y", 9)
      .attr("dy", ".35em")
      .style("text-anchor", "end")
      .text(function(d) { return d; });

  svg.selectAll(".dot")
      .data(data)
      .enter().append("circle")
      .attr("class", "dot")
      .attr("r", 10.5)
      .attr("cx", function(d) { return x(d.pc1); })
      .attr("cy", function(d) { return y(d.pc2); })
      .style("fill", function(d) { return color(d['species']); })
      .on('mouseover', onMouseOverAttribute)
      .on('mouseleave', onMouseLeave);


 svg.selectAll("text.brand")
      .data(brands)
      .enter().append("text")
      .attr("class", "label-brand")
      .attr("x", function(d) { return x(d.pc1) + 10; })
      .attr("y", function(d) { return y(d.pc2) + 0; })
      .text(function(d) { return d['brand']})

svg.selectAll("marker.brand")
      .data(brands)
         .enter().append("svg:marker") 
        .attr('id', 'end-arrow')
        .attr('viewBox', '0 -5 10 10')
        .attr('refX', 6)
        .attr('markerWidth', 10)
        .attr('markerHeight', 10)
        .attr('orient', 'auto')
        .append('svg:path')
              .attr('d', 'M0,-5L10,0L0,5')
              .style("fill", function(d) { return color(d['brand']); });

  svg.selectAll(".line")
      .data(brands)
      .enter().append("line")
      .attr("class", "square")
      .attr('x1', function(d) { return x(-d.pc1);})
      .attr('y1', function(d) { return y(-d.pc2); })
      .attr("x2", function(d) { return x(d.pc1); })
      .attr("y2", function(d) { return y(d.pc2); })
      .style("stroke", function(d) { return color(d['brand']); })
      .style('marker-end', "url(#end-arrow)")
      .on('mouseover', onMouseOverBrand)
      .on('mouseleave', onMouseLeave);


  svg.selectAll("text.attr")
      .data(data)
      .enter().append("text")
      .attr("class", "label-attr")
      .attr("x", function(d,i ) { return x(d.pc1)+4 ; })
      .attr("y", function(d ,i) { return y(d.pc2) + (label_offset[d.ATTRIBUTE]||0); })
      .text(function(d,i) { return d.ATTRIBUTE})

  var pctFmt = d3.format('0%')
  var tip = d3.tip()
      .attr('class', 'd3-tip')
      .offset([10, 20])
      .direction('e')
      .html(function(values,title) {
        var str =''
        str += '<h3>' + (title.length==1 ? 'Brand ' : '' )+ title  + '</h3>'
        str += "<table>";
        for (var i=0; i<values.length; i++) {
          if (values[i].key != 'ATTRIBUTE' && values[i].key != 'pc1' && values[i].key != 'pc2') {
            str += "<tr>";
            str += "<td>" + values[i].key + "</td>";
            str += "<td class=pct>" + pctFmt(values[i].value) + "</td>";
            str + "</tr>";
          }
        }
        str += "</table>";

        return str;
      });

  svg.call(tip);

  function getSpPoint(A,B,C){
    var x1=A.x, y1=A.y, x2=B.x, y2=B.y, x3=C.x, y3=C.y;
    var px = x2-x1, py = y2-y1, dAB = px*px + py*py;
    var u = ((x3 - x1) * px + (y3 - y1) * py) / dAB;
    var x = x1 + u * px, y = y1 + u * py;
    return {x:x, y:y}; //this is D
  }


// draw line from the attribute a perpendicular to each brand b
  function onMouseOverAttribute(a,j) {

    brands.forEach(function(b, idx) {
      var A = { x: 0, y:0 };
      var B = { x: b.pc1,  y: b.pc2 };
      var C = { x: a.pc1,  y: a.pc2 };

      b.D = getSpPoint(A,B,C);
    });

    svg.selectAll('.tracer')
        .data(brands)
        .enter()
        .append('line')
        .attr('class', 'tracer')
        .attr('x1', function(b,i) { return x(a.pc1); return x1; })
        .attr('y1', function(b,i) { return y(a.pc2); return y1; })
        .attr('x2', function(b,i) { return x(b.D.x); return x2; })
        .attr('y2', function(b,i) { return y(b.D.y); return y2; })
        .style("stroke", function(d) { return "#aaa"});

    delete a.D;
    var tipText = d3.entries(a);
    tip.show(tipText, a.ATTRIBUTE);
  };

// draw line from the brand axis a perpendicular to each attribute b
  function onMouseOverBrand(b,j) {

    data.forEach(function(a, idx) {
      var A = { x: 0, y:0 };
      var B = { x: b.pc1,  y: b.pc2 };
      var C = { x: a.pc1,  y: a.pc2 };

      a.D = getSpPoint(A,B,C);
    });

    svg.selectAll('.tracer')
        .data(data)
        .enter()
        .append('line')
        .attr('class', 'tracer')
        .attr('x1', function(a,i) { return x(a.D.x);  })
        .attr('y1', function(a,i) { return y(a.D.y);  })
        .attr('x2', function(a,i) { return x(a.pc1);  })
        .attr('y2', function(a,i) { return y(a.pc2); })
        .style("stroke", function(d) { return "#aaa"});

    var tipText = data.map(function(d) {
      return {key: d.ATTRIBUTE, value: d[b['brand']] }
    })
    tip.show(tipText, b.brand);
  };

  function onMouseLeave(b,j) {
    svg.selectAll('.tracer').remove()
    tip.hide();
  }

});
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:4)

在为每一行创建svg:marker时,您会为它们提供相同的id。当它们在line元素上使用时,由于它们都具有相同的ID,因此您只能使用其中一个元素。

简单修复,给他们独特的ID:

svg.selectAll("marker.brand")
    .data(brands)
    .enter().append("svg:marker")
    .attr('id', function(d,i){
      return 'end-arrow' + i; //<-- append index postion
    })
    ...

svg.selectAll(".line")
    .data(brands)
    .enter().append("line")
    .attr("class", "square")
    .attr('x1', function(d) {
      return x(-d.pc1);
    })
    .attr('y1', function(d) {
      return y(-d.pc2);
    })
    .attr("x2", function(d) {
      return x(d.pc1);
    })
    .attr("y2", function(d) {
      return y(d.pc2);
    })
    .style("stroke", function(d) {
      return color(d['brand']);
    })
    .style('marker-end', function(d,i){
      return "url(#end-arrow"+i+")"; //<--use the one with the right id
    })
    ....

示例here