I have two javascript functions that generate a D3 Stream graph. I feel I may be excessively and unnecessarily iterating over my dataset to get it into the desired form.
How might I optimize or simplify this?
d3.json("/sleeps", function(d) {
sleepStream(d);
});
function sleepStream(d) {
var divheight = $(document).height()
, divwidth = $(document).width()
, margin = { top: 20 , right: 20 , bottom: 20 , left: 10 }
, width = divwidth - margin.left - margin.right
, height = (divheight / 8) - margin.top - margin.bottom;
var colorrange = ["#80B2B7", "#4B98B1", "#1C7BAC", "#235AA0", "#453485"]
, datearray = []
, strokecolor = '#FFF';
var x = d3.time.scale().range([0, width])
, y = d3.scale.linear().range([height, 0])
, z = d3.scale.ordinal().range(colorrange);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.months);
var yAxis = d3.svg.axis().scale(y);
var nest = d3.nest()
.key(function (d) { return d.key; });
/*
* Helper function to extract (subset) Jawbone results.
* Iterates through data and returns desired groups and
* values.
*/
function getData(d) {
var _data = [];
var parseDate = d3.time.format("%Y%m%d").parse;
for (var i = 0; i < d.length; i++) {
var _date = parseDate(d[i].date.toString());
_data[i] = {
'date' : _date
, 'light': Math.round(d[i].details.light / 60)
// Make it a calculated value due to some dirty data
// that screws up path vals
, 'deep' : Math.round((d[i].details.duration - (d[i].details.light + d[i].details.awake)) /60)
, 'awake': Math.round(d[i].details.awake / 60)
, 'duration': Math.round(d[i].details.duration / 60)
};
};
return _data;
};
var tmpdata = getData(d);
var data = [];
/*
* RFORMAT tmpdata JSON FOR D3 STREAM
*/
tmpdata.forEach(function (e) {
for (var key in e) {
if (key !== 'date')
data.push({
key: key
, date: e.date
, value: e[key]
});
}
});
/*
* Pad missing values
*/
assignDefaultValues(data); //data.forEach(function(d){ console.log(d); });
var stack = d3.layout.stack()
.offset("silhouette")
.values(function (d) { return d.values; })
.x(function (d) { return d.date; })
.y(function (d) { return d.value; });
var layers = stack(nest.entries(data));
var area = d3.svg.area()
.interpolate("basis")
.x(function (d) { return x(d.date); })
.y0(function (d) { return y(d.y0); })
.y1(function (d) { return y(d.y0 + d.y); });
var svg = d3.select("#sleep-stream-chart").append("svg")
.attr("width", width - margin.left - margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(data, function (d) { return d.date; }));
y.domain([0, d3.max(data, function (d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function (d) { return area(d.values); })
.attr("transform", "translate(8, 0)")
.style("fill", function (d, i) { return z(i); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(8," + height + ")")
.attr("class", "axis")
.attr("fill", "#fff")
.call(xAxis);
var tooltip = d3.select("#sleep-stream-chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "20")
.style("visibility", "hidden")
.style("top", (height / 2) + "px")
.style("left", (width / 3) + "px");
svg.selectAll(".layer")
.attr("opacity", .5)
.on("mouseover", function (d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function (d, j) {
return j != i ? 0.5 : 1;
})
})
.on("mousemove", function (d, i) {
mousex = d3.mouse(this);
mousex = mousex[0];
var invertedx = x.invert(mousex);
invertedx = invertedx.getMonth() + invertedx.getDate();
var selected = (d.values);
for (var k = 0; k < selected.length; k++) {
datearray[k] = selected[k].date;
datearray[k] = datearray[k].getMonth() + datearray[k].getDate();
}
_mousedate = datearray.indexOf(invertedx);
_tmpdate = x.invert(mousex);
pro = d.values[_mousedate].value;
d3.select(this)
.classed("hover", true)
.attr("stroke", strokecolor)
.attr("stroke-width", "0.5px");
tooltip.html("<p>" + _tmpdate + " " + pro + " " + d.key +"</p>")
.style("visibility", "visible")
.style("color", "white");
})
.on("mouseout", function (d, i) {
svg.selectAll(".layer")
.transition()
.duration(250)
.attr("opacity", "1");
d3.select(this)
.classed("hover", false)
.attr("stroke-width", "0px")
tooltip.html("<p>" + _tmpdate + " " + pro + " " + d.key +"</p>")
.style("visibility", "hidden");
})
/*
* A vertical white bar that tracks horizontal mouse movement.
* It aids visual inspection if substreams by aligning a point
* with the x axis.
*/
var vertical = d3.select("#sleep-stream-chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "19")
.style("width", "1px")
.style("height", divheight)
.style("top", "10px")
.style("bottom", "0px")
.style("left", "0px")
.style("background", "#fff");
d3.select("#sleep-stream-chart")
.on("mousemove", function () {
mousex = d3.mouse(this);
vertical.style("left", (mousex[0]) + "px")
})
.on("mouseover", function () {
mousex = d3.mouse(this);
vertical.style("left", (mousex[0]) + "px")
});
/*
* Turn off the Loading Spinner
*/
spinner4.stop();
};
function assignDefaultValues(dataset) {
defaultValue = 0;
keys = ['Group1', 'Group2', 'Group3', 'Group4'];
hadData = [true, true, true, true];
newData = [];
previousdate = new Date();
sortByDate = function (a, b) {
return a.date > b.date ? 1 : -1;
};
dataset.sort(sortByDate);
dataset.forEach(function (row) {
if (row.date.valueOf() !== previousdate.valueOf()) {
for (var i = 0; i < keys.length; ++i) {
if (hadData[i] === false) {
newData.push({
key: keys[i]
, value: defaultValue
, date: previousdate
});
}
hadData[i] = false;
}
previousdate = row.date;
}
hadData[keys.indexOf(row.key)] = true;
});
for (i = 0; i < keys.length; ++i) {
if (hadData[i] === false) {
newData.push({
key: keys[i]
, value: defaultValue
, date: previousdate
});
}
}
return dataset.concat(newData).sort(sortByDate);
}