block by milroc 5988531

basis interpolation + circles

Full Screen

This example is showcasing an issue with syncing other representations of data (to see hover over a chart to see a circle) on a non-linear (e.g: basis) line or area chart.

index.html

<!DOCTYPE html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="//littlesparkvt.com/flatstrap/assets/css/bootstrap.css"/>
    <link type="text/css" rel="stylesheet" href="style.css"/>
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="src.js"></script>
  </head>
  <body>
    <div id="chart-1"></div>
    <div id="chart-2"></div>
    <div id="chart-3"></div>
    <div id="chart-4"></div>
    <div id="chart-5"></div>
    <div id="chart-6"></div>
    <div id="chart-7"></div>
    <div id="chart-8"></div>
    <div id="chart-9"></div>
    <div id="chart-10"></div>
    <div id="chart-11"></div>
    <div id="chart-12"></div>

    <script type="text/javascript">
var parseDate = d3.time.format("%b %Y").parse;
var colors = ["#88E48E",
"#EBAACD",
"#E9B859",
"#7CD2EC",
"#F0A487",
"#D4E264",
"#70E2B8",
"#BCB8E8",
"#B9DB95",
"#84DCD3",
"#D0C578",
"#C5CCDB"];

function randomDate(start, end) {
    return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()))
}

var start = new Date(2008, 0, 0),
    end = new Date(),
    dates = d3.range(200).map(function(d, i) { 
      return {
        x: randomDate(start, end)
      };
    }).sort(function(a, b) { return a.x - b.x; });

var j = 0;

function random(y) {
  ++j;
  return dates.map(function(d, i) {
    return {
      x: d.x,
      y: Math.random() * y,
      i: 'chart-' + j
    };
  });
}

var line = d3.custom.area()
                    .y(d3.scale.linear().domain([0, 120]))
                    .x(d3.time.scale().domain([start, end]))
                    .height(120)
                    .width(900)
                    .interpolation('basis')
                    .yAxis(d3.svg.axis().ticks(4))
                    .xAxis(false)
                    .title('chart-' + i)
                    // .line(false)
                    // .area(false)
                    .darken(0);
d3.select('#chart-1')
    .datum(random(100))
    .call(line
            .colors(colors[0])
            .title('chart-' + 1));

for (var i = 2; i <= 12; i++) {
  var line = d3.custom.area()
                      .y(d3.scale.linear().domain([0, 120]))
                      .x(d3.time.scale().domain([start, end]))
                      .height(120)
                      .width(900)
                      .interpolation('basis')
                      .yAxis(d3.svg.axis().ticks(4))
                      .xAxis(false)
                      .title('chart-' + i)
                      // .line(false)
                      // .area(false)
                      .darken(0);
  d3.select('#chart-'+i)
      .datum(random(100))
      .call(line.colors(colors[i])
                .title('chart-' + i));
}  
    </script>
  </body>
</html>

sp500-fake.csv

date,price
Jan 2000,200
Feb 2000,200
Mar 2000,200
Apr 2000,200
May 2000,200
Jun 2000,200
Jul 2000,1000
Aug 2000,1000
Sep 2000,1000
Oct 2000,1000
Nov 2000,1000
Dec 2000,1000
Jan 2001,1000
Feb 2001,1000
Mar 2001,1000
Apr 2001,200
May 2001,200
Jun 2001,200
Jul 2001,200
Aug 2001,200
Sep 2001,200
Oct 2001,200
Nov 2001,200
Dec 2001,200
Jan 2002,200
Feb 2002,200
Mar 2002,200
Apr 2002,200
May 2002,200
Jun 2002,200
Jul 2002,200
Aug 2002,200
Sep 2002,200
Oct 2002,200
Nov 2002,200
Dec 2002,200
Jan 2003,200
Feb 2003,200
Mar 2003,200
Apr 2003,200
May 2003,200
Jun 2003,200
Jul 2003,200
Aug 2003,200
Sep 2003,200
Oct 2003,200
Nov 2003,200
Dec 2003,200
Jan 2004,200
Feb 2004,200
Mar 2004,200
Apr 2004,200
May 2004,200
Jun 2004,200
Jul 2004,200
Aug 2004,200
Sep 2004,200
Oct 2004,200
Nov 2004,200
Dec 2004,200
Jan 2005,200
Feb 2005,200
Mar 2005,200
Apr 2005,200
May 2005,200
Jun 2005,200
Jul 2005,200
Aug 2005,200
Sep 2005,200
Oct 2005,200
Nov 2005,200
Dec 2005,200
Jan 2006,200
Feb 2006,200
Mar 2006,200
Apr 2006,200
May 2006,200
Jun 2006,200
Jul 2006,200
Aug 2006,200
Sep 2006,200
Oct 2006,200
Nov 2006,200
Dec 2006,200
Jan 2007,200
Feb 2007,200
Mar 2007,200
Apr 2007,200
May 2007,200
Jun 2007,200
Jul 2007,200
Aug 2007,200
Sep 2007,200
Oct 2007,200
Nov 2007,200
Dec 2007,200
Jan 2008,200
Feb 2008,200
Mar 2008,200
Apr 2008,200
May 2008,200
Jun 2008,200
Jul 2008,200
Aug 2008,200
Sep 2008,200
Oct 2008,200
Nov 2008,200
Dec 2008,200
Jan 2009,200
Feb 2009,200
Mar 2009,200
Apr 2009,200
May 2009,200
Jun 2009,200
Jul 2009,200
Aug 2009,200
Sep 2009,200
Oct 2009,200
Nov 2009,200
Dec 2009,200
Jan 2010,200
Feb 2010,200
Mar 2010,200

