Comment couper la ligne d'un texte svg dans javascript?

107

Alors voici ce que j'ai:

<path class="..." onmousemove="show_tooltip(event,'very long text 
    \\\n I would like to linebreak')" onmouseout="hide_tooltip()" d="..."/>

<rect class="tooltip_bg" id="tooltip_bg" ... />
<text class="tooltip" id="tooltip" ...>Tooltip</text>

<script>
<![CDATA[
function show_tooltip(e,text) {
    var tt = document.getElementById('tooltip');
    var bg = document.getElementById('tooltip_bg');

    // set position ...

    tt.textContent=text;

    bg.setAttribute('width',tt.getBBox().width+10);
    bg.setAttribute('height',tt.getBBox().height+6);

    // set visibility ...
}
...

Maintenant, mon très long texte d'info-bulle n'a pas de saut de ligne, même si j'utilise alert (); cela me montre que le texte a en fait deux lignes. (Il contient un "\" cependant, comment puis-je supprimer celui-là?)
Je ne peux pas faire fonctionner CDATA n'importe où.

sollniss
la source
2
Y a - t- il une chance que ce svgjs.com/textflow puisse vous aider avec votre info-bulle?
Alvin K.
@AlvinK. le lien est rompu. J'ai essayé de trouver le nouvel emplacement, mais j'ai échoué.
guettli

Réponses:

150

Ce n'est pas quelque chose que SVG 1.1 prend en charge. SVG 1.2 a l' textAreaélément, avec un retour à la ligne automatique, mais il n'est pas implémenté dans tous les navigateurs. SVG 2 ne prévoit pas de mise en œuvretextArea , mais il contient du texte enveloppé automatiquement .

Cependant, étant donné que vous savez déjà où vos sauts de ligne doivent se produire, vous pouvez diviser votre texte en plusieurs <tspan>s, chacun avec x="0"et dy="1.4em"pour simuler des lignes de texte réelles. Par exemple:

<g transform="translate(123 456)"><!-- replace with your target upper left corner coordinates -->
  <text x="0" y="0">
    <tspan x="0" dy="1.2em">very long text</tspan>
    <tspan x="0" dy="1.2em">I would like to linebreak</tspan>
  </text>
</g>

Bien sûr, puisque vous voulez faire cela à partir de JavaScript, vous devrez créer et insérer manuellement chaque élément dans le DOM.

Sergiu Dumitriu
la source
2
Et comment savoir où mettre le <tspan>s? Remplacer? Divisé?
sollniss
2
Je l'ai essayé var tspan = document.createElement('tspan') tspan.setAttribute('x','0'); tspan.setAttribute('dy','1.2em'); tspan.textContent = text; tt.appendChild(tspan); ne montre aucun texte.
sollniss
2
Souhaitez-vous expliquer pourquoi x = '0' dy = '1.2em' est nécessaire? Cela fonctionne, en effet, comme vous l'avez dit. Cependant, je m'attendais à ce que cela fonctionne même sans ces attributs. Au lieu de cela, rien n'est affiché ... De plus, je ne suis pas tout à fait clair sur la raison pour laquelle le saut de ligne se produit. Ce n'est pas comme si nous avions réglé la largeur du conteneur sur quelque chose de correct, de sorte qu'il puisse imposer un saut de ligne, n'est-ce pas?
Konrad Viltersten
4
x=0est une coordonnée absolue: déplace le fragment de texte à l'origine du système de coordonnées courant . L' transformattribut sur l' gélément définit un nouveau système de coordonnées courant, et en supposant que le texte est aligné à gauche, le tspan est déplacé vers la gauche. Cela agit comme une instruction de retour chariot. dy=1.2emest une coordonnée relative : déplacez le fragment de texte de cette valeur par rapport au fragment de texte actuel. Cela agit comme une instruction de saut de ligne. Combiné, vous obtenez un CR / LF.
Sergiu Dumitriu
Pas encore essayé: Pourriez-vous aussi faire cela sans le groupe? <text x = "100" y = "100"> <tspan x = "100" y = "100"> texte très long </tspan> <tspan x = "100" y = "115"> Je voudrais saut de ligne </tspan> </text> ??
Richard
25

Je suppose que vous avez déjà réussi à le résoudre, mais si quelqu'un cherche une solution similaire, cela a fonctionné pour moi:

 g.append('svg:text')
  .attr('x', 0)
  .attr('y', 30)
  .attr('class', 'id')
  .append('svg:tspan')
  .attr('x', 0)
  .attr('dy', 5)
  .text(function(d) { return d.name; })
  .append('svg:tspan')
  .attr('x', 0)
  .attr('dy', 20)
  .text(function(d) { return d.sname; })
  .append('svg:tspan')
  .attr('x', 0)
  .attr('dy', 20)
  .text(function(d) { return d.idcode; })

Il y a 3 lignes séparées par un saut de ligne.

Kristīne Glode
la source
21
FWIW: on dirait que l'OP utilisait du JavaScript pur; cette réponse semble tirer parti de D3 .
Ben Mosher
J'utilise D3 et votre approche a fonctionné pour moi. Merci de l'avoir publié. J'ai trouvé que je devais supprimer les anciennes tspans avant d'en ajouter de nouvelles, comme ceci: focus.selectAll ("tspan"). Remove ();
Darren Parker
1
Attention à cette approche car elle imbrique les balises <tspan> car elle enchaîne .append (). Cela peut causer des maux de tête mineurs avec CSS en fonction de ce que vous voulez faire.
seneyr
Voir ici pour une approche qui évite l'imbrication décrite par @seneyr
bszom
16

Avec la solution tspan, disons que vous ne savez pas à l'avance où mettre vos sauts de ligne: vous pouvez utiliser cette fonction sympa, que j'ai trouvée ici: http://bl.ocks.org/mbostock/7555321

Cela fait automatiquement des sauts de ligne pour le texte long svg pour une largeur donnée en pixel.

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);
      }
    }
  });
}
stéco
la source
9

