Ubah ukuran svg ketika jendela diubah ukurannya di d3.js

183

Saya menggambar sebar dengan d3.js. Dengan bantuan pertanyaan ini:
Dapatkan ukuran layar, halaman web saat ini dan jendela browser

Saya menggunakan jawaban ini:

var w = window,
    d = document,
    e = d.documentElement,
    g = d.getElementsByTagName('body')[0],
    x = w.innerWidth || e.clientWidth || g.clientWidth,
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

Jadi saya dapat memasukkan plot saya ke jendela pengguna seperti ini:

var svg = d3.select("body").append("svg")
        .attr("width", x)
        .attr("height", y)
        .append("g");

Sekarang saya ingin sesuatu mengatur ukuran plot ketika pengguna mengubah ukuran jendela.

PS: Saya tidak menggunakan jQuery dalam kode saya.

mthpvg
sumber

Jawaban:

295

Cari 'SVG responsif' sangat mudah untuk membuat SVG responsif dan Anda tidak perlu khawatir tentang ukuran lagi.

Inilah cara saya melakukannya:

d3.select("div#chartId")
   .append("div")
   // Container class to make it responsive.
   .classed("svg-container", true) 
   .append("svg")
   // Responsive SVG needs these 2 attributes and no width and height attr.
   .attr("preserveAspectRatio", "xMinYMin meet")
   .attr("viewBox", "0 0 600 400")
   // Class to make it responsive.
   .classed("svg-content-responsive", true)
   // Fill with a rectangle for visualization.
   .append("rect")
   .classed("rect", true)
   .attr("width", 600)
   .attr("height", 400);
.svg-container {
  display: inline-block;
  position: relative;
  width: 100%;
  padding-bottom: 100%; /* aspect ratio */
  vertical-align: top;
  overflow: hidden;
}
.svg-content-responsive {
  display: inline-block;
  position: absolute;
  top: 10px;
  left: 0;
}

svg .rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<div id="chartId"></div>

Catatan: Segala sesuatu di gambar SVG akan berskala dengan lebar jendela. Ini termasuk lebar dan ukuran font stroke (bahkan yang diatur dengan CSS). Jika ini tidak diinginkan, ada solusi alternatif yang lebih terlibat di bawah ini.

Info lebih lanjut / tutorial:

http://thenewcode.com/744/Membuat-SVG-Responsive

http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php

cminatti
sumber
8
Ini jauh lebih elegan dan juga menggunakan pendekatan penskalaan kontainer yang harus ada di setiap perangkat pengembang front-end (dapat digunakan untuk penskalaan apa pun dalam rasio aspek tertentu). Harus menjadi jawaban teratas.
kontur
13
Hanya untuk menambahkan ini: viewBoxAtribut harus diatur ke tinggi dan lebar yang relevan dari plot SVG Anda: di .attr("viewBox","0 0 " + width + " " + height) mana widthdan heightdidefinisikan di tempat lain. Yaitu, kecuali Anda ingin SVG Anda dipotong oleh kotak tampilan. Anda mungkin juga ingin memperbarui padding-bottomparameter di css Anda agar sesuai dengan aspek rasio elemen svg Anda. Respons yang sangat baik - sangat membantu, dan berfungsi dengan baik. Terima kasih!
Andrew Guy
13
Berdasarkan ide yang sama, akan menarik untuk memberikan jawaban yang tidak melibatkan pelestarian aspek rasio. Pertanyaannya adalah tentang memiliki elemen svg yang terus menutupi jendela penuh pada ukuran, yang dalam banyak kasus berarti rasio aspek yang berbeda.
Nicolas Le Thierry d'Ennequin
37
Hanya catatan tentang UX: Untuk beberapa kasus penggunaan, memperlakukan bagan d3 berbasis SVG Anda seperti aset dengan rasio aspek tetap tidak ideal. Bagan yang menampilkan teks, atau dinamis, atau secara umum merasa lebih seperti Antarmuka Pengguna yang tepat daripada aset, memiliki lebih banyak keuntungan dengan rendering ulang dengan dimensi yang diperbarui. Misalnya, viewBox memaksa font dan kutu sumbu untuk meningkatkan / menurunkan, berpotensi terlihat canggung dibandingkan dengan teks html. Sebaliknya, render ulang memungkinkan bagan untuk tetap memberi label ukuran yang konsisten, ditambah lagi memberikan peluang untuk menambah atau menghapus kutu.
Karim Hernandez
1
Mengapa top: 10px;? Tampaknya salah bagi saya dan memberikan offset. Puting ke 0px berfungsi dengan baik.
TimZaman
43

