|
'use strict'; |
|
|
|
function parseTime(time) { |
|
if (!time) { |
|
return undefined; |
|
} |
|
var parts = time.split(':'); |
|
if (parts.length !== 3) { |
|
return undefined; |
|
} |
|
parts = parts.map(Number); |
|
return parts[0] * 60 + parts[1] + parts[0] / 60; |
|
} |
|
|
|
d3.csv('results.csv', function (row, _, columns) { |
|
// parse the times into seconds |
|
var checkpoints = columns.slice(3); |
|
checkpoints.forEach(function (checkpoint) { |
|
row[checkpoint] = parseTime(row[checkpoint]); |
|
}); |
|
return row; |
|
}, csvLoaded); |
|
|
|
var keyValueToObject = function keyValueToObject(keyValues) { |
|
var obj = {}; |
|
keyValues.forEach(function (k) { |
|
obj[k.key] = k.value; |
|
}); |
|
return obj; |
|
}; |
|
|
|
function csvLoaded(error, data) { |
|
if (error) { |
|
console.error(error); |
|
} |
|
|
|
// compute the checkpoint deltas |
|
var checkpoints = data.columns.slice(3); |
|
data.forEach(function (row) { |
|
checkpoints.forEach(function (checkpoint, index) { |
|
if (index > 1) { |
|
row[checkpoint + '-Delta'] = row[checkpoint] - row[checkpoints[index - 1]]; |
|
} else { |
|
row[checkpoint + '-Delta'] = row[checkpoint]; |
|
} |
|
}); |
|
}); |
|
var checkpointFilter = { |
|
checkpoint: checkpoints[0], |
|
values: [0, 0], |
|
valuesDomain: [0, 0] |
|
}; |
|
|
|
// compute the scale domians |
|
var yExtent = fc.extentLinear().pad([0.1, 0.1]); |
|
var yScales = checkpoints.map(function (checkpoint) { |
|
return d3.scaleLinear().domain(yExtent(data.map(function (d) { |
|
return d[checkpoint + '-Delta']; |
|
}))); |
|
}); |
|
|
|
var xScale = d3.scalePoint().domain(checkpoints); |
|
|
|
var lineData = d3.line().defined(function (d) { |
|
return d.value; |
|
}).x(function (d) { |
|
return xScale(d.checkpoint); |
|
}).y(function (d, i) { |
|
return yScales[i](d.value); |
|
}); |
|
|
|
var rowToLine = function rowToLine(row) { |
|
var arr = checkpoints.map(function (checkpoint) { |
|
return { |
|
value: row[checkpoint + '-Delta'], |
|
checkpoint: checkpoint |
|
}; |
|
}); |
|
return lineData(arr); |
|
}; |
|
|
|
var brush = d3.brushY().on('brush', function (d, i) { |
|
if (d3.event.sourceEvent && d3.event.sourceEvent.type === 'draw') return; |
|
checkpointFilter = { |
|
checkpoint: d, |
|
values: d3.event.selection, |
|
valuesDomain: d3.event.selection.map(yScales[i].invert) |
|
}; |
|
d3.select('#chart').node().requestRedraw(); |
|
}); |
|
|
|
var xScaleLocation; |
|
|
|
d3.select('#chart').on('measure', function (d, i, nodes) { |
|
yScales.forEach(function (scale) { |
|
return scale.range([event.detail.height, 0]); |
|
}); |
|
xScale.range([0, event.detail.width]); |
|
brush.extent([[-8, 0], [8, event.detail.height]]); |
|
xScaleLocation = event.detail.height; |
|
}).on('draw', function (d, i, nodes) { |
|
var svg = d3.select(nodes[i]).select('svg'); |
|
|
|
var pathJoin = fc.dataJoin('g', 'run'); |
|
var join = pathJoin(svg, data); |
|
|
|
join.enter().append('path'); |
|
join.select('path').attr('d', rowToLine); |
|
join.classed('highlight', function (d) { |
|
return d[checkpointFilter.checkpoint + '-Delta'] > checkpointFilter.valuesDomain[1] && d[checkpointFilter.checkpoint + '-Delta'] < checkpointFilter.valuesDomain[0]; |
|
}); |
|
|
|
join.enter().append('text').text(function (d) { |
|
return d.Name; |
|
}); |
|
join.select('text').attr('transform', function (d) { |
|
return 'translate(-5, ' + yScales[0](d[checkpoints[0]]) + ')'; |
|
} |
|
|
|
// render the y-axes |
|
);var axisJoin = fc.dataJoin('g', 'y-axis'); |
|
axisJoin(svg, yScales).each(function (d, index, group) { |
|
var axis = d3.axisRight().scale(d); |
|
d3.select(group[index]).attr('transform', 'translate(' + xScale(checkpoints[index]) + ', 0)').call(axis); |
|
}); |
|
|
|
// render the x-axis |
|
var xAxisJoin = fc.dataJoin('g', 'x-axis'); |
|
xAxisJoin(svg, [0]).classed('x-scale', true).call(d3.axisBottom().scale(xScale)).attr('transform', 'translate(0, ' + xScaleLocation + ')'); |
|
|
|
// render the brushes |
|
var brushJoin = fc.dataJoin('g', 'brush'); |
|
|
|
brushJoin(svg, checkpoints).attr('transform', function (d, i) { |
|
return 'translate(' + xScale(checkpoints[i]) + ', 0)'; |
|
}).call(brush).call(brush.move, function (d) { |
|
if (checkpointFilter.checkpoint === d) { |
|
return checkpointFilter.values; |
|
} else { |
|
return undefined; |
|
} |
|
}).selectAll("rect").attr("x", -8).attr("width", 16); |
|
}).node().requestRedraw(); |
|
} |