Je pense que cela fait ce que vous voulez:

function ShowTooltip(evt, mouseovertext){
    // Make tooltip text        
    var tooltip_text = tt.childNodes.item(1);
    var words = mouseovertext.split("\\\n");
    var max_length = 0;

    for (var i=0; i<3; i++){
        tooltip_text.childNodes.item(i).firstChild.data = i<words.length ?  words[i] : " ";
        length = tooltip_text.childNodes.item(i).getComputedTextLength();
        if (length > max_length) {max_length = length;}
    }

    var x = evt.clientX + 14 + max_length/2;
    var y = evt.clientY + 29;
    tt.setAttributeNS(null,"transform", "translate(" + x + " " + y + ")")

    // Make tooltip background
    bg.setAttributeNS(null,"width", max_length+15);
    bg.setAttributeNS(null,"height", words.length*15+6);
    bg.setAttributeNS(null,"x",evt.clientX+8);
    bg.setAttributeNS(null,"y",evt.clientY+14);

    // Show everything
    tt.setAttributeNS(null,"visibility","visible");
    bg.setAttributeNS(null,"visibility","visible");
}

Il divise le texte sur \\\net pour chacun met chaque fragment dans un tspan. Ensuite, il calcule la taille de la boîte requise en fonction de la plus grande longueur de texte et du nombre de lignes. Vous devrez également modifier l'élément de texte de l'infobulle pour qu'il contienne trois tspans:

<g id="tooltip" visibility="hidden">
    <text><tspan>x</tspan><tspan x="0" dy="15">x</tspan><tspan x="0" dy="15">x</tspan></text>
</g>

Cela suppose que vous n'ayez jamais plus de trois lignes. Si vous voulez plus de trois lignes, vous pouvez ajouter plus de cuillères à café et augmenter la longueur de la boucle for.

Peter Collingridge
la source
Pourquoi est-ce "\\\n"plutôt que "\n"?
ralien
3

utiliser HTML au lieu de javascript

<html>
  <head><style> * { margin: 0; padding: 0; } </style></head>
  <body>
    <h1>svg foreignObject to embed html</h1>

    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 300 300"
      x="0" y="0" height="300" width="300"
    >

      <circle
        r="142" cx="150" cy="150"
        fill="none" stroke="#000000" stroke-width="2"
      />

      <foreignObject
        x="50" y="50" width="200" height="200"
      >
        <div
          xmlns="http://www.w3.org/1999/xhtml"
          style="
            width: 196px; height: 196px;
            border: solid 2px #000000;
            font-size: 32px;
            overflow: auto; /* scroll */
          "
        >
          <p>this is html in svg 1</p>
          <p>this is html in svg 2</p>
          <p>this is html in svg 3</p>
          <p>this is html in svg 4</p>
        </div>
      </foreignObject>

    </svg>

</body></html>

Mila Nautikus
la source
Je pense que vous voulez dire "utiliser SVG au lieu de JavaScript"
Valerio Bozz
C'est "HTML in SVG", la meilleure solution pour moi!
Kévin Berthommier le
2

J'ai un peu adapté la solution par @steco, en basculant la dépendance de d3à jqueryet en ajoutant l' heightélément de texte en paramètre

function wrap(text, width, height) {
  text.each(function(idx,elem) {
    var text = $(elem);
    text.attr("dy",height);
        var 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 (elem.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);
      }
    }
  });
}
Loretoparisi
la source