Как включить новые строки в ярлыки в диаграммах D3?

Я использую D3 для создания гистограммы (я адаптировал код из этот пример). Ярлыки, которые я использую на оси x, имеют длину в пару слов, и поскольку это перекрывает перекрытие ярлыков, мне нужно разбить эти метки по строкам. (Это будет хорошо, если я смогу заменить все пробелы в каждой метке новыми символами.)

Я изначально попробовал это, заменив пробелы буквальными символами новой строки (&#xA;) и установив xml:space="preserve" в элементы label <text>. К сожалению, оказывается, что SVG не уважает это свойство. Затем я попытался обернуть каждое слово в <tspan>, которое я мог бы позже создать. Я передал каждую метку через эту функцию:

function (text) {
    return '<tspan>' + text.replace(/ /g, '</tspan><tspan>') + '</tspan>';
}

но это просто помещает литерал <tspan> в вывод. Как я могу обернуть мои текстовые метки в tspan (или сделать что-то еще), чтобы мои ярлыки не перекрывались?

Ответ 1

В итоге я использовал следующий код, чтобы разбить каждую метку оси x на линии:

var insertLinebreaks = function (d) {
    var el = d3.select(this);
    var words = d.split(' ');
    el.text('');

    for (var i = 0; i < words.length; i++) {
        var tspan = el.append('tspan').text(words[i]);
        if (i > 0)
            tspan.attr('x', 0).attr('dy', '15');
    }
};

svg.selectAll('g.x.axis g text').each(insertLinebreaks);

Обратите внимание, что это предполагает, что метки уже созданы. (Если вы выполните пример канонической гистограммы, тогда метки будут настроены так, как вам нужно.) Также не существует реальной логики прерывания строки; функция преобразует каждое пространство в новую строку. Это подходит для моих целей, но вам может понадобиться отредактировать строку split(), чтобы быть более умным в том, как она разбивает части строки на строки.

Ответ 2

Текстовый элемент SVG не поддерживает перенос текста, поэтому есть два варианта:

  • разделить текст на несколько текстовых элементов SVG.
  • используйте оверлейный HTML-div поверх SVG

См. комментарий Майка Бостока к этому здесь.

Ответ 3

Что-то, что мне показалось полезным, это использовать тег 'foreignObject' вместо текстовых или tspan-элементов. Это позволяет простое вложение HTML, позволяющее разрывать слова естественным образом. Предостережение - это габариты объекта, отвечающего конкретным потребностям:

var myLabel = svg.append('foreignObject')
    .attr({
        height: 50,
        width: 100, // dimensions determined based on need
        transform: 'translate(0,0)' // put it where you want it...
     })
     .html('<div class"style-me"><p>My label or other text</p></div>');

Независимо от того, какие элементы вы помещаете внутри этого объекта, позднее можно получить, используя d3.select/selectAll, чтобы динамически обновлять текстовые значения.

Ответ 4

Осмотревшись, я обнаружил, что Майк Босток предоставил решение, позволяющее обернуть текст раундом.

http://bl.ocks.org/mbostock/7555321

Чтобы реализовать его на моем коде (я использую свернутую древовидную диаграмму). Я просто скопировал метод "wrap".

Затем добавлено следующее

    // Standard code for a node    
    nodeEnter.append("text")
        .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
        .attr("dy", ".35em")
        .text(function(d) { return d.text; })
        // New added line to call the function to wrap after a given width
        .call(wrap, 40);

Я не вижу причин, по которым это не должно работать для направленного на усиление, бара или любого другого шаблона

Поправка:

Я изменил функцию обертывания на следующую для тех, кто читает это, и использует скользящий граф. Изменение атрибута "x" задает правильность присвоения, добавление линейного номера выполнялось отдельной строкой, поскольку в исходном коде были отмечены проблемы, а "y" было жестко установлено на ноль, в противном случае возникли проблемы, при которых интервал между линиями увеличивался с каждой строке.

function wrap(text, width) {
    text.each(function() {
        var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy")),
        lineHeight = 1.1, // ems
        tspan = text.text(null).append("tspan").attr("x", function(d) { return d.children || d._children ? -10 : 10; }).attr("y", y).attr("dy", dy + "em");     
        while (word = words.pop()) {
            line.push(word);
            tspan.text(line.join(" "));
            var textWidth = tspan.node().getComputedTextLength();
            if (tspan.node().getComputedTextLength() > width) {
                line.pop();
                tspan.text(line.join(" "));
                line = [word];
                ++lineNumber;
                tspan = text.append("tspan").attr("x", function(d) { return d.children || d._children ? -10 : 10; }).attr("y", 0).attr("dy", lineNumber * lineHeight + dy + "em").text(word);
            }
        }
    });
}

Ответ 5

Там также этот ответ на обертку длинных меток.

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

.bar {
  fill: steelblue;
}

.bar:hover {
  fill: brown;
}

.title {
  font: bold 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.axis {
  font: 10px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.x.axis path {
  display: none;
}

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

var margin = {top: 80, right: 180, bottom: 80, left: 180},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1, .3);

var y = d3.scale.linear()
    .range([height, 0]);

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

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(8, "%");

var svg = d3.select("body").append("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.tsv("data.tsv", type, function(error, data) {
  x.domain(data.map(function(d) { return d.name; }));
  y.domain([0, d3.max(data, function(d) { return d.value; })]);

  svg.append("text")
      .attr("class", "title")
      .attr("x", x(data[0].name))
      .attr("y", -26)
      .text("Why Are We Leaving Facebook?");

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis)
    .selectAll(".tick text")
      .call(wrap, x.rangeBand());

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);

  svg.selectAll(".bar")
      .data(data)
    .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.name); })
      .attr("width", x.rangeBand())
      .attr("y", function(d) { return y(d.value); })
      .attr("height", function(d) { return height - y(d.value); });
});

function wrap(text, width) {
  text.each(function() {
    var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.1, // ems
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy")),
        tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
      }
    }
  });
}

function type(d) {
  d.value = +d.value;
  return d;
}

</script>

и файл данных "data.tsv":

name    value
Family in feud with Zuckerbergs .17
Committed 671 birthdays to memory   .19
Ex is doing too well    .10
High school friends all dead now    .15
Discovered how to "like" things mentally    .27
Not enough politics .12

Ответ 6

использовать <tspan>

и в nv.d3

nv.models.axis = function() {

...

      .select('text')
            .attr('dy', '0em')
            .attr('y', -axis.tickPadding())
            .attr('text-anchor', 'middle')
            .text(function(d,i) {
              var v = fmt(d);
              return ('' + v).match('NaN') ? '' : v;
            });

изменить все вхождения .text(в .html(