#!/usr/local/bin/pythonw
# coding: iso-8859-1
# Copyright © 2008 by Amos Newcombe
# 
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.

'''Implement a Graph as a Drawing.
'''

from imagelib.Drawing.Drawing import Drawing, Line, Metadata
from Axis import Axis

class Graph(Drawing):
	'''A Graph creates a visual representation of a data set contained in an FiniteSeries instance.
	'''
	
	# ¤ Creation
	
	def __init__(self, **d):
		self.fsData = d['fsData']
		self.laxisH, self.laxisV = [], []
		self.dMetadata = dict(
			title = d.get('title', 'SVG Graph via xmllib'),
			desc  = d.get('desc',  ''),
		)
		Drawing.__init__(self, Metadata(**self.dMetadata))
	
	# ¤ Graph Interface
	
	def AddAxis(self, axis):
		'Add an axis to the Graph.'
		if   axis.getDirection() == 'horizontal': self.axisH = axis
		elif axis.getDirection() == 'vertical'  : self.axisV = axis
		else: raise ValueError, 'unknown axis direction %s' % repr(axis.getDirection())
	
	def AddAxes(self, *laxis):
		'''Add a number of axes to the graph.
		
		Currently, graphs are limited to two dimensions, and there must be one horizontal and one vertical axis.
		'''
		for axis in laxis: self.AddAxis(axis)
	
	def getAxes(self): return (self.axisH, self.axisV)
	
	def TransformData(self):
		'Transform graph coordinates into pixels'
# 		print 'Graph.TransformData()'
		# transform the data points
		self.ldpxData = [dx.copy() for dx in self.fsData.getData()]
		for dpx in self.ldpxData:
			for axis in [self.axisH, self.axisV]: 
				for s in axis.lsFields:
					if dpx[s] != None: dpx[s] = axis.Transform(dpx[s])
		# a list of data pairs for each series
		sX = self.fsData.ddMeta['_instance']['_independent']
# 		print self.axisV.getFields()
		self.dltppxData = dict([
			(sY, [(dpx[sX], dpx[sY]) for dpx in self.ldpxData if dpx[sY] != None])
			for sY in self.axisV.getFields()
		])
# 		print self.dltppxData
		# transform the grid points and extremes
		self.llpxGrid     = [axis.getPxGrid()     for axis in [self.axisH, self.axisV]]
		self.llpxExtremes = [axis.getPxExtremes() for axis in [self.axisH, self.axisV]]
	
	def Assemble(self):
		self.AddContents(
			self.DrawGrid(),
			self.DrawGraph(),
		)
# 		print self.lContents
		return self
	
	def DrawGrid(self):
		'Draw the grid lines for each axis.'
		return Drawing(*([
			Line(                      # horizontal extremes
				(self.llpxExtremes[0][0], px),
				(self.llpxExtremes[0][1], px),
			)
			for px in self.llpxGrid[1] # with vertical grid coordinates
		] + [
			Line(                      # vertical extremes
				(px, self.llpxExtremes[1][0]),
				(px, self.llpxExtremes[1][1]),
			)
			for px in self.llpxGrid[0] # with horizontal grid coordinates
		]))
	
	def DrawGraph(self):
		return  Drawing(*[
			Line(*self.dltppxData[sY])
			for sY in self.axisV.getFields()
		])

if __name__ == '__main__':
	
	from mathlib.FiniteSeries.FiniteSeries import FiniteSeries
	from mathlib.rangeX import rangeX
	from random import gauss
	from math import exp
	from imagelib.SVG.SVG import SVGDocument
	from imagelib.Drawing.SVGDrawing import SVGImage
	from imagelib.Drawing.PILDrawing import PILImage
	
	def MathGraph(**d):
		'A sample client for graphing math functions, where both axes have the same scale.'
		g = Graph(fsData = FiniteSeries(
			[
				dict([('x', x)] + [
					(s, f(x))
					for f, s in d['ltpSeries']
				])
				for x in rangeX(*d['tpRange'])
			], 
			_independent='x', 
			**d.get('dMeta', {})
		), **d)
		axisX = Axis('X', g.fsData, [
			g.fsData.ddMeta['_instance']['_independent']
		], [0.]).setTransform(pxLength=d['width']  )
		axisY = Axis('Y', g.fsData, [
			tp[1] 
			for tp in d['ltpSeries']
		], [0.]).setTransform(pxUnit=axisX.pxUnit)
		# X and Y axes are to the same scale.
		g.AddAxes(axisX, axisY)
		g.TransformData()
# 		g['height'] = g.llpxExtremes[1][1] - g.llpxExtremes[1][0]
		g.Assemble()
		return g
	
	drwg = MathGraph(
		tpRange = (-3., 3.03125, .03125), 
		ltpSeries = [
			(lambda x: gauss(0, 0.25), 'sample'), 
			(lambda x: exp(-x**2/2), 'gaussian'),
		],
		width=512, title='Gaussians: density &amp; sample',
	)
	
# 	print repr(drwg)
	tppxBox = tuple([lpx[1] for lpx in drwg.llpxExtremes])
	
	open('Graph.svg', 'w').write(str(SVGDocument(xmlElem = SVGImage(
		('3in', '3in'), 
		tppxBox, 
		drwg,
	))))
	
	img = PILImage('RGB', tppxBox, 'white', drwg)
	img.save('Graph.png')

