簡體   English   中英

使 d3.js 可視化布局響應的最佳方法是什么?

[英]What's the best way to make a d3.js visualisation layout responsive?

假設我有一個構建 960 500 svg 圖形的直方圖腳本。 如何使這種響應式調整圖形寬度和高度是動態的?

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

完整示例直方圖要點是: https : //gist.github.com/993912

還有另一種不需要重新繪制圖形的方法,它涉及修改<svg>元素上的viewBoxpreserveAspectRatio屬性:

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

2015 年 11 月 24 日更新:大多數現代瀏覽器可以從viewBox推斷SVG 元素的縱橫比,因此您可能不需要保持圖表的大小是最新的。 如果您需要支持舊瀏覽器,您可以在窗口調整大小時調整元素大小,如下所示:

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

並且 svg 內容將自動縮放。 你可以看到這樣一個工作示例(有一些修改)這里:只是調整窗口大小或看看它是如何反應的底部右側窗格中。

尋找“響應式 SVG”,使 SVG 響應式非常簡單,您不必再擔心尺寸。

這是我如何做到的:

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

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

更多信息/教程:

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

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

我編寫了一個小要點來解決這個問題。

一般的解決方案是這樣的:

  1. 將腳本分解為計算和繪圖功能。
  2. 確保繪制函數動態繪制並由可視化寬度和高度變量驅動(最好的方法是使用 d3.scale api)
  3. 將繪圖綁定/鏈接到標記中的參考元素。 (我為此使用了 jquery,因此導入了它)。
  4. 如果它已經繪制,請記住將其刪除。 使用 jquery 從引用的元素中獲取維度。
  5. 將繪制函數綁定/鏈接到窗口調整大小函數。 向該鏈引入去抖動(超時)以確保我們僅在超時后重繪。

我還添加了縮小的 d3.js 腳本以提高速度。 要點在這里: https : //gist.github.com/2414111

jquery 參考回溯代碼:

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

去抖代碼:

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

享受!

不使用 ViewBox

以下是不依賴於使用viewBox的解決方案示例:

關鍵在於更新用於放置數據的比例尺范圍

首先,計算您的原始縱橫比:

var ratio = width / height;

然后,在每次調整大小時,更新xyrange

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

請注意,高度基於寬度和縱橫比,以便保持原始比例。

最后,“重繪”圖表——更新任何依賴於xy尺度的屬性:

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

