Змініть розмір svg, коли вікно буде змінено в d3.js


183

Я малюю скетерплот з d3.js. За допомогою цього питання:
Отримайте розмір екрана, поточної веб-сторінки та вікна браузера

Я використовую цю відповідь:

var w = window,
    d = document,
    e = d.documentElement,
    g = d.getElementsByTagName('body')[0],
    x = w.innerWidth || e.clientWidth || g.clientWidth,
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

Тож я можу прилаштувати свою ділянку до вікна користувача так:

var svg = d3.select("body").append("svg")
        .attr("width", x)
        .attr("height", y)
        .append("g");

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

PS: Я не використовую jQuery у своєму коді.


Відповіді:


295

Шукайте "чуйний SVG", щоб SVG був чуйним, і вам більше не доведеться турбуватися про розміри.

Ось як я це зробив:

d3.select("div#chartId")
   .append("div")
   // Container class to make it responsive.
   .classed("svg-container", true) 
   .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)
   // Fill with a rectangle for visualization.
   .append("rect")
   .classed("rect", true)
   .attr("width", 600)
   .attr("height", 400);
.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;
}

svg .rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<div id="chartId"></div>

Примітка: Все у SVG-зображенні буде масштабуватися з шириною вікна. Сюди входять ширина штриха та розміри шрифту (навіть ті, які встановлені за допомогою CSS). Якщо цього не потрібно, нижче є додаткові альтернативні рішення.

Більше інформації / навчальні посібники:

http://thenewcode.com/744/Make-SVG-Responsive

http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php


8
Це набагато більш елегантно, а також використовується підхід до масштабування контейнерів, який повинен бути в кожному інструментальному інструменті розробника (можливий для масштабування будь-якого у певному співвідношенні сторін). Повинно відповісти.
kontur

13
Просто до цього: viewBoxАтрибут повинен бути встановлений відповідної висоти та ширини вашого SVG ділянки: .attr("viewBox","0 0 " + width + " " + height) де widthта heightвизначені в інших місцях. Тобто, якщо ви не хочете, щоб ваш SVG був вирізаний вікном перегляду. Ви також можете оновити padding-bottomпараметр у своєму css, щоб він відповідав співвідношенню сторін вашого SVG-елемента. Хоча чудова відповідь - дуже корисна, і працює приємно. Дякую!
Ендрю Гай

13
Виходячи з тієї ж ідеї, було б цікаво дати відповідь, яка не передбачає збереження співвідношення сторін. Питання полягало в тому, щоб мати SVG-елемент, який продовжує охоплювати повне вікно за розміром, що в більшості випадків означає інше співвідношення сторін.
Nicolas Le Thierry d'Ennequin

37
Лише зауваження щодо UX: У деяких випадках використання трактування діаграми на основі SVG на основі SVG як активу з фіксованим співвідношенням сторін не є ідеальним. Діаграми, які відображають текст або динамічні, або взагалі більше схожі на належний користувальницький інтерфейс, ніж на актив, мають більше виграшу, повторно відтворюючи оновлені параметри. Наприклад, viewBox змушує шрифти та галочки осі масштабувати вгору / вниз, потенційно виглядаючи незручно порівняно з текстом html. На відміну від цього, повторний візуалізація дозволяє діаграмі зберігати мітку послідовного розміру, а також надає можливість додавати або видаляти галочки.
Карім Ернандес

1
Чому top: 10px;? Мені здається невірним і забезпечує компенсацію. Наведення 0px працює чудово.
TimZaman

43

Використовуйте window.onresize :

function updateWindow(){
    x = w.innerWidth || e.clientWidth || g.clientWidth;
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

    svg.attr("width", x).attr("height", y);
}
d3.select(window).on('resize.updatesvg', updateWindow);

http://jsfiddle.net/Zb85u/1/


11
Це не є гарною відповіддю, оскільки передбачає встановлення ставок на події DOM ... Набагато кращим рішенням є використання лише d3 та css
alem0lars

@ alem0lars досить смішно, версія css / d3 не працювала для мене, і ця зробила ...
Крістіан

32

ОНОВЛЕННЯ просто використовуйте новий спосіб від @cminatti


стара відповідь для історичних цілей

IMO, краще використовувати select () та on (), оскільки таким чином ви можете мати декілька обробників змін розміру ... просто не злітайте

d3.select(window).on('resize', resize); 

function resize() {
    // update width
    width = parseInt(d3.select('#chart').style('width'), 10);
    width = width - margin.left - margin.right;

    // resize the chart
    x.range([0, width]);
    d3.select(chart.node().parentNode)
        .style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
        .style('width', (width + margin.left + margin.right) + 'px');

    chart.selectAll('rect.background')
        .attr('width', width);

    chart.selectAll('rect.percent')
        .attr('width', function(d) { return x(d.percent); });

    // update median ticks
    var median = d3.median(chart.selectAll('.bar').data(), 
        function(d) { return d.percent; });

    chart.selectAll('line.median')
        .attr('x1', x(median))
        .attr('x2', x(median));


    // update axes
    chart.select('.x.axis.top').call(xAxis.orient('top'));
    chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));

}

http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/


