使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
Mike Gorski 2013年

我发现的最简单的方法是使svg的宽度和高度:100%并在DOM容器(例如div)上应用大小调整stackoverflow.com/questions/47076163/…–
mtx

Answers:


316

还有另一种方法,不需要重新绘制图形,它涉及到修改元素上的viewBoxpreserveAspectRatio属性<svg>

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

15年11月24日更新:大多数现代浏览器都可以从推断出 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 viewbox是否可以跨浏览器工作?2.在您的示例中,圆会调整大小,但不适合窗口显示。SVG视图框或宽高比出问题了。
马特·阿尔考克

4
喜欢这种方法,不确定上面的方法是否正确。正如Matt所说,圆会重新调整大小,但是从图形左侧到第一个圆的距离不会以与圆相同的比例调整大小。我尝试在jsfiddle中摆弄一些东西,但无法使用Google Chrome使其按预期工作。viewBox和preserveAspectRatio兼容哪些浏览器?
克里斯·威瑟斯

9
实际上,仅将viewBox和preserveAspectRatio设置为宽度高度设置为父级的100%的SVG,并且父级作为自举/伸缩网格对我
有用,

2
确认Deepu是正确的。与Bootstrap / flex网格一起使用。谢谢@Deepu。
Mike O'Connor 2015年

3
注意:在多个d3教程中1.)在html中设置了svg的宽度和高度,以及2.)通过onload 函数调用进行可视化。如果在实际的html中设置了svg的宽度和高度,并且您使用上述技术,则图像将无法缩放。
SumNeuron '17

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-sensitive-web-design.php


1
在我的情况下,padding-bottom:100%将使我的容器在底部具有额外的填充,因此我将其更改为50%以将其删除。使用您的代码并成功使svg响应,谢谢。
V-SHY

20

我已经编写了一个小要点来解决这个问题。

通用解决方案模式是这样的:

  1. 将脚本分为计算和绘图功能。
  2. 确保绘图功能是动态绘制的并且由可视化宽度和高度变量驱动(执行此操作的最佳方法是使用d3.scale api)
  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文件的窗口的大小,则随着窗口变小,直方图仍然会被切掉……
克里斯·威瑟斯2012年

1
SVG是矢量格式。您可以设置任何虚拟视图框大小,然后将其缩放为实际尺寸。无需使用JS。
菲尔·皮罗

4

不使用ViewBox

这是不依赖于使用 viewBox

关键是在更新的范围内的的用于放置数据。

首先,计算您的原始长宽比:

var ratio = width / height;

然后,在每次调整大小时,更新rangeof xy

function resize() {
  x.rangeRoundBands([0, window.innerWidth]);
  y.range([0, window.innerWidth / ratio]);
  svg.attr("height", window.innerHeight);
}

请注意,高度基于宽度和纵横比,因此可以保持原始比例。

最后,“重绘”图表-更新取决于xy比例尺的任何属性:

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可以使用上限的rangey,而不是明确地使用高度:

.attr("y", function(d) { return y.range()[1] - y(d.y); })


3

如果您通过c3.js使用d3.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

如果您使用的是诸如plottable.js之类的d3包装器,请注意,最简单的解决方案可能是添加一个事件侦听器,然后调用一个redraw函数(在plottable.js中)。在plottable.js的情况下,它可以很好地工作(这种方法的文献记载很少):redraw

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

最好取消此功能的抖动,以避免尽快触发重绘
Ian

1

如果人们仍然在问这个问题,这对我有用:

  • 将iframe包围在div中,然后使用css向该div添加40%的填充(百分比取决于所需的长宽比)。然后将iframe本身的宽度和高度都设置为100%。

  • 在包含要在iframe中加载的图表的html文档中,将width设置为svg附加到的div的宽度(或正文的宽度),并将height设置为width *宽高比。

  • 编写一个函数,该函数可在调整窗口大小时重新加载iframe内容,以便在人们旋转手机时适应图表的大小。

我的网站上的示例:http : //dirkmjk.nl/zh/2016/05/embedding-d3js-charts-sensitive-website

更新2016年12月30日

我上面描述的方法有一些缺点,特别是它没有考虑到不属于D3创建的svg的任何标题和标题的高度。从那以后,我遇到了我认为是更好的方法:

  • 将D3图表的宽度设置为其所附的div的宽度,并使用宽高比相应地设置其高度;
  • 让嵌入式页面使用HTML5的postMessage将其高度和url发送到父页面;
  • 在父页面上,使用url标识相应的iframe(如果您的页面上有多个iframe,则可以使用)并将其高度更新为嵌入式页面的高度。

我的网站上的示例:http : //dirkmjk.nl/en/2016/12/embedding-d3js-charts-sensitive-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);

您可以在此Codepen中看到它的运行情况:

https://codepen.io/ColinEberhardt/pen/dOBvOy

您可以在其中调整窗口大小并验证是否正确地重新绘制了图表。

请注意,作为完整的披露,我是d3fc的维护者之一。


感谢您保持最新答案!我已经看到了您的一些帖子的最新更新,非常感谢人们重新访问自己的内容!但是,请注意,此修订版不再完全适合该问题,因为您在D3 v4中进行了编辑,而OP显然是指v3,这可能会使以后的读者感到困惑。就我自己的答案而言,我个人倾向于在保留原始v3部分的同时添加v4部分。我认为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

这里有很多复杂的答案。

基本上,您需要做的就是抛弃widthand 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”使div对不同的设备作出响应。您可以在以下处了解更多信息 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;

通过使用id月视图获取div的宽度来设置宽度。

在我的情况下,高度不应该包括整个区域。我在栏上方也有一些文字,因此我也需要计算该面积。这就是为什么我用id响应文本标识文本区域的原因。为了计算条形的允许高度,我从div的高度中减去了文本的高度。

这样您就可以拥有一个采用所有不同屏幕/ div尺寸的栏。这样做可能不是最好的方法,但是肯定可以满足我项目的需要。


1
嗨,我试过了,但是svg内容没有调整大小,如日历或图表。
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.