Метки горизонтальных связей в сети d3 force - PullRequest
0 голосов
/ 16 мая 2018

У меня есть силовая сеть d3 (v3) с изогнутыми ссылками, которая выглядит следующим образом:

enter image description here

Я пытаюсь сделать так, чтобы элементы textPath ссылок были горизонтальными, так как они числа, а "81" должен выглядеть не так, как "18". Я также хотел бы иметь какую-то белую тень / внешнее свечение / фон, так как я помещаю их прямо в ссылки. Прямо сейчас у меня есть белый штрих, но он работает не совсем правильно, потому что иногда штрих одной цифры проникает в соседнюю цифру.

Здесь приведен воспроизводимый пример, который я, по общему признанию, соединил с другими ответами SO: https://jsfiddle.net/2gbekL7m/

Соответствующая часть кода:

var link_label = svg.selectAll(".link_label")
  .data(links)
  .enter()
  .append("text")
  .attr("class", "link_label")
  .attr("paint-order", "stroke")
  .attr("stroke", "white")
  .attr("stroke-width", 4)
  .attr("stroke-opacity", 1)
  .attr("stroke-linecap", "butt")
  .attr("stroke-linejoin", "miter")
  .style("fill", "black")
  .attr("dy", 5)
  .append("textPath")
  .attr("startOffset", "50%")
  .attr("xlink:href", function(d, i) {
    return "#link_" + i;
  })
  .text(function(d, i) {
    return d.n;
  });

Кто-нибудь знает, как можно улучшить читаемость ярлыков ссылок, исправив ориентацию и добавив фоновое поле?

Ответы [ 2 ]

0 голосов
/ 19 мая 2018

Повторное использование Ответ Герардо с использованием getPointAtLength(), вот альтернатива, основанная на белом круге для второй части вопроса относительно тени / фона меток:

var nodes = [{
      "ix": 0
    },
    {
      "ix": 1
    },
    {
      "ix": 2
    },
    {
      "ix": 3
    }
  ];

  var links = [{
      "source": 0,
      "target": 2,
      "n": 12
    },
    {
      "source": 0,
      "target": 1,
      "n": 34
    },
    {
      "source": 1,
      "target": 2,
      "n": 56
    },
    {
      "source": 1,
      "target": 0,
      "n": 78
    },
    {
      "source": 0,
      "target": 3,
      "n": 90
    }
  ];

  var w = 400,
    h = 400;

  var force = d3.layout.force()
    .size([w, h])
    .nodes(nodes)
    .links(links)
    .gravity(1)
    .linkDistance(30)
    .charge(-20000)
    .linkStrength(1);

  force.start();

  var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h)
    .attr("preserveAspectRatio", "xMinYMin meet")
    .attr("viewBox", "0 0 " + w + " " + h)
    .append("g");

  var marker = svg.selectAll("marker")
    .append("svg:defs")
    .data(["end-arrow"])
    .enter()
    .append("svg:marker")
    .attr("id", String)
    .attr("viewBox", "0 -3 6 6")
    .attr("refX", 11.3)
    .attr("refY", -0.2)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-3L6,0L0,3");

  var link = svg.selectAll("line.link")
    .data(links)
    .enter()
    .append("svg:path")
    .attr("id", function(d, i) {
      return "link_" + i;
    })
    .style("stroke", "black")
    .style("stroke-width", 2)
    .style("fill", "none")
    .attr("marker-end", "url(#end-arrow)");

  var link_label_shadow = svg.selectAll(".link_label_shadow")
    .data(links)
    .enter()
    .append("circle")
    .attr("r", 10)
    .style("fill", "white");

  var link_label = svg.selectAll(".link_label")
    .data(links)
    .enter()
    .append("text")
    .style("text-anchor", "middle")
    .style("dominant-baseline", "central")
    .attr("class", "shadow")
    .text(function(d, i) {
      return d.n;
    });

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

  node.append("svg:circle")
    .attr("r", 10)
    .style("fill", "black");

  node.call(force.drag);

  force.on("tick", function() {
    link.attr("d", function(d) {
      var dx = d.target.x - d.source.x;
      var dy = d.target.y - d.source.y;
      var dr = Math.sqrt(dx * dx + dy * dy);

      return "M" +
        d.source.x +
        "," +
        d.source.y +
        "A" +
        dr +
        "," +
        dr +
        " 0 0,1 " +
        d.target.x +
        "," +
        d.target.y;
    });

    link_label.attr("x", function(d, i) {
        var pathLength = d3.select("#link_" + i).node().getTotalLength();
        d.point = d3.select("#link_" + i).node().getPointAtLength(pathLength / 2);
        return d.point.x
      })
      .attr("y", function(d) {
        return d.point.y
      })
    node.attr("transform", function(d) {
      return ("translate(" + d.x + "," + d.y + ")");
    });

    link_label_shadow.attr("cx", function(d, i) {
        var pathLength = d3.select("#link_" + i).node().getTotalLength();
        d.point = d3.select("#link_" + i).node().getPointAtLength(pathLength / 2);
        return d.point.x
      })
      .attr("cy", function(d) {
        return d.point.y
      })
    node.attr("transform", function(d) {
      return ("translate(" + d.x + "," + d.y + ")");
    });
  });
  
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Так же, как метка устанавливается с помощью getPointAtLength(), мы можем включить белый кружок между ссылкой и текстовой меткой в ​​той же позиции.

