从带线的数据转换日期和时间
Date and time transition from data with line
我使用 D3 V5 创建了这个图表。另外,我在 fiddle 上附上了示例数据,您可以 view by clicking here.
我已经包含了 tick
函数代码块,它为 x 和 y 尺度附加了新的域,并且 line/data 在向左滑动的路径上:
当 tick 函数执行时,线条会重新构建,使其看起来像是在弹跳。
重建线路的时候怎么能流畅,一点弹跳都没有?
var tr = d3
.transition()
.duration(obj.tick.duration)
.ease(d3.easeLinear);
function tick() {
return setInterval(function() {
var newData = [];
var tickFunction = obj.tick.fnTickData;
if (tickFunction !== undefined && typeof tickFunction === "function") {
newData = tickFunction();
for (var i = 0; i < newData.length; i++) {
obj.data.push(newData[i]);
}
}
if (newData.length > 0) {
var newMaxDate, newMinDate, newDomainX;
if (isKeyXDate) {
newMaxDate = new Date(
Math.max.apply(
null,
obj.data.map(function(e) {
return new Date(e[obj.dataKeys.keyX]);
})
)
);
newMinDate = new Date(
Math.min.apply(
null,
obj.data.map(function(e) {
return new Date(e[obj.dataKeys.keyX]);
})
)
);
newDomainX = [newMinDate, newMaxDate];
} else {
newDomainX = [
d3.min(obj.data, function(d) {
return d[obj.dataKeys.keyX];
}),
d3.max(obj.data, function(d) {
return d[obj.dataKeys.keyX];
})
];
}
// update the domains
//x.domain([newMinDate, newMaxDate]);
if (obj.tick.updateXDomain) {
newDomainX = obj.tick.updateXDomain;
}
x.domain(newDomainX);
if (obj.tick.updateYDomain) {
y.domain(obj.tick.updateYDomain);
}
path.attr("transform", null);
// slide the line left
if (obj.area.allowArea) {
areaPath.attr("transform", null);
areaPath
.transition()
.transition(tr)
.attr("d", area);
}
path
.transition()
.transition(tr)
.attr("d", line);
svg
.selectAll(".x")
.transition()
.transition(tr)
.call(x.axis);
svg
.selectAll(".y")
.transition()
.transition(tr)
.call(y.axis);
// pop the old data point off the front
obj.data.shift();
}
}, obj.tick.tickDelay);
}
this.interval = tick();
当您转换 d
属性时,bounce 实际上是预期的结果,它只是一个字符串。
这里有几种解决方案。无需过多重构您的代码,一个简单的方法是使用 Mike Bostock 在 bl.ocks: https://bl.ocks.org/mbostock/3916621 中编写的 pathTween
函数。在这里,我对其进行了一些更改,以便您可以像这样传递数据:
path.transition()
.transition(tr)
.attrTween("d", function(d) {
var self = this;
var thisd = line(d);
return pathTween(thisd, 1, self)()
})
这是分叉的插件:https://plnkr.co/edit/aAqpdSb9JozwHsErpqa9?p=preview
正如 Gerardo 指出的那样,转换路径的 d
属性不会很好地工作,除非您修改该方法。如果只是更新路径的 d
属性,则会出现这种 wiggle/bouncing 的简单示例:
pᴏɪɴᴛsᴛʀᴀɴSɪᴛɪᴏɴɪɴɢSsssssssssssssssssssSᴄʀᴇᴇɴsɪᴛɪᴏɴɪɴɢsɪᴛɪᴏɴɪɴɢsɪᴛɪᴏɴɪɴɢsᴇᴛsᴇᴛsᴇᴛsᴇᴛsᴇᴛsᴇᴛx。
Mike Bostock 在一篇短文中提到了上述行为 here,下面是重现上述动画的片段:
var n = 10;
var data = d3.range(n).map(function(d) {
return {x: d, y:Math.random() }
})
var x = d3.scaleLinear()
.domain(d3.extent(data, function(d) { return d.x; }))
.range([10,490])
var y = d3.scaleLinear()
.range([290,10]);
var line = d3.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); })
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height", 400)
.append("g");
var path = svg.append("path")
.datum(data)
.attr("d", line);
var points = svg.selectAll("circle")
.data(data, function(d) { return d.x; })
.enter()
.append("circle")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", 5);
function tick() {
var transition = d3.transition()
.duration(1000);
var newPoint = {x:n++, y: Math.random() };
data.shift()
data.push(newPoint);
x.domain(d3.extent(data,function(d) { return d.x; }))
points = svg.selectAll("circle").data(data, function(d) { return d.x; })
points.exit()
.transition(transition)
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.remove();
points.enter().append("circle")
.attr("cx", function(d) { return x(d.x)+30; })
.attr("cy", function(d) { return y(d.y); })
.merge(points)
.transition(transition)
.attr("cx", function(d) { return x(d.x); })
.attr("r", 5);
path.datum(data)
.transition(transition)
.attr("d", line)
.on("end", tick);
}
tick();
path {
fill: none;
stroke: black;
stroke-width: 2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
这个 wiggle/bounce 的一个解决方案是:
- 向数据添加一个额外的点,
- 用最近添加到数据数组的线重新绘制线
- 找出数据的下一个范围
- 将行向左移动
- 更新比例并转换轴
- 删除第一个数据点
我链接到的迈克的文章中也提出了这一点。这是您的代码的基本实现:
我通过在最后一个转换结束时递归调用该函数来避免使用 setInterval 函数:
function slide() {
// Stop any ongoing transitions:
d3.selectAll().interrupt();
// A transition:
var transition = d3.transition()
.duration(2000)
.ease(d3.easeLinear)
// 1. add an additional point(s) to the data
var newData = obj.tick.fnTickData();
obj.data.push(...newData);
// 2. redraw the line with the recently added to data array
path.datum(obj.data)
areaPath.datum(obj.data)
// Redraw the graph, without the translate, with less data:
path.attr("transform","translate(0,0)")
.attr("d", line)
areaPath.attr("transform","translate(0,0)")
.attr("d", area)
// 3. find out the next extent of the data
// Assuming data is in chronological order:
var min = obj.data[newData.length][obj.dataKeys.keyX];
var max = obj.data[obj.data.length-1][obj.dataKeys.keyX];
// 4. transition the line to the left
path.datum(obj.data)
.transition(transition)
.attr("transform", "translate("+(-x(new Date(min)))+",0)");
areaPath.datum(obj.data)
.transition(transition)
.attr("transform", "translate("+(-x(new Date(min)))+",0)");
// 5. update the scale and transition the axis
x.domain([new Date(min),new Date(max)])
// Update the xAxis:
svg.selectAll('.x')
.transition(transition)
.call(x.axis)
.on("end",slide); // Trigger a new transition at the end.
// 6. remove the first data point(s)
obj.data.splice(0,newData.length)
}
slide();
这是更新后的 plunkr。
我使用 D3 V5 创建了这个图表。另外,我在 fiddle 上附上了示例数据,您可以 view by clicking here.
我已经包含了 tick
函数代码块,它为 x 和 y 尺度附加了新的域,并且 line/data 在向左滑动的路径上:
当 tick 函数执行时,线条会重新构建,使其看起来像是在弹跳。
重建线路的时候怎么能流畅,一点弹跳都没有?
var tr = d3
.transition()
.duration(obj.tick.duration)
.ease(d3.easeLinear);
function tick() {
return setInterval(function() {
var newData = [];
var tickFunction = obj.tick.fnTickData;
if (tickFunction !== undefined && typeof tickFunction === "function") {
newData = tickFunction();
for (var i = 0; i < newData.length; i++) {
obj.data.push(newData[i]);
}
}
if (newData.length > 0) {
var newMaxDate, newMinDate, newDomainX;
if (isKeyXDate) {
newMaxDate = new Date(
Math.max.apply(
null,
obj.data.map(function(e) {
return new Date(e[obj.dataKeys.keyX]);
})
)
);
newMinDate = new Date(
Math.min.apply(
null,
obj.data.map(function(e) {
return new Date(e[obj.dataKeys.keyX]);
})
)
);
newDomainX = [newMinDate, newMaxDate];
} else {
newDomainX = [
d3.min(obj.data, function(d) {
return d[obj.dataKeys.keyX];
}),
d3.max(obj.data, function(d) {
return d[obj.dataKeys.keyX];
})
];
}
// update the domains
//x.domain([newMinDate, newMaxDate]);
if (obj.tick.updateXDomain) {
newDomainX = obj.tick.updateXDomain;
}
x.domain(newDomainX);
if (obj.tick.updateYDomain) {
y.domain(obj.tick.updateYDomain);
}
path.attr("transform", null);
// slide the line left
if (obj.area.allowArea) {
areaPath.attr("transform", null);
areaPath
.transition()
.transition(tr)
.attr("d", area);
}
path
.transition()
.transition(tr)
.attr("d", line);
svg
.selectAll(".x")
.transition()
.transition(tr)
.call(x.axis);
svg
.selectAll(".y")
.transition()
.transition(tr)
.call(y.axis);
// pop the old data point off the front
obj.data.shift();
}
}, obj.tick.tickDelay);
}
this.interval = tick();
当您转换 d
属性时,bounce 实际上是预期的结果,它只是一个字符串。
这里有几种解决方案。无需过多重构您的代码,一个简单的方法是使用 Mike Bostock 在 bl.ocks: https://bl.ocks.org/mbostock/3916621 中编写的 pathTween
函数。在这里,我对其进行了一些更改,以便您可以像这样传递数据:
path.transition()
.transition(tr)
.attrTween("d", function(d) {
var self = this;
var thisd = line(d);
return pathTween(thisd, 1, self)()
})
这是分叉的插件:https://plnkr.co/edit/aAqpdSb9JozwHsErpqa9?p=preview
正如 Gerardo 指出的那样,转换路径的 d
属性不会很好地工作,除非您修改该方法。如果只是更新路径的 d
属性,则会出现这种 wiggle/bouncing 的简单示例:
pᴏɪɴᴛsᴛʀᴀɴSɪᴛɪᴏɴɪɴɢSsssssssssssssssssssSᴄʀᴇᴇɴsɪᴛɪᴏɴɪɴɢsɪᴛɪᴏɴɪɴɢsɪᴛɪᴏɴɪɴɢsᴇᴛsᴇᴛsᴇᴛsᴇᴛsᴇᴛsᴇᴛx。
Mike Bostock 在一篇短文中提到了上述行为 here,下面是重现上述动画的片段:
var n = 10;
var data = d3.range(n).map(function(d) {
return {x: d, y:Math.random() }
})
var x = d3.scaleLinear()
.domain(d3.extent(data, function(d) { return d.x; }))
.range([10,490])
var y = d3.scaleLinear()
.range([290,10]);
var line = d3.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); })
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height", 400)
.append("g");
var path = svg.append("path")
.datum(data)
.attr("d", line);
var points = svg.selectAll("circle")
.data(data, function(d) { return d.x; })
.enter()
.append("circle")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", 5);
function tick() {
var transition = d3.transition()
.duration(1000);
var newPoint = {x:n++, y: Math.random() };
data.shift()
data.push(newPoint);
x.domain(d3.extent(data,function(d) { return d.x; }))
points = svg.selectAll("circle").data(data, function(d) { return d.x; })
points.exit()
.transition(transition)
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.remove();
points.enter().append("circle")
.attr("cx", function(d) { return x(d.x)+30; })
.attr("cy", function(d) { return y(d.y); })
.merge(points)
.transition(transition)
.attr("cx", function(d) { return x(d.x); })
.attr("r", 5);
path.datum(data)
.transition(transition)
.attr("d", line)
.on("end", tick);
}
tick();
path {
fill: none;
stroke: black;
stroke-width: 2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
这个 wiggle/bounce 的一个解决方案是:
- 向数据添加一个额外的点,
- 用最近添加到数据数组的线重新绘制线
- 找出数据的下一个范围
- 将行向左移动
- 更新比例并转换轴
- 删除第一个数据点
我链接到的迈克的文章中也提出了这一点。这是您的代码的基本实现:
我通过在最后一个转换结束时递归调用该函数来避免使用 setInterval 函数:
function slide() {
// Stop any ongoing transitions:
d3.selectAll().interrupt();
// A transition:
var transition = d3.transition()
.duration(2000)
.ease(d3.easeLinear)
// 1. add an additional point(s) to the data
var newData = obj.tick.fnTickData();
obj.data.push(...newData);
// 2. redraw the line with the recently added to data array
path.datum(obj.data)
areaPath.datum(obj.data)
// Redraw the graph, without the translate, with less data:
path.attr("transform","translate(0,0)")
.attr("d", line)
areaPath.attr("transform","translate(0,0)")
.attr("d", area)
// 3. find out the next extent of the data
// Assuming data is in chronological order:
var min = obj.data[newData.length][obj.dataKeys.keyX];
var max = obj.data[obj.data.length-1][obj.dataKeys.keyX];
// 4. transition the line to the left
path.datum(obj.data)
.transition(transition)
.attr("transform", "translate("+(-x(new Date(min)))+",0)");
areaPath.datum(obj.data)
.transition(transition)
.attr("transform", "translate("+(-x(new Date(min)))+",0)");
// 5. update the scale and transition the axis
x.domain([new Date(min),new Date(max)])
// Update the xAxis:
svg.selectAll('.x')
.transition(transition)
.call(x.axis)
.on("end",slide); // Trigger a new transition at the end.
// 6. remove the first data point(s)
obj.data.splice(0,newData.length)
}
slide();
这是更新后的 plunkr。