'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(); }