web-dev-qa-db-ja.com

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

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> 

完全なヒストグラムGistの例: https://Gist.github.com/993912

211
Matt Alcock

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

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

Update 11/24/15:最新のブラウザのほとんどは アスペクト比を推測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の内容は自動的にスケーリングされます。これの実際の例を見ることができます(いくつかの変更を加えて) here :ウィンドウまたは右下のペインのサイズを変更して、どのように反応するかを確認します。

303
Shawn Allen

「レスポンシブ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

30
cminatti

これを解決するために小さな要点をコーディングしました。

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

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

楽しい!

20
Matt Alcock

ViewBoxを使用しない場合

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

重要なのは、データの配置に使用されるscalesrangeを更新することです。

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

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); })
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 d.x;
  }))

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

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(d.x);
    })
    // .attr("y", function(d) { return height - y(d.y); })
    .attr("y", function(d) {
      return y.range()[1] - y(d.y);
    })
    .attr("height", function(d) {
      return y(d.y);
    });
}

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>
4
Paul Murray

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

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

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

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

plottable.js のようなd3 wrapperを使用している場合、解決策は、イベントリスナーを追加してから、redraw関数( plottable.jsのredraw)。 plottable.jsの場合、これは非常にうまく機能します(このアプローチの文書化は不十分です)。

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

ショーン・アレンの答えは素晴らしかった。ただし、毎回これを行う必要はありません。ホストを 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
Phuoc Do

人々がまだこの質問を訪問している場合-私のために働いたのは次のとおりです:

  • Iframeをdivで囲み、cssを使用して、そのdivにたとえば40%のパディングを追加します(パーセンテージは希望するアスペクト比に依存します)。次に、iframe自体の幅と高さの両方を100%に設定します。

  • Iframeに読み込まれるチャートを含むHTMLドキュメントで、svgが追加されるdivの幅(または本文の幅)にwidthを設定し、高さをwidth *アスペクト比に設定します。

  • ウィンドウのサイズ変更時に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

1
dirkmjk

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

他にもやるべきことがいくつかあります。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のメンテナーの一人です。

0
ColinE

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

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

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
Dominic