Thay đổi kích thước svg khi cửa sổ được thay đổi kích thước trong d3.js


183

Tôi đang vẽ một biểu đồ phân tán với d3.js. Với sự trợ giúp của câu hỏi này:
Nhận kích thước của màn hình, trang web hiện tại và cửa sổ trình duyệt

Tôi đang sử dụng câu trả lời này:

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;

Vì vậy, tôi có thể điều chỉnh cốt truyện của mình với cửa sổ của người dùng như thế này:

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

Bây giờ tôi muốn một cái gì đó quan tâm đến việc thay đổi kích thước cốt truyện khi người dùng thay đổi kích thước cửa sổ.

PS: Tôi không sử dụng jQuery trong mã của mình.


Câu trả lời:


294

Hãy tìm 'SVG đáp ứng' khá đơn giản để tạo một SVG đáp ứng và bạn không phải lo lắng về kích thước nữa.

Đây là cách tôi đã làm nó:

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>

Ghi chú: Mọi thứ trong ảnh SVG sẽ chia tỷ lệ với chiều rộng cửa sổ. Điều này bao gồm chiều rộng nét và kích thước phông chữ (ngay cả những người được đặt bằng CSS). Nếu điều này là không mong muốn, có nhiều giải pháp thay thế liên quan dưới đây.

Thêm thông tin / hướng dẫn:

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

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


8
Điều này thanh lịch hơn nhiều và cũng sử dụng cách tiếp cận mở rộng vùng chứa trong mọi bộ công cụ của nhà phát triển mặt trước (có thể được sử dụng để nhân rộng mọi thứ theo tỷ lệ khung hình cụ thể). Nên là câu trả lời hàng đầu.
kontur

13
Chỉ cần thêm vào điều này: viewBoxThuộc tính phải được đặt thành chiều cao và chiều rộng có liên quan của lô SVG của bạn: .attr("viewBox","0 0 " + width + " " + height) nơi widthheightđược xác định ở nơi khác. Đó là, trừ khi bạn muốn SVG của bạn được cắt bằng hộp xem. Bạn cũng có thể muốn cập nhật padding-bottomtham số trong css của mình để khớp với tỷ lệ khung hình của phần tử svg của bạn. Phản ứng tuyệt vời mặc dù - rất hữu ích, và làm việc một điều trị. Cảm ơn!
Andrew Guy

13
Dựa trên cùng một ý tưởng, sẽ rất thú vị khi đưa ra một câu trả lời không liên quan đến việc duy trì tỷ lệ khung hình. Câu hỏi là về việc có một phần tử svg luôn che toàn bộ cửa sổ khi thay đổi kích thước, trong hầu hết các trường hợp có nghĩa là tỷ lệ khung hình khác nhau.
Nicolas Le Thierry d'Enơcanh

37
Chỉ là một lưu ý liên quan đến UX: Đối với một số trường hợp sử dụng, việc xử lý biểu đồ d3 dựa trên SVG của bạn giống như một tài sản có tỷ lệ khung hình cố định là không lý tưởng. Các biểu đồ hiển thị văn bản, hoặc động, hoặc nói chung cảm thấy giống như Giao diện người dùng phù hợp hơn là một tài sản, có nhiều thứ hơn để đạt được bằng cách hiển thị lại với kích thước được cập nhật. Ví dụ: viewBox buộc phông chữ trục và dấu tick để tăng / giảm tỷ lệ, có thể trông khó xử so với văn bản html. Ngược lại, kết xuất lại cho phép biểu đồ giữ cho nhãn của nó có kích thước phù hợp, cộng với nó cung cấp cơ hội để thêm hoặc xóa dấu vết.
Karim Hernandez

1
Tại sao top: 10px;? Có vẻ không chính xác cho tôi và cung cấp bù đắp. Đưa về 0px hoạt động tốt.
TimZaman

43

Sử dụng 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/


11
Đây không phải là một câu trả lời hay vì nó liên quan đến việc tham gia các sự kiện DOM ... Một giải pháp tốt hơn nhiều là chỉ sử dụng d3 và css
alem0lars

@ alem0lars đủ vui, phiên bản css / d3 không hoạt động với tôi và phiên bản này đã làm ...
Christian

32

CẬP NHẬT chỉ sử dụng cách mới từ @cminatti


câu trả lời cũ cho mục đích lịch sử

IMO tốt hơn là sử dụng select () và on () vì theo cách đó bạn có thể có nhiều trình xử lý sự kiện thay đổi kích thước ... đừng quá điên rồ

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/