Gunakan window.onresize :

function updateWindow(){
    x = w.innerWidth || e.clientWidth || g.clientWidth;
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

    svg.attr("width", x).attr("height", y);
}
d3.select(window).on('resize.updatesvg', updateWindow);

http://jsfiddle.net/Zb85u/1/

Adam Pearce
sumber
11
Ini bukan jawaban yang baik karena ini melibatkan pengikatan acara DOM ... Solusi yang jauh lebih baik adalah hanya menggunakan d3 dan css
alem0lars
@ alem0lars cukup lucu, versi css / d3 tidak bekerja untuk saya dan yang ini ...
Christian
32

UPDATE cukup gunakan cara baru dari @cminatti


jawaban lama untuk tujuan bersejarah

IMO lebih baik menggunakan select () dan on () karena dengan begitu Anda dapat memiliki beberapa event handler ukuran ... hanya jangan terlalu gila

d3.select(window).on('resize', resize); 

function resize() {
    // update width
    width = parseInt(d3.select('#chart').style('width'), 10);
    width = width - margin.left - margin.right;

    // resize the chart
    x.range([0, width]);
    d3.select(chart.node().parentNode)
        .style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
        .style('width', (width + margin.left + margin.right) + 'px');

    chart.selectAll('rect.background')
        .attr('width', width);

    chart.selectAll('rect.percent')
        .attr('width', function(d) { return x(d.percent); });

    // update median ticks
    var median = d3.median(chart.selectAll('.bar').data(), 
        function(d) { return d.percent; });

    chart.selectAll('line.median')
        .attr('x1', x(median))
        .attr('x2', x(median));


    // update axes
    chart.select('.x.axis.top').call(xAxis.orient('top'));
    chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));

}

http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/

slf
sumber
Saya sangat setuju. Metode cminatti akan bekerja pada semua file d3js yang kita tangani. Metode transformasi dengan ukuran per grafik terlalu banyak. Juga, itu perlu ditulis ulang untuk setiap grafik yang mungkin kita bisa pikirkan. Metode yang disebutkan di atas berfungsi untuk semuanya. Saya sudah mencoba 4 resep termasuk jenis isi ulang yang telah Anda sebutkan dan tidak ada yang bekerja untuk banyak bidang seperti jenis yang digunakan dalam kubisme.
Eamonn Kenny
1
@EamonnKenny saya tidak bisa setuju dengan Anda lagi. Jawaban lain 7 bulan di masa depan lebih unggul :)
slf
Untuk metode ini: "d3.select (window) .on" Saya tidak dapat menemukan "d3.select (window) .off" atau "d3.select (window) .unbind"
sea-kg
Jawaban ini sama sekali tidak inferior. Jawaban mereka akan skala setiap aspek grafik (termasuk kutu, sumbu, garis, dan penjelasan teks) yang dapat terlihat canggung, dalam beberapa ukuran layar, buruk pada yang lain dan tidak dapat dibaca dalam ukuran ekstrim. Saya merasa lebih baik untuk mengubah ukuran menggunakan metode ini (atau, untuk solusi yang lebih modern, @Angu Agarwal).
Rúnar Berg
12

Agak jelek jika kode pengubahan ukuran hampir sepanjang kode untuk membangun grafik di tempat pertama. Jadi alih-alih mengubah ukuran setiap elemen dari bagan yang ada, mengapa tidak me-reloadnya saja? Inilah cara kerjanya untuk saya:

function data_display(data){
   e = document.getElementById('data-div');
   var w = e.clientWidth;
   // remove old svg if any -- otherwise resizing adds a second one
   d3.select('svg').remove();
   // create canvas
   var svg = d3.select('#data-div').append('svg')
                                   .attr('height', 100)
                                   .attr('width', w);
   // now add lots of beautiful elements to your graph
   // ...
}

