在d3.js中调整窗口大小时调整SVG大小


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。


Answers:


294

寻找“响应式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-sensitive-web-design.php


8
这更加优雅,并且还使用了容器缩放方法,该方法应该在每个前端开发人员的工具包中使用(可用于缩放特定长宽比的任何内容)。应该是最佳答案。
kontur 2015年

13
只需添加:viewBox属性应设置为SVG图的相关高度和宽度:.attr("viewBox","0 0 " + width + " " + height)widthheight在其他位置定义。也就是说,除非您希望SVG被视图框剪切。您可能还需要更新padding-bottomcss中的参数以匹配svg元素的宽高比。出色的回应-非常有帮助,并且可以治疗。谢谢!
安德鲁·盖伊2015年

13
基于相同的想法,给出一个不涉及保持宽高比的答案会很有趣。问题在于是否有一个svg元素在调整大小时始终覆盖整个窗口,这在大多数情况下意味着不同的宽高比。
Nicolas Le Thierry d'Ennequin '16

37
只是关于UX的注意事项:在某些用例中,将基于SVG的d3图表像具有固定长宽比的资产一样对待是不理想的。通过显示具有更新尺寸的图表来重新显示文字,这些图表可以显示文本,或者是动态的,或者通常感觉更像是适当的用户界面而不是资产。例如,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事件的
竞标

@ alem0lars很有趣,css / d3版本对我不起作用,而这个对我却有用...
Christian

32

UPDATE仅使用@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/sensitive-charts-with-d3/


我不能同意。cminatti的方法将适用于我们处理的所有d3js文件。这种按图表调整大小的转换方法是过大的。同样,我们需要考虑的所有可能图表都需要重写它。上面提到的方法适用于所有情况。我已经尝试了4种配方,其中包括您提到的重新加载类型,但它们都不能用于多区域图,例如立体派中使用的类型。
伊莫肯尼(Eamonn Kenny)

1
@EamonnKenny我不能再同意你的看法。未来7个月的其他答案更好:)
slf

对于这种方法:“ d3.select(window).on”我找不到“ d3.select(window).off”或“ d3.select(window).unbind”
sea-kg


这个答案绝不逊色。他们的答案将缩放图形的各个方面(包括刻度,轴,线和文本注释),这些方面在某些屏幕尺寸下看起来笨拙,在其他屏幕上不好并且在极端尺寸下不可读。我发现使用此方法(或者,对于更现代的解决方案,@ 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

在强制布局中,仅设置'height'和'width'属性无法将图重新居中/移动到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个连接)非常有效。谢谢。
tribe84 '16

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

如果您想将自定义逻辑绑定到事件大小调整中,那么现在您可以开始将ResizeObserver浏览器API用于SVGElement的边界框。
当附近的元素大小改变而调整容器大小时,这也将处理。
有一个polyfill用于更广泛的浏览器支持。

这是它在UI组件中的工作方式:

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()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.