Какой лучший способ сделать макет визуализации d3.js адаптивным? - PullRequest
212 голосов
/ 22 февраля 2012

Предположим, у меня есть скрипт гистограммы, который создает графику 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

Ответы [ 11 ]

304 голосов
/ 02 марта 2012

Есть еще один способ сделать это, который не требует перерисовки графика, и включает изменение атрибутов 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 будет автоматически масштабироваться. Вы можете увидеть рабочий пример этого (с некоторыми изменениями) здесь : просто измените размер окна или нижней правой панели, чтобы увидеть, как оно реагирует.

30 голосов
/ 22 сентября 2014

Ищите «отзывчивый 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

20 голосов
/ 23 февраля 2012

Я решил небольшой вопрос, чтобы решить эту проблему.

Общий шаблон решения таков:

  1. Разбейте скрипт на функции вычисления и рисования.
  2. Убедитесь, что функция рисования отрисовывается динамически и управляется переменные ширины и высоты визуализации (лучший способ сделать это использовать API d3.scale)
  3. Привязать / связать чертеж с ссылкой элемент в разметке. (Я использовал для этого jquery, поэтому импортировал его).
  4. Не забудьте удалить его, если он уже нарисован. Получить размеры от ссылочный элемент, использующий jquery.
  5. Привязать / связать функцию рисования с функция изменения размера окна. Введите debounce (тайм-аут) к этому цепь, чтобы убедиться, что мы перерисовываем только после тайм-аута.

Я также добавил минимизированный сценарий 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);

Наслаждайтесь!

4 голосов
/ 21 ноября 2015

без использования ViewBox

Вот пример решения, которое не основано на использовании viewBox:

Ключ в обновлении диапазона шкал , которые используются для размещения данных.

Сначала рассчитайте исходное соотношение сторон:

var ratio = width / height;

Затем при каждом изменении размера обновляйте range из x и y:

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 вы можете использовать верхнюю границу range из y, а не явно использовать высоту:

.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>
3 голосов
/ 30 декабря 2014

Если вы используете от d3.js до c3.js , решение проблемы отзывчивости довольно простое:

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

где сгенерированный HTML выглядит так:

<div id="chart">
    <svg>...</svg>
</div>
2 голосов
/ 26 апреля 2018

В случае, если вы используете d3-оболочку наподобие plottable.js , помните, что самым простым решением может быть добавление прослушивателя событий , а затемвызов функции redraw (redraw в plottable.js).В случае plottable.js это будет отлично работать (этот подход плохо документирован):

    window.addEventListener("resize", function() {
      table.redraw();
    });
2 голосов
/ 18 декабря 2014

Ответ Шона Аллена был великолепен. Но вы не можете делать это каждый раз. Если вы разместите его на 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 .

1 голос
/ 05 мая 2016

Если люди все еще посещают этот вопрос - вот что сработало для меня:

  • Заключите iframe в div и используйте css для добавления отступа, скажем, 40% к этому div (процент в зависимости от желаемого соотношения сторон). Затем установите ширину и высоту самого iframe на 100%.

  • В html-документе, содержащем диаграмму, которая будет загружена в iframe, задайте ширину шириной div, к которому добавляется svg (или шириной тела), и установите высоту width * аспект соотношение.

  • Напишите функцию, которая перезагружает содержимое iframe при изменении размера окна, чтобы адаптировать размер диаграммы, когда люди поворачивают свой телефон.

Пример здесь на моем сайте: http://dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

ОБНОВЛЕНИЕ 30 декабря 2016

Подход, который я описал выше, имеет некоторые недостатки, особенно в том, что он не учитывает высоту любых заголовков и подписей, которые не являются частью созданного D3 svg. С тех пор я столкнулся с тем, что считаю лучшим подходом:

  • Установите ширину диаграммы D3 равной ширине div, к которому она прикреплена, и используйте форматное соотношение, чтобы соответственно установить ее высоту;
  • Пусть встроенная страница отправит свою высоту и URL на родительскую страницу, используя postMessage HTML5;
  • На родительской странице используйте URL, чтобы определить соответствующий iframe (полезно, если на вашей странице более одного iframe) и обновите его высоту до высоты встроенной страницы.

Пример здесь на моем сайте: http://dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution

0 голосов
/ 11 октября 2017

Я бы не использовал такие решения, как чума, потому что они неэффективны и могут вызвать проблемы в вашем приложении (например, всплывающая подсказка пересчитывает позицию, которая должна появиться при изменении размера окна, затем через мгновение ваша диаграмма тоже изменяет размер иперекомпоновка страницы, и теперь ваша всплывающая подсказка снова неверна).

Вы можете смоделировать это поведение в некоторых старых браузерах, которые не поддерживают его должным образом, например, в IE11, используя элемент <canvas>, который поддерживает его аспект.

Учитывая 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>
0 голосов
/ 26 июня 2016

Одним из основных принципов объединения данных 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.

...