d3.js視覚化レイアウトをレスポンシブにする最良の方法は何ですか?


224

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です。


5
私はこの答えでは方法が簡単であることが判明:stackoverflow.com/a/11948988/511203
マイク・ゴルスキ

私が見つけた最も簡単な方法は、svgの幅と高さを100%にして、DOMコンテナー(divなど)にサイズを適用することです。stackoverflow.com
questions / 47076163 /…

回答:


316

グラフの再描画を必要としない別の方法があり、要素のviewBox属性とpreserveAspectRatio属性を変更する必要があります<svg>

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

更新日11/24/15:最新のブラウザのほとんどは、SVG要素のアスペクト比をから推測できるviewBoxため、チャートのサイズを最新に保つ必要がない場合があります。古いブラウザをサポートする必要がある場合は、ウィンドウのサイズが変更されたときに要素のサイズを変更できます。

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の内容は自動的にスケーリングされます。あなたは、(いくつかの変更を加えた)本の実施例を参照することができ、ここで:ちょうどそれがどのように反応するかを確認するために、ウィンドウまたは右下のペインのサイズを変更します。


1
私はこのアプローチが本当に好きです、2つの質問。1ビューボックスはブラウザ間で機能しますか?2.あなたの例では、円はサイズ変更されますが、ウィンドウに収まりません。SVGビューボックスまたはアスペクト比に問題があります。
マット

4
このアプローチが大好きです。上記が正しく機能するかどうかはわかりません。マットが言うように、円はサイズ変更されますが、グラフの左側から最初の円までの距離は、円と同じレートでサイズ変更されません。私はjsfiddleを少しいじってみましたが、Google Chromeを使用して期待どおりに機能させることができませんでした。viewBoxとpreserveAspectRatioはどのブラウザーと互換性がありますか?
Chris Withers 2012年

9
実際には、viewBoxとpreserveAspectRatioだけを幅の高さを親の100%に設定したSVGに設定し、親がブートストラップ/フレックスグリッドであることは、サイズ変更イベントを処理せずに機能します
Deepu

2
Deepuが正しいことを確認します。Bootstrap / flexグリッドで動作します。@Deepuに感謝します。
マイク・オコナー、

3
注:いくつかのd3チュートリアルでは、1。)htmlでsvgの幅と高さが設定され、2)視覚化はonload 関数呼び出しを介して行われます。SVGの幅と高さは、実際のHTMLに設定されている場合と、あなたは上記の技術を使用し、画像は、スケーラブルではありません。
SumNeuron 2017

34

「レスポンシブ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
私の場合、padding-bottom:100%を指定すると、コンテナの下部に追加のパディングが作成されるため、50%に変更して削除します。あなたのコードを使用して、svgを応答可能にしました。
V-SHY 2017年

20

これを解決するために小さな要点をコード化しました。

一般的なソリューションパターンは次のとおりです。

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

楽しい!


これは機能していないようです。修正した要点に含まれているhtmlファイルのウィンドウのサイズを変更しても、ウィンドウが小さくなるにつれて、ヒストグラムが途切れます...
Chris Withers

1
SVGはベクター形式です。仮想ビューボックスのサイズを設定して、実際のサイズに拡大縮小できます。JSを使用する必要はありません。
phil pirozhkov 2013

4

ViewBoxを使用しない場合

以下は、の使用に依存しないソリューションの例ですviewBox

キーはスケールの範囲を更新することですデータの配置に使用される。

最初に、元のアスペクト比を計算します。

var ratio = width / height;

その後、各リサイズに、更新rangeのをxy

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

高さは幅とアスペクト比に基づいているため、元の比率が維持されることに注意してください。

最後に、チャートを「再描画」します。xまたはyスケールのいずれかに依存する属性を更新します。

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場合、高さを明示的に使用するのrangeyはなく、の上限を使用できることに注意してください。

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


3

c3.jsからd3.jsを使用している場合、応答性の問題の解決策は非常に簡単です。

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

生成されたHTMLは次のようになります。

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

2

ショーン・アレンの答えは素晴らしかった。しかし、これを毎回実行したくない場合もあります。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構築します。


2