Поскольку белые кружки создаются после их связанной ссылки, они будут располагаться над ссылками и, таким образом, будут скрывать свою среднюю часть. Затем вставляются только метки, и поэтому они находятся над белыми кружками.

0 голосов
/ 19 мая 2018

Прежде всего, поскольку вы не опубликовали свой код, мой ответ будет касаться кода в JSFiddle , которым вы поделились.

Давайте разберемся с вашими двумяВыдает отдельно:

Расположение меток

Мне кажется, что вы хотите поместить метки в середину ссылок и всегда горизонтально, как обычный текст.В этом случае решение отбрасывает textPath, что фактически упростит ваш выбор:

var link_label = svg.selectAll(".link_label")
    .data(links)
    .enter()
    .append("text")
    .text(function(d, i) {
        return d.n;
    });

Теперь это просто вопрос получения среднего положения этих путей, что мы можем сделать, используяgetTotalLength() и getPointAtLength() внутри функции tick:

link_label.attr("x", function(d, i) {
        var pathLength = d3.select("#link_" + i).node().getTotalLength();
        d.point = d3.select("#link_" + i).node().getPointAtLength(pathLength / 2);
        return d.point.x
    })
    .attr("y", function(d) {
        return d.point.y
    })

Здесь я сохраняю значение в свойстве, поскольку оно будет одинаковым как для x, так и для y позиции.Таким образом мы избегаем ненужных повторных расчетов.Кроме того, кто-то может поспорить, что вычисление следует размещать вне функции tick, чтобы получить длину пути только один раз: к сожалению, это не так, потому что во время моделирования длина пути постоянно меняется.

Тени для текста

Здесь есть несколько разных подходов.Простой, вероятно, не самый красивый, использует text-shadow .Вот простая белая тень вверх, вправо, вниз и влево:

.shadow {
    text-shadow: 2px 2px 0 #fff, 2px -2px 0 #fff, -2px 2px 0 #fff, -2px -2px 0 #fff
}

То, что CSS работает на Chrome и FireFox, но не на Safari.

Все вместе, вот демонстрация:

var nodes = [{
      "ix": 0
    },
    {
      "ix": 1
    },
    {
      "ix": 2
    },
    {
      "ix": 3
    }
  ];

  var links = [{
      "source": 0,
      "target": 2,
      "n": 12
    },
    {
      "source": 0,
      "target": 1,
      "n": 34
    },
    {
      "source": 1,
      "target": 2,
      "n": 56
    },
    {
      "source": 1,
      "target": 0,
      "n": 78
    },
    {
      "source": 0,
      "target": 3,
      "n": 90
    }
  ];

  var w = 400,
    h = 400;

  var force = d3.layout.force()
    .size([w, h])
    .nodes(nodes)
    .links(links)
    .gravity(1)
    .linkDistance(30)
    .charge(-20000)
    .linkStrength(1);

  force.start();

  var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h)
    .attr("preserveAspectRatio", "xMinYMin meet")
    .attr("viewBox", "0 0 " + w + " " + h)
    .append("g");

  var marker = svg.selectAll("marker")
    .append("svg:defs")
    .data(["end-arrow"])
    .enter()
    .append("svg:marker")
    .attr("id", String)
    .attr("viewBox", "0 -3 6 6")
    .attr("refX", 11.3)
    .attr("refY", -0.2)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-3L6,0L0,3");

  var link = svg.selectAll("line.link")
    .data(links)
    .enter()
    .append("svg:path")
    .attr("id", function(d, i) {
      return "link_" + i;
    })
    .style("stroke", "black")
    .style("stroke-width", 2)
    .style("fill", "none")
    .attr("marker-end", "url(#end-arrow)");

  var link_label = svg.selectAll(".link_label")
    .data(links)
    .enter()
    .append("text")
    .style("text-anchor", "middle")
    .style("dominant-baseline", "central")
    .attr("class", "shadow")
    .text(function(d, i) {
      return d.n;
    });

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

  node.append("svg:circle")
    .attr("r", 10)
    .style("fill", "black");

  node.call(force.drag);

  force.on("tick", function() {
    link.attr("d", function(d) {
      var dx = d.target.x - d.source.x;
      var dy = d.target.y - d.source.y;
      var dr = Math.sqrt(dx * dx + dy * dy);

      return "M" +
        d.source.x +
        "," +
        d.source.y +
        "A" +
        dr +
        "," +
        dr +
        " 0 0,1 " +
        d.target.x +
        "," +
        d.target.y;
    });

    link_label.attr("x", function(d, i) {
        var pathLength = d3.select("#link_" + i).node().getTotalLength();
        d.point = d3.select("#link_" + i).node().getPointAtLength(pathLength / 2);
        return d.point.x
      })
      .attr("y", function(d) {
        return d.point.y
      })
    node.attr("transform", function(d) {
      return ("translate(" + d.x + "," + d.y + ")");
    });
  });
.shadow {
  text-shadow: 2px 2px 0 #fff, 2px -2px 0 #fff, -2px 2px 0 #fff, -2px -2px 0 #fff
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Редактировать: взгляните на Ответ Ксавье , белые круги выглядят гораздо лучше, чем тень текста.

...