[英]d3 real time graph syncing issues
I've spent several days trying to create a real-time timeline where data is appended asynchronously. 我花了几天时间尝试创建一个实时时间轴,其中数据是异步附加的。 It seems impossible to get things working smoothly without breaking something else each time.
似乎不可能在不破坏其他东西的情况下让事情顺利进行。
I had a look at several examples but none of them seem to match my case. 我看了几个例子,但似乎没有一个与我的情况相符。 Namely, most of the real-time examples either rely on the data to increment the timeline by a step each time, or either they all assume that the data comes continuously in regular intervals.
也就是说,大多数实时示例要么依赖于数据来每次将时间线递增一步,要么它们都假设数据以规则的间隔连续出现。
Issues I'm having: 我遇到的问题:
<!doctype html><html lang="en">
<head><script src="https://d3js.org/d3.v5.min.js"></script></head>
<body><script>
const LENGTH = 10 // in seconds
const TICKS = 10 // num of ticks in time axis
const HEIGHT = 240
const WIDTH = 950
const MARGIN_LEFT = 40
const MARGIN_TOP = 40
var datapoints = []
// Create root element + background rect
svg = d3.select('body').append('svg')
.attr('width', WIDTH)
.attr('height', HEIGHT)
svg.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', 'rgba(59, 58, 52, 0.8)')
$graphs = svg.append('g')
$slidables = $graphs.append('g')
// We use two scalers for x. This solves the issue with the axis being out
// of sync
scaleX = d3.scaleTime().range([MARGIN_LEFT, WIDTH-MARGIN_LEFT])
scaleY = d3.scaleLinear().range([MARGIN_TOP, HEIGHT-MARGIN_TOP])
updateTimeScale()
// -------------------------------------------------------------------------
function logDate(date){
console.log(date.toTimeString().split(' GMT')[0] + ' ' + date.getMilliseconds() + 'ms')
}
function logPoint(point){
const date = point[0]
console.log(
date.toTimeString().split(' GMT')[0] + ' ' + date.getMilliseconds() + 'ms, ',
point[1]
)
}
function oneSecondAgo(){
d = new Date()
d.setSeconds(d.getSeconds() - 1)
return d
}
function leftDate(){
d = new Date()
d.setSeconds(d.getSeconds() - LENGTH)
return d
}
function tickDist(){
return scaleX(new Date()) - scaleX(oneSecondAgo())
}
// -------------------------------- Init -----------------------------------
/* Resets timescale to the current time */
function updateTimeScale(){
right = new Date()
left = new Date()
right.setSeconds(right.getSeconds())
left.setSeconds(right.getSeconds()-LENGTH)
scaleX.domain([left, right])
}
function init(){
// Setup axis
xaxis = d3.axisBottom(scaleX).ticks(TICKS)
$xaxis = svg.append('g')
.attr('transform', 'translate(0, ' + (HEIGHT - MARGIN_TOP) + ')')
.call(xaxis)
yaxis = d3.axisLeft(scaleY)
$yaxis = svg.append('g')
.attr('transform', 'translate(' + MARGIN_LEFT + ', 0)')
.call(yaxis)
// Garbage collect old points every second
setInterval(function(){
while (datapoints.length > 0 && scaleX(datapoints[0][0]) <= MARGIN_LEFT){
datapoints.shift()
}
$slidables.selectAll('circle')
.data(datapoints, d=>d)
.exit()
.remove()
}, 1000)
// Slide axis at interval
function tick(){
right = new Date()
left = new Date()
right.setSeconds(right.getSeconds()+1)
left.setSeconds(right.getSeconds()-LENGTH)
scaleX.domain([left, right])
$xaxis.transition()
.ease(d3.easeLinear)
.duration(new Date() - oneSecondAgo())
.call(xaxis)
}
tick()
setInterval(tick, 1000)
}
// ------------------------------ Update -----------------------------------
/* Update graph with points
We always set right edge to current time
*/
function update(points){
datapoints = datapoints.concat(points)
logPoint(points[0])
updateTimeScale()
// Add new points, transition until left edge and then remove
$slidablesEnter = $slidables.selectAll('circle')
.data(datapoints, d=>d)
.enter()
$slidablesEnter
.append("circle")
.style("fill", "rgb(74, 255, 0)")
.attr("r", 2)
.attr("cx", p=>scaleX(p[0])) // put it at right
.attr("cy", p=>scaleY(p[1]))
.transition()
.duration(function(p){
remainingTime = p[0] - leftDate()
return remainingTime
})
.ease(d3.easeLinear)
.attr("cx", p => MARGIN_LEFT)
.remove()
}
// Start everything with two random datapoints
init()
d1 = new Date()
d2 = new Date()
d2.setMilliseconds(d2.getMilliseconds()-1500)
update([[d1, Math.random()]])
update([[d2, Math.random()]])
</script></body></html>
Just some updates. 只是一些更新。 My workaround is to repaint everything on a tiny interval (ie 20ms).
我的解决方法是在很短的间隔(即20毫秒)重绘所有内容。
This solves all the syncing issues but not sure if there will be a difference on performance. 这解决了所有同步问题,但不确定性能是否会有所不同。
Something like this 像这样的东西
function tick(){
// Redraw all elements
scaleX.domain([now(-TIMELINE_LENGTH_MS), now()])
$slidables.selectAll('circle')
.data(datapoints, d=>d)
.attr("cx", p => scaleX(p[0]))
.transition()
.duration(0)
}
setInterval(tick, REFRESH_INTERVAL)
Setting REFRESH_INTERVAL
to 20ms, looks pretty much the same as having a transition. 将
REFRESH_INTERVAL
设置为20ms,看起来与进行转换非常相似。 Anything above starts looking chopppy, but at least is more accurate than before. 上面的任何东西开始看起来很狡猾,但至少比以前更准确。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.