使d3.js可视化布局响应的最佳方式是什么?

假设我有一个直方图脚本来build立一个960 500 svggraphics。 如何使这个响应如此重新调整graphics的宽度和高度是dynamic的?

<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 dx; })) .rangeRoundBands([0, width]); var y = d3.scale.linear() .domain([0, d3.max(histogram.map(function(d) { return dy; }))]) .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(dx); }) .attr("y", function(d) { return height - y(dy); }) .attr("height", function(d) { return y(dy); }); svg.append("line") .attr("x1", 0) .attr("x2", width) .attr("y1", height) .attr("y2", height); </script> 

完整的示例直方图的要点是: https : //gist.github.com/993912

还有另一种方法可以做到这一点,不需要重新绘制graphics,而是需要修改<svg>元素上的viewBox和preserveAspectRatio属性:

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

2015年11月24日更新 :大多数现代浏览器都可以从视图框中 推断出 SVG元素的纵横比 ,所以您可能不需要保持图表的大小最新。 如果您需要支持较旧的浏览器,则可以在窗口resize时调整元素的大小,如下所示:

 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内容将自动缩放。 你可以在这里看到一个有效的例子(有一些修改):调整窗口或者右下方的窗格,看看它是如何反应的。

寻找“响应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

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

一般的解决scheme模式是这样的:

  1. 将脚本分解为计算和绘图function。
  2. 确保绘图函数dynamic绘制并由可视化宽度和高度variables驱动(最好的方法是使用d3.scale api)
  3. 将graphics绑定/链接到标记中的引用元素。 (我用这个jquery,所以导入它)。
  4. 请记住,如果已经绘制,请将其删除。 使用jquery从引用的元素获取维度。
  5. 将绘图函数绑定/链接到窗口大小调整function。 引入一个去抖动(超时)到这个链,以确保我们只在超时后重绘。

我还添加了缩小的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); 

请享用!

如果您通过c3.js使用d3.js,则响应问题的解决scheme非常简单:

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

生成的HTML如下所示:

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

不使用ViewBox

这是一个不依赖于viewBox的解决scheme的例子:

关键在于更新用于放置数据的尺度 范围

首先,计算你的原始高宽比:

 var ratio = width / height; 

然后,在每个resize,更新xyrange

 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(dx); }) .attr("y", function(d) { return y.range()[1] - y(dy); }) .attr("height", function(d) { return y(dy); }); } 

请注意,在重新调整rects的大小时,可以使用y range的上限,而不是显式使用高度:

 .attr("y", function(d) { return y.range()[1] - y(dy); }) 
 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 dx; })) var y = d3.scale.linear() .domain([0, d3.max(histogram, function(d) { return dy; })]) 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(dx); }) // .attr("y", function(d) { return height - y(dy); }) .attr("y", function(d) { return y.range()[1] - y(dy); }) .attr("height", function(d) { return y(dy); }); } 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="ajax/libs/d3/3.4.11/d3.min.js"></script> 

肖恩·艾伦的回答非常好。 但是你可能不想每一次都这样做。 如果您在vida.io上托pipe它,您会自动响应您的svg可视化。

你可以用这个简单的embedded代码获得响应式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构build这个function。

D3数据连接的基本原则之一是它是幂等的。 换句话说,如果您重复评估具有相同数据的数据连接,则呈现的输出是相同的。 因此,只要您正确渲染图表,请注意input,更新和退出select – 在尺寸发生变化时您只需重新渲染图表即可。

还有一些其他的事情你应该做,其中一个是反弹窗口调整处理程序,以遏制它。 而且,不是硬编码宽度/高度,而是通过测量包含元素来实现。

作为替代,这里是使用d3fc呈现的图表,它是一组正确处理数据连接的D3组件。 它也有一个笛卡儿图表,它包含的元素使得创build“响应式”图表变得很容易:

 // 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 dy; }, function(d) { return dz; } ]) .pad([0.4, 0.4]) .padUnit('domain'); var xExtent = fc.extentLinear() .accessors([function(d) { return dx; }]); // 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 dx; }) .mainValue(function(d) { return dy; }) .decorate(function(selection) { selection.enter() .style('stroke', 'purple'); }); var cosLine = fc.seriesSvgArea() .crossValue(function(d) { return dx; }) .mainValue(function(d) { return dz; }) .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

在那里你可以调整窗口的大小,并validation图表是否被正确地重新渲染。

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

如果人们仍然访问这个问题 – 这对我来说是有用的:

  • 将div放在一个div中,并使用css来为该div添加40%的填充(百分比取决于所需的高宽比)。 然后将iframe的宽度和高度都设置为100%。

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

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

示例: http : //dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

更新2016年12月30日

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

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

示例: http : //dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution

我会避免像瘟疫一样resize/打勾的解决scheme,因为它们效率低下,并可能导致应用程序出现问题(例如,工具提示重新计算应该出现在窗口大小上的位置,过了一会儿,您的图表也resize,布局,现在你的工具提示又是错误的)。

您可以在一些旧版本的浏览器中模拟这种行为,像IE11一样不能正确支持IE浏览器,使用<canvas>元素来维护它的方面。

鉴于960×540这是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> 

您还可以使用引导程序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 月份视图的帮助下访问图表。

我不会详细介绍d3代码,我只会input适应不同屏幕尺寸所需的部分。

 var width = document.getElementById('month-view').offsetWidth; var height = document.getElementById('month-view').offsetHeight - document.getElementById('responsivetext2').offsetHeight; 

宽度是通过获取ID为month-view的div的宽度来设置的。

在我的情况下,高度不应该包括整个区域。 我也有一些文本上面的文本,所以我需要计算该区域以及。 这就是为什么我用id响应文本标识文本的区域。 为了计算栏的高度,我从div的高度中减去了文本的高度。

这可以让你有一个酒吧,将采取所有不同的屏幕/格大小。 这可能不是最好的方式,但它肯定是为我的项目的需要。

 width = window.innerWidth, height = window.innerHeight