Cách tốt nhất để làm cho bố cục trực quan hóa d3.js đáp ứng là gì?


224

Giả sử tôi có một kịch bản biểu đồ xây dựng đồ họa 960 500 svg. Làm cách nào để tôi thực hiện phản hồi này để thay đổi kích thước chiều rộng và chiều cao đồ họa là động?

<script> 

var n = 10000, // number of trials
    m = 10,    // number of random variables
    data = [];

// Generate an Irwin-Hall distribution.
for (var i = 0; i < n; i++) {
  for (var s = 0, j = 0; j < m; j++) {
    s += Math.random();
  }
  data.push(s);
}

var histogram = d3.layout.histogram()
    (data);

var width = 960,
    height = 500;

var x = d3.scale.ordinal()
    .domain(histogram.map(function(d) { return d.x; }))
    .rangeRoundBands([0, width]);

var y = d3.scale.linear()
    .domain([0, d3.max(histogram.map(function(d) { return d.y; }))])
    .range([0, height]);

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

svg.selectAll("rect")
    .data(histogram)
  .enter().append("rect")
    .attr("width", x.rangeBand())
    .attr("x", function(d) { return x(d.x); })
    .attr("y", function(d) { return height - y(d.y); })
    .attr("height", function(d) { return y(d.y); });

svg.append("line")
    .attr("x1", 0)
    .attr("x2", width)
    .attr("y1", height)
    .attr("y2", height);

</script> 

Ví dụ đầy đủ về biểu đồ chính là: https://gist.github.com/993912


5
Tôi tìm thấy phương pháp trong câu trả lời này dễ dàng hơn: stackoverflow.com/a/11948988/511203
Mike Gorski

cách đơn giản nhất mà tôi tìm thấy là tạo chiều rộng và chiều cao của
Svg

Câu trả lời:


316

Có một cách khác để làm điều này mà không yêu cầu vẽ lại đồ thị, và nó liên quan đến sửa đổi viewBoxpreserveAspectRatio thuộc tính trên <svg>phần tử:

<svg id="chart" width="960" height="500"
  viewBox="0 0 960 500"
  preserveAspectRatio="xMidYMid meet">
</svg>

Cập nhật 24/11/15 : hầu hết các trình duyệt hiện đại có thể suy ra tỷ lệ khung hình của các yếu tố SVG từ viewBox, do đó bạn có thể không cần phải cập nhật kích thước của biểu đồ. Nếu bạn cần hỗ trợ các trình duyệt cũ hơn, bạn có thể thay đổi kích thước phần tử của mình khi cửa sổ thay đổi kích thước như vậy:

var aspect = width / height,
    chart = d3.select('#chart');
d3.select(window)
  .on("resize", function() {
    var targetWidth = chart.node().getBoundingClientRect().width;
    chart.attr("width", targetWidth);
    chart.attr("height", targetWidth / aspect);
  });

Và các nội dung svg sẽ được thu nhỏ tự động. Bạn có thể xem một ví dụ hoạt động về điều này (với một số sửa đổi) ở đây : chỉ cần thay đổi kích thước cửa sổ hoặc khung bên phải phía dưới để xem nó phản ứng như thế nào.


1
Tôi thực sự thích cách tiếp cận này shawn, 2 câu hỏi. 1 viewbox có hoạt động trên trình duyệt không? 2. Trong ví dụ của bạn, các vòng tròn thay đổi kích thước nhưng không vừa trong cửa sổ. Có điều gì đó không ổn với hộp xem svg hoặc tỷ lệ khung hình.
Matt Alcock

4
Yêu cách tiếp cận này, không chắc chắn các hoạt động trên chính xác. Như Matt nói, các vòng tròn có kích thước lại nhưng khoảng cách từ bên trái của biểu đồ đến vòng tròn đầu tiên không thay đổi kích thước với cùng tốc độ với các vòng tròn. Tôi đã cố gắng thay đổi một chút công bằng trong jsfiddle nhưng không thể làm cho nó hoạt động như mong đợi khi sử dụng Google Chrome. Những trình duyệt nào là viewBox và reservedAspectRatio tương thích với?
Chris Withers

9
Trên thực tế, chỉ thiết lập viewBox và reservedAspectRatio thành một SVG với chiều cao được đặt thành 100% của cha mẹ và cha mẹ là một lưới bootstrap / flex hoạt động với tôi mà không xử lý sự kiện thay đổi kích thước
Deepu 10/12/14