Я не можу погодитися менше. Метод cminatti буде працювати на всіх файлах d3js, з якими ми маємо справу. Цей спосіб перетворення з розміром на графік є надмірним. Крім того, його потрібно переписати для всіх можливих діаграм, які ми могли б придумати. Згаданий вище метод працює на все. Я спробував 4 рецепти, включаючи тип перезавантаження, який ви згадали, і жоден з них не працює для декількох ділянок, таких як тип кубізму.
Еймонн Кенні

1
@EamonnKenny Я більше не можу погодитися з тобою. Інша відповідь на 7 місяців у майбутньому перевершує :)
slf

Для цього методу: "d3.select (window) .on" я не зміг знайти "d3.select (window) .off" або "d3.select (window) .unbind"
морський кг


Ця відповідь аж ніяк не поступається. Їх відповідь дозволить масштабувати всі аспекти графіка (включаючи галочки, вісь, лінії та текстові анотації), які можуть виглядати незграбно, в деяких розмірах екрана, поганими в інших та нечитабельними в надзвичайних розмірах. Я вважаю, що краще змінити розмір, використовуючи цей (або, для більш сучасних рішень, метод @Angu Agarwal).
Рунар Берг

12

Це щось некрасиво, якщо зміна коду майже дорівнює коду для побудови графіка в першу чергу. Тож замість зміни розміру кожного елемента існуючої діаграми, чому б просто не перезавантажити його? Ось як це працювало для мене:

function data_display(data){
   e = document.getElementById('data-div');
   var w = e.clientWidth;
   // remove old svg if any -- otherwise resizing adds a second one
   d3.select('svg').remove();
   // create canvas
   var svg = d3.select('#data-div').append('svg')
                                   .attr('height', 100)
                                   .attr('width', w);
   // now add lots of beautiful elements to your graph
   // ...
}

data_display(my_data); // call on page load

window.addEventListener('resize', function(event){
    data_display(my_data); // just call it again...
}

Найважливіша лінія d3.select('svg').remove();. Інакше кожне зміна розміру додасть ще один елемент SVG нижче попереднього.


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

2
Але це правильно: під час візуалізації ви можете визначити, чи маєте ви мати осі вставки, приховати легенду, робити інші речі на основі наявного вмісту. Використовуючи суто рішення CSS / viewbox, все, що робить, це робить визуалізацію вигляд витонченою. Добре для зображень, погано для даних.
Кріс Нолл

8

У силових макетах просто встановлення атрибутів 'висота' та 'ширина' не працюватиме, щоб перецентрувати / перемістити графік у контейнер svg. Однак є дуже проста відповідь, яка працює для форматів форсунки, знайдених тут . Підсумовуючи:

Використовуйте ті самі (будь-які) події, які вам подобаються.

window.on('resize', resize);

Тоді припустимо, що у вас є змінні svg & force:

var svg = /* D3 Code */;
var force = /* D3 Code */;    

function resize(e){
    // get width/height with container selector (body also works)
    // or use other method of calculating desired values
    var width = $('#myselector').width(); 
    var height = $('#myselector').height(); 

    // set attrs and 'resume' force 
    svg.attr('width', width);
    svg.attr('height', height);
    force.size([width, height]).resume();
}

Таким чином, ви не рендерифікуєте повністю графік, ми встановлюємо атрибути і d3 перераховує речі за потребою. Це принаймні працює, коли ви використовуєте точку тяжіння. Я не впевнений, що це необхідна умова цього рішення. Хтось може підтвердити чи спростувати?

Ура, ж


Це чудово спрацювало на моєму силовому макеті, який має близько 100 з'єднань. Дякую.
плем'я84

1

Для тих, хто використовує графіки, спрямовані на силу в D3 v4 / v5, sizeметод вже не існує. Щось подібне працювало для мене (на основі цього випуску github ):

simulation
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("x", d3.forceX(width / 2))
    .force("y", d3.forceY(height / 2))
    .alpha(0.1).restart();

1

Якщо ви хочете прив'язати власну логіку, щоб змінити розмір події, сьогодні ви можете почати використовувати API браузера ResizeObserver для обмежувального вікна SVGElement.
Це також вирішить випадок, коли контейнер змінить розмір через зміну розміру елементів поблизу.
Існує поліфайл для широкої підтримки браузера.

Ось як це може працювати в компоненті інтерфейсу:

function redrawGraph(container, { width, height }) {
  d3
    .select(container)
    .select('svg')
    .attr('height', height)
    .attr('width', width)
    .select('rect')
    .attr('height', height)
    .attr('width', width);
}

// Setup observer in constructor
const resizeObserver = new ResizeObserver((entries, observer) => {
  for (const entry of entries) {
    // on resize logic specific to this component
    redrawGraph(entry.target, entry.contentRect);
  }
})

// Observe the container
const container = document.querySelector('.graph-container');
resizeObserver.observe(container)
.graph-container {
  height: 75vh;
  width: 75vw;
}

.graph-container svg rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 3px;
}
<script src="https://unpkg.com/resize-observer-polyfill@1.5.1/dist/ResizeObserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<figure class="graph-container">
  <svg width="100" height="100">
    <rect x="0" y="0" width="100" height="100" />
  </svg>
</figure>

// unobserve in component destroy method
this.resizeObserver.disconnect()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.