diff options
Diffstat (limited to 'assets/js/cw-app.js')
-rw-r--r-- | assets/js/cw-app.js | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/assets/js/cw-app.js b/assets/js/cw-app.js new file mode 100644 index 0000000..f97ae17 --- /dev/null +++ b/assets/js/cw-app.js @@ -0,0 +1,221 @@ +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<i; j++) { + if (data[j].id != p) continue + sum += data[j].y + } + return sum; + } + + graph.max = function(x, y) { + return (x > 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 = 210, // 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() |