2
Khẳng định rằng Deepu là chính xác. Hoạt động với lưới Bootstrap / flex. Cảm ơn @Deepu.
Mike O'Connor

3
Lưu ý: trong một số hướng dẫn d3 1.) trong html, chiều rộng và chiều cao của Svg được đặt và 2.) trực quan hóa được thực hiện thông qua một onload lệnh gọi hàm. Nếu chiều rộng và chiều cao của Svg được đặt trong html thực tế bạn sử dụng kỹ thuật trên, hình ảnh sẽ không thể mở rộng được.
SumNeuron

34

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")
   .classed("svg-container", true) //container class to make it responsive
   .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); 

Mã CSS:

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

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

http://demosthenes.info/blog/744/Make-SVG-Responsive

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


1
Trong trường hợp của tôi, phần đệm dưới cùng: 100% sẽ làm cho thùng chứa của tôi có phần đệm thêm ở phía dưới, do đó tôi đã thay đổi nó thành 50% để loại bỏ nó. Đã sử dụng mã của bạn và thành công làm cho Svg đáp ứng, cảm ơn.
V-SHY

20

Tôi đã mã hóa một ý chính nhỏ để giải quyết điều này.

Mẫu giải pháp chung là đây:

  1. Đột phá kịch bản thành các chức năng tính toán và vẽ.
  2. Đảm bảo chức năng vẽ vẽ linh hoạt và được điều khiển bởi các biến chiều rộng và chiều cao trực quan (Cách tốt nhất để làm điều này là sử dụng api d3.scale)
  3. Ràng buộc / xâu chuỗi bản vẽ với một yếu tố tham chiếu trong đánh dấu. (Tôi đã sử dụng jquery cho việc này, vì vậy đã nhập nó).
  4. Nhớ loại bỏ nó nếu nó đã được vẽ. Lấy kích thước từ phần tử được tham chiếu bằng jquery.
  5. Ràng buộc / chuỗi chức năng vẽ vào chức năng thay đổi kích thước cửa sổ. Giới thiệu một bản sửa lỗi (hết thời gian) cho chuỗi này để đảm bảo chúng tôi chỉ vẽ lại sau khi hết thời gian.

Tôi cũng đã thêm tập lệnh d3.js rút gọn cho tốc độ. Ý chính ở đây: https://gist.github.com/2414111

mã tham chiếu jquery trở lại:

$(reference).empty()
var width = $(reference).width();

Mã báo cáo:

var debounce = function(fn, timeout) 
{
  var timeoutID = -1;
  return function() {
     if (timeoutID > -1) {
        window.clearTimeout(timeoutID);
     }
   timeoutID = window.setTimeout(fn, timeout);
  }
};

var debounced_draw = debounce(function() {
    draw_histogram(div_name, pos_data, neg_data);
  }, 125);

 $(window).resize(debounced_draw);

Thưởng thức!


Điều này dường như không hoạt động; nếu tôi thay đổi kích thước cửa sổ của tệp html có trong ý chính đã sửa, biểu đồ vẫn bị cắt nhỏ khi cửa sổ trở nên nhỏ hơn ...
Chris Withers

1
SVG là một định dạng vector. Bạn có thể đặt bất kỳ kích thước hộp xem ảo nào và sau đó chia tỷ lệ thành kích thước thực. Không cần sử dụng JS.
phil pirozhkov

4

Không sử dụng ViewBox

Dưới đây là một ví dụ về giải pháp không dựa vào việc sử dụng viewBox:

Điều quan trọng là cập nhật phạm vi của các thang đo được sử dụng để đặt dữ liệu.

Đầu tiên, tính tỷ lệ khung hình gốc của bạn:

var ratio = width / height;

Sau đó, trên mỗi thay đổi kích thước, cập nhật rangecủa xy:

function resize() {
  x.rangeRoundBands([0, window.innerWidth]);
  y.range([0, window.innerWidth / ratio]);
  svg.attr("height", window.innerHeight);
}

Lưu ý rằng chiều cao dựa trên chiều rộng và tỷ lệ khung hình, để tỷ lệ ban đầu của bạn được duy trì.

Cuối cùng, "vẽ lại" biểu đồ - cập nhật bất kỳ thuộc tính nào phụ thuộc vào một trong hai xhoặc ytỷ lệ:

function redraw() {
    rects.attr("width", x.rangeBand())
      .attr("x", function(d) { return x(d.x); })
      .attr("y", function(d) { return y.range()[1] - y(d.y); })
      .attr("height", function(d) { return y(d.y); });
}

Lưu ý rằng khi định cỡ lại, rectsbạn có thể sử dụng giới hạn trên rangecủa ythay vì sử dụng chiều cao một cách rõ ràng:

.attr("y", function(d) { return y.range()[1] - y(d.y); })


3

Nếu bạn đang sử dụng d3.js thông qua c3.js , giải pháp cho vấn đề phản hồi khá đơn giản:

var chart = c3.generate({bindTo:"#chart",...});
chart.resize($("#chart").width(),$("#chart").height());

HTML được tạo như thế nào:

<div id="chart">
    <svg>...</svg>
</div>

2

Câu trả lời của Shawn Allen thật tuyệt. Nhưng bạn có thể không muốn làm điều này mỗi lần. Nếu bạn lưu trữ nó trên vida.io , bạn sẽ nhận được phản hồi tự động cho trực quan hóa của bạn.

Bạn có thể nhận iframe phản hồi với mã nhúng đơn giản này:

<div id="vida-embed">
<iframe src="http://embed.vida.io/documents/9Pst6wmB83BgRZXgx" width="auto" height="525" seamless frameBorder="0" scrolling="no"></iframe>
</div>

#vida-embed iframe {
  position: absolute;
  top:0;
  left: 0;
  width: 100%;
  height: 100%;
}

http://jsfiddle.net/dnprock/npxp3v9d/1/

Tiết lộ: Tôi xây dựng tính năng này tại vida.io .


2

Trong trường hợp bạn đang sử dụng trình bao bọc d3 như plottable.js , hãy lưu ý rằng giải pháp đơn giản nhất có thể là thêm trình lắng nghe sự kiện và sau đó gọi hàm vẽ lại ( redrawtrong plottable.js). Trong trường hợp plottable.js, nó sẽ hoạt động rất tốt (cách tiếp cận này được ghi lại kém):

    window.addEventListener("resize", function() {
      table.redraw();
    });

Có lẽ tốt nhất là nên gỡ bỏ chức năng này để tránh bắn lại vẽ nhanh nhất có thể
Ian

1

Trong trường hợp mọi người vẫn truy cập câu hỏi này - đây là những gì làm việc cho tôi:

  • Gửi kèm iframe trong div và sử dụng css để thêm phần đệm, giả sử, 40% cho div đó (tỷ lệ phần trăm tùy thuộc vào tỷ lệ khung hình bạn muốn). Sau đó, đặt cả chiều rộng và chiều cao của iframe thành 100%.

  • Trong tài liệu html chứa biểu đồ sẽ được tải trong iframe, đặt chiều rộng thành chiều rộng của div mà svg được gắn vào (hoặc theo chiều rộng của thân) và đặt tỷ lệ chiều cao thành chiều rộng *.

  • Viết hàm tải lại nội dung iframe khi thay đổi kích thước cửa sổ, để điều chỉnh kích thước của biểu đồ khi mọi người xoay điện thoại của họ.

Ví dụ ở đây trên trang web của tôi: http://dirkmjk.nl/en/2016/05/embpping-d3js-charts-responsive-website

CẬP NHẬT ngày 30 tháng 12 năm 2016

Cách tiếp cận tôi mô tả ở trên có một số nhược điểm, đặc biệt là nó không tính đến chiều cao của bất kỳ tiêu đề và chú thích nào không phải là một phần của Svg do D3 tạo ra. Kể từ khi tôi bắt gặp những gì tôi nghĩ là một cách tiếp cận tốt hơn:

  • Đặt chiều rộng của biểu đồ D3 thành chiều rộng của div được đính kèm và sử dụng tỷ lệ khung hình để đặt chiều cao của nó cho phù hợp;
  • Yêu cầu trang nhúng gửi chiều cao và url của nó đến trang mẹ bằng cách sử dụng postMessage của HTML5;
  • Trên trang mẹ, sử dụng url để xác định iframe tương ứng (hữu ích nếu bạn có nhiều iframe trên trang của mình) và cập nhật chiều cao của nó lên chiều cao của trang được nhúng.