data_display(my_data); // call on page load

window.addEventListener('resize', function(event){
    data_display(my_data); // just call it again...
}

Garis yang penting adalah d3.select('svg').remove();. Jika tidak, setiap pengubahan ukuran akan menambahkan elemen SVG lain di bawah yang sebelumnya.

Raik
sumber
Ini benar-benar akan mematikan kinerja jika Anda menggambar ulang seluruh elemen pada setiap acara pengubahan ukuran jendela
sdemurjian
2
Tapi ini benar: Ketika Anda membuat Anda dapat menentukan apakah Anda harus memiliki sumbu inset, menyembunyikan legenda, melakukan hal-hal lain berdasarkan konten yang tersedia. Menggunakan solusi CSS / viewbox murni, semua yang dilakukannya adalah membuat tampilan visualisasi diperas. Bagus untuk gambar, buruk untuk data.
Chris Knoll
8

Dalam tata letak paksa, cukup atur atribut 'tinggi' dan 'lebar' tidak akan berfungsi untuk memusatkan kembali / memindahkan plot ke wadah svg. Namun, ada jawaban yang sangat sederhana yang berfungsi untuk Force Layouts yang ditemukan di sini . Singkatnya:

Gunakan acara yang sama (apa saja) yang Anda suka.

window.on('resize', resize);

Maka dengan asumsi Anda memiliki variabel svg & force:

var svg = /* D3 Code */;
var force = /* D3 Code */;    

function resize(e){
    // get width/height with container selector (body also works)
    // or use other method of calculating desired values
    var width = $('#myselector').width(); 
    var height = $('#myselector').height(); 

    // set attrs and 'resume' force 
    svg.attr('width', width);
    svg.attr('height', height);
    force.size([width, height]).resume();
}

Dengan cara ini, Anda tidak membuat ulang grafik sepenuhnya, kami mengatur atribut dan menghitung ulang hal-hal yang diperlukan. Setidaknya ini berfungsi saat Anda menggunakan titik gravitasi. Saya tidak yakin apakah itu prasyarat untuk solusi ini. Adakah yang bisa mengkonfirmasi atau menyangkal?

Ceria, g

gavs
sumber
Ini bekerja dengan sempurna pada tata letak kekuatan saya yang memiliki sekitar 100 koneksi. Terima kasih.
tribe84
1

Bagi mereka yang menggunakan grafik gaya langsung di D3 v4 / v5, sizemetode ini tidak ada lagi. Sesuatu seperti yang berikut ini bekerja untuk saya (berdasarkan masalah github ini ):

simulation
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("x", d3.forceX(width / 2))
    .force("y", d3.forceY(height / 2))
    .alpha(0.1).restart();
Justin Lewis
sumber
1

Jika Anda ingin mengikat logika khusus untuk mengubah ukuran acara, saat ini Anda dapat mulai menggunakan API browser ResizeObserver untuk kotak pembatas dari SVGElement.
Ini juga akan menangani kasus ketika wadah diubah ukurannya karena perubahan ukuran elemen di dekatnya.
Ada polyfill untuk dukungan browser yang lebih luas.

Ini adalah cara kerjanya di komponen UI:

function redrawGraph(container, { width, height }) {
  d3
    .select(container)
    .select('svg')
    .attr('height', height)
    .attr('width', width)
    .select('rect')
    .attr('height', height)
    .attr('width', width);
}

// Setup observer in constructor
const resizeObserver = new ResizeObserver((entries, observer) => {
  for (const entry of entries) {
    // on resize logic specific to this component
    redrawGraph(entry.target, entry.contentRect);
  }
})

// Observe the container
const container = document.querySelector('.graph-container');
resizeObserver.observe(container)
.graph-container {
  height: 75vh;
  width: 75vw;
}

.graph-container svg rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 3px;
}
<script src="https://unpkg.com/[email protected]/dist/ResizeObserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<figure class="graph-container">
  <svg width="100" height="100">
    <rect x="0" y="0" width="100" height="100" />
  </svg>
</figure>

// unobserve in component destroy method
this.resizeObserver.disconnect()
Anbu Agarwal
sumber