sp500-fake2.csv

date,price
Jan 2000,200
Feb 2000,200
Mar 2000,200
Apr 2000,200
May 2000,200
Jun 2000,200
Jul 2000,1000
Aug 2000,1000
Sep 2000,1000
Oct 2000,1000
Nov 2000,1000
Dec 2000,1000
Jan 2001,1000
Feb 2001,1000
Mar 2001,1000
Apr 2001,200
May 2001,200
Jun 2001,200
Jul 2001,200
Aug 2001,200
Sep 2001,200
Oct 2001,200
Nov 2001,200
Dec 2001,200
Jan 2002,200
Feb 2002,200
Mar 2002,200
Apr 2002,200
May 2002,200
Jun 2002,200
Jul 2002,200
Aug 2002,200
Sep 2002,200
Oct 2002,200
Nov 2002,200
Dec 2002,200
Jan 2003,200
Feb 2003,200
Mar 2003,200
Apr 2003,200
May 2003,200
Jun 2003,200
Jul 2003,1200
Aug 2003,1200
Sep 2003,1200
Oct 2003,1200
Nov 2003,1200
Dec 2003,1200
Jan 2004,1200
Feb 2004,1200
Mar 2004,1200
Apr 2004,1200
May 2004,1200
Jun 2004,1200
Jul 2004,1200
Aug 2004,1200
Sep 2004,1200
Oct 2004,1200
Nov 2004,1200
Dec 2004,1200
Jan 2005,1200
Feb 2005,1200
Mar 2005,1200
Apr 2005,1200
May 2005,1200
Jun 2005,1200
Jul 2005,1200
Aug 2005,1200
Sep 2005,1200
Oct 2005,1200
Nov 2005,1200
Dec 2005,1200
Jan 2006,1200
Feb 2006,1200
Mar 2006,1200
Apr 2006,1200
May 2006,1200
Jun 2006,1200
Jul 2006,1200
Aug 2006,1200
Sep 2006,1200
Oct 2006,1200
Nov 2006,1200
Dec 2006,1200
Jan 2007,1200
Feb 2007,1200
Mar 2007,1200
Apr 2007,1200
May 2007,1200
Jun 2007,1200
Jul 2007,1200
Aug 2007,200
Sep 2007,200
Oct 2007,200
Nov 2007,200
Dec 2007,200
Jan 2008,200
Feb 2008,200
Mar 2008,200
Apr 2008,200
May 2008,200
Jun 2008,200
Jul 2008,200
Aug 2008,200
Sep 2008,200
Oct 2008,200
Nov 2008,200
Dec 2008,200
Jan 2009,200
Feb 2009,200
Mar 2009,200
Apr 2009,200
May 2009,200
Jun 2009,200
Jul 2009,200
Aug 2009,200
Sep 2009,200
Oct 2009,200
Nov 2009,200
Dec 2009,200
Jan 2010,200
Feb 2010,200
Mar 2010,200

sp500.csv