Ví dụ ở đây trên trang web của tôi: http://dirkmjk.nl/en/2016/12/embpping-d3js-charts-responsive-website-better-solution


0

Một trong những nguyên tắc cơ bản của tham gia dữ liệu D3 là nó không hoạt động. Nói cách khác, nếu bạn liên tục đánh giá việc nối dữ liệu với cùng một dữ liệu, thì kết quả đầu ra được hiển thị là như nhau. Do đó, miễn là bạn hiển thị biểu đồ của mình một cách chính xác, hãy cẩn thận với các lựa chọn nhập, cập nhật và thoát - tất cả những gì bạn phải làm khi thay đổi kích thước, sẽ hiển thị lại toàn bộ biểu đồ.

Có một vài điều khác bạn nên làm, một là gỡ bỏ trình xử lý thay đổi kích thước cửa sổ để điều chỉnh nó. Ngoài ra, thay vì chiều rộng / chiều cao mã hóa cứng, điều này cần đạt được bằng cách đo phần tử chứa.

Thay vào đó, đây là biểu đồ của bạn được hiển thị bằng d3fc , một tập hợp các thành phần D3 xử lý chính xác các phép nối dữ liệu. Nó cũng có một biểu đồ cartesian đo lường nó có chứa phần tử giúp dễ dàng tạo các biểu đồ 'đáp ứng':

// create some test data
var data = d3.range(50).map(function(d) {
  return {
    x: d / 4,
    y: Math.sin(d / 4),
    z: Math.cos(d / 4) * 0.7
  };
});

var yExtent = fc.extentLinear()
  .accessors([
    function(d) { return d.y; },
    function(d) { return d.z; }
  ])
  .pad([0.4, 0.4])
  .padUnit('domain');

var xExtent = fc.extentLinear()
  .accessors([function(d) { return d.x; }]);

// create a chart
var chart = fc.chartSvgCartesian(
    d3.scaleLinear(),
    d3.scaleLinear())
  .yDomain(yExtent(data))
  .yLabel('Sine / Cosine')
  .yOrient('left')
  .xDomain(xExtent(data))
  .xLabel('Value')
  .chartLabel('Sine/Cosine Line/Area Chart');

// create a pair of series and some gridlines
var sinLine = fc.seriesSvgLine()
  .crossValue(function(d) { return d.x; })
  .mainValue(function(d) { return d.y; })
  .decorate(function(selection) {
    selection.enter()
      .style('stroke', 'purple');
  });

var cosLine = fc.seriesSvgArea()
  .crossValue(function(d) { return d.x; })
  .mainValue(function(d) { return d.z; })
  .decorate(function(selection) {
    selection.enter()
      .style('fill', 'lightgreen')
      .style('fill-opacity', 0.5);
  });

var gridlines = fc.annotationSvgGridline();

// combine using a multi-series
var multi = fc.seriesSvgMulti()
  .series([gridlines, sinLine, cosLine]);

chart.plotArea(multi);

// render
d3.select('#simple-chart')
  .datum(data)
  .call(chart);

Bạn có thể thấy nó hoạt động trong codepen này:

https://codepen.io/ColinEberhardt/pen/dOBvOy

nơi bạn có thể thay đổi kích thước cửa sổ và xác minh rằng biểu đồ được kết xuất lại chính xác.

Xin lưu ý, như một tiết lộ đầy đủ, tôi là một trong những người duy trì d3fc.


Cảm ơn đã giữ cho câu trả lời của bạn cập nhật! Tôi đã thấy các bản cập nhật gần đây cho một số bài đăng của bạn và tôi thực sự đánh giá cao mọi người xem lại nội dung của chính họ! Tuy nhiên, như một lời cảnh báo, bản sửa đổi này không hoàn toàn phù hợp với câu hỏi nữa, bởi vì bạn chỉnh sửa trong D3 v4 , trong khi OP đề cập rõ ràng đến v3 , có thể gây nhầm lẫn cho độc giả trong tương lai. Cá nhân, đối với câu trả lời của riêng tôi, tôi có xu hướng thêm phần v4 trong khi vẫn giữ phần v3 gốc . Tôi nghĩ rằng v4 có thể có giá trị câu trả lời của riêng mình thêm một tài liệu tham khảo lẫn nhau cho cả hai bài. Nhưng đó chỉ là hai xu của tôi ...
altocumulus

