Подтвердить что ты не робот

Какой лучший способ сделать макет визуализации d3.js отзывчивым?

Предположим, у меня есть гистограмма script, которая строит графику svg размером 960 500. Как это сделать так, чтобы изменить размер графической ширины и высоты динамически?

<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

4b9b3361

Ответ 1

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

Ответ 2

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

Ответ 3

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

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

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

Я также добавил миниатюрный d3.js script для скорости. Суть здесь: 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

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

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

где сгенерированный HTML выглядит следующим образом:

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

Ответ 5

Без использования 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>

Ответ 6

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

Ответ 7

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

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

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

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

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

ОБНОВЛЕНИЕ 30 Дек 2016

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

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

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

Ответ 8

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

Ответ 9

Я бы избегал решений resize/tick, таких как чума, поскольку они неэффективны и могут вызывать проблемы в вашем приложении (например, всплывающая подсказка пересчитывает позицию, которую она должна отображаться при изменении размера окна, затем через мгновение ваша диаграмма также изменится, и page re-layouts, и теперь ваша подсказка снова ошибочна).

Вы можете моделировать это поведение в некоторых старых браузерах, которые не поддерживают его так же, как 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>

Ответ 10

Вы также можете использовать bootstrap 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" делает диск чувствительным для разных устройств. Вы можете узнать больше на 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;

Ширина устанавливается путем получения ширины div с видом месяца id.

Высота в моем случае не должна включать всю область. У меня также есть текст над баром, поэтому мне нужно также вычислить эту область. Поэтому я определил область текста с идентификатором id responsivetext. Для вычисления допустимой высоты бара я вычитал высоту текста с высоты div.

Это позволяет вам иметь панель, которая будет принимать все различные размеры экрана /div. Возможно, это не лучший способ сделать это, но он, безусловно, работает для нужд моего проекта.