什么是最好的方法,使一个d3.js可视化布局响应?

假设我有一个直方图脚本,构建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 < / p >
135820 次浏览

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

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

  1. 将脚本分解为计算函数和绘图函数。
  2. 确保绘图函数动态绘制并驱动 可视化宽度和高度变量(最好的方法是 使用d3。缩放api)
  3. 将绘图绑定/链接到引用 元素。(我使用jquery,所以导入它) 如果它已经被绘制,记得删除它。从 使用jquery引用的元素。李< / > 绑定/链接绘制函数到 窗口大小调整函数。为此引入一个反弹(超时) 链,以确保我们只在超时后重新绘制。李< / >
为了提高速度,我还添加了简化的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);

享受吧!

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

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

更新11/24/15:大多数现代浏览器可以从viewBox推断纵横比 SVG元素,所以你可能不需要保持图表的大小为最新。如果你需要支持旧的浏览器,你可以在窗口调整大小时调整元素的大小,就像这样:

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内容将自动缩放。你可以看到在这里的一个工作示例(经过一些修改):只需调整窗口或右下窗格的大小,看看它如何反应。

你也可以使用引导3来调整可视化的大小。例如,我们可以将超文本标记语言代码设置为:

<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代码,我只会输入适应不同屏幕尺寸所需的部分。

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


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

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

在我的例子中,高度不应该包括整个区域。我也有一些文字上面的酒吧,所以我需要计算的面积以及。这就是为什么我用id responsivetext标识文本区域的原因。为了计算栏的允许高度,我用div的高度减去文本的高度。

这允许你有一个条,将采用所有不同的屏幕/div大小。这可能不是最好的方法,但它肯定能满足我的项目的需要。

寻找“响应式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 < a href = " http://demosthenes.info/blog/744/Make-SVG-Responsive " > < / >

http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php < a href = " http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php " > < / >

肖恩·阿伦的回答很棒。但你可能不想每次都这么做。如果你把它托管在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上构建了这个特性。

如果你通过c3.js使用d3.js,响应性问题的解决方案非常简单:

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

生成的HTML是这样的:

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

不使用ViewBox

下面是一个不依赖于使用viewBox的解决方案的例子:

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

首先,计算你的原始纵横比:

var ratio = width / height;

然后,在每次调整大小时,更新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(d.x); })
.attr("y", function(d) { return y.range()[1] - y(d.y); })
.attr("height", function(d) { return y(d.y); });
}

注意,在调整rects的大小时,可以使用yrange的上限,而不是显式地使用高度:

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

.
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 d.x;
}))


var y = d3.scale.linear()
.domain([0, d3.max(histogram, function(d) {
return d.y;
})])


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


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="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

以防人们还在问这个问题——以下是对我有用的方法:

  • 将iframe包含在一个div中,并使用css为该div添加40%的填充(百分比取决于您想要的纵横比)。然后将iframe本身的宽度和高度都设置为100%。

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

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

在我的网站上的例子: http://dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website < / p >

2016年12月30日更新

我上面描述的方法有一些缺点,特别是它没有考虑任何不在d3创建的svg中的标题和说明文字的高度。后来我想到了一个我认为更好的方法:

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

例子在我的网站:http://dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution

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);

你可以在这个代码库中看到它的作用:

< a href = " https://codepen。io/ColinEberhardt/pen/dOBvOy" rel="nofollow noreferrer">https://codepen.io/ColinEberhardt/pen/dOBvOy

在这里,您可以调整窗口的大小,并验证图表是否被正确重新渲染。

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

我会避免像瘟疫一样调整大小/标记的解决方案,因为它们效率很低,并且会在你的应用程序中引起问题(例如,工具提示重新计算它应该出现在窗口调整大小上的位置,然后一会儿你的图表也会调整大小,页面重新布局,现在你的工具提示又错了)。

你可以在一些旧的浏览器中模拟这种行为,比如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>

在使用plottable.js这样的d3包装的情况下,请注意最简单的解决方案可能是添加事件监听器,然后调用重绘函数(在plottable.js中是redraw)。在plottable.js的情况下,这将非常有效(这种方法很少有文档):

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

这里有很多复杂的答案。

基本上你所需要做的就是抛弃widthheight属性,转而使用viewBox属性:

width = 500;
height = 500;


const svg = d3
.select("#chart")
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)

如果你有页边距,你可以把它们添加到width/height中,然后在后面追加g并像平常一样转换它。