eka*_*atz 5 javascript zoom large-data d3.js
我正在创建一个类似于Mike Bostock的可缩放区域图表的图表.
对于我的具体项目,我有一堆传感器,每隔30秒(温度,光线,湿度和声音)记录值.我有缩放实现工作,但是当我缩小到一年的比例时,图表的密度减慢了浏览器和图形也没有读取.
如何编辑脚本以使折线图的密度相对于缩放量发生变化?换句话说,x域控制着值线上的点数.当我放大到一小时的时间范围时,我想要全密度(每30秒录制一次),当我缩小时,我希望密度更低(每天录制一次).有任何想法吗?使用上面链接中的脚本实现将很有帮助.
谢谢!
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<script type="text/javascript" src="d3/d3.js"></script>
<script type="text/javascript" src="d3/d3.csv.js"></script>
<script type="text/javascript" src="d3/d3.time.js"></script>
<link type="text/css" rel="stylesheet" href="style.css"/>
<style type="text/css">
svg {
font-size: 10px;
}
.axis {
shape-rendering: crispEdges;
}
.axis path, .axis line {
fill: none;
stroke-width: .5px;
}
.x.axis path {
stroke: #000;
}
.x.axis line {
stroke: #fff;
stroke-opacity: .5;
}
.y.axis line {
stroke: #ddd;
}
path.line {
fill: none;
stroke: #000;
stroke-width: .5px;
}
rect.pane {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
</head>
<body>
<div id="body">
<div id="footer">
<span>…</span>
<div class="hint">mousewheel to zoom, drag to pan</div>
</div>
</div>
<script type="text/javascript">
var m = [79, 80, 160, 79],
w = 1280 - m[1] - m[3],
h = 800 - m[0] - m[2],
parse = d3.time.format("%Y-%m-%d").parse,
format = d3.time.format("%Y");
// Scales. Note the inverted domain for the y-scale: bigger is up!
var x = d3.time.scale().range([0, w]),
y = d3.scale.linear().range([h, 0]),
xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(-h, 0).tickPadding(6),
yAxis = d3.svg.axis().scale(y).orient("right").tickSize(-w).tickPadding(6);
// An area generator.
var area = d3.svg.area()
.interpolate("step-after")
.x(function(d) { return x(d.date); })
.y0(y(0))
.y1(function(d) { return y(d.value); });
// A line generator.
var line = d3.svg.line()
.interpolate("step-after")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
var svg = d3.select("body").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
var gradient = svg.append("svg:defs").append("svg:linearGradient")
.attr("id", "gradient")
.attr("x2", "0%")
.attr("y2", "100%");
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#fff")
.attr("stop-opacity", .5);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#999")
.attr("stop-opacity", 1);
svg.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("x", x(0))
.attr("y", y(1))
.attr("width", x(1) - x(0))
.attr("height", y(0) - y(1));
svg.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + w + ",0)");
svg.append("svg:path")
.attr("class", "area")
.attr("clip-path", "url(#clip)")
.style("fill", "url(#gradient)");
svg.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")");
svg.append("svg:path")
.attr("class", "line")
.attr("clip-path", "url(#clip)");
svg.append("svg:rect")
.attr("class", "pane")
.attr("width", w)
.attr("height", h)
.call(d3.behavior.zoom().on("zoom", zoom));
d3.csv("flights-departed.csv", function(data) {
// Parse dates and numbers.
data.forEach(function(d) {
d.date = parse(d.date);
d.value = +d.value;
});
// Compute the maximum price.
x.domain([new Date(1999, 0, 1), new Date(2003, 0, 0)]);
y.domain([0, d3.max(data, function(d) { return d.value; })]);
// Bind the data to our path elements.
svg.select("path.area").data([data]);
svg.select("path.line").data([data]);
draw();
});
function draw() {
svg.select("g.x.axis").call(xAxis);
svg.select("g.y.axis").call(yAxis);
svg.select("path.area").attr("d", area);
svg.select("path.line").attr("d", line);
d3.select("#footer span").text("U.S. Commercial Flights, " + x.domain().map(format).join("-"));
}
function zoom() {
d3.event.transform(x); // TODO d3.behavior.zoom should support extents
draw();
}
</script>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
Lars和Ari是正确的,这绝对不是一个小问题.但我认为它是一个重要的,可以用于许多人(包括将来可能是我自己),因此值得花时间去弄清楚.
所以你可以跟随,这是我对迈克博斯托克的每日飞行计数图的调整,这样它可以在缩小时(而不是单个天)绘制每周/每月/每年平均每日飞行计数的图表,并且只显示可以的数据子集在任何缩放级别显示:http:
//fiddle.jshell.net/ncy5J/2/
这是我必须做的一步一步细分:
获取非常大的csv数据表作为JSFiddle脚本中的嵌入变量.我假设你不会这样做,但我提到它是因为这是一个麻烦.\n\在我可以d3.csv.parse()在字符串上运行之前,必须在每行的末尾添加一个.
创建数周,数月和数年的备用数据阵列,并计算这些时间段的平均每日值:
使用d3.nest使用d3 函数interval.floor()的关键函数来获取同一年,月等的所有日期以嵌套在一起;
Array.forEach在具有自定义函数的嵌套数组上使用,以访问嵌套对象数组,计算其值的平均值,然后将对象创建的对象替换nest()为与原始数据的格式匹配的对象(下面的代码).
将数据绑定步骤从初始化移动到重绘功能,并将该功能更改为接受数据数组作为参数.
更新d3.behavior.zoom方法以匹配D3版本3 API(原始示例使用v2.4,其具有用于将缩放行为链接到缩放的不同方法).
zoom将缩放行为调用的函数更改为
从x刻度访问可见数据域(由缩放行为自动更新);
计算该域所涵盖的时间范围;
选择我的四个数据数组中的一个,该数组将具有该范围的适当精度级别;
扫描数组以找到可见域中的元素部分(正如我在代码注释中提到的,当你一直放大时,这会有点慢;你也可以使用日期时间数学来弄清楚数组的右边部分,因为连续元素之间的时间间隔总是相同的).
使用相应的数组切片调用重绘功能作为传入数据.
这是步骤2中的自定义嵌套/平均例程:
AllData.yearly = d3.nest().key(function(d){
return d3.time.year.floor(d.date);
})
.entries(data);
AllData.yearly.forEach(meanDaily);
function meanDaily(nestedObject, i, array){
//This function is passed to the Array.forEach() method
//which passes in:
// (1) the element in the array
// (2) the element's index, and
// (3) the array as a whole
//It replaces the element in the array
//(an object with properties key and values, where
// values is an array of nested objects)
//with a single object that has the nesting key
//value parsed back into a date,
//and the mean of the nested values' value
//as the value. It makes sense, I swear.
array[i] = {date:new Date(nestedObject.key),
value:d3.mean(nestedObject.values,
function(d) {return d.value;}
)};
}
Run Code Online (Sandbox Code Playgroud)
zoom方法只是基本的javascript,关键部分是你可以直接从x-scale访问可见域,然后使用它来确定哪些数据点传递给draw函数.
PS以不同的平均尺度查看数据很有意思.9月11日之后航班的急剧下降在每日,每周和每月排行榜上脱颖而出,但从年平均值中消失.相反,年度平均值表明,2002年的平均每日航班数量比2001年低,这提醒人们,许多人在飞行禁令解除后不敢长途飞行.