在 D3 中制作不同颜色的线条补丁

Make patch of line different color in D3

我的可视化使用节点之间的线来表示它们之间的关系。线越宽,两个节点的连接越多。在这种情况下,我想将一小部分线条设置为较暗的色调,以便将更多信息附加到可视化中。离直线上的"mark"最近的那个节点就是主导节点。

是否有一些命令可以只更改部分行的颜色?或者创建一个与直线具有相同宽度和角度的矩形会更容易吗?

我附上了当前的可视化效果,我在其中创建了一些丑陋的小矩形来说明我正在尝试做什么。另外附上代码(可惜有点乱)

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.node circle {
  stroke: white;
  stroke-width: 2px;
  opacity: 1.0;
}

line {
  stroke-width: 3.5px;
  stroke-opacity: 1.0;
}

</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script>

data = {
        nodes: [
                  {size: 200, fill: '#ffffff', line: '#8C8C8C', id: 'Me'}, 
                  {size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Bob'},
                  {size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Alice'},
                  {size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Tim Tim'},
                  {size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Mustafa'},
                  {size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Zeus'},
                  {size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'McHammer'},
                  {size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Thoooor'},
                  {size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Le Viking'}
                  ],
        links: [
                  {source: 0,target: 1, calls: 20, texts:0},
                  {source: 0,target: 2, calls: 5, texts:0},
                  {source: 0,target: 3, calls: 8, texts:0},
                  {source: 0,target: 4, calls: 3, texts:0},
                  {source: 0,target: 5, calls: 2, texts:0},
                  {source: 0,target: 6, calls: 3, texts:0},
                  {source: 0,target: 7, calls: 5, texts:0},
                  {source: 0,target: 8, calls: 2, texts:0}
                ]
        }

var total_interactions = data.links.reduce(function(result, currentObject) {
  return result + currentObject.calls;
}, 0);
console.log(total_interactions);

var mouseOverFunction = function(d) {
  var circle = d3.select(this);

  circle
    .transition(500)
      .attr("r", function(){ return 1.4 * node_radius(d)});
}

var mouseOutFunction = function() {
  var circle = d3.select(this);

  circle
    .transition(500)
      .attr("r", node_radius);
}

function isConnected(a, b) {
    return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}

function isConnectedAsSource(a, b) {
    return linkedByIndex[a.index + "," + b.index];
}

function isConnectedAsTarget(a, b) {
    return linkedByIndex[b.index + "," + a.index];
}

function isEqual(a, b) {
    return a.index == b.index;
}

var line_diff = 0

function tick() {
  callLink
    .attr("x1", function(d) { return d.source.x-line_diff; })
    .attr("y1", function(d) { return d.source.y-line_diff; })
    .attr("x2", function(d) { return d.target.x-line_diff; })
    .attr("y2", function(d) { return d.target.y-line_diff; });
  textLink
    .attr("x1", function(d) { return d.source.x+line_diff; })
    .attr("y1", function(d) { return d.source.y+line_diff; })
    .attr("x2", function(d) { return d.target.x+line_diff; })
    .attr("y2", function(d) { return d.target.y+line_diff; });

  node
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}

function node_radius(d) { return Math.pow(40.0 * d.size, 1/3); } 

var width = 1000;
var height = 500;

var nodes = data.nodes
var links = data.links

var force = d3.layout.force()
              .nodes(nodes)
              .links(links)
              .charge(-3000) // -3000
              .friction(0.6) 
              .gravity(0.6)
              .size([width,height])
              .start();

var linkedByIndex = {};
links.forEach(function(d) {
  linkedByIndex[d.source.index + "," + d.target.index] = true;
});

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

var callLink = svg.selectAll(".call-line")
            .data(links)
            .enter().append("line");
var textLink = svg.selectAll(".text-line")
            .data(links)
            .enter().append("line");
var link = svg.selectAll("line");

var node = svg.selectAll(".node")
              .data(nodes)
            .enter().append("g")
              .attr("class", "node")
              .call(force.drag);

node
  .append("circle")
  .attr("r", node_radius)
  .style("fill", function(d)  {return d.fill; })
  .style("stroke", function(d)  {return d.line; })
  .on("mouseover", mouseOverFunction)
  .on("mouseout", mouseOutFunction);

svg
  .append("marker")
  .attr("id", "arrowhead")
  .attr("refX", 6 + 7)
  .attr("refY", 2)
  .attr("markerWidth", 6)
  .attr("markerHeight", 4)
  .attr("orient", "auto")
  .append("path")
  .attr("d", "M 0,0 V 4 L6,2 Z");

// link
  // .style("stroke-width", function stroke(d)  {return d.calls/data.total_interactions*20; })

 var line_width_factor = 40 // width for a line with all points

 textLink
  .style("stroke-width", function stroke(d)  {return d.texts / total_interactions*line_width_factor; })
  .style("stroke", "#70C05A")

 callLink
  .style("stroke-width", function stroke(d)  {return d.calls / total_interactions*line_width_factor; })
  .style("stroke", "#438DCA")

force
  .on("tick",tick);

</script>
</body>

其实你can't apply CSS gradients to SVG elements。你必须使用 SVG 渐变。

要在图表中沿 link 创建 SVG 渐变,一般形式为:

defs.append("linearGradient")                
        .attr("id", gradient_id)
        .attr("gradientUnits", "userSpaceOnUse")
        .attr("x1", source.px)
        .attr("y1", source.py)
        .attr("x2", target.px)
        .attr("y2", target.py)    
    .selectAll("stop")                      
        .data([                             
            {offset: "0%", color: "cyan"},   //color 1
            {offset: "50%", color: "cyan"},  //start the gradient from haf distance
            {offset: "100%", color: "darkBlue"} //to a darker color
        ])                  
    .enter().append("stop")         
        .attr("offset", function(d) { return d.offset; })   
        .attr("stop-color", function(d) { return d.color; });   

在你的例子中,sourcetarget 的位置随着每个 force.tick() 而改变。因此,您需要为每个 tick 中的所有 link 计算并应用梯度。通过将此行添加到 tick 函数:

callLink.each(function(d){applyGradient(this, d)});

接下来,我们需要定义applyGradient(line, d)函数:

function applyGradient(line, d){
    var self = d3.select(line); //select the current link

    // store the currently used gradient to delete it later.
    var current_gradient = self.style("stroke")
    current_gradient = current_gradient.substring(4, current_gradient.length-1);

    // Generate a random id for the gradient calculated for this link
    var new_gradient_id = "line-gradient" + getRandomInt();

    // Determine the direction of the gradient. In your example, you need
    // the darker hue to be near the bigger node.
    var from = d.source.size < d.target.size? d.source: d.target;
    var to = d.source.size < d.target.size? d.target: d.source;


    // Add the gradient to the svg:defs
    defs.append("linearGradient")                
        .attr("id", new_gradient_id)
        .attr("gradientUnits", "userSpaceOnUse")
        .attr("x1", from.px)
        .attr("y1", from.py)
        .attr("x2", to.px)
        .attr("y2", to.py)    
    .selectAll("stop")                      
        .data([                             
            {offset: "0%", color: "cyan"},       
            {offset: "50%", color: "cyan"},         
            {offset: "100%", color: "darkBlue"}
        ])                  
    .enter().append("stop")         
        .attr("offset", function(d) { return d.offset; })   
        .attr("stop-color", function(d) { return d.color; });   

    //apply the gradient
    self.style("stroke", "url(#" + new_gradient_id + ")")

    //Remove the old gradient from defs.
    defs.select(current_gradient).remove();
}

这是一个working fiddle