¿Cómo saltar de línea un texto svg dentro de javascript?

107

Así que esto es lo que tengo:

<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 ...
}
...

Ahora mi texto de información sobre herramientas muy largo no tiene un salto de línea, aunque si uso alert (); me muestra que el texto en realidad TIENE dos líneas. (Sin embargo, contiene un "\", ¿cómo lo elimino por cierto?)
No puedo hacer que CDATA funcione en ninguna parte.

sollniss
fuente
2
¿Alguna posibilidad de que svgjs.com/textflow pueda ayudar con su información sobre herramientas?
Alvin K.
@AlvinK. el vínculo está roto. Intenté encontrar la nueva ubicación, pero fallé.
guettli

Respuestas:

150

Esto no es algo que sea compatible con SVG 1.1. SVG 1.2 tiene el textAreaelemento, con ajuste automático de palabras, pero no está implementado en todos los navegadores. SVG 2 no planea implementarlotextArea , pero sí tiene texto ajustado automáticamente .

Sin embargo, dado que ya sabe dónde deben producirse los saltos de línea, puede dividir el texto en varios <tspan>s, cada uno con x="0"y dy="1.4em"para simular líneas de texto reales. Por ejemplo:

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

Por supuesto, dado que desea hacer eso desde JavaScript, tendrá que crear e insertar manualmente cada elemento en el DOM.

Sergiu Dumitriu
fuente
2
¿Y cómo reconozco dónde poner el <tspan>s? ¿Reemplazar? ¿División?
sollniss
2
Probé que var tspan = document.createElement('tspan') tspan.setAttribute('x','0'); tspan.setAttribute('dy','1.2em'); tspan.textContent = text; tt.appendChild(tspan); no muestra ningún texto.
sollniss
2
¿Le importaría explicar por qué se necesita x = '0' dy = '1.2em' ? De hecho, funciona, como dijiste. Sin embargo, esperaba que funcionara incluso sin esos atributos. En cambio, no se muestra nada ... Además, no tengo del todo claro por qué ocurre el salto de línea. No es como si hubiéramos configurado el ancho del contenedor en algo fijo, de modo que pueda imponer un salto de línea, ¿verdad?
Konrad Viltersten
4
x=0es una coordenada absoluta: mueve el fragmento de texto al origen del sistema de coordenadas actual . El transformatributo del gelemento define un nuevo sistema de coordenadas actual y, suponiendo que el texto está alineado a la izquierda, el tspan se mueve hacia la izquierda. Esto actúa como una instrucción de retorno de carro. dy=1.2emes una coordenada relativa : mueve el fragmento de texto en esta cantidad, en relación con el fragmento de texto actual. Esto actúa como una instrucción de avance de línea. Combinado, obtienes un CR / LF.
Sergiu Dumitriu
No he probado esto todavía: ¿Podrías hacer esto también sin el grupo? <text x = "100" y = "100"> <tspan x = "100" y = "100"> texto muy largo </tspan> <tspan x = "100" y = "115"> Me gustaría salto de línea </tspan> </text> ??
Richard
25

Supongo que ya lograste resolverlo, pero si alguien está buscando una solución similar, esto funcionó para mí:

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

Hay 3 líneas separadas por salto de línea.

Kristīne Glode
fuente
21
FWIW: parece que el OP estaba usando JavaScript puro; esta respuesta parece estar aprovechando D3 .
Ben Mosher
Estoy usando D3 y su enfoque funcionó para mí. Gracias por publicarlo. Descubrí que necesitaba eliminar primero los tspans antiguos antes de agregar los nuevos, como este: focus.selectAll ("tspan"). Remove ();
Darren Parker
1
Tenga cuidado con este enfoque que anida las etiquetas <tspan> ya que encadena .append (). Esto puede causar algunos dolores de cabeza menores con CSS dependiendo de lo que quiera hacer.
seneyr
Vea aquí un enfoque que evita la anidación descrita por @seneyr
bszom
16

Con la solución tspan, digamos que no sabe de antemano dónde colocar sus saltos de línea: puede usar esta función agradable, que encontré aquí: http://bl.ocks.org/mbostock/7555321

Eso automáticamente hace saltos de línea para svg de texto largo para un ancho dado en píxeles.

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);
      }
    }
  });
}
steco
fuente
9

Creo que esto hace lo que quieres:

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

Divide el texto \\\ny para cada uno coloca cada fragmento en un tspan. Luego calcula el tamaño del cuadro requerido según la longitud más larga del texto y el número de líneas. También necesitará cambiar el elemento de texto de información sobre herramientas para que contenga tres 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>

Esto supone que nunca tiene más de tres líneas. Si desea más de tres líneas, puede agregar más tspans y aumentar la longitud del bucle for.

Peter Collingridge
fuente
¿Por qué es en "\\\n"lugar de "\n"?
ralien
2

He adaptado un poco la solución por @steco, el cambio de la dependencia de d3a jqueryy la adición de la heightdel elemento de texto como parámetro

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
fuente
2

usa HTML en lugar 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
fuente
Creo que te refieres a "usar SVG en lugar de JavaScript"
Valerio Bozz
Es "HTML en SVG", ¡la mejor solución para mí!
Kévin Berthommier