text: Rickshaw.namespace("Rickshaw.Graph.Renderer");
  
  Rickshaw.Graph.Renderer = Rickshaw.Class.create( {
  
  	initialize: function(args) {
  		this.graph = args.graph;
  		this.tension = args.tension || this.tension;
  		this.configure(args);
  	},
  
  	seriesPathFactory: function() {
  		//implement in subclass
  	},
  
  	seriesStrokeFactory: function() {
  		// implement in subclass
  	},
  
  	defaults: function() {
  		return {
  			tension: 0.8,
  			strokeWidth: 2,
  			unstack: true,
  			padding: { top: 0.01, right: 0, bottom: 0.01, left: 0 },
  			stroke: false,
  			fill: false
  		};
  	},
  
  	domain: function(data) {
  		// Requires that at least one series contains some data
  		var stackedData = data || this.graph.stackedData || this.graph.stackData();
  
  		var xMin = +Infinity;
  		var xMax = -Infinity;
  
  		var yMin = +Infinity;
  		var yMax = -Infinity;
  
  		stackedData.forEach( function(series) {
  
  			series.forEach( function(d) {
  
  				if (d.y == null) return;
  
  				var y = d.y + d.y0;
  
  				if (y < yMin) yMin = y;
  				if (y > yMax) yMax = y;
  			} );
  
  			if (!series.length) return;
  
  			if (series[0].x < xMin) xMin = series[0].x;
  			if (series[series.length - 1].x > xMax) xMax = series[series.length - 1].x;
  		} );
  
  		xMin -= (xMax - xMin) * this.padding.left;
  		xMax += (xMax - xMin) * this.padding.right;
  
  		yMin = this.graph.min === 'auto' ? yMin : this.graph.min || 0;
  		yMax = this.graph.max === undefined ? yMax : this.graph.max;
  
  		if (this.graph.min === 'auto' || yMin < 0) {
  			yMin -= (yMax - yMin) * this.padding.bottom;
  		}
  
  		if (this.graph.max === undefined) {
  			yMax += (yMax - yMin) * this.padding.top;
  		}
  
  		return { x: [xMin, xMax], y: [yMin, yMax] };
  	},
  
  	render: function(args) {
  
  		args = args || {};
  
  		var graph = this.graph;
  		var series = args.series || graph.series;
  
  		var vis = args.vis || graph.vis;
  		vis.selectAll('*').remove();
  
  		var data = series
  			.filter(function(s) { return !s.disabled })
  			.map(function(s) { return s.stack });
  
  		var pathNodes = vis.selectAll("path.path")
  			.data(data)
  			.enter().append("svg:path")
  			.classed('path', true)
  			.attr("d", this.seriesPathFactory());
  
  		if (this.stroke) {
                          var strokeNodes = vis.selectAll('path.stroke')
                                  .data(data)
                                  .enter().append("svg:path")
  				.classed('stroke', true)
  				.attr("d", this.seriesStrokeFactory());
  		}
  
  		var i = 0;
  		var j;
  		series.forEach( function(series) {
  			if (series.disabled) return;
  			series.path = pathNodes[0][i];
  			if (this.stroke) series.stroke = strokeNodes[0][i];
  			this._styleSeries(series);
  			i++;
  
  			// support for a line breakpoint
  			if (series.lineBreakPoint && series.path.pathSegList && (series.data.length > series.lineBreakPoint) && !series.noPrediction) {
  
  				var pathStash = [];
  				for (var j = series.path.pathSegList.numberOfItems; j > series.lineBreakPoint; j--) {
  					pathStash.push( series.path.pathSegList.removeItem(series.path.pathSegList.numberOfItems - 1) );
  				}
  
  				var len = series.path.getTotalLength(); // This might crash Chrome<37/Webkit on certain lines
  
  				//restore
  				for (j = pathStash.length - 1; j >= 0; j--) {
  					series.path.pathSegList.appendItem(pathStash[j]);
  				}
  
  				var len2 = series.path.getTotalLength();
  
  				var times = ((len2 - len) / 8) + 2; // get the times we need to add the dashed pattern plus a safety
  
  				var strokes = [];
  				for (j = 0; j < times; j++) {
  					strokes.push("5,3");
  				}
  
  				series.path.setAttribute('style', 'stroke-dasharray:' + len + strokes.join(', '));
  			}
  
  		}, this );
  
  	},
  
  	_styleSeries: function(series) {
  
  		var fill = this.fill ? series.color : 'none';
  		var stroke = this.stroke ? series.color : 'none';
  
  		series.path.setAttribute('fill', fill);
  		series.path.setAttribute('stroke', stroke);
  		series.path.setAttribute('stroke-width', this.strokeWidth);
  
  		if (series.className) {
  			d3.select(series.path).classed(series.className, true);
  		}
  		if (series.className && this.stroke) {
  			d3.select(series.stroke).classed(series.className, true);
  		}
  	},
  
  	configure: function(args) {
  
  		args = args || {};
  
  		Rickshaw.keys(this.defaults()).forEach( function(key) {
  
  			if (!args.hasOwnProperty(key)) {
  				this[key] = this[key] || this.graph[key] || this.defaults()[key];
  				return;
  			}
  
  			if (typeof this.defaults()[key] == 'object') {
  
  				Rickshaw.keys(this.defaults()[key]).forEach( function(k) {
  
  					this[key][k] =
  						args[key][k] !== undefined ? args[key][k] :
  						this[key][k] !== undefined ? this[key][k] :
  						this.defaults()[key][k];
  				}, this );
  
  			} else {
  				this[key] =
  					args[key] !== undefined ? args[key] :
  					this[key] !== undefined ? this[key] :
  					this.graph[key] !== undefined ? this.graph[key] :
  					this.defaults()[key];
  			}
  
  		}, this );
  	},
  
  	setStrokeWidth: function(strokeWidth) {
  		if (strokeWidth !== undefined) {
  			this.strokeWidth = strokeWidth;
  		}
  	},
  
  	setTension: function(tension) {
  		if (tension !== undefined) {
  			this.tension = tension;
  		}
  	}
  } );
  
  