d3.custom = {}; d3.custom.utils = {}; d3.custom.utils.windowResize = function() {}; d3.custom.area = function() { // basic data var margin = {top: 4, bottom: 20, left: 20, right: 50}, width = 2500, height = 200, // accessors xValue = function(d) { return new Date(+d.x); }, yValue = function(d) { return +d.y; }, colorValue = function(d) { return d.color; }, // chart underpinnings x = d3.time.scale(), y = d3.scale.linear().ticks(2), colors = d3.scale.category20(), // axis xAxis = d3.svg.axis(), yAxis = d3.svg.axis(), // chart enhancements elastic = { x: false, y: false }, zeroed = { x: false, y: true }, convertData = true, // TODO fix to allow this to be exposed darken = 0, line = d3.svg.line(), area = d3.svg.area(), duration = 500, bisector = null, title = '', interpolation = "linear", formatNumber = d3.format(',f') formatTime = d3.time.format("%Y-%m-%d"), context = this; function render(selection) { // TODO realize that all the chartEnter stuff won't work if someone changes // something to enable an x-axis selection.each(function(data) { // setup the basics var w = width - margin.left - margin.right, h = height - margin.top - margin.bottom; // data if (convertData) { // TODO realize that lambda d[i]; only applies to converted data data = data.map(function(d, i) { return [xValue.call(data, d, i), parseFloat(yValue.call(data, d, i))]; // why parseFloat here }); } var bisect = d3.bisector(function(d) { return d[0]; }).left; // scales if (elastic.x) { var extent = d3.extent(data, function(d) { return d[0]; }); if (zeroed.x) extent[0] = 0; x.domain(extent); } if (elastic.y) { var extent = d3.extent(data, function(d) { return d[1]; }); if (zeroed.y) extent[0] = 0; y.domain(extent); } x.rangeRound([0, w], .1); y.range([h, 0]); var svg = selection.selectAll('svg').data([data]), svgEnter = svg.enter(); var cEnter = svgEnter.append('svg') .attr('width', width) .attr('height', height); var chartEnter = cEnter.append('g') .attr('width', w) .attr('height', h) .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') .classed('d3-custom-chart', true); // TODO handle the variables better cEnter.append('text') .attr('class', 'title'); var chart = svg.select('.d3-custom-chart'); if (xAxis) { xAxis.scale(x).orient('bottom'); chartEnter.append('g') .classed('x axis', true) .attr('transform', 'translate(' + 0 + ',' + h + ')'); chart.select('.x.axis') .transition() .duration(duration) .call(xAxis); } if (yAxis) { yAxis.scale(y).orient('right'); chartEnter.append('g') .attr('transform', 'translate(' + w + ',' + 0 + ')') .classed('y axis', true); chart.select('.y.axis') .transition() .duration(duration) .call(yAxis); } // line and area helpers if (area) { area.interpolate(interpolation) .x(function(d) { return x(d[0]); }) .y0(h) .y1(function(d) { return y(d[1]); }); chart.append('path') .attr('d', area(data)) .style('fill', function(d) { return d3.hsl(colors(d[1])); }) .classed('area', true); } if (line) { line.interpolate(interpolation) .x(function(d) { return x(d[0]); }) .y(function(d) { return y(d[1]); }); chart.append('path') .attr('d', line(data)) .style('stroke', function(d) { return d3.hsl(colors(d[1])).darker(darken); }) .classed('line', true); } if (title) { svg.select('.title') .attr('transform', 'translate(' + (w - title.length * 6) + ',' + (h - 8) + ')') .text(title); } chart.append('g') .attr('class', 'focus') .style('display', 'none'); var focus = svg.select('.focus'); focus.append('circle') .attr('stroke', colors()) .attr('r', 4.5); focus.append('text') .attr('x', 9) .attr('dy', '.35em'); svg.append('rect') .attr('width', w) .attr('height', h) .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') .classed('overlay', true) .on("mouseover", function() { focus.style("display", null); }) .on("mouseout", function() { focus.style("display", "none"); }) .on("mousemove", mousemove); // TODO handle logic for preventing focus SVG clipping // TODO figure out why colors() spits out the last other color used here function mousemove() { var xPixelSpace = d3.mouse(this)[0], x0 = x.invert(xPixelSpace), i = bisect(data, x0), d0 = data[i - 1], d1 = data[i], d = (x0 - d0[0] > d1[0] - x0) ? d1 : d0; focus.attr("transform", "translate(" + x(d[0]) + "," + y(d[1]) + ")"); focus.select("text").text(formatTime(d[0]) + ': ' + formatNumber(d[1])); // TODO overall focus so we can remove date console.log(xPixelSpace, x0, i, d0, d1, d); } }); } // basic data render.margin = function(_) { if (!arguments.length) return margin; margin = _; return render; }; render.width = function(_) { if (!arguments.length) return width; width = _; return render; }; render.height = function(_) { if (!arguments.length) return height; height = _; return render; }; // accessors render.xValue = function(_) { if (!arguments.length) return xValue; xValue = _; return render; }; render.yValue = function(_) { if (!arguments.length) return yValue; yValue = _; return render; }; // scales render.x = function(_) { if (!arguments.length) return x; x = _; return render; }; render.y = function(_) { if (!arguments.length) return y; y = _; return render; }; render.colors = function(_) { if (!arguments.length) return colors; colors = d3.functor(_); return render; }; render.title = function(_) { if (!arguments.length) return title; title = _; return render; }; // axis render.xAxis = function(_) { if (!arguments.length) return xAxis; xAxis = _; return render; }; render.yAxis = function(_) { if (!arguments.length) return yAxis; yAxis = _; return render; }; // chart underpinnings render.line = function(_) { if (!arguments.length) return line; line = _; return render; }; render.area = function(_) { if (!arguments.length) return area; area = _; return render; }; render.interpolation = function(_) { if (!arguments.length) return interpolation; interpolation = _; return render; }; // render.elastic = function(_) { // // } render.darken = function(_) { if (!arguments.length) return darken; darken = _; return render; }; // cloning render.clone = function() { var that = this; var temp = function temporary() { return that.apply(this, arguments); }; for( key in this ) { temp[key] = this[key]; } return temp; }; return render; };