Tôi không thể đồng ý ít hơn. Phương pháp của cminatti sẽ hoạt động trên tất cả các tệp d3js mà chúng tôi xử lý. Phương pháp chuyển đổi với thay đổi kích thước trên mỗi biểu đồ là quá mức cần thiết. Ngoài ra, nó cần phải được viết lại cho mọi biểu đồ có thể mà chúng ta có thể nghĩ ra. Phương pháp được đề cập ở trên hoạt động cho tất cả mọi thứ. Tôi đã thử 4 công thức nấu ăn bao gồm loại tải lại mà bạn đã đề cập và không có công thức nào trong số chúng hoạt động cho nhiều lô khu vực như loại được sử dụng trong lập thể.
Eamonn Kenny

1
@EamonnKenny Tôi không thể đồng ý với bạn nhiều hơn. Câu trả lời khác 7 tháng trong tương lai là vượt trội :)
slf 15/03/2016

Đối với phương pháp này: "d3.select (window) .on" Tôi không thể tìm thấy "d3.select (window) .off" hoặc "d3.select (window) .unbind"
sea-kg


Câu trả lời này không có nghĩa là kém hơn. Câu trả lời của họ sẽ chia tỷ lệ mọi khía cạnh của biểu đồ (bao gồm dấu tick, trục, đường và chú thích văn bản) có thể trông khó xử, ở một số kích thước màn hình, xấu ở những kích thước khác và không thể đọc được ở kích thước cực lớn. Tôi thấy tốt hơn khi thay đổi kích thước bằng cách sử dụng phương pháp này (hoặc, cho một giải pháp hiện đại hơn, phương pháp @Angu Agarwal).
Rúnar Berg

12

Thật là xấu xí nếu mã thay đổi kích thước gần như là mã để xây dựng biểu đồ ở vị trí đầu tiên. Vì vậy, thay vì thay đổi kích thước mọi yếu tố của biểu đồ hiện có, tại sao không chỉ cần tải lại nó? Đây là cách nó làm việc cho tôi:

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

Dòng quan trọng là d3.select('svg').remove();. Nếu không, mỗi lần thay đổi kích thước sẽ thêm một phần tử SVG khác bên dưới phần tử trước đó.


Điều này sẽ giết chết hoàn toàn hiệu suất nếu bạn vẽ lại toàn bộ thành phần trên mỗi sự kiện thay đổi kích thước cửa sổ
sdemurjian

2
Nhưng điều này là chính xác: Khi bạn kết xuất, bạn có thể xác định xem bạn có nên chèn trục, ẩn một chú giải, làm những việc khác dựa trên nội dung có sẵn hay không. Sử dụng một giải pháp CSS / khung nhìn hoàn toàn, tất cả những gì nó làm là làm cho hình ảnh trực quan trông bị cắt xén. Tốt cho hình ảnh, xấu cho dữ liệu.
Chris Knoll

8

Trong bố trí lực lượng, chỉ cần đặt các thuộc tính 'chiều cao' và 'chiều rộng' sẽ không hoạt động để căn giữa / di chuyển cốt truyện vào thùng chứa svg. Tuy nhiên, có một câu trả lời rất đơn giản phù hợp với Bố cục lực lượng được tìm thấy ở đây . Tóm tắt:

Sử dụng cùng (bất kỳ) sự kiện bạn muốn.

window.on('resize', resize);

Sau đó, giả sử bạn có các biến Svg & lực lượng:

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

Theo cách này, bạn không kết xuất lại biểu đồ hoàn toàn, chúng tôi đặt các thuộc tính và d3 tính lại mọi thứ khi cần thiết. Điều này ít nhất hoạt động khi bạn sử dụng một điểm trọng lực. Tôi không chắc đó có phải là điều kiện tiên quyết cho giải pháp này không. Bất cứ ai có thể xác nhận hay từ chối ?

Chúc mừng, g


Điều này hoạt động hoàn hảo trên bố trí lực lượng của tôi có khoảng 100 kết nối. Cảm ơn.
lạc84

1

Đối với những người sử dụng biểu đồ hướng lực trong D3 v4 / v5, sizephương thức này không còn tồn tại nữa. Một cái gì đó giống như sau đã làm việc cho tôi (dựa trên vấn đề github này ):

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

1

Nếu bạn muốn liên kết logic tùy chỉnh để thay đổi kích thước sự kiện, hiện tại bạn có thể bắt đầu sử dụng API trình duyệt ResizeObserver cho hộp giới hạn của một SVGEuity.
Điều này cũng sẽ xử lý trường hợp khi container được thay đổi kích thước do thay đổi kích thước phần tử gần đó.
Có một polyfill để hỗ trợ trình duyệt rộng hơn.

Đây là cách nó có thể hoạt động trong thành phần 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/resize-observer-polyfill@1.5.1/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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.