Đó là một điểm thực sự tốt! Thật dễ dàng để được mang đi và chỉ tập trung vào tương lai, và những gì mới. Đánh giá bằng các câu hỏi d3 mới nhất Tôi nghĩ rằng rất nhiều người vẫn đang sử dụng v3. Nó cũng bực bội làm thế nào tinh tế một số khác biệt là! Tôi sẽ xem lại các chỉnh sửa của mình :-)
ColinE

0

Tôi sẽ tránh thay đổi kích thước / đánh dấu các giải pháp như bệnh dịch vì chúng không hiệu quả và có thể gây ra sự cố trong ứng dụng của bạn (ví dụ: chú giải công cụ tính lại vị trí nó sẽ xuất hiện trên cửa sổ thay đổi kích thước, sau đó một lúc sau biểu đồ của bạn cũng thay đổi kích thước và trang sẽ thay đổi kích thước bố trí và bây giờ tooltip của bạn lại bị sai).

Bạn có thể mô phỏng hành vi này trong một số trình duyệt cũ không hỗ trợ chính xác như IE11 bằng cách sử dụng <canvas> yếu tố duy trì khía cạnh của nó.

Cho 960x540, một khía cạnh của 16: 9:

<div style="position: relative">
  <canvas width="16" height="9" style="width: 100%"></canvas>
  <svg viewBox="0 0 960 540" preserveAspectRatio="xMidYMid meet" style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; -webkit-tap-highlight-color: transparent;">
  </svg>
</div>

0

Rất nhiều câu trả lời phức tạp ở đây.

Về cơ bản, tất cả những gì bạn cần làm là bỏ các thuộc tính widthheightthuộc tính có lợi cho viewBoxthuộc tính:

width = 500;
height = 500;

const svg = d3
  .select("#chart")
  .append("svg")
  .attr("viewBox", `0 0 ${width} ${height}`)

Nếu bạn có lề, bạn chỉ cần thêm chúng vào chiều rộng / chiều cao sau đó chỉ cần nối thêm gsau đó và biến đổi nó như bình thường.


-1

Bạn cũng có thể sử dụng bootstrap 3 để điều chỉnh kích thước của trực quan hóa. Ví dụ: chúng ta có thể thiết lập mã HTML là:

<div class="container>
<div class="row">

<div class='col-sm-6 col-md-4' id="month-view" style="height:345px;">
<div id ="responsivetext">Something to write</div>
</div>

</div>
</div>

Tôi đã thiết lập một chiều cao cố định vì nhu cầu của tôi, nhưng bạn cũng có thể để kích thước tự động. "Col-sm-6 col-md-4" giúp div đáp ứng cho các thiết bị khác nhau. Bạn có thể tìm hiểu thêm tại http://getbootstrap.com/css/#grid-example-basic

Chúng tôi có thể truy cập biểu đồ với sự trợ giúp của chế độ xem tháng id .

Tôi sẽ không đi sâu vào chi tiết về mã d3, tôi sẽ chỉ nhập phần cần thiết để thích ứng với các kích thước màn hình khác nhau.

var width = document.getElementById('month-view').offsetWidth;

var height = document.getElementById('month-view').offsetHeight - document.getElementById('responsivetext2').offsetHeight;

Độ rộng được đặt bằng cách lấy chiều rộng của div với chế độ xem theo tháng id.

Chiều cao trong trường hợp của tôi không nên bao gồm toàn bộ khu vực. Tôi cũng có một số văn bản trên thanh vì vậy tôi cũng cần tính diện tích đó. Đó là lý do tại sao tôi xác định khu vực của văn bản với id responsivetext. Để tính chiều cao cho phép của thanh, tôi đã trừ chiều cao của văn bản từ chiều cao của div.

Điều này cho phép bạn có một thanh sẽ chấp nhận tất cả các kích thước màn hình / div khác nhau. Nó có thể không phải là cách tốt nhất để làm điều đó, nhưng nó chắc chắn hoạt động cho nhu cầu của dự án của tôi.


1
Xin chào, tôi đã thử điều này, nhưng nội dung svg không thay đổi kích thước, như lịch hoặc biểu đồ.
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.