請注意,在重新調整rects大小時,您可以使用y range的上限,而不是明確使用高度:

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

 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 ratio = width / height; var x = d3.scale.ordinal() .domain(histogram.map(function(d) { return dx; })) var y = d3.scale.linear() .domain([0, d3.max(histogram, function(d) { return dy; })]) var svg = d3.select("body").append("svg") .attr("width", "100%") .attr("height", height); var rects = svg.selectAll("rect").data(histogram); rects.enter().append("rect"); function redraw() { rects.attr("width", x.rangeBand()) .attr("x", function(d) { return x(dx); }) // .attr("y", function(d) { return height - y(dy); }) .attr("y", function(d) { return y.range()[1] - y(dy); }) .attr("height", function(d) { return y(dy); }); } function resize() { x.rangeRoundBands([0, window.innerWidth]); y.range([0, window.innerWidth / ratio]); svg.attr("height", window.innerHeight); } d3.select(window).on('resize', function() { resize(); redraw(); }) resize(); redraw();
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

肖恩艾倫的回答很棒。 但是您可能不想每次都這樣做。 如果你在vida.io上托管它,你會自動響應你的 svg 可視化。

您可以使用這個簡單的嵌入代碼獲得響應式 iframe:

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

披露:我在vida.io 上構建了此功能。

如果您通過c3.js使用 d3.js,則響應問題的解決方案非常簡單:

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

生成的 HTML 如下所示:

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

如果您使用的是像plottable.js這樣的d3 包裝器,請注意最簡單的解決方案可能是添加事件偵聽器,然后調用重繪函數(在 plottable.js 中redraw )。 在 plottable.js 的情況下,這將很好地工作(這種方法記錄不足):

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

如果人們仍在訪問這個問題 - 這對我有用:

  • 將 iframe 包含在 div 中,並使用 css 向該 div 添加 40% 的填充(百分比取決於您想要的縱橫比)。 然后將 iframe 本身的寬度和高度設置為 100%。

  • 在包含要加載到 iframe 中的圖表的 html 文檔中,將寬度設置為 svg 附加到的 div 的寬度(或主體的寬度),並將高度設置為寬度 * 縱橫比。

  • 編寫一個函數,在窗口調整大小時重新加載 iframe 內容,以便在人們旋轉手機時調整圖表的大小。

我網站上的示例: http : //dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

2016 年 12 月 30 日更新

我上面描述的方法有一些缺點,特別是它沒有考慮任何不屬於 D3 創建的 svg 的標題和說明的高度。 從那以后,我遇到了我認為更好的方法:

  • 將 D3 圖表的寬度設置為它所附加的 div 的寬度,並使用縱橫比相應地設置其高度;
  • 讓嵌入頁面使用 HTML5 的 postMessage 將其高度和 url 發送到父頁面;
  • 在父頁面上,使用 url 來標識相應的 iframe(如果頁面上有多個 iframe,則很有用)並將其高度更新為嵌入頁面的高度。

我網站上的示例: http : //dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution

D3 數據連接的基本原則之一是它是冪等的。 換句話說,如果您使用相同的數據重復評估數據連接,則呈現的輸出是相同的。 因此,只要您正確渲染圖表,注意您的進入、更新和退出選擇 - 當大小發生變化時,您要做的就是重新完整地渲染圖表。

您還應該做其他幾件事,其中之一是消除窗口調整大小處理程序的抖動以限制它。 此外,這應該通過測量包含元素來實現,而不是硬編碼寬度/高度。

作為替代方案,這里是使用d3fc呈現的圖表,它是一組正確處理數據連接的 D3 組件。 它還有一個笛卡爾圖,可以測量它包含的元素,從而可以輕松創建“響應式”圖表:

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

您可以在此代碼筆中看到它的運行情況:

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

您可以在其中調整窗口大小並驗證圖表是否正確重新渲染。

請注意,作為一個完整的披露,我是 d3fc 的維護者之一。

我會避免像瘟疫這樣的調整大小/刻度解決方案,因為它們效率低下並且可能會導致您的應用程序出現問題(例如,工具提示會重新計算它應該出現在窗口大小調整上的位置,然后片刻之后您的圖表也會調整大小並且頁面重新-布局,現在你的工具提示又錯了)。

您也可以使用保持其外觀的<canvas>元素在一些不能正確支持它的舊瀏覽器(如 IE11)中模擬此行為。

鑒於 960x540 是 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>

這里有很多復雜的答案。

基本上你需要做的就是放棄widthheight屬性,轉而使用viewBox屬性:

width = 500;
height = 500;

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

如果您有邊距,您可以將它們添加到寬度/高度中,然后在其后附加g並像往常一樣轉換它。

您還可以使用引導程序 3來調整可視化的大小。 例如,我們可以將HTML代碼設置為:

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

由於我的需要,我設置了固定高度,但您也可以保留自動大小。 "col-sm-6 col-md-4" 使 div 響應不同的設備。 您可以在http://getbootstrap.com/css/#grid-example-basic了解更多信息

我們可以在 id月視圖的幫助下訪問圖表。

d3代碼我就不贅述了,只輸入適應不同屏幕尺寸需要的部分。

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

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

通過獲取帶有 id 月視圖的 div 的寬度來設置寬度。

在我的情況下,高度不應包括整個區域。 我在欄上方還有一些文本,所以我也需要計算那個區域。 這就是為什么我用 id 響應文本標識文本區域的原因。 為了計算欄的允許高度,我從 div 的高度中減去了文本的高度。

這允許您擁有一個將采用所有不同屏幕/div 大小的欄。 這可能不是最好的方法,但它肯定能滿足我的項目需求。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM