Commits

Jason R. Coombs committed e6a1a76

Updated much of the legacy documentation

  • Participants
  • Parent commits a5e9732

Comments (0)

Files changed (4)

File svg/charts/bar.py

 __all__ = ('VerticalBar', 'HorizontalBar')
 
 class Bar(Graph):
-	"A superclass for bar-style graphs.  Do not instantiate directly."
+	"""
+	Create presentation quality SVG bar graphs easily.
 
+	Synopsis
+	========
+
+	from svg.charts import bar
+
+
+	fields = 'Jan Feb Mar'.split()
+	data_sales_02 = [12, 45, 21]
+
+	bc = bar.VerticalBar(fields, {'width': 300, 'height': 500})
+
+	bc.add_data({'data': data_sales_02, 'title': 'Sales 2002'})
+
+	print("Content-type: image/svg+xml\r\n\r\n")
+	print(bc.burn())
+
+	Description
+	===========
+
+	This object aims to allow you to easily create high quality
+	`SVG <http://www.w3c.org/tr/svg>`_ bar graphs. You can either use the default
+	style sheet or supply your own. Either way there are many options which
+	can be configured to give you control over how the graph is generated -
+	with or without a key, data elements at each point, title, subtitle etc.
+
+	Notes
+	=====
+
+	The default stylesheet handles upto 12 data sets, if you
+	use more you must create your own stylesheet and add the
+	additional settings for the extra data sets. You will know
+	if you go over 12 data sets as they will have no style and
+	be in black.
+
+	Examples
+	========
+
+	See the example usage in tests/samples.py
+
+	See also
+	========
+
+	* svg.charts.graph
+	* svg.charts.line
+	* svg.charts.pie
+	* svg.charts.plot
+	* svg.charts.timeseries
+	"""
+
 	# gap between bars
 	bar_gap = True
 	# how to stack adjacent dataset series
 	# top - stack bars on top of one another
 	# side - stack bars side-by-side
 	stack = 'overlap'
-	
+
 	scale_divisions = None
 
 	stylesheet_names = Graph.stylesheet_names + ['bar.css']
 		if self.scale_integers:
 			result = map(int, result)
 		return result
-	
+
 	# adapted from plot (very much like calling data_range('y'))
 	def data_range(self):
 		min_value = self.data_min()
 
 		data_pad = range / 20.0 or 10
 		scale_range = (max_value + data_pad) - min_value
-		
+
 		scale_division = self.scale_divisions or (scale_range / 10.0)
-		
+
 		if self.scale_integers:
 			scale_division = round(scale_division) or 1
-			
+
 		return min_value, max_value, scale_division
 
 	def get_field_labels(self):
 		return max(chain(*map(lambda set: set['data'], self.data)))
 		# above is same as
 		# return max(map(lambda set: max(set['data']), self.data))
-		
+
 	def data_min(self):
 		if not getattr(self, 'min_scale_value') is None: return self.min_scale_value
 		min_value = min(chain(*map(lambda set: set['data'], self.data)))
 
 
 class VerticalBar(Bar):
-	"""    # === Create presentation quality SVG bar graphs easily
-    #
-    # = Synopsis
-    #
-    #   require 'SVG/Graph/Bar'
-    #
-    #   fields = %w(Jan Feb Mar);
-    #   data_sales_02 = [12, 45, 21]
-    #
-    #   graph = SVG::Graph::Bar.new(
-    #     :height => 500,
-    #     :width => 300,
-    #     :fields => fields
-    #  )
-    #
-    #   graph.add_data(
-    #     :data => data_sales_02,
-    #     :title => 'Sales 2002'
-    #  )
-    #
-    #   print "Content-type: image/svg+xml\r\n\r\n"
-    #   print graph.burn
-    #
-    # = Description
-    #
-    # This object aims to allow you to easily create high quality
-    # SVG[http://www.w3c.org/tr/svg bar graphs. You can either use the default
-    # style sheet or supply your own. Either way there are many options which
-    # can be configured to give you control over how the graph is generated -
-    # with or without a key, data elements at each point, title, subtitle etc.
-    #
-    # = Notes
-    #
-    # The default stylesheet handles upto 12 data sets, if you
-    # use more you must create your own stylesheet and add the
-    # additional settings for the extra data sets. You will know
-    # if you go over 12 data sets as they will have no style and
-    # be in black.
-    #
-    # = Examples
-    #
-    # * http://germane-software.com/repositories/public/SVG/test/test.rb
-    #
-    # = See also
-    #
-    # * SVG::Graph::Graph
-    # * SVG::Graph::BarHorizontal
-    # * SVG::Graph::Line
-    # * SVG::Graph::Pie
-    # * SVG::Graph::Plot
-    # * SVG::Graph::TimeSeries
-"""
 	top_align = top_font = 1
 
 	def get_x_labels(self):
 		unit_size = (float(self.graph_height) - self.font_size*2*self.top_font)
 		unit_size /= (max(self.get_data_values()) - min(self.get_data_values()))
 
-		bar_gap = self.get_bar_gap(self.get_field_width())		
+		bar_gap = self.get_bar_gap(self.get_field_width())
 
 		bar_width = self.get_field_width() - bar_gap
 		if self.stack == 'side':
 			bar_width /= len(self.data)
-		
+
 		x_mod = (self.graph_width - bar_gap)/2
 		if self.stack == 'side':
 			x_mod -= bar_width/2
 
 		bottom = self.graph_height
-		
+
 		for field_count, field in enumerate(self.fields):
 			for dataset_count, dataset in enumerate(self.data):
 				# cases (assume 0 = +ve):
 				#    +ve   -ve  value - 0
 				#    -ve   -ve  value.abs - 0
 				value = dataset['data'][field_count]
-				
+
 				left = self.get_field_width() * field_count
-				
+
 				length = (abs(value) - max(min_value, 0)) * unit_size
 				# top is 0 if value is negative
 				top = bottom - ((max(value,0) - min_value) * unit_size)
 					'height': str(length),
 					'class': 'fill%s' % (dataset_count+1),
 				})
-				
+
 				self.make_datapoint_text(left + bar_width/2.0, top-6, value)
 
 class HorizontalBar(Bar):
 	show_x_guidelines = True
 	show_y_guidelines = False
 	right_align = right_font = True
-	
 
+
 	def get_x_labels(self):
 		return self.get_data_labels()
-	
+
 	def get_y_labels(self):
 		return self.get_field_labels()
 
 	def y_label_offset(self, height):
 		return height / -2.0
-	
+
 	def draw_data(self):
 		min_value = self.data_min()
 
 		unit_size = float(self.graph_width)
 		unit_size -= self.font_size*2*self.right_font
 		unit_size /= max(self.get_data_values()) - min(self.get_data_values())
-		
+
 		bar_gap = self.get_bar_gap(self.get_field_height())
 
 		bar_height = self.get_field_height() - bar_gap
 		for field_count, field in enumerate(self.fields):
 			for dataset_count, dataset in enumerate(self.data):
 				value = dataset['data'][field_count]
-		
+
 				top = self.graph_height - (self.get_field_height() * (field_count+1))
 				if self.stack == 'side':
 					top += (bar_height * dataset_count)
 					'height': str(bar_height),
 					'class': 'fill%s' % (dataset_count+1),
 				})
-				
+
 				self.make_datapoint_text(left+length+5, top+y_mod, value,
-										 "text-anchor: start; ")
+										 "text-anchor: start; ")

File svg/charts/plot.py

 		return func()
 
 class Plot(Graph):
-	"""=== For creating SVG plots of scalar data
-
-	= Synopsis
-
-	  require 'SVG/Graph/Plot'
-
-	  # Data sets are x,y pairs
-	  # Note that multiple data sets can differ in length, and that the
-	  # data in the datasets needn't be in order; they will be ordered
-	  # by the plot along the X-axis.
-	  projection = [
-		6, 11,    0, 5,   18, 7,   1, 11,   13, 9,   1, 2,   19, 0,   3, 13,
-		7, 9
-	 ]
-	  actual = [
-		0, 18,    8, 15,    9, 4,   18, 14,   10, 2,   11, 6,  14, 12,
-		15, 6,   4, 17,   2, 12
-	 ]
-
-	  graph = SVG::Graph::Plot.new({
-	   :height => 500,
-		   :width => 300,
-		:key => true,
-		:scale_x_integers => true,
-		:scale_y_integerrs => true,
-	 })
-
-	  graph.add_data({
-	   :data => projection
-		 :title => 'Projected',
-	 })
-
-	  graph.add_data({
-	   :data => actual,
-		 :title => 'Actual',
-	 })
-
-	  print graph.burn()
-
-	= Description
-
+	"""
+	An SVG plot of scalar data.
+
+	Synopsis
+	========
+
+	from svg.charts import plot
+
+	# Data sets are x,y pairs
+	# Note that multiple data sets can differ in length, and that the
+	# data in the datasets needn't be in order; they will be ordered
+	# by the plot along the x-axis.
+	projection = [
+		6, 11,  0, 5,  18, 7,  1, 11,  13, 9,  1, 2,  19, 0,  3, 13,  7, 9,
+	]
+	actual = [
+		0, 18,  8, 15,  9, 4,  18, 14,  10, 2,  11, 6,  14, 12,  15, 6,
+		4, 17,  2, 12,
+	]
+
+	p = plot.Plot(dict(
+		height = 500,
+		width = 300,
+		key = true,
+		scale_x_integers = True,
+		scale_y_integerrs = True,
+	))
+
+	p.add_data({
+		'data': projection,
+		'title': 'Projected',
+	})
+
+	p.add_data({
+		'data': actual,
+		'title': 'Actual',
+	})
+
+	print(p.burn())
+
+	Description
+	===========
+
 	Produces a graph of scalar data.
 
 	This object aims to allow you to easily create high quality
