Bagaimana cara memecah baris teks svg dalam javascript?

107

Jadi inilah yang saya miliki:

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

Sekarang teks tooltip saya yang sangat panjang tidak memiliki pemutusan baris, meskipun jika saya menggunakan alert (); ini menunjukkan kepada saya bahwa teks sebenarnya TIDAK memiliki dua baris. (Ini berisi "\" meskipun, bagaimana cara menghapusnya?)
Saya tidak bisa membuat CDATA berfungsi di mana pun.

sollniss
sumber
2
Adakah kemungkinan svgjs.com/textflow ini dapat membantu dengan tooltip Anda?
Alvin K.
@Bayu_joo tautannya rusak. Saya mencoba mencari lokasi baru, tetapi gagal.
guettli

Jawaban:

150

Ini bukanlah sesuatu yang didukung oleh SVG 1.1. SVG 1.2 memang memiliki textAreaelemen, dengan pembungkusan kata otomatis, tetapi tidak diterapkan di semua browser. SVG 2 tidak berencana untuk mengimplementasikannyatextArea , tetapi memiliki teks yang dibungkus otomatis .

Namun, mengingat Anda telah mengetahui di mana penggalan baris harus terjadi, Anda dapat memecah teks menjadi beberapa <tspan>s, masing-masing dengan x="0"dan dy="1.4em"untuk mensimulasikan baris teks yang sebenarnya. Sebagai contoh:

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

Tentu saja, karena Anda ingin melakukannya dari JavaScript, Anda harus membuat dan memasukkan setiap elemen secara manual ke DOM.

Sergiu Dumitriu
sumber
2
Dan bagaimana saya mengenali di mana harus meletakkan <tspan>s? Menggantikan? Membagi?
sollniss
2
Sudah dicoba var tspan = document.createElement('tspan') tspan.setAttribute('x','0'); tspan.setAttribute('dy','1.2em'); tspan.textContent = text; tt.appendChild(tspan); tidak menampilkan teks sama sekali.
sollniss
2
Maukah Anda menjelaskan mengapa x = '0' dy = '1.2em' dibutuhkan? Memang berhasil, seperti yang Anda katakan. Namun, saya mengharapkannya untuk bekerja bahkan tanpa atribut tersebut. Sebaliknya, tidak ada yang ditampilkan ... Selain itu, saya tidak sepenuhnya jelas mengapa pemutusan baris terjadi sama sekali. Ini tidak seperti kita telah mengatur lebar wadah menjadi sesuatu yang diperbaiki, sehingga itu dapat memaksakan pemutusan garis, bukan?
Konrad Viltersten
4
x=0adalah koordinat absolut: pindahkan fragmen teks ke sistem koordinat asal saat ini . The transformatribut pada gelemen mendefinisikan saat baru sistem koordinat, dan dengan asumsi bahwa teks yang tersisa-blok, tspan tersebut akan dipindahkan ke sebelah kiri. Ini bertindak seperti instruksi pengembalian kereta. dy=1.2emadalah koordinat relatif : pindahkan fragmen teks dengan jumlah ini, relatif terhadap fragmen teks saat ini. Ini bertindak seperti instruksi baris-feed. Jika digabungkan, Anda mendapatkan CR / LF.
Sergiu Dumitriu
Belum mencoba ini: Bisakah Anda melakukan ini tanpa grup? <text x = "100" y = "100"> <tspan x = "100" y = "100"> teks sangat panjang </tspan> <tspan x = "100" y = "115"> Saya ingin penggalan baris </tspan> </text> ??
Richard
25

Saya harap Anda sudah berhasil menyelesaikannya, tetapi jika seseorang mencari solusi serupa maka ini berhasil untuk saya:

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

Ada 3 baris yang dipisahkan dengan linebreak.

Kristīne Glode
sumber
21
FWIW: Sepertinya OP menggunakan JavaScript murni; jawaban ini tampaknya memanfaatkan D3 .
Ben Mosher
Saya menggunakan D3, dan pendekatan Anda berhasil untuk saya. Terima kasih telah mempostingnya. Saya menemukan bahwa saya perlu menghapus tspan lama terlebih dahulu sebelum menambahkan yang baru, seperti ini: focus.selectAll ("tspan"). Remove ();
Darren Parker
1
Berhati-hatilah dengan pendekatan ini karena ia menyarangkan tag <tspan> karena itu menghubungkan .append (). Ini dapat menyebabkan sakit kepala kecil dengan CSS tergantung pada apa yang ingin Anda lakukan.
seneyr
Lihat di sini untuk pendekatan yang menghindari bersarang yang dijelaskan oleh @seneyr
bszom
16

Dengan solusi tspan, katakanlah Anda tidak tahu sebelumnya di mana harus meletakkan jeda baris Anda: Anda dapat menggunakan fungsi bagus ini, yang saya temukan di sini: http://bl.ocks.org/mbostock/7555321

Itu secara otomatis melakukan jeda baris untuk teks panjang svg untuk lebar tertentu dalam piksel.

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

Saya pikir ini melakukan apa yang Anda inginkan:

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

Ini membagi teks \\\ndan untuk masing-masing menempatkan setiap fragmen dalam satu tspan. Kemudian menghitung ukuran kotak yang dibutuhkan berdasarkan panjang teks terpanjang dan jumlah baris. Anda juga perlu mengubah elemen teks tooltip agar berisi tiga sendok teh:

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

Ini mengasumsikan bahwa Anda tidak pernah memiliki lebih dari tiga baris. Jika Anda menginginkan lebih dari tiga baris, Anda dapat menambahkan lebih banyak sdt dan menambah panjang loop for.

Peter Collingridge
sumber
Mengapa "\\\n"lebih dari itu "\n"?
ralien
2

Saya telah mengadaptasi sedikit solusi dengan @steco, mengalihkan ketergantungan dari d3ke jquerydan menambahkan heightelemen teks sebagai parameter

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

gunakan HTML, bukan 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
sumber
Saya pikir yang Anda maksud adalah "gunakan SVG, bukan JavaScript"
Valerio Bozz
Ini adalah "HTML dalam SVG", solusi terbaik untuk saya!
Kévin Berthommier