Сделать участок линии другого цвета в D3

В моей визуализации используются линии между узлами, чтобы указать их взаимосвязь. Чем шире линия, тем больше связаны два узла. В этом контексте я хотел бы сделать небольшой участок линий более темным оттенком, чтобы добавить больше информации к визуализации. Тот узел, который находится ближе всего к «метке» на линии, является доминирующим узлом.

Есть ли какая-то команда для изменения цвета только части строки? Или было бы проще создать прямоугольник с той же шириной и углом, что и линия?

Я приложил текущую визуализацию, где я создал несколько уродливых маленьких прямоугольников, чтобы проиллюстрировать, что я пытаюсь сделать. Кроме того, код прилагается (хотя, к сожалению, он немного запутан).

введите здесь описание изображения

<!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>

person pir    schedule 03.03.2015    source источник
comment
вы можете использовать CSS-градиенты.   -  person ee2Dev    schedule 03.03.2015
comment
Спасибо. Можете ли вы немного расширить это?   -  person pir    schedule 03.03.2015
comment
Я пытался следовать этому руководству: d3noob .org/2013/01/applying-color-gradient-to-graph-line.html, но градиент задается с использованием положения объекта на экране, а не длины линии.   -  person pir    schedule 03.03.2015


Ответы (1)


На самом деле вы не можете применять градиенты CSS к элементам SVG. Вы должны использовать градиенты SVG.

Общая форма создания градиента 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; });   

В вашем случае позиции source и target меняются с каждым force.tick(). Итак, вам нужно рассчитать и применить градиенты для всех ссылок в каждом tick. Добавив эту строку в функцию 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();
}

Вот рабочая скрипта

person ahmohamed    schedule 03.03.2015
comment
Странно, скрипка не работает в Firefox. В любом случае, спасибо за решение! Как я могу использовать переменные, связанные со ссылками, для установки смещения? Я пробовал '{offset: function(d) { return Math.round(d.calls/d.texts) + %; }, цвет: голубой}", но получаю "Ошибка: недопустимое значение". - person pir; 04.03.2015
comment
Вам нужно заранее создать переменную data, возможно, используя функцию d3.map. Обратите внимание, что приведенный вами пример d.calls/d.texts невозможен из-за деления на ноль d.texts. - person ahmohamed; 04.03.2015
comment
Спасибо, моя текущая версия имеет ненулевые значения для d.texts, но она просто использовалась в качестве примера, чтобы увидеть, как я могу получить доступ к переменным. JavaScript меня немного смущает. Использование var mid_offset = Math.round((d.calls)/(d.calls+d.texts)) + "%"; выше, а затем {offset: mid_offset, color: "cyan"} работает. Какая польза от функции d3.map в этом контексте? - person pir; 04.03.2015
comment
Моя ошибка, я забыл, что мы вызываем applyGradient с уже раскрытым d. Ваше решение в порядке. - person ahmohamed; 04.03.2015