-	SVG[http://www.w3c.org/tr/svg] scalar plots. You can either use the
+	`SVG <http://www.w3c.org/tr/svg>`_ scalar plots. You can either use the
 	default style sheet or supply your own. Either way there are many options
 	which can be configured to give you control over how the graph is
 	generated - with or without a key, data elements at each point, title,
 	subtitle etc.
-
-	= Examples
-
-	http://www.germane-software/repositories/public/SVG/test/plot.rb
-
-	= Notes
-
+
+	Examples
+	========
+
+	See the examples in tests/samples.py
+
+	Notes
+	=====
+
 	The default stylesheet handles upto 10 data sets, if you
 	use more you must create your own stylesheet and add the
 	additional settings for the extra data sets. You will know
 	if you go over 10 data sets as they will have no style and
 	be in black.
 
-	Unlike the other types of charts, data sets must contain x,y pairs:
-
-	  [1, 2]    # A data set with 1 point: (1,2)
-	  [1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
-
-	= See also
-
-	* SVG::Graph::Graph
-	* SVG::Graph::BarHorizontal
-	* SVG::Graph::Bar
-	* SVG::Graph::Line
-	* SVG::Graph::Pie
-	* SVG::Graph::TimeSeries
-
-	== Author
-
-	Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
-
-	Copyright 2004 Sean E. Russell
-	This software is available under the Ruby license[LICENSE.txt]"""
-
+	Unlike the other types of charts, data sets must contain x,y pairs::
+
+	  [1, 2]     # A data set with 1 point: (1,2)
+	  [1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
+
+	"""
+
 	top_align = right_align = top_font = right_font = 1
 
-
-	"""Determines the scaling for the Y axis divisions.
-
-	  graph.scale_y_divisions = 0.5
-
-	would cause the graph to attempt to generate labels stepped by 0.5; EG:
-	0, 0.5, 1, 1.5, 2, ..."""
+
 	scale_y_divisions = None
+	"""
+	Determines the scaling for the Y axis divisions.
+
+	  Plot.scale_y_divisions = 0.5
+
+	would cause the graph to attempt to generate labels stepped by 0.5; e.g.:
+	0, 0.5, 1, 1.5, 2, ...
+	"""
+	scale_x_integers = False
 	"Make the X axis labels integers"
-	scale_x_integers = False
-	"Make the Y axis labels integers"
 	scale_y_integers = False
-	"Fill the area under the line"
+	"Make the Y axis labels integers"
 	area_fill = False
+	"Fill the area under the line"
+	show_data_points = True
 	"""Show a small circle on the graph where the line
 	goes from one point to the next."""
-	show_data_points = True
-	"Indicate whether the lines should be drawn between points"
 	draw_lines_between_points = True
-	"Set the minimum value of the X axis"
+	"Indicate whether the lines should be drawn between points"
 	min_x_value = None
-	"Set the minimum value of the Y axis"
+	"Set the minimum value of the X axis"
 	min_y_value = None
-	"Set the maximum value of the X axis"
+	"Set the minimum value of the Y axis"
 	max_x_value = None
-	"Set the maximum value of the Y axis"
+	"Set the maximum value of the X axis"
 	max_y_value = None
+	"Set the maximum value of the Y axis"
 
 	stacked = False
 
 
 	@apply
 	def scale_x_divisions():
-		doc = """Determines the scaling for the X axis divisions.
-
+		doc = """
+			Determines the scaling for the X axis divisions.
+
 			graph.scale_x_divisions = 2
-
-			would cause the graph to attempt to generate labels stepped by 2; EG:
-			0,2,4,6,8..."""
+
+			would cause the graph to attempt to generate labels stepped by 2; e.g.:
+			0,2,4,6,8...
+			"""
 		def fget(self):
 			return getattr(self, '_scale_x_divisions', None)
 		def fset(self, val):
 		# above is same as
 		#max_value = max(map(lambda set: max(set['data'][data_index]), self.data))
 		spec_max = getattr(self, 'max_%s_value' % axis)
-		# Python 3 doesn't allow comparing None to int, so use -
+		# Python 3 doesn't allow comparing None to int, so use -8
 		if spec_max is None: spec_max = float('-Inf')
 		max_value = max(max_value, spec_max)
 		return max_value
 			self.make_datapoint_text(gx, gy-6, dy)
 
 	def format(self, x, y):
-		return '(%0.2f, %0.2f)' % (x,y)
+		return '(%0.2f, %0.2f)' % (x,y)

File svg/charts/schedule.py

 
 class Schedule(Graph):
 	"""
-	    # === For creating SVG plots of scalar temporal data
-	
-	= Synopsis
-	
-	  require 'SVG/Graph/Schedule'
-	
-	  # Data sets are label, start, end tripples.
-	  data1 = [
-		"Housesitting", "6/17/04", "6/19/04", 
+	Represents SVG plots of scalar temporal data
+
+	Synopsis
+	========
+
+	from svg.charts import schedule
+
+	# Data sets are label, start, end triples.
+	data1 = [
+		"Housesitting", "6/17/04", "6/19/04",
 		"Summer Session", "6/15/04", "8/15/04",
-	  ]
-	
-	  graph = SVG::Graph::Schedule.new( {
-		:width => 640,
-		:height => 480,
-		:graph_title => title,
-		:show_graph_title => true,
-		:no_css => true,
-		:scale_x_integers => true,
-		:scale_y_integers => true,
-		:min_x_value => 0,
-		:min_y_value => 0,
-		:show_data_labels => true,
-		:show_x_guidelines => true,
-		:show_x_title => true,
-		:x_title => "Time",
-		:stagger_x_labels => true,
-		:stagger_y_labels => true,
-		:x_label_format => "%m/%d/%y",
-	  })
-	  
-	  graph.add_data({
-	   :data => data1,
-		 :title => 'Data',
-	  })
-	
-	  print graph.burn()
-	
-	= Description
-	
+	]
+
+	sched = schedule.Schedule(dict(
+		width = 640,
+		height = 480,
+		graph_title = "My Schedule",
+		show_graph_title = True,
+		no_css = True,
+		scale_x_integers = True,
+		scale_y_integers = True,
+		min_x_value = 0,
+		min_y_value = 0,
+		show_data_labels = True,
+		show_x_guidelines = True,
+		show_x_title = True,
+		x_title = "Time",
+		stagger_x_labels = True,
+		stagger_y_labels = True,
+		x_label_format = "%m/%d/%y",
+	))
+
+	sched.add_data(dict(
+		data = data1,
+		title = 'Data',
+	))
+
+	print(sched.burn())
+
+	Description
+	===========
+
 	Produces a graph of temporal scalar data.
-	
-	= Examples
-	
-	http://www.germane-software/repositories/public/SVG/test/schedule.rb
-	
-	= Notes
-	
+
+	Examples
+	========
+
+	See tests/samples.py for an example.
+
+	Notes
+	=====
+
 	The default stylesheet handles upto 10 data sets, if you
 	use more you must create your own stylesheet and add the
 	additional settings for the extra data sets. You will know
 	if you go over 10 data sets as they will have no style and
 	be in black.
-	
-	Note that multiple data sets within the same chart can differ in 
-	length, and that the data in the datasets needn't be in order; 
+
+	Note that multiple data sets within the same chart can differ in
+	length, and that the data in the datasets needn't be in order;
 	they will be ordered by the plot along the X-axis.
-	
+
 	The dates must be parseable by ParseDate, but otherwise can be
 	any order of magnitude (seconds within the hour, or years)
-	
-	= See also
-	
-	* SVG::Graph::Graph
-	* SVG::Graph::BarHorizontal
-	* SVG::Graph::Bar
-	* SVG::Graph::Line
-	* SVG::Graph::Pie
-	* SVG::Graph::Plot
-	* SVG::Graph::TimeSeries
-	
-	== Author
-	
-	Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
-	
-	Copyright 2004 Sean E. Russell
-	This software is available under the Ruby license[LICENSE.txt]
-	
 	"""
-	
-	"The format string to be used to format the X axis labels"
+
 	x_label_format = '%Y-%m-%d %H:%M:%S'
-	
+	"The format string to be used to format the X axis labels"
+
+	timescale_divisions = None
 	"""
 	Use this to set the spacing between dates on the axis.  The value
 	must be of the form
 	"\d+ ?((year|month|week|day|hour|minute|second)s?)?"
-	
+
 	e.g.
-	
+
 		graph.timescale_divisions = '2 weeks'
 		graph.timescale_divisions = '1 month'
 		graph.timescale_divisions = '3600 seconds'  # easier would be '1 hour'
 	"""
-	timescale_divisions = None
-	
-	"The formatting used for the popups.  See x_label_format"
+
 	popup_format = '%Y-%m-%d %H:%M:%S'
+	"The formatting used for the popups.  See x_label_format"
 
 	_min_x_value = None
 	scale_x_divisions = False
 	def add_data(self, data):
 		"""
 		Add data to the plot.
-	
-		  # A data set with 1 point: Lunch from 12:30 to 14:00
-		  d1 = [ "Lunch", "12:30", "14:00" ] 
-		  # A data set with 2 points: "Cats" runs from 5/11/03 to 7/15/04, and
-		  #                           "Henry V" runs from 6/12/03 to 8/20/03
-		  d2 = [ "Cats", "5/11/03", "7/15/04",
-				 "Henry V", "6/12/03", "8/20/03" ]
-									   
-		  graph.add_data( 
-			:data => d1,
-			:title => 'Meetings'
-		  )
-		  graph.add_data(
-			:data => d2,
-			:title => 'Plays'
-		  )
-	
+
+		# A data set with 1 point: Lunch from 12:30 to 14:00
+		d1 = [ "Lunch", "12:30", "14:00" ]
+
+		# A data set with 2 points: "Cats" runs from 5/11/03 to 7/15/04, and
+		#                           "Henry V" runs from 6/12/03 to 8/20/03
+		d2 = [
+			"Cats",    "5/11/03", "7/15/04",
+			"Henry V", "6/12/03", "8/20/03",
+		]
+
+		sched.add_data(dict(
+			data = d1,
+			title = 'Meetings',
+		))
+		sched.add_data(dict(
+			data = d2,
+			title = 'Plays',
+		))
+
 		Note that the data must be in time,value pairs, and that the date format
-		may be any date that is parseable by ParseDate.
+		may be any date that is parseable by dateutil.
 		Also note that, in this example, we're mixing scales; the data from d1
 		will probably not be discernable if both data sets are plotted on the same
 		graph, since d1 is too granular.
 		super(Schedule, self).process_data(conf)
 		data = conf['data']
 		triples = grouper(3, data)
-		
+
 		labels, begin_dates, end_dates = zip(*triples)
-		
+
 		begin_dates = map(self.parse_date, begin_dates)
 		end_dates = map(self.parse_date, end_dates)
 
 		# reconstruct the triples in a new order
 		reordered_triples = zip(begin_dates, end_dates, labels)
-		
+
 		# because of the reordering, this will sort by begin_date
 		#  then end_date, then label.
 		reordered_triples.sort()
-		
+
 		conf['data'] = reordered_triples
 
 	def parse_date(self, date_string):
 		return parse(date_string)
-	
+
 	def set_min_x_value(self, value):
 		if isinstance(value, basestring):
 			value = self.parse_date(value)
 
 	def get_min_x_value(self):
 		return self._min_x_value
-		
+
 	min_x_value = property(get_min_x_value, set_min_x_value)
-	
+
 	def format(self, x, y):
 		return x.strftime(self.popup_format)
-	
+
 	def get_x_labels(self):
 		format = lambda x: x.strftime(self.x_label_format)
 		return map(format, self.get_x_values())
-	
+
 	def y_label_offset(self, height):
 		return height / -2.0
-	
+
 	def get_y_labels(self):
 		# ruby version uses the last data supplied
 		last = -1
 		data = self.data[last]['data']
 		begin_dates, start_dates, labels = zip(*data)
 		return labels
-	
+
 	def draw_data(self):
 		bar_gap = self.get_bar_gap(self.get_field_height())
-		
+
 		subbar_height = self.get_field_height() - bar_gap
-		
+
 		y_mod = (subbar_height / 2) + (self.font_size / 2)
 		x_min,x_max,div = self._x_range()
 		x_range = x_max - x_min
 		# time_scale
 		#scale /= x_range
 		scale = TimeScale(width, x_range)
-		
+
 		# ruby version uses the last data supplied
 		last = -1
 		data = self.data[last]['data']
-		
+
 		for index, (x_start, x_end, label) in enumerate(data):
 			count = index + 1 # index is 0-based, count is 1-based
 			y = self.graph_height - (self.get_field_height()*count)
 			bar_width = scale*(x_end-x_start)
 			bar_start = scale*(x_start-x_min)
-			
+
 			etree.SubElement(self.graph, 'rect', {
 				'x': str(bar_start),
 				'y': str(y),
 				'class': 'fill%s' % (count+1),
 			})
 
-			
+
 	def _x_range(self):
 		# ruby version uses teh last data supplied
 		last = -1
 		data = self.data[last]['data']
-		
+
 		start_dates, end_dates, labels = zip(*data)
 		all_dates = start_dates + end_dates
 		max_value = max(all_dates)
 		range = max_value - min_value
 		right_pad = divide_timedelta_float(range, 20.0) or relativedelta(days=10)
 		scale_range = (max_value + right_pad) - min_value
-		
+
 		#scale_division = self.scale_x_divisions or (scale_range / 10.0)
 		# todo, remove timescale_x_divisions and use scale_x_divisions only
 		# but as a time delta
 		scale_division = divide_timedelta_float(scale_range, 10.0)
-		
+
 		# this doesn't make sense, because x is a timescale
 		#if self.scale_x_integers:
 		#	scale_division = min(round(scale_division), 1)
-		
+
 		return min_value, max_value, scale_division
-	
+
 	def get_x_values(self):
 		x_min, x_max, scale_division = self._x_range()
 		if self.timescale_divisions:
 			m = pattern.match(self.timescale_divisions)
 			if not m:
 				raise ValueError, "Invalid timescale_divisions: %s" % self.timescale_divisions
-			
+
 			magnitude = int(m.group(1))
 			units = m.group(2)
-			
+
 			parameter = self.lookup_relativedelta_parameter(units)
-			
+
 			delta = relativedelta(**{parameter:magnitude})
-			
+
 			scale_division = delta
 
 		return date_range(x_min, x_max, scale_division)

File svg/charts/time_series.py

 from .util import float_range
 
 class Plot(svg.charts.plot.Plot):
-	"""=== For creating SVG plots of scalar temporal data
-		
-		= Synopsis
-		
-		  import SVG.TimeSeries
-		
-		  # Data sets are x,y pairs
-		  data1 = ["6/17/72", 11,    "1/11/72", 7,    "4/13/04 17:31", 11, 
-				  "9/11/01", 9,    "9/1/85", 2,    "9/1/88", 1,    "1/15/95", 13]
-		  data2 = ["8/1/73", 18,    "3/1/77", 15,    "10/1/98", 4, 
-				  "5/1/02", 14,    "3/1/95", 6,    "8/1/91", 12,    "12/1/87", 6, 
-				  "5/1/84", 17,    "10/1/80", 12]
-		
-		  graph = SVG::Graph::TimeSeries.new({
-			:width => 640,
-			:height => 480,
-			:graph_title => title,
-			:show_graph_title => true,
-			:no_css => true,
-			:key => true,
-			:scale_x_integers => true,
-			:scale_y_integers => true,
-			:min_x_value => 0,
-			:min_y_value => 0,
-			:show_data_labels => true,
-			:show_x_guidelines => true,
-			:show_x_title => true,
-			:x_title => "Time",
-			:show_y_title => true,
-			:y_title => "Ice Cream Cones",
-			:y_title_text_direction => :bt,
-			:stagger_x_labels => true,
-			:x_label_format => "%m/%d/%y",
-		 })
-		  
-		  graph.add_data({
-		   :data => projection
-			 :title => 'Projected',
-		 })
-		
-		  graph.add_data({
-		   :data => actual,
-			 :title => 'Actual',
-		 })
-		  
-		  print graph.burn()
-		
-		= Description
-		
-		Produces a graph of temporal scalar data.
-		
-		= Examples
-		
-		http://www.germane-software/repositories/public/SVG/test/timeseries.rb
-		
-		= Notes
-		
-		The default stylesheet handles upto 10 data sets, if you
-		use more you must create your own stylesheet and add the
-		additional settings for the extra data sets. You will know
-		if you go over 10 data sets as they will have no style and
-		be in black.
-		
-		Unlike the other types of charts, data sets must contain x,y pairs:
-		
-		  ["12:30", 2]          # A data set with 1 point: ("12:30",2)
-		  ["01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and 
-								  #                           ("14:20",6)  
-		
-		Note that multiple data sets within the same chart can differ in length, 
-		and that the data in the datasets needn't be in order; they will be ordered
-		by the plot along the X-axis.
-		
-		The dates must be parseable by ParseDate, but otherwise can be
-		any order of magnitude (seconds within the hour, or years)
-		
-		= See also
-		
-		* SVG::Graph::Graph
-		* SVG::Graph::BarHorizontal
-		* SVG::Graph::Bar
-		* SVG::Graph::Line
-		* SVG::Graph::Pie
-		* SVG::Graph::Plot
-		
-		== Author
-		
-		Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
-		
-		Copyright 2004 Sean E. Russell
-		This software is available under the Ruby license[LICENSE.txt]
-"""
+	"""
+	For creating SVG plots of scalar temporal data
+
+	Synopsis
+	========
+
+	from svg.charts import time_series
+
+	# Data sets are x,y pairs
+	data1 = ["6/17/72", 11,  "1/11/72", 7,  "4/13/04 17:31", 11,
+		"9/11/01", 9,  "9/1/85", 2,  "9/1/88", 1,  "1/15/95", 13]
+	data2 = ["8/1/73", 18,  "3/1/77", 15,  "10/1/98", 4,  "5/1/02", 14,
+		"3/1/95", 6,  "8/1/91", 12,  "12/1/87", 6,  "5/1/84", 17,
+		"10/1/80", 12]
+
+	ts = time_series.Plot(dict(
+		width = 640,
+		height = 480,
+		graph_title = "TS Title",
+		show_graph_title = True,
+		no_css = True,
+		key = True,
+		scale_x_integers = True,
+		scale_y_integers = True,
+		min_x_value = 0,
+		min_y_value = 0,
+		show_data_labels = True,
+		show_x_guidelines = True,
+		show_x_title = True,
+		x_title = "Time",
+		show_y_title = True,
+		y_title = "Ice Cream Cones",
+		y_title_text_direction = 'bt',
+		stagger_x_labels = True,
+		x_label_format = "%m/%d/%y",
+	))
+
+	ts.add_data(dict(
+		data = projection,
+		title = 'Projected',
+	))
+
+	ts.add_data(dict(
+		data = actual,
+		title = 'Actual',
+	))
+
+	print(ts.burn())
+
+	Description
+	===========
+
+	Produces a graph of temporal scalar data.
+
+	Examples
+	========
+
+	See tests/samples.py for an example.
+
+	Notes
+	=====
+
+	The default stylesheet handles upto 10 data sets, if you
+	use more you must create your own stylesheet and add the
+	additional settings for the extra data sets. You will know
+	if you go over 10 data sets as they will have no style and
+	be in black.
+
+	Unlike the other types of charts, data sets must contain x,y pairs:
+
+	["12:30", 2]           # A data set with 1 point: ("12:30",2)
+	["01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
+	                       #                           ("14:20",6)
+
+	Note that multiple data sets within the same chart can differ in length,
+	and that the data in the datasets needn't be in order; they will be ordered
+	by the plot along the X-axis.
+
+	The dates must be parseable by ParseDate, but otherwise can be
+	any order of magnitude (seconds within the hour, or years)
+	"""
+
 	popup_format = x_label_format = '%Y-%m-%d %H:%M:%S'
-	__doc_popup_format_ = "The formatting usped for the popups.  See x_label_format"
+	"The formatting usped for the popups.  See x_label_format"
 	__doc_x_label_format_ = "The format string used to format the X axis labels.  See strftime."
-	
+
 	timescale_divisions = None
-	__doc_timescale_divisions_ = """Use this to set the spacing between dates on the axis.  The value
-		must be of the form 
-		"\d+ ?(days|weeks|months|years|hours|minutes|seconds)?"
+	"""
+	Use this to set the spacing between dates on the axis.  The value
+	must be of the form
+	"\d+ ?(days|weeks|months|years|hours|minutes|seconds)?"
 
-		EG:
+	For example:
 
-		graph.timescale_divisions = "2 weeks"
+	ts.timescale_divisions = "2 weeks"
 
-		will cause the chart to try to divide the X axis up into segments of
-		two week periods."""
-	
+	will cause the chart to try to divide the X axis up into segments of
+	two week periods.
+	"""
+
 	def add_data(self, data):
-		"""Add data to the plot.
-			d1 = ["12:30", 2]          # A data set with 1 point: ("12:30",2)
-			d2 = ["01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and 
-										 #                           ("14:20",6)  
-			graph.add_data(
-			  :data => d1,
-			  :title => 'One'
-			)
-			graph.add_data(
-			  :data => d2,
-			  :title => 'Two'
-			)
-			
-			Note that the data must be in time,value pairs, and that the date format
-			may be any date that is parseable by ParseDate."""
+		"""
+		Add data to the plot.
+
+		d1 = ["12:30", 2]             # A data set with 1 point: ("12:30",2)
+		d2 = ["01:00", 2, "14:20", 6] # A data set with 2 points: ("01:00",2) and
+		                              #                           ("14:20",6)
+		graph.add_data(
+			data = d1,
+			title = 'One',
+		)
+		graph.add_data(
+			data = d2,
+			title = 'Two',
+		)
+
+		Note that the data must be in time,value pairs, and that the date format
+		may be any date that is parseable by dateutil.
+		"""
 		super(Plot, self).add_data(data)
-		
+
 	def process_data(self, data):
 		super(Plot, self).process_data(data)
 		# the date should be in the first element, so parse it out
 		data['data'][0] = map(self.parse_date, data['data'][0])
 
-	_min_x_value = svg.charts.plot.Plot.min_x_value	
+	_min_x_value = svg.charts.plot.Plot.min_x_value
 	def get_min_x_value(self):
 		return self._min_x_value
 	def set_min_x_value(self, date):
 		self._min_x_value = self.parse_date(date)
 	min_x_value = property(get_min_x_value, set_min_x_value)
-	
+
 	def format(self, x, y):
 		return fromtimestamp(x).strftime(self.popup_format)
-	
+
 	def get_x_labels(self):
 		return map(lambda t: fromtimestamp(t).strftime(self.x_label_format), self.get_x_values())
 
 		result = self.get_x_timescale_division_values()
 		if result: return result
 		return tuple(float_range(*self.x_range()))
-			
+
 	def get_x_timescale_division_values(self):
 		if not self.timescale_divisions: return
 		min, max, scale_division = self.x_range()
 		delta = relativedelta(**{division_units: amount})
 		result = tuple(self.get_time_range(min, max, delta))
 		return result
-	
+
 	def get_time_range(self, start, stop, delta):
 		start, stop = map(fromtimestamp, (start, stop))
 		current = start
 		while current <= stop:
 			yield mktime(current.timetuple())
 			current += delta
-			
+
 	def parse_date(self, date_string):
 		return mktime(parse(date_string).timetuple())