aboutsummaryrefslogtreecommitdiff
path: root/assets/js/cw-app.js
diff options
context:
space:
mode:
Diffstat (limited to 'assets/js/cw-app.js')
-rw-r--r--assets/js/cw-app.js221
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()