plottable.jsのようなd3ラッパーを使用している場合、最も簡単な解決策は、イベントリスナー追加してから再描画関数を呼び出すことです(plottable.js内)。plottable.jsの場合、これはうまく機能します(このアプローチは十分に文書化されていません):redraw

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

おそらく、再描画ができるだけ速く実行されないようにするには、この機能をデバウンスするのが最善でしょう
Ian

1

人々がまだこの質問を訪問している場合-ここに私のために働いたものがあります:

  • 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


0

D3データ結合の基本原則の1つは、べき等であることです。つまり、同じデータでデータ結合を繰り返し評価すると、レンダリングされた出力は同じになります。したがって、チャートを正しくレンダリングする限り、Enter、Update、Exitの選択に注意してください。サイズが変わったときに行う必要があるのは、チャート全体を再描画することだけです。

あなたがしなければならない他のいくつかのことがあります、1つはそれを抑制するためにウィンドウサイズ変更ハンドラをデバウンスすることです。また、幅/高さをハードコーディングするのではなく、これを含む要素を測定することによって達成する必要があります。

別の方法として、データ結合を正しく処理する一連のD3コンポーネントであるd3fcを使用してレンダリングされたグラフを次に示します。また、含まれる要素を測定するデカルトチャートもあり、「レスポンシブ」チャートを簡単に作成できます。

// 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のメンテナーの1人であることに注意してください。


回答を最新の状態に維持していただきありがとうございます。私はあなたのいくつかの投稿の最近の更新を見ました、そして私は本当に自分のコンテンツを再訪する人々に感謝します!ただし、注意が必要ですが、D3 v4で編集するため、このリビジョンは質問に完全には適合しません。一方、OPは明らかにv3を参照しており、将来の読者を混乱させる可能性があります。個人的には、私自身の答えとして、元のv3セクションを維持したままv4セクションを追加する傾向があります。v4は、両方の投稿に相互参照を追加することで、独自の回答に十分価値があると思います。しかし、それは私の2セントにすぎません...
高積雲

それは本当に良い点です!夢中になるのはとても簡単で、未来に焦点を合わせるだけで、何が新しいのか。最新のd3の質問から判断すると、多くの人がまだv3を使用していると思います。また、いくつかの違いがどれほど微妙であるかについてもイライラします。私は私の編集を再訪します:-)
ColinE 2016

0

ペストのようなサイズ変更/ティックソリューションは非効率的であり、アプリで問題を引き起こす可能性があるため、回避します(たとえば、ツールチップがウィンドウのサイズ変更で表示される位置を再計算し、しばらくしてからグラフもサイズ変更され、ページがサイズ変更されます)。レイアウト、そして今あなたのツールチップは再び間違っています)。

<canvas>アスペクトを維持する要素を使用して、IE11のように正しく動作しない一部の古いブラウザでこの動作をシミュレートできます。

16:9のアスペクトである960x540の場合:

<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

ここにはたくさんの複雑な答えがあります。

基本的にあなたがする必要があるのはwidthheight属性を優先してと属性を捨てることviewBoxです:

width = 500;
height = 500;

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

マージンがある場合は、そこに幅/高さを追加し、gその後に追加するだけで、通常のように変換できます。


-1

ブートストラップ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>

必要に応じて高さを固定しましたが、サイズをautoのままにすることもできます。「col-sm-6 col-md-4」は、divをさまざまなデバイスに対して応答可能にします。詳細については、http://getbootstrap.com/css/#grid-example-basicをご覧ください。

id month-viewを使用してグラフにアクセスできます。

d3コードについては詳しく説明しません。さまざまな画面サイズに適応するために必要な部分のみを入力します。

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

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

幅は、idがmonth-viewのdivの幅を取得することによって設定されます。

私の場合、高さは領域全体を含むべきではありません。バーの上にテキストもあるので、その面積も計算する必要があります。これが、テキストの領域をid responsivetextで識別した理由です。バーの許容高さを計算するために、divの高さからテキストの高さを引きました。

これにより、すべての異なる画面/ divサイズを採用するバーを作成できます。それは最善の方法ではないかもしれませんが、私のプロジェクトのニーズにはきっとうまくいきます。


1
こんにちは、私はこれを試しましたが、SVGコンテンツはカレンダーやグラフのようにサイズ変更されません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.