function viz19() { var data = [], lines, line, path, circles, circle, areas, tip = 0, // Highest Y point so far max = 500 // Maximum length of data var graph = function(s) { if (s == undefined) { console.error('selection is undefined.') return } data = [] svg.append("defs") .append("clipPath") .attr("id", "clip") .append("rect") .attr('width', W) .attr('height', H) graph.render() } graph.datum = function(_) { if (arguments.length == 0) return datum datum = _ data.push(datum) return graph } function sum_y1_up_to_i(data, d, i) { var sum = 0 var n = d.id var p = d.id > 0 ? d.id - 1 : d3.max(data, function(d) { return d.id }) for (let j=0; j y) ? x : y; } graph.transition = function(_) { if (arguments.length == 0) return transition transition = d3.transition().ease(d3.easeLinear).duration(_ * Math.random()) return graph } // Update SVG graph.render = function() { if (data.length <= 1) return if (data.length > max) data.shift(data.length - max) // Update time scale to zoom in the latest steps xmin = new Date(d3.min(data, function(d) { return d.x })).getTime() xmax = new Date(d3.max(data, function(d) { return d.x })).getTime() xScale.domain([xmin, xmax]).nice() // Keep all Y values in sight tip = graph.max(tip, sum_y1_up_to_i(data, data[0], data.length)) yScale.domain([ d3.min(data, function(d) { return d.y }), tip ]).nice() // Find how many players are in var players = d3.set(data, function(d) { return d.id }) var update = svg.selectAll('g.player').data(players.values()) var enter = update.enter().append('g') var exit = update.exit() exit.attr('class', 'inactive player') .transition(graph.transition).style('opacity', 0).remove() update.attr('class', 'active player') enter.attr('class', 'new player') .attr('id', function(d) { return 'player-' + d[0] }) // Play each player d3.timeout(function() { players.each(graph.play) }, 300) } // Update SVG for one player graph.play = function(player_id) { // Only take the first datum var pdata = data.filter(function(d) { return d.id == player_id }) // Update transition duration var t = graph.transition(timeout * (0.5 + Math.random() / 2)) // Current player group var player_group = svg.select('g.player#player-' + player_id) // Selection var update = player_group.selectAll('path').data([pdata]) var enter = update.enter().append('path') var exit = update.exit() function max(x, y) { if (x <= 0) x = 0 if (y <= 0) y = 0 return Math.max(x, y) } function min(x, y) { if (x <= 0) x = 0 if (y <= 0) y = 0 return Math.min(x, y) } var area = d3.area() .x(function(d, i) { return xScale(d.x) }) .y0(function(d, i) { return yScale(sum_y1_up_to_i(data, d, i)) }) .y1(function(d, i) { return yScale(d3.sum(data.slice(0, max(0, i)), function(d) { return d.y })) }) .curve(d3.curveBasisOpen) // UPDATE existing nodes update .attr('class', 'update') .attr('data-move', function(d, i) { return d[i].n }) .attr('d', area) // ENTER new nodes enter .attr('class', 'enter') .attr('data-move', function(d, i) { return d[i].n }) .transition(t).duration(400) .attr('d', area) .attr('fill', function(d, i) { return color(d[i].id) }) // EXIT removed nodes exit.style('fill', 'red').remove() } graph.run = function() { graph.render() d3.interval(graph.render, 50) } return graph } function randomInteger(min, max) { return min + Math.floor(Math.random() * (max - min + 1)) } function genDatum() { if (halted) { clearTimeout(gen) console.log('genDatum: halted.') return } // Pick a random timestamp within one second of now var now = new Date(new Date().getTime() + (Math.random() - 0.5) * 1000) var obj = { n: n++, id: randomInteger(1, 5), x: now.getTime(), y: d3.randomNormal(12, 3)() } // Append datum to data array graph.datum(obj) // Update delay to next object timeout = Math.round(normal()) // TODO: Use d3.timeInterval clearTimeout(gen) gen = setTimeout(genDatum, timeout) } // Variables var halted = false, timeout = 1000, now = new Date().getTime(), n = 0, gen = setTimeout(genDatum, timeout) // Random delay for timeout //var normal = d3.randomNormal(1000, 200) var normal = d3.randomNormal(750, 200) // Constants const W = 1366, // SVG Width H = 320, // SVG Height R = 32, // Point Radius D = 50 // Duration in ms const color = d3.scaleOrdinal(d3.schemeCategory10) const xScale = d3.scaleLinear().domain([0, 1]).range([W, 0]) const yScale = d3.scaleLinear().domain([0, 1]).range([H, 0]) const graph = viz19() const view = d3.select('#view') const svg = view.append('svg').attr('width', W).attr('height', H) // Runtime console.clear() view.call(graph) graph.run()