Como quebrar a linha de um texto svg dentro de javascript?


107

Então aqui está o que eu tenho:

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

Agora, meu texto de dica de ferramenta muito longo não tem quebra de linha, mesmo que eu use alert (); isso me mostra que o texto realmente TEM duas linhas. (Porém, ele contém um "\", como faço para removê-lo?)
Não consigo fazer o CDATA funcionar em lugar nenhum.


2
alguma chance de que svgjs.com/textflow possa ajudar com sua dica de ferramenta?
Alvin K.

@AlvinK. o link está quebrado. Tentei encontrar o novo local, mas não consegui.
guettli

Respostas:


150

Isso não é algo compatível com o SVG 1.1. O SVG 1.2 possui o textAreaelemento, com quebra automática de linha, mas não é implementado em todos os navegadores. O SVG 2 não planeja implementaçãotextArea , mas possui texto com quebra automática .

No entanto, dado que você já sabe onde as quebras de linha devem ocorrer, você pode quebrar seu texto em vários <tspan>s, cada um com x="0"e dy="1.4em"para simular linhas reais de texto. Por exemplo:

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

Obviamente, como você deseja fazer isso a partir do JavaScript, terá que criar e inserir manualmente cada elemento no DOM.


2
E como faço para reconhecer onde colocar o <tspan>s? Substituir? Dividido?
sollniss de

2
Tentei var tspan = document.createElement('tspan') tspan.setAttribute('x','0'); tspan.setAttribute('dy','1.2em'); tspan.textContent = text; tt.appendChild(tspan); não mostrar nenhum texto.
sollniss

2
Você gostaria de explicar por que x = '0' dy = '1.2em' é necessário? Funciona, de fato, como você disse. No entanto, eu esperava que funcionasse mesmo sem esses atributos. Em vez disso, nada é exibido ... Além disso, não estou totalmente certo sobre o motivo da quebra de linha. Não é como se tivéssemos configurado a largura do contêiner para algo fixo, de modo que ele possa impor quebra de linha, não é?
Konrad Viltersten

4
x=0é uma coordenada absoluta: move o fragmento de texto para a origem do sistema de coordenadas atual . O transformatributo no gelemento define um novo sistema de coordenadas atual e, supondo que o texto esteja alinhado à esquerda, o tspan é movido para a esquerda. Isso funciona como uma instrução de retorno de carro. dy=1.2emé uma coordenada relativa : move o fragmento de texto por esta quantidade, em relação ao fragmento de texto atual. Isso funciona como uma instrução de alimentação de linha. Combinado, você obtém um CR / LF.
Sergiu Dumitriu

Ainda não tentei fazer isso: você também poderia fazer isso sem o grupo? <text x = "100" y = "100"> <tspan x = "100" y = "100"> texto muito longo </tspan> <tspan x = "100" y = "115"> Eu gostaria de linebreak </tspan> </text> ??
Richard,

25

Suponho que você já conseguiu resolvê-lo, mas se alguém está procurando uma solução semelhante, isso funcionou para mim:

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

Existem 3 linhas separadas por quebra de linha.


21
FWIW: parece que o OP estava usando JavaScript puro; essa resposta parece estar aproveitando D3 .
Ben Mosher

Estou usando o D3 e sua abordagem funcionou para mim. Obrigado por postar. Descobri que precisava excluir os tspans antigos primeiro, antes de adicionar os novos, como este: focus.selectAll ("tspan"). Remove ();
Darren Parker

1
Cuidado com essa abordagem, pois ela aninha as tags <tspan>, uma vez que encadeia .append (). Isso pode causar algumas pequenas dores de cabeça com CSS, dependendo do que você deseja fazer.
seneyr

Veja aqui uma abordagem que evita o aninhamento descrito por @seneyr
bszom

16

Com a solução tspan, digamos que você não saiba com antecedência onde colocar as quebras de linha: você pode usar esta função bacana, que encontrei aqui: http://bl.ocks.org/mbostock/7555321

Isso faz quebras de linha automaticamente para texto longo svg para uma dada largura em 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);
      }
    }
  });
}

9

Eu acho que isso faz o que você quer:

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

Ele divide o texto \\\ne, para cada um, coloca cada fragmento em um tspan. Em seguida, ele calcula o tamanho da caixa necessária com base no comprimento mais longo do texto e no número de linhas. Você também precisará alterar o elemento de texto de dica de ferramenta para conter três 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>

Isso pressupõe que você nunca tenha mais de três linhas. Se você quiser mais de três linhas, pode adicionar mais tspans e aumentar o comprimento do loop for.


Por que é "\\\n"ao invés de "\n"?
ralien

2

Adaptei um pouco a solução de @steco, mudando a dependência de d3para jquerye adicionando o heightdo 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);
      }
    }
  });
}

2

use HTML em vez 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>


Acho que você quer dizer "usar SVG em vez de JavaScript"
Valerio Bozz

É "HTML em SVG", a melhor solução para mim!
Kévin Berthommier
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.