web-dev-qa-db-fra.com

Comment inclure des sauts de ligne dans les étiquettes des graphiques D3?

J'utilise D3 pour générer un graphique à barres (j'ai adapté le code de cet exemple ). Les étiquettes que j'utilise sur l'axe x - sont longues de deux mots chacune, et comme cela fait que toutes les étiquettes se chevauchent, je dois les casser ces étiquettes à travers les lignes. (Ce sera bien si je peux remplacer tous les espaces de chaque étiquette par des retours à la ligne.)

J'ai d'abord essayé cela en remplaçant les espaces par des sauts de ligne littéraux (&#xA;) et le réglage xml:space="preserve" sur les étiquettes '<text> éléments. Malheureusement, il s'avère que SVG ne respecte pas cette propriété. Ensuite, j'ai essayé d'envelopper chaque mot dans un <tspan> que je pourrais plus tard coiffer. J'ai passé chaque étiquette à travers cette fonction:

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

mais cela met juste littéral <tspan>s dans la sortie. Comment puis-je envelopper mes étiquettes de texte dans tspans (ou faire autre chose) afin que mes étiquettes ne se chevauchent pas?

62
bdesham

J'ai fini par utiliser le code suivant pour casser chaque x - étiquette d'axe sur les lignes:

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);

Notez que cela suppose que les étiquettes ont déjà été créées. (Si vous suivez l'exemple d'histogramme canonique alors les étiquettes auront été configurées exactement comme vous en avez besoin.) Il n'y a pas non plus de véritable logique de rupture de ligne; la fonction convertit chaque espace en une nouvelle ligne. Cela convient parfaitement à mes objectifs, mais vous devrez peut-être modifier la ligne split() pour être plus intelligent sur la façon dont elle partitionne les parties de la chaîne en lignes.

86
bdesham

L'élément de texte SVG ne prend pas en charge l'habillage de texte, il existe donc deux options:

  • diviser le texte en plusieurs éléments de texte SVG
  • utiliser un div HTML de superposition au-dessus du SVG

Voir le commentaire de Mike Bostock à ce sujet ici .

7
Ilya Boyandin

J'ai trouvé utile d'utiliser une balise 'foreignObject' au lieu d'éléments text ou tspan. Cela permet l'intégration simple de HTML, permettant aux mots de se casser naturellement. La mise en garde étant les dimensions globales de l'objet répondant à des besoins spécifiques:

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>');

Les éléments que vous placez à l'intérieur de cet objet peuvent être obtenus ultérieurement à l'aide de d3.select/selectAll pour mettre également à jour dynamiquement les valeurs de texte.

6
chrisjordanme

Après avoir regardé autour de moi, j'ai trouvé que Mike Bostock a fourni une solution vous permettant de boucler le texte.

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

Pour l'implémenter sur mon code (j'utilise un diagramme d'arbre réduit). J'ai simplement copié la méthode "wrap".

Puis a ajouté ce qui suit

    // 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);

Je ne vois aucune raison pour laquelle cela ne devrait pas fonctionner pour une force dirigée, une barre ou tout autre motif

Amendement:

J'ai modifié la fonction wrap comme suit pour quiconque lit ceci et utilise un graphique collapisible. La modification de l'attribut "x" définit correctement l'alignement, l'incrémentation du nombre de lignes a été effectuée sur une ligne distincte car des problèmes ont été notés dans le code d'origine et "y" a été défini sur zéro, sinon des problèmes se produiraient dans lesquels l'espacement des lignes augmenterait avec chaque ligne.

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);
            }
        }
    });
}
3
John Duskin

Il y a aussi this réponse sur l'emballage de longues étiquettes.

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

et le fichier de données "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
0
Nav