Який найкращий спосіб зробити макет візуалізації 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 та збереженняAspectRatio на <svg>елементі:

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

Оновлення 24.11.2015 : більшість сучасних веб-переглядачів можуть зробити висновок про співвідношення сторін 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 та зберігаютьAspectRatio?
Кріс Вітерс

9
Насправді встановлення просто viewBox та збереженняAspectRatio до SVG з висотою ширини, встановленою на 100% від батьківського, а батьківська мережа для завантаження / гнучкої сітки працює для мене без обробки події зміни розміру
Deepu

2
Підтвердження правильності Deepu. Працює з Bootstrap / flex grid. Дякую @Deepu.
Майк О'Коннор

3
Примітка: в декількох навчальних посібниках d3 1.) в html встановлюються ширина і висота svg і 2.) візуалізація здійснюється через onload виклик функції. Якщо ширина і висота svg встановлені в фактичному html і ви використовуєте описану вище техніку, зображення не буде масштабованим.
SumNeuron

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
У моєму випадку "нижня частина": 100% зробить мій контейнер додатковою підкладкою внизу, отже, я змінив його на 50%, щоб видалити його. Використовуйте свій код і успішно зробіть SVG чуйним, дякую.
V-SHY

20

Я зашифрував невелику суть, щоб вирішити це.

Загальна схема рішення така:

  1. Розбийте сценарій на функції обчислення та малювання.
  2. Переконайтеся, що функція малювання динамічно малює та керується змінними візуалізації шириною та висотою (найкращий спосіб це зробити - використовувати api d3.scale)
  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-файлу, що входить до виправленої суті, гістограма все одно відсікається, коли вікно стає меншим ...
Кріс Вітерс

1
SVG - векторний формат. Ви можете встановити будь-який розмір віртуального вікна перегляду, а потім його масштабувати до реальних розмірів. Не потрібно використовувати JS.
phil pirozhkov

4

Без використання 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); })


3

Якщо ви використовуєте d3.js через c3.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

У випадку, якщо ви використовуєте обгортку d3, як plottable.js , пам’ятайте, що найпростішим рішенням може бути додавання слухача подій, а потім виклик функції перемальовування ( redrawу plottable.js). У випадку plottable.js це буде відмінно працювати (такий підхід погано задокументований):

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

Напевно, найкраще було б зняти цю функцію, щоб уникнути запуску перемальовки якомога швидше
Ian

1

Якщо люди все ще відвідують це питання - ось що для мене спрацювало:

  • Вкладіть рамку if у рамку і використовуйте css, щоб додати, наприклад, 40% до цього div (відсоток залежно від пропорції). Потім встановіть як ширину, так і висоту самого рамки на 100%.

  • У html doc, що містить діаграму для завантаження у рамку iframe, встановіть ширину до ширини ключа, до якого svg додається (або до ширини тіла), і встановіть співвідношення сторін висота до ширини *.

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

Приклад тут на моєму веб-сайті: http://dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

ОНОВЛЕННЯ 30 грудня 2016 року

Описаний вище підхід має деякі недоліки, особливо те, що він не враховує висоту будь-якого заголовка та заголовків, які не входять до створеного D3 svg. З тих пір я натрапив на те, що я вважаю кращим підходом:

  • Встановіть ширину діаграми D3 на ширину діва, до якого вона додається, і використовуйте співвідношення сторін, щоб відповідно встановити її висоту;
  • Попросіть вбудовану сторінку надіслати її висоту та URL на батьківську сторінку, використовуючи пост-повідомлення Messenger HTML5;
  • На батьківській сторінці використовуйте URL для визначення відповідного iframe (корисно, якщо на вашій сторінці є більше одного iframe) та оновіть його висоту до висоти вбудованої сторінки.

Приклад тут на моєму веб-сайті: http://dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution


0

Одним із основних принципів з'єднання даних 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.


Дякуємо за те, що постійно відповідали за відповіді! Я бачив останні оновлення деяких ваших публікацій, і я дуже вдячний людям, які переглядають свій власний вміст! Однак, як застереження, ця редакція більше не відповідає питанню, оскільки ви редагуєте в D3 v4 , тоді як OP чітко посилається на v3 , що може заплутати майбутніх читачів. Особисто для своїх власних відповідей я прагну додати розділ v4 , зберігаючи початковий розділ v3 . Я думаю, що v4 може бути вартим власної відповіді, додаючи взаємну посилання на обидва посади. Але це лише два мої центи ...
altocumulus

Це дійсно вдалий момент! Це так просто захопитися і просто зосередитись на майбутньому та новому. Судячи з останніх запитань d3, я думаю, що багато людей все ще використовують v3. Це також засмучує, наскільки тонкі деякі відмінності! Я перегляну свої зміни :-)
ColinE

0

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

Ви можете імітувати цю поведінку в деяких старих браузерах, які не підтримують її належним чином, як 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

Тут багато складних відповідей.

В основному все, що вам потрібно зробити, - це викопати widthіheight атрибути на користь 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>

Я встановив фіксовану висоту через мої потреби, але ви також можете залишити автоматичний розмір. "Col-sm-6 col-md-4" робить діву чутливою для різних пристроїв. Ви можете дізнатися більше на 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.

Висота в моєму випадку не повинна включати всю площу. У мене також є текст над смужкою, тому мені потрібно також обчислити цю площу. Тому я ідентифікував область тексту з id respovetext. Для обчислення дозволеної висоти смуги я відняв висоту тексту від висоти діва.

Це дозволяє мати панель, яка буде приймати всі різні розміри екрана / діва. Це може бути не найкращий спосіб зробити це, але це, безумовно, працює для потреб мого проекту.


1
Привіт, я спробував це, але вміст svg не змінює розмір, як календар чи діаграми.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.