date,price
Jan 2000,1394.46
Feb 2000,1366.42
Mar 2000,1498.58
Apr 2000,1452.43
May 2000,1420.6
Jun 2000,1454.6
Jul 2000,1430.83
Aug 2000,1517.68
Sep 2000,1436.51
Oct 2000,1429.4
Nov 2000,1314.95
Dec 2000,1320.28
Jan 2001,1366.01
Feb 2001,1239.94
Mar 2001,1160.33
Apr 2001,1249.46
May 2001,1255.82
Jun 2001,1224.38
Jul 2001,1211.23
Aug 2001,1133.58
Sep 2001,1040.94
Oct 2001,1059.78
Nov 2001,1139.45
Dec 2001,1148.08
Jan 2002,1130.2
Feb 2002,1106.73
Mar 2002,1147.39
Apr 2002,1076.92
May 2002,1067.14
Jun 2002,989.82
Jul 2002,911.62
Aug 2002,916.07
Sep 2002,815.28
Oct 2002,885.76
Nov 2002,936.31
Dec 2002,879.82
Jan 2003,855.7
Feb 2003,841.15
Mar 2003,848.18
Apr 2003,916.92
May 2003,963.59
Jun 2003,974.5
Jul 2003,990.31
Aug 2003,1008.01
Sep 2003,995.97
Oct 2003,1050.71
Nov 2003,1058.2
Dec 2003,1111.92
Jan 2004,1131.13
Feb 2004,1144.94
Mar 2004,1126.21
Apr 2004,1107.3
May 2004,1120.68
Jun 2004,1140.84
Jul 2004,1101.72
Aug 2004,1104.24
Sep 2004,1114.58
Oct 2004,1130.2
Nov 2004,1173.82
Dec 2004,1211.92
Jan 2005,1181.27
Feb 2005,1203.6
Mar 2005,1180.59
Apr 2005,1156.85
May 2005,1191.5
Jun 2005,1191.33
Jul 2005,1234.18
Aug 2005,1220.33
Sep 2005,1228.81
Oct 2005,1207.01
Nov 2005,1249.48
Dec 2005,1248.29
Jan 2006,1280.08
Feb 2006,1280.66
Mar 2006,1294.87
Apr 2006,1310.61
May 2006,1270.09
Jun 2006,1270.2
Jul 2006,1276.66
Aug 2006,1303.82
Sep 2006,1335.85
Oct 2006,1377.94
Nov 2006,1400.63
Dec 2006,1418.3
Jan 2007,1438.24
Feb 2007,1406.82
Mar 2007,1420.86
Apr 2007,1482.37
May 2007,1530.62
Jun 2007,1503.35
Jul 2007,1455.27
Aug 2007,1473.99
Sep 2007,1526.75
Oct 2007,1549.38
Nov 2007,1481.14
Dec 2007,1468.36
Jan 2008,1378.55
Feb 2008,1330.63
Mar 2008,1322.7
Apr 2008,1385.59
May 2008,1400.38
Jun 2008,1280
Jul 2008,1267.38
Aug 2008,1282.83
Sep 2008,1166.36
Oct 2008,968.75
Nov 2008,896.24
Dec 2008,903.25
Jan 2009,825.88
Feb 2009,735.09
Mar 2009,797.87
Apr 2009,872.81
May 2009,919.14
Jun 2009,919.32
Jul 2009,987.48
Aug 2009,1020.62
Sep 2009,1057.08
Oct 2009,1036.19
Nov 2009,1095.63
Dec 2009,1115.1
Jan 2010,1073.87
Feb 2010,1104.49
Mar 2010,1140.45

src.js

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;
};

style.css

body {
  font: 14px helvetica;
  color: #f0f0f0;
  background-color: #3E4651;
}

.row {
  padding: 5px;
}

.y.axis line,
.y.axis path {
  fill: none;
  stroke: #FFF;
  shape-rendering: crispEdges;
}

.x.axis line,
.x.axis path {
  fill: none;
  stroke: none;
}

.axis text {
  fill: #FFF;
}

.brush .extent {
  stroke: #FFF;
  fill-opacity: .125;
  shape-rendering: crispEdges;
}

.area {
  opacity: .6;
}
.line {
  fill: none;
  /*shape-rendering: crispEdges;*/
}

.title {
  fill: #FFF;
  stroke: none;
}

.focus circle {
  fill: none;
  stroke-width: 2px;
}

.focus text {
  fill: #FFF;
}

.overlay {
  fill: none;
  pointer-events: all;
}