biggles.py :  » GUI » Biggles » python2-biggles-1.6.6 » src » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » GUI » Biggles 
Biggles » python2 biggles 1.6.6 » src » biggles.py
#
# $Id: biggles.py,v 1.235 2008/11/28 00:38:20 mrnolta Exp $
#
# Copyright (C) 2000-2008 Mike Nolta <mrnolta@users.sourceforge.net>
#
# 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 2
# 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, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA  02111-1307, USA.
#

import copy, math, os, string
import numpy

import config, _biggles
from geometry import *

import libplot.renderer
renderer = libplot.renderer

# miscellaneous ---------------------------------------------------------------

_true, _false = 1, 0

def _floor(x):
  return long(math.floor(x))

def _ceil(x):
  return long(math.ceil(x))

def _tsil(x):
  l = list(x)
  l.reverse()
  return l

def _message( s ):
  print "biggles:", s 

def _series( m, n, a=1, b=0 ):
  return map( lambda x,y=a,z=b: x*y+z, range(m, n+1) )

def _first_not_none( *args ):
  for arg in args:
    if arg is not None:
      return arg
  return None

class _Alias:

  def __init__( self, *args ):
    self.__dict__["objs"] = args[:]

  def __call__( self, *args, **kw ):
    for obj in self.objs:
      apply( obj, args, kw )

  def __getattr__( self, name ):
    objs = []
    for obj in self.objs:
      objs.append( getattr(obj, name) )
    return apply( _Alias, objs )

  def __setattr__( self, name, value ):
    for obj in self.objs:
      setattr( obj, name, value )

  def __setitem__( self, key, value ):
    for obj in self.objs:
      obj[key] = value

# BigglesError ----------------------------------------------------------------

class BigglesError( Exception ):
  pass

# relative size ---------------------------------------------------------------

def _size_relative( relsize, bbox ):
  w = bbox.width()
  h = bbox.height()
  yardstick = math.sqrt(8) * w * h / (w + h)
  return (relsize/100.) * yardstick

def _fontsize_relative( relsize, bbox, device ):
  devsize = _size_relative( relsize, bbox )
  devsize_min = _size_relative(
    config.value('default','fontsize_min'), device.bbox )
  return max( devsize, devsize_min )

# _PlotContext -----------------------------------------------------------------

class _PlotGeometry:

  _logfunc = math.log10
  _logfunc_vec = numpy.log10

  def __init__( self, src, dest, xlog=0, ylog=0 ):
    self.src_bbox = src
    self.dest_bbox = dest
    self.xlog = xlog
    self.ylog = ylog
    a, b = src.lowerleft()
    c, d = src.upperright()
    if xlog:
      a = self._logfunc(a)
      c = self._logfunc(c)
    if ylog:
      b = self._logfunc(b)
      d = self._logfunc(d)
    fsrc = BoundingBox( (a,b), (c,d) )
    self.aff = RectilinearMap( fsrc, dest )

  def __call__( self, x, y ):
    u, v = x, y
    if self.xlog: u = self._logfunc(x)
    if self.ylog: v = self._logfunc(y)
    return self.aff( u, v )

  def call_vec( self, x, y ):
    u = numpy.asarray( x )
    v = numpy.asarray( y )
    if self.xlog: u = self._logfunc_vec(u)
    if self.ylog: v = self._logfunc_vec(v)
    return self.aff.call_vec( u, v )

  def geodesic( self, x, y, div=1 ):
    return [(x, y)]

class _PlotContext:

  def __init__( self, device, dev, data, xlog=0, ylog=0 ):
    self.draw = device
    self.dev_bbox = dev
    self.data_bbox = data
    self.xlog = xlog
    self.ylog = ylog
    self.geom = _PlotGeometry( data, dev, xlog=xlog, ylog=ylog )
    self.plot_geom = _PlotGeometry( BoundingBox((0,0),(1,1)), dev )

  def do_clip( self ):
    xr = self.dev_bbox.xrange()
    yr = self.dev_bbox.yrange()
    self.draw.set( "cliprect", (xr[0], xr[1], yr[0], yr[1]) )

# _StyleKeywords --------------------------------------------------------------

def _kw_func_relative_fontsize( context, key, value ):
  device_size = _fontsize_relative( value, context.dev_bbox, context.draw )
  context.draw.set( key, device_size )

def _kw_func_relative_size( context, key, value ):
  device_size = _size_relative( value, context.dev_bbox )
  context.draw.set( key, device_size )

def _kw_func_relative_width( context, key, value ):
  device_width = _size_relative( value/10., context.dev_bbox )
  context.draw.set( key, device_width )

class _StyleKeywords:

  kw_style = None
  kw_defaults = {}
  kw_rename = {}
  kw_func = {
    'fontsize' : _kw_func_relative_fontsize,
    'linewidth' : _kw_func_relative_width,
    'symbolsize' : _kw_func_relative_size,
  }

  def kw_init( self, kw=None ):
    self.kw_style = {}
    self.kw_style.update( self.kw_defaults )
    if kw is not None:
      for key, value in kw.items():
        self.kw_set( key, value )

  def kw_set( self, key, value ):
    if self.kw_style is None:
      self.kw_init()
    key = self.kw_rename.get( key, key )
    self.kw_style[key] = value

  def style( self, **kw ):
    for key,val in kw.items():
      self.kw_set( key, val )

  def kw_get( self, key, notfound=None ):
    if self.kw_style is not None:
      return self.kw_style.get( key, notfound )
    else:
      return None

  def kw_predraw( self, context ):
    context.draw.save_state()
    if self.kw_style is not None:
      for key, value in self.kw_style.items():
        if self.kw_func.has_key(key):
          method = self.kw_func[key]
          apply( method, (context,key,value) )
        else:
          context.draw.set( key, value )

  def kw_postdraw( self, context ):
    context.draw.restore_state()

# _ConfAttributes -----------------------------------------------------------

class _ConfAttributes:

  def conf_setattr( self, section, **kw ):
    import copy, string
    sec = config.options( section )
    if sec is not None:
      for key,val in sec.items():
        x = string.split( key, "." )
        obj = self
        for y in x[:-1]:
          obj = getattr( obj, y )
        setattr( obj, x[-1], copy.copy(val) )
    for key,val in kw.items():
      setattr( self, key, copy.copy(val) )

# _DeviceObject ---------------------------------------------------------------

class _DeviceObject( _StyleKeywords ):

  def bbox( self, context ):
    return BoundingBox()

  def draw( self, context ):
    raise BigglesError

  def render( self, context ):
    self.kw_predraw( context )
    self.draw( context )
    self.kw_postdraw( context )

class _SymbolObject( _DeviceObject ):

  kw_rename = {
    'type' : 'symboltype',
    'size' : 'symbolsize',
  }

  def __init__( self, pos, **kw ):
    self.kw_init( kw )
    self.pos = pos

  def bbox( self, context ):
    self.kw_predraw( context )
    symbolsize = context.draw.get( 'symbolsize' )
    self.kw_postdraw( context )

    dp = symbolsize/2, symbolsize/2
    p = pt_sub( self.pos, dp )
    q = pt_add( self.pos, dp )
    return BoundingBox( p, q )

  def draw( self, context ):
    context.draw.symbol( self.pos )

class _TextObject( _DeviceObject ):

  kw_defaults = {
    'textangle'  : 0,
    'texthalign'  : 'center',
    'textvalign'  : 'center',
  }

  kw_rename = {
    'face'    : 'fontface',
    'size'    : 'fontsize',
    'angle'    : 'textangle',
    'halign'  : 'texthalign',
    'valign'  : 'textvalign',
  }

  def __init__( self, pos, str, **kw ):
    self.kw_init( kw )
    self.pos = pos
    self.str = str

  __halign_offset = { 'right':(-1,0), 'center':(-.5,.5), 'left':(0,1) }
  __valign_offset = { 'top':(-1,0), 'center':(-.5,.5), 'bottom':(0,1) }

  def bbox( self, context ):
    self.kw_predraw( context )
    angle = context.draw.get( 'textangle' ) * math.pi/180.
    halign = context.draw.get( 'texthalign' )
    valign = context.draw.get( 'textvalign' )
    width = context.draw.textwidth( self.str )
    height = context.draw.textheight( self.str )
    self.kw_postdraw( context )

    hvec = pt_mul( width, _TextObject.__halign_offset[halign] )
    vvec = pt_mul( height, _TextObject.__valign_offset[valign] )

    p = self.pos[0] + hvec[0], self.pos[1] + vvec[0]
    q = self.pos[0] + hvec[1], self.pos[1] + vvec[1]

    bb = BoundingBox( p, q )
    bb.rotate( angle, self.pos )
    return bb

  def draw( self, context ):
    #bb = self.bbox( context )
    #context.draw.rect( bb.lowerleft(), bb.upperright() )
    context.draw.text( self.pos, self.str )

class _LabelsObject( _DeviceObject ):

  kw_defaults = {
    'textangle'  : 0,
    'texthalign'  : 'center',
    'textvalign'  : 'center',
  }

  kw_rename = {
    'face'    : 'fontface',
    'size'    : 'fontsize',
    'angle'    : 'textangle',
    'halign'  : 'texthalign',
    'valign'  : 'textvalign',
  }

  def __init__( self, points, labels, **kw ):
    self.kw_init( kw )
    self.points = points
    self.labels = labels

  __halign_offset = { 'right':(-1,0), 'center':(-.5,.5), 'left':(0,1) }
  __valign_offset = { 'top':(-1,0), 'center':(-.5,.5), 'bottom':(0,1) }

  def bbox( self, context ):
    bb = BoundingBox()
    self.kw_predraw( context )

    angle = context.draw.get( 'textangle' ) * math.pi/180.
    halign = context.draw.get( 'texthalign' )
    valign = context.draw.get( 'textvalign' )

    height = context.draw.textheight( self.labels[0] )
    ho = _LabelsObject.__halign_offset[halign]
    vo = _LabelsObject.__valign_offset[valign]

    for i in range(len(self.labels)):
      pos = self.points[i]
      width = context.draw.textwidth( self.labels[i] )

      p = pos[0] + width * ho[0], pos[1] + height * vo[0]
      q = pos[0] + width * ho[1], pos[1] + height * vo[1]

      bb_label = BoundingBox( p, q )
      if angle != 0:
        bb_label.rotate( angle, pos )
      bb.union( bb_label )

    self.kw_postdraw( context )
    return bb

  def draw( self, context ):
    for i in range(len(self.labels)):
      context.draw.text( self.points[i], self.labels[i] )

class _LineTextObject( _TextObject ):

  kw_rename = {
    'face'    : 'fontface',
    'size'    : 'fontsize',
  }

  def __init__( self, p, q, str, offset, **kw ):
    self.kw_init( kw )
    self.str = str

    midpoint = pt_mul( 0.5, pt_add(p, q) )
    direction = pt_unit( pt_sub(q, p) )
    angle = pt_angle( direction )
    direction = pt_rot( direction, math.pi/2 )
    self.pos = pt_add( midpoint, pt_mul(offset, direction) )

    self.kw_set( 'textangle', angle * 180./math.pi )
    self.kw_set( 'texthalign', 'center' )
    if offset > 0:
      self.kw_set( 'textvalign', 'bottom' )
    else:
      self.kw_set( 'textvalign', 'top' )

class _LineObject( _DeviceObject ):

  kw_rename = {
    'width'    : 'linewidth',
    'type'    : 'linetype',
  }

  def __init__( self, p, q, **kw ):
    self.kw_init( kw )
    self.p = p
    self.q = q

  def bbox( self, context ):
    return BoundingBox( self.p, self.q )

  def draw( self, context ):
    context.draw.line( self.p, self.q )

class _PolygonObject( _DeviceObject ):

  kw_rename = {
    'width'    : 'linewidth',
    'type'    : 'linetype',
  }

  def __init__( self, points, **kw ):
    self.kw_init( kw )
    self.points = points

  def bbox( self, context ):
    return apply( BoundingBox, self.points )

  def draw( self, context ):
    context.draw.polygon( self.points )

class _PathObject( _DeviceObject ):

  kw_rename = {
    'width'    : 'linewidth',
    'type'    : 'linetype',
  }

  def __init__( self, x, y, **kw ):
    self.kw_init( kw )
    self.x = x
    self.y = y

  def bbox( self, context ):
    xmin, xmax = _biggles.range( self.x )
    ymin, ymax = _biggles.range( self.y )
    return BoundingBox( (xmin,ymin), (xmax,ymax) )

  def draw( self, context ):
    context.draw.curve( self.x, self.y )

class _SymbolsObject( _DeviceObject ):

  kw_rename = {
    'type' : 'symboltype',
    'size' : 'symbolsize',
  }

  def __init__( self, x, y, **kw ):
    self.kw_init( kw )
    self.x = x
    self.y = y

  def bbox( self, context ):
    xmin, xmax = _biggles.range( self.x )
    ymin, ymax = _biggles.range( self.y )
    return BoundingBox( (xmin,ymin), (xmax,ymax) )

  def draw( self, context ):
    context.draw.symbols( self.x, self.y )

class _ColoredSymbolsObject( _DeviceObject ):

  kw_rename = {
    'type' : 'symboltype',
    'size' : 'symbolsize',
  }

  def __init__( self, x, y, c, **kw ):
    self.kw_init( kw )
    self.x = x
    self.y = y
    self.c = c

  def bbox( self, context ):
    xmin, xmax = _biggles.range( self.x )
    ymin, ymax = _biggles.range( self.y )
    return BoundingBox( (xmin,ymin), (xmax,ymax) )

  def draw( self, context ):
    context.draw.colored_symbols( self.x, self.y, self.c )

class _DensityObject( _DeviceObject ):

  kw_rename = {
  }

  def __init__( self, densgrid, ((xmin,ymin), (xmax,ymax)), **kw ):
    self.kw_init( kw )
    self.densgrid = densgrid
    self.extent   = ( (xmin,ymin), (xmax,ymax) )

  def bbox( self, context ):
    return apply( BoundingBox, self.extent )

  def draw( self, context ):
    #from numpy import rank
    #if rank(self.densgrid) == 3:
    if len(self.densgrid.shape) == 3:
      context.draw.color_density_plot( self.densgrid, self.extent )
    else:
      context.draw.density_plot( self.densgrid, self.extent )

class _EllipseObject( _DeviceObject ):

  def __init__( self, p, rx, ry, angle=0., **kw ):
    self.kw_init( kw )
    self.p = p
    self.rx = rx
    self.ry = ry
    self.angle = angle

  def bbox( self, context ):
    r = self.rx, self.ry
    p = pt_add( self.p, r )
    q = pt_sub( self.p, r )
    bb = BoundingBox( p, q )
    bb.rotate( self.p, self.angle )
    return bb

  def draw( self, context ):
    context.draw.ellipse( self.p, self.rx, self.ry, self.angle )

class _CombObject( _DeviceObject ):

  def __init__( self, points, dp, **kw ):
    self.kw_init( kw )
    self.points = points
    self.dp = dp

  def bbox( self, context ):
    return apply( BoundingBox, self.points )

  def draw( self, context ):
    for p in self.points:
      context.draw.move( p )
      context.draw.linetorel( self.dp )

class _BoxObject( _DeviceObject ):

  def __init__( self, p, q, **kw ):
    self.kw_init( kw )
    self.p = p
    self.q = q

  def bbox( self, context ):
    return BoundingBox( self.p, self.q )

  def draw( self, context ):
    context.draw.rect( self.p, self.q )

class _ArcObject( _DeviceObject ):

  def __init__( self, pc, p0, p1, **kw ):
    self.kw_init( kw )
    self.pc = pc
    self.p0 = p0
    self.p1 = p1

  def bbox( self, context ):
    return BoundingBox( self.pc, self.p0, self.p1 )

  def draw( self, context ):
    context.draw.arc( self.pc, self.p0, self.p1 )

# _PlotComponent --------------------------------------------------------------

class _PlotComponent( _StyleKeywords, _ConfAttributes ):

  def __init__( self ):
    self.clear()

  def add( self, *args ):
    for obj in args:
      self.device_objects.append( obj )

  def limits( self ):
    return BoundingBox()

  def clear( self ):
    self.device_objects = []

  def make( self, context ):
    raise BigglesError

  def make_key( self, bbox ):
    pass

  def bbox( self, context ):
    self.clear()
    self.make( context )
    bb = BoundingBox()
    for obj in self.device_objects:
      bb.union( obj.bbox(context) )
    return bb

  def render( self, context ):
    self.clear()
    self.make( context )
    self.kw_predraw( context )
    for obj in self.device_objects:
      obj.render( context )
    self.kw_postdraw( context )

# _LabelComponent -------------------------------------------------------------

class _LabelComponent( _PlotComponent ):

  kw_rename = {
    'face'    : 'fontface',
    'size'    : 'fontsize',
    'angle'    : 'textangle',
    'halign'  : 'texthalign',
    'valign'  : 'textvalign',
  }

  def __init__( self, x, y, str, **kw ):
    _PlotComponent.__init__( self )
    self.conf_setattr( "_LabelComponent" )
    self.kw_init( kw )
    self.pos = x, y
    self.str = str

  def limits( self ):
    return BoundingBox()

class DataLabel( _LabelComponent ):

  def make( self, context ):
    pos = apply( context.geom, self.pos )
    t = apply( _TextObject, (pos, self.str), self.kw_style )
    self.add( t )

class PlotLabel( _LabelComponent ):

  def make( self, context ):
    pos = apply( context.plot_geom, self.pos )
    t = apply( _TextObject, (pos, self.str), self.kw_style )
    self.add( t )

# _LabelsComponent ------------------------------------------------------------

class _LabelsComponent( _PlotComponent ):

  kw_rename = {
    'face'    : 'fontface',
    'size'    : 'fontsize',
    'angle'    : 'textangle',
    'halign'  : 'texthalign',
    'valign'  : 'textvalign',
  }

  def __init__( self ):
    _PlotComponent.__init__( self )
    self.conf_setattr( "_LabelsComponent" )

class Labels( _LabelsComponent ):

  def __init__( self, x, y, labels, **kw ):
    _LabelsComponent.__init__( self )
    self.conf_setattr( "Labels" )
    self.kw_init( kw )
    self.x = x
    self.y = y
    self.labels = labels

  def limits( self ):
    p = min(self.x), min(self.y)
    q = max(self.x), max(self.y)
    return BoundingBox( p, q )

  def make( self, context ):
    x, y = context.geom.call_vec( self.x, self.y )
    l = apply( _LabelsObject, (zip(x,y), self.labels), self.kw_style )
    self.add( l )

# _LineComponent --------------------------------------------------------------

class _LineComponent( _PlotComponent ):

  def __init__( self ):
    _PlotComponent.__init__( self )
    self.conf_setattr( "_LineComponent" )

  kw_rename = {
    'color' : 'linecolor',
    'width' : 'linewidth',
    'type' : 'linetype',
  }

  def make_key( self, bbox ):
    xr = bbox.xrange()
    y = bbox.center()[1]
    p = xr[0], y
    q = xr[1], y
    return apply( _LineObject, (p,q), self.kw_style )

class Curve( _LineComponent ):

  def __init__( self, x, y, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "Curve" )
    self.kw_init( kw )
    self.x = x
    self.y = y

  def limits( self ):
    p0 = min(self.x), min(self.y)
    p1 = max(self.x), max(self.y)
    return BoundingBox( p0, p1 )

  def make( self, context ):
    segs = context.geom.geodesic( self.x, self.y )
    for seg in segs:
      x, y = context.geom.call_vec( seg[0], seg[1] )
      self.add( _PathObject(x, y) )

class DataLine( _LineComponent ):

  def __init__( self, p, q, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "DataLine" )
    self.kw_init( kw )
    self.p = p
    self.q = q

  def limits( self ):
    return BoundingBox( self.p, self.q )

  def make( self, context ):
    a = apply( context.geom, self.p )
    b = apply( context.geom, self.q )
    self.add( _LineObject(a, b) )

class Geodesic( _LineComponent ):

  def __init__( self, p, q, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "Geodesic" )
    self.kw_init( kw )
    self.p = p
    self.q = q

  def limits( self ):
    return BoundingBox( self.p, self.q )

  def make( self, context ):
    l = self.p[0], self.q[0]
    b = self.p[1], self.q[1]
    segs = context.geom.geodesic( l, b, self.divisions )
    for seg in segs:
      x, y = context.geom.call_vec( seg[0], seg[1] )
      self.add( _PathObject(x, y) )

class Histogram( _LineComponent ):

  def __init__( self, values, x0=0, binsize=1, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "Histogram" )
    self.kw_init( kw )
    self.values = values
    self.x0 = x0
    self.binsize = binsize

  def limits( self ):
    nval = len( self.values )
    if self.drop_to_zero:
      p = self.x0, min( 0, min(self.values) )
    else:
      p = self.x0, min(self.values)
    q = self.x0 + nval*self.binsize, max(self.values)
    return BoundingBox( p, q )

  def make( self, context ):
    nval = len( self.values )
    x = []
    y = []
    if self.drop_to_zero:
      x.append( self.x0 )
      y.append( 0 )
    for i in range(0,nval):
      xi = self.x0 + i * self.binsize
      yi = self.values[i]
      x.extend( [xi, xi + self.binsize] )
      y.extend( [yi, yi] )
    if self.drop_to_zero:
      x.append( self.x0 + nval*self.binsize )
      y.append( 0 )
    u, v = context.geom.call_vec( x, y )
    self.add( _PathObject(u, v) )

class LineX( _LineComponent ):

  def __init__( self, x, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "LineX" )
    self.kw_init( kw )
    self.x = x

  def limits( self ):
    return BoundingBox( (self.x,None), (self.x,None) )

  def make( self, context ):
    yrange = context.data_bbox.yrange()
    p = self.x, yrange[0]
    q = self.x, yrange[1]
    a = apply( context.geom, p )
    b = apply( context.geom, q )
    self.add( _LineObject(a, b) )

class LineY( _LineComponent ):

  def __init__( self, y, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "LineY" )
    self.kw_init( kw )
    self.y = y

  def limits( self ):
    return BoundingBox( (None,self.y), (None,self.y) )

  def make( self, context ):
    xrange = context.data_bbox.xrange()
    p = xrange[0], self.y
    q = xrange[1], self.y
    a = apply( context.geom, p )
    b = apply( context.geom, q )
    self.add( _LineObject(a, b) )

class PlotLine( _LineComponent ):

  def __init__( self, p, q, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "PlotLine" )
    self.kw_init( kw )
    self.p = p
    self.q = q

  def make( self, context ):
    a = apply( context.plot_geom, self.p )
    b = apply( context.plot_geom, self.q )
    self.add( _LineObject(a, b) )

class Slope( _LineComponent ):

  def __init__( self, slope, intercept=None, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "Slope" )
    self.kw_init( kw )
    self.slope = slope
    self.intercept = intercept
    if intercept is None:
      self.intercept = (0.,0.)

  def _x( self, y ):
    x0, y0 = self.intercept
    return x0 + float(y - y0) / self.slope

  def _y( self, x ):
    x0, y0 = self.intercept
    return y0 + (x - x0) * self.slope

  def make( self, context ):
    xrange = context.data_bbox.xrange()
    yrange = context.data_bbox.yrange()
    if self.slope == 0:
      l = [ ( xrange[0], self.intercept[1] ),\
            ( xrange[1], self.intercept[1] ) ]
    else:
      l = [ ( xrange[0], self._y(xrange[0]) ),\
            ( xrange[1], self._y(xrange[1]) ),\
            ( self._x(yrange[0]), yrange[0] ),\
            ( self._x(yrange[1]), yrange[1] ) ]
    m = filter( context.data_bbox.contains, l )
    m.sort()
    if len(m) > 1:
      a = apply( context.geom, m[0] )
      b = apply( context.geom, m[-1] )
      self.add( _LineObject(a, b) )

class DataBox( _LineComponent ):

  def __init__( self, p, q, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "DataBox" )
    self.kw_init( kw )
    self.p = p
    self.q = q

  def limits( self ):
    return BoundingBox( self.p, self.q )

  def make( self, context ):
    a = apply( context.geom, self.p )
    b = apply( context.geom, self.q )
    self.add( _BoxObject(a, b) )

class PlotBox( _LineComponent ):

  def __init__( self, p, q, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "PlotBox" )
    self.kw_init( kw )
    self.p = p
    self.q = q

  def make( self, context ):
    a = apply( context.plot_geom, self.p )
    b = apply( context.plot_geom, self.q )
    self.add( _BoxObject(a, b) )

class PlotArc( _LineComponent ):

  def __init__( self, pc, r, a0, a1, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "PlotArc" )
    self.kw_init( kw )
    self.pc = pc
    self.p0 = p[0] + r*math.cos(a0), p[1] + r*math.sin(a0)
    self.p1 = p[0] + r*math.cos(a1), p[1] + r*math.sin(a1)

  def make( self, context ):
    pc = apply( context.plot_geom, self.pc )
    p0 = apply( context.plot_geom, self.p0 )
    p1 = apply( context.plot_geom, self.p1 )
    self.add( _ArcObject(pc, p0, p1) )

class DataArc( _LineComponent ):

  def __init__( self, pc, r, a0, a1, **kw ):
    _LineComponent.__init__( self )
    self.conf_setattr( "DataArc" )
    self.kw_init( kw )
    self.pc = pc
    self.p0 = p[0] + r*math.cos(a0), p[1] + r*math.sin(a0)
    self.p1 = p[0] + r*math.cos(a1), p[1] + r*math.sin(a1)

  def limits( self ):
    return BoundingBox( self.pc, self.p0, self.p1 )

  def make( self, context ):
    pc = apply( context.geom, self.pc )
    p0 = apply( context.geom, self.p0 )
    p1 = apply( context.geom, self.p1 )
    self.add( _ArcObject(pc, p0, p1) )

# _SymbolDataComponent --------------------------------------------------------

class _SymbolDataComponent( _PlotComponent ):

  kw_rename = {
    'type' : 'symboltype',
    'size' : 'symbolsize',
  }

  def make_key( self, bbox ):
    pos = bbox.center()
    return apply(_SymbolObject, (pos,), self.kw_style)

class Points( _SymbolDataComponent ):

  kw_defaults = {
    'symboltype' : config.value('Points','symboltype'),
    'symbolsize' : config.value('Points','symbolsize'),
  }

  def __init__( self, x, y, **kw ):
    _SymbolDataComponent.__init__( self )
    self.conf_setattr( "Points" )
    self.kw_init( kw )
    self.x = x
    self.y = y

  def limits( self ):
    p = min(self.x), min(self.y)
    q = max(self.x), max(self.y)
    return BoundingBox( p, q )

  def make( self, context ):
    x, y = context.geom.call_vec( self.x, self.y )
    self.add( _SymbolsObject(x, y) )

def Point( x, y, **kw ):
  return apply( Points, ([x],[y]), kw )

class ColoredPoints( _SymbolDataComponent ):

  kw_defaults = {
    'symboltype' : config.value('Points','symboltype'),
    'symbolsize' : config.value('Points','symbolsize'),
  }

  def __init__( self, x, y, c=None, **kw ):
    _SymbolDataComponent.__init__( self )
    self.conf_setattr( "Points" )
    self.kw_init( kw )
    self.x = x
    self.y = y
    self.c = c

  def limits( self ):
    p = min(self.x), min(self.y)
    q = max(self.x), max(self.y)
    return BoundingBox( p, q )

  def make( self, context ):
    x, y = context.geom.call_vec( self.x, self.y )
    self.add( _ColoredSymbolsObject(x, y, self.c) )

def ColoredPoint( x, y, **kw ):
  return apply( ColoredPoints, ([x],[y]), kw )

# _DensityComponent -----------------------------------------------------------

class Density( _PlotComponent ):

  kw_defaults = {
    'foo' : config.value('Points','symbolsize'),
  }

  def __init__( self, densgrid, ((xmin,ymin), (xmax,ymax)), **kw ):
    _PlotComponent.__init__( self )
    self.conf_setattr( "Density" )
    self.kw_init( kw )
    self.densgrid = densgrid
    self.extent   = ((xmin,ymin), (xmax,ymax))

  def limits( self ):
    return apply( BoundingBox, self.extent )

  def make( self, context ):
    (x0,y0),(x1,y1) = self.extent
    (x0,x1),(y0,y1) = context.geom.call_vec((x0,x1),(y0,y1))
    self.add( _DensityObject(self.densgrid, ((x0,y0),(x1,y1))) )

# _FillComponent --------------------------------------------------------------

class _FillComponent( _PlotComponent ):

  kw_defaults = {
    'color' : config.value('_FillComponent','fillcolor'),
    'filltype' : config.value('_FillComponent','filltype'),
  }

  def make_key( self, bbox ):
    p = bbox.lowerleft()
    q = bbox.upperright()
    return apply( _BoxObject, (p,q), self.kw_style )

class FillAbove( _FillComponent ):

  def __init__( self, x, y, **kw ):
    _FillComponent.__init__( self )
    self.conf_setattr( "FillAbove" )
    self.kw_init( kw )
    self.x = x
    self.y = y

  def limits( self ):
    p = min(self.x), min(self.y)
    q = max(self.x), max(self.y)
    return BoundingBox( p, q )

  def make( self, context ):
    coords = map( context.geom, self.x, self.y )
    max_y = context.data_bbox.yrange()[1]
    coords.append( context.geom(self.x[-1], max_y) )
    coords.append( context.geom(self.x[0], max_y) )
    self.add( _PolygonObject(coords) )

class FillBelow( _FillComponent ):

  def __init__( self, x, y, **kw ):
    _FillComponent.__init__( self )
    self.conf_setattr( "FillBelow" )
    self.kw_init( kw )
    self.x = x
    self.y = y

  def limits( self ):
    p = min(self.x), min(self.y)
    q = max(self.x), max(self.y)
    return BoundingBox( p, q )

  def make( self, context ):
    coords = map( context.geom, self.x, self.y )
    min_y = context.data_bbox.yrange()[0]
    coords.append( context.geom(self.x[-1], min_y) )
    coords.append( context.geom(self.x[0], min_y) )
    self.add( _PolygonObject(coords) )

class FillBetween( _FillComponent ):

  def __init__( self, x1, y1, x2, y2, **kw ):
    _FillComponent.__init__( self )
    self.conf_setattr( "FillBetween" )
    self.kw_init( kw )
    self.x1, self.y1 = x1, y1
    self.x2, self.y2 = x2, y2

  def limits( self ):
    min_x = min( min(self.x1), min(self.x2) )
    max_x = max( max(self.x1), max(self.x2) )
    min_y = min( min(self.y1), min(self.y2) )
    max_y = max( max(self.y1), max(self.y2) )
    return BoundingBox( (min_x,min_y), (max_x,max_y) )

  def make( self, context ):
    x = list(self.x1) + _tsil(self.x2)
    y = list(self.y1) + _tsil(self.y2)
    coords = map( context.geom, x, y )
    self.add( _PolygonObject(coords) )

# ErrorBars -------------------------------------------------------------------

class _ErrorBar( _PlotComponent ):

  kw_rename = {
    'color' : 'linecolor',
    'width' : 'linewidth',
    'type' : 'linetype',
  }

  def __init__( self ):
    _PlotComponent.__init__( self )
    self.conf_setattr( "_ErrorBar" )

class ErrorBarsX( _ErrorBar ):

  def __init__( self, y, lo, hi, **kw ):
    _ErrorBar.__init__( self )
    self.conf_setattr( "ErrorBarsX" )
    self.kw_init( kw )
    self.y = y
    self.lo = lo
    self.hi = hi

  def limits( self ):
    p = min( min(self.lo), min(self.hi) ), min(self.y)
    q = max( max(self.lo), max(self.hi) ), max(self.y)
    return BoundingBox( p, q )

  def make( self, context ):
    l = _size_relative( self.barsize, context.dev_bbox ) 
    for i in range(len(self.y)):
      p = context.geom( self.lo[i], self.y[i] )
      q = context.geom( self.hi[i], self.y[i] )
      l0 = _LineObject( p, q )
      l1 = _LineObject( (p[0],p[1]-l), (p[0],p[1]+l) )
      l2 = _LineObject( (q[0],q[1]-l), (q[0],q[1]+l) )
      self.add( l0, l1, l2 )

class ErrorBarsY( _ErrorBar ):

  def __init__( self, x, lo, hi, **kw ):
    _ErrorBar.__init__( self )
    self.conf_setattr( "ErrorBarsY" )
    self.kw_init( kw )
    self.x = x
    self.lo = lo
    self.hi = hi

  def limits( self ):
    p = min(self.x), min( min(self.lo), min(self.hi) )
    q = max(self.x), max( max(self.lo), max(self.hi) )
    return BoundingBox( p, q )

  def make( self, context ):
    l = _size_relative( self.barsize, context.dev_bbox )
    for i in range(len(self.x)):
      p = context.geom( self.x[i], self.lo[i] )
      q = context.geom( self.x[i], self.hi[i] )
      l0 = _LineObject( p, q )
      l1 = _LineObject( (p[0]-l,p[1]), (p[0]+l,p[1]) )
      l2 = _LineObject( (q[0]-l,q[1]), (q[0]+l,q[1]) )
      self.add( l0, l1, l2 )

def SymmetricErrorBarsX( x, y, err, **kw ):
  import operator
  xlo = map( operator.sub, x, err )
  xhi = map( operator.add, x, err )
  return apply( ErrorBarsX, (y, xlo, xhi), kw )

def SymmetricErrorBarsY( x, y, err, **kw ):
  import operator
  ylo = map( operator.sub, y, err )
  yhi = map( operator.add, y, err )
  return apply( ErrorBarsY, (x, ylo, yhi), kw )

# Limits ----------------------------------------------------------------------

class _ErrorLimit( _PlotComponent ):

  kw_rename = {
    'color' : 'linecolor',
    'width' : 'linewidth',
    'type' : 'linetype',
  }

  def __init__( self ):
    _PlotComponent.__init__( self )
    self.conf_setattr( "_ErrorLimit" )

class UpperLimits( _ErrorLimit ):

  def __init__( self, x, ulimit, **kw ):
    _ErrorLimit.__init__( self )
    self.conf_setattr( "UpperLimits" )
    self.kw_init( kw )
    self.x = x
    self.ulimit = ulimit

  def limits( self ):
    p = min(self.x), min(self.ulimit)
    q = max(self.x), max(self.ulimit)
    return BoundingBox( p, q )

  def make( self, context ):
    l = _size_relative( self.size, context.dev_bbox )
    for i in range(len(self.x)):
      p = context.geom( self.x[i], self.ulimit[i] )
      l1 = _LineObject( (p[0]-l,p[1]), (p[0]+l,p[1]) )
      l2 = _LineObject( (p[0],p[1]-2*l), (p[0],p[1]) )
      l3 = _LineObject( (p[0],p[1]-2*l), (p[0]+l,p[1]-l) )
      l4 = _LineObject( (p[0],p[1]-2*l), (p[0]-l,p[1]-l) )
      self.add( l1, l2, l3, l4 )

class LowerLimits( _ErrorLimit ):

  def __init__( self, x, llimit, **kw ):
    _ErrorLimit.__init__( self )
    self.conf_setattr( "UpperLimits" )
    self.kw_init( kw )
    self.x = x
    self.llimit = llimit

  def limits( self ):
    p = min(self.x), min(self.llimit)
    q = max(self.x), max(self.llimit)
    return BoundingBox( p, q )

  def make( self, context ):
    l = _size_relative( self.size, context.dev_bbox )
    for i in range(len(self.x)):
      p = context.geom( self.x[i], self.llimit[i] )
      l1 = _LineObject( (p[0]-l,p[1]), (p[0]+l,p[1]) )
      l2 = _LineObject( (p[0],p[1]+2*l), (p[0],p[1]) )
      l3 = _LineObject( (p[0],p[1]+2*l), (p[0]+l,p[1]+l) )
      l4 = _LineObject( (p[0],p[1]+2*l), (p[0]-l,p[1]+l) )
      self.add( l1, l2, l3, l4 )

# Ellipses --------------------------------------------------------------------

class Ellipses( _PlotComponent ):

  kw_rename = {
    'color' : 'linecolor',
    'width' : 'linewidth',
    'type' : 'linetype',
  }

  def __init__( self, x, y, rx, ry, angle=None, **kw ):
    _PlotComponent.__init__( self )
    self.kw_init( kw )
    self.x = x
    self.y = y
    self.rx = rx
    self.ry = ry
    self.angle = angle

  def limits( self ):
    # XXX:kludge
    minx, maxx = self.x[0], self.x[0]
    miny, maxy = self.y[0], self.y[0]
    for i in range(len(self.x)):
      r = max( self.rx[i], self.ry[i] )
      minx = min( minx, self.x[i]-r )
      miny = min( miny, self.y[i]-r )
      maxx = max( maxx, self.x[i]+r )
      maxy = max( maxy, self.y[i]+r )
    return BoundingBox( (minx,miny), (maxx,maxy) )

  def make( self, context ):
    for i in range(len(self.x)):
      p = context.geom( self.x[i], self.y[i] )
      r = context.geom( \
        self.x[i] + self.rx[i], \
        self.y[i] + self.ry[i] )
      rx, ry = pt_sub( r, p )
      
      if self.angle is not None:
        e = _EllipseObject( p, rx, ry, self.angle[i] )
      else:
        e = _EllipseObject( p, rx, ry )
      self.add( e )

def Ellipse( x, y, rx, ry, angle=None, **kw ):
  if angle is None:
    args = ([x],[y],[rx],[ry])
  else:
    args = ([x],[y],[rx],[ry],[angle])
  return apply( Ellipses, args, kw )

def Circles( x, y, r, **kw ):
  return apply( Ellipses, (x,y,r,r), kw )

def Circle( x, y, r, **kw ):
  return apply( Circles, ([x],[y],[r]), kw )

# _PlotKey --------------------------------------------------------------------

class PlotKey( _PlotComponent ):

  kw_rename = {
    'face'    : 'fontface',
    'size'    : 'fontsize',
    'angle'    : 'textangle',
    'halign'  : 'texthalign',
    'valign'  : 'textvalign',
  }

  def __init__( self, x, y, components, **kw ):
    _PlotComponent.__init__( self )
    self.conf_setattr( "PlotKey" )
    self.kw_init( kw )
    self.x = x
    self.y = y
    self.components = components

  def make( self, context ):
    key_pos = context.plot_geom( self.x, self.y )
    key_width = _size_relative( self.key_width, context.dev_bbox )
    key_height = _size_relative( self.key_height, context.dev_bbox )
    key_hsep = _size_relative( self.key_hsep, context.dev_bbox )
    key_vsep = _size_relative( self.key_vsep, context.dev_bbox )

    halign = self.kw_get( 'texthalign' )
    if halign == 'left':
      text_pos = pt_add( (key_width/2+key_hsep,0), key_pos )
    else:
      text_pos = pt_add( (-key_width/2-key_hsep,0), key_pos )
    bbox = BoundingBox( (-key_width/2,-key_height/2),
      (key_width/2,key_height/2) )
    bbox.shift( key_pos )
    dp = 0, -(key_vsep + key_height)

    for comp in self.components:
      try:
        obj,str = comp
      except:
        obj = comp
        str = getattr( comp, "label", "" )
      t = apply( _TextObject, (text_pos,str), self.kw_style )
      self.add( t, obj.make_key(bbox) )
      text_pos = pt_add( text_pos, dp )
      bbox.shift( dp )

# XXX:deprecated
def OldKey( x, y, labels, align='left', **kw ):
  kw['texthalign'] = align
  return apply( PlotKey, (x,y,labels), kw )

# _HalfAxis -------------------------------------------------------------------

def _magform( x ):
  "Given x, returns (a,b), where x = a*10^b [a >= 1., b integral]."
  if x == 0:
    return 0., 0
  a, b = math.modf(math.log10(abs(x)))
  a, b = math.pow(10,a), int(b)
  if a < 1.:
    a, b = a * 10, b - 1
  if x < 0.:
    a = -a
  return a, b

def _format_ticklabel( x, range=0. ):
  if x == 0:
    return "0"
  a, b = _magform( x )
  if abs(b) > 4:
    if a == 1.:
      return r"$10^{%d}$" % b
    elif a == -1.:
      return r"-$10^{%d}$" % b
    else:
      return r"$%g\times 10^{%d}$" % (a,b)
  if range < 1e-6:
    a, b = _magform( range )
    return "%.*f" % (abs(b),x)
  return "%g" % x

def _ticklist_linear( lo, hi, sep, origin=0. ):
  r = []
  a = _ceil(float(lo - origin)/float(sep))
  b = _floor(float(hi - origin)/float(sep))
  #for i in range( a, b+1 ):
  #  r.append( origin + i * sep )
  r0 = origin + a*sep
  for i in range( b-a+1 ):
    r.append( r0 + i*sep )
  return r

def _pow10(x):
  return math.pow(10,x)

def _log10(x):
  return math.log10(x)

def _ticks_default_linear( lim ):
  a, b = _magform( (lim[1] - lim[0])/5. )
  if a < (1 + 2)/2.:
    x = 1
  elif a < (2 + 5)/2.:
    x = 2
  elif a < (5 + 10)/2.:
    x = 5
  else:
    x = 10

  major_div = x * math.pow(10, b)
  return _ticklist_linear( lim[0], lim[1], major_div )

def _ticks_default_log( lim ):
  log_lim = _log10(lim[0]), _log10(lim[1])
  nlo = _ceil( math.log10(lim[0]) )
  nhi = _floor( math.log10(lim[1]) )
  nn = nhi - nlo +1

  if nn >= 10:
    return map( _pow10, _ticks_default_linear(log_lim) )
  elif nn >= 2:
    return map( _pow10, range(nlo, nhi+1) )
  else:
    return _ticks_default_linear( lim )

def _ticks_num_linear( lim, num ):
  ticks = []
  a = lim[0]
  b = (lim[1] - lim[0])/float(num-1)
  for i in range(num):
    ticks.append( a + i*b )
  return ticks

def _ticks_num_log( lim, num ):
  ticks = []
  a = math.log10(lim[0])
  b = (math.log10(lim[1]) - a)/float(num - 1)
  for i in range(num):
    ticks.append( a + i*b )
  return map( _pow10, ticks )

def _subticks_linear( lim, ticks, num=None ):
  major_div = (ticks[-1] - ticks[0])/float(len(ticks) - 1)
  if num is None:
    _num = 4
    a, b = _magform( major_div )
    if 1. < a < (2 + 5)/2.:
      _num = 3
  else:
    _num = num
  minor_div = major_div/float(_num+1)
  return _ticklist_linear( lim[0], lim[1], minor_div, ticks[0] )

def _subticks_log( lim, ticks, num=None ):
  log_lim = _log10(lim[0]), _log10(lim[1])
  nlo = _ceil( math.log10(lim[0]) )
  nhi = _floor( math.log10(lim[1]) )
  nn = nhi - nlo +1

  if nn >= 10:
    return map( _pow10, _subticks_linear(log_lim, map(_log10,ticks), num) )
  elif nn >= 2:
    minor_ticks = []
    for i in range(nlo-1,nhi+1):
      for j in range(1,10):
        z = j * _pow10(i)
        if lim[0] <= z and z <= lim[1]:
          minor_ticks.append(z)
    return minor_ticks
  else:
    return _subticks_linear( lim, ticks, num )

class _Group:

  def __init__( self, objs ):
    self.objs = objs[:]
  
  def bbox( self, context ):
    bb = BoundingBox()
    for obj in self.objs:
      bb.union( obj.bbox(context) )
    return bb

class _HalfAxis( _PlotComponent ):

  func_ticks_default  = _ticks_default_linear, _ticks_default_log
  func_ticks_num    = _ticks_num_linear, _ticks_num_log

  func_subticks_default  = _subticks_linear, _subticks_log
  func_subticks_num  = _subticks_linear, _subticks_log

  _attr_map = {
    'labeloffset'    : 'label_offset',
    'major_ticklabels'  : 'ticklabels',
    'major_ticks'    : 'ticks',
    'minor_ticks'    : 'subticks',
  }

  def __init__( self, **kw ):
    _PlotComponent.__init__( self )
    self.kw_init( kw )
    self.conf_setattr( "_HalfAxis" )

  def __getattr__( self, name ):
    return self.__dict__[ self._attr_map.get(name,name) ]

  def __setattr__( self, name, value ):
    self.__dict__[ self._attr_map.get(name,name) ] = value

  def _ticks( self, context ):
    log = self._log( context )
    _range = self._range( context )
    if self.ticks is None:
      return self.func_ticks_default[log]( _range )
    elif type(self.ticks) == type(0):
      return self.func_ticks_num[log]( _range, self.ticks )
    else:
      return self.ticks

  def _subticks( self, context, ticks ):
    log = self._log( context )
    _range = self._range( context )
    if self.subticks is None:
      return self.func_subticks_default[log]( _range, ticks )
    elif type(self.subticks) == type(0):
      return self.func_subticks_num[log]( \
        _range, ticks, self.subticks )
    else:
      return self.subticks

  def _ticklabels( self, context, ticks ):
    if self.ticklabels is None:
      range = [ max(ticks) - min(ticks) ] * len(ticks)
      return map( _format_ticklabel, ticks, range )
    else:
      return self.ticklabels

  def _make_ticklabels( self, context, pos, labels ):
    if labels is None or not len(labels) > 0:
      return

    dir = self.ticklabels_dir
    offset = _size_relative( self.ticklabels_offset, \
      context.dev_bbox )
    if self.draw_ticks and self.tickdir > 0:
      offset = offset + _size_relative( \
        self.ticks_size, context.dev_bbox )
    labelpos = []
    for i in range(len(labels)):
      labelpos.append( \
        self._pos(context, pos[i], dir*offset) )

    halign, valign = self._align()

    style = { "halign" : halign, "valign" : valign }
    style.update( self.ticklabels_style )

    l = apply( _LabelsObject, (labelpos, labels), style )
    self.add( l )

  def _make_spine( self, context ):
    a, b = self._range( context )
    p = self._pos( context, a )
    q = self._pos( context, b )
    self.add( apply(_LineObject, (p, q), self.spine_style) )

  def _make_ticks( self, context, ticks, size, style ):
    if ticks is None or not len(ticks) > 0:
      return

    dir = self.tickdir * self.ticklabels_dir
    ticklen = self._dpos( dir *\
      _size_relative(size, context.dev_bbox) )
    tickpos = []
    for tick in ticks:
      tickpos.append( self._pos(context, tick) )

    self.add( apply(_CombObject, (tickpos, ticklen), style) )

  def make( self, context ):
    if self.draw_nothing:
      return

    ticks = self._ticks( context )
    subticks = self._subticks( context, ticks )
    ticklabels = self._ticklabels( context, ticks )

    implicit_draw_subticks = self.draw_subticks is None and \
      self.draw_ticks

    implicit_draw_ticklabels = self.draw_ticklabels is None and \
      (self.range is not None or self.ticklabels is not None)

    if self.draw_grid:
      self._make_grid( context, ticks )

    if self.draw_axis:
      if self.draw_subticks or implicit_draw_subticks:
        self._make_ticks( context, subticks, \
          self.subticks_size, \
          self.subticks_style )

      if self.draw_ticks:
        self._make_ticks( context, ticks, \
          self.ticks_size, self.ticks_style )

      if self.draw_spine:
        self._make_spine( context )

    if self.draw_ticklabels or implicit_draw_ticklabels:
      self._make_ticklabels( context, ticks, ticklabels )

    ## has to be made last
    if self.label is not None:
      self.add( apply(_BoxLabel,
        (_Group(self.device_objects),
        self.label, self._side(), self.label_offset),\
        self.label_style) )

class _HalfAxisX( _HalfAxis ):

  def _pos( self, context, a, db=0. ):
    p = context.geom( a, self._intercept(context) )
    return p[0], p[1] + db

  def _dpos( self, d ):
    return 0., d

  def _align( self ):
    if self.ticklabels_dir < 0:
      return 'center', 'top'
    else:
      return 'center', 'bottom'

  def _intercept( self, context ):
    if self.intercept is not None:
      return self.intercept
    limits = context.data_bbox
    if self.ticklabels_dir < 0:
      return limits.yrange()[0]
    else:
      return limits.yrange()[1]

  def _log( self, context ):
    if self.log is None:
      return context.xlog
    return self.log

  def _side( self ):
    if self.ticklabels_dir < 0:
      return 'bottom'
    else:
      return 'top'

  def _range( self, context ):
    if self.range is not None:
      a,b = self.range
      if a is None or b is None:
        c,d = context.data_bbox.xrange()
        if a is None: a = c
        if b is None: b = d
        return a,b
      else:
        return self.range
    return context.data_bbox.xrange()

  def _make_grid( self, context, ticks ):
    if ticks is None:
      return
    for tick in ticks:
      self.add( apply(LineX, (tick,), self.grid_style) )

class _HalfAxisY( _HalfAxis ):

  def _pos( self, context, a, db=0. ):
    p = context.geom( self._intercept(context), a )
    return p[0] + db, p[1]

  def _dpos( self, d ):
    return d, 0.

  def _align( self ):
    if self.ticklabels_dir > 0:
      return 'left', 'center'
    else:
      return 'right', 'center'

  def _intercept( self, context ):
    if self.intercept is not None:
      return self.intercept
    limits = context.data_bbox
    if self.ticklabels_dir > 0:
      return limits.xrange()[1]
    else:
      return limits.xrange()[0]

  def _log( self, context ):
    if self.log is None:
      return context.ylog
    return self.log

  def _side( self ):
    if self.ticklabels_dir > 0:
      return 'right'
    else:
      return 'left'

  def _range( self, context ):
    if self.range is not None:
      a,b = self.range
      if a is None or b is None:
        c,d = context.data_bbox.yrange()
        if a is None: a = c
        if b is None: b = d
        return a,b
      else:
        return self.range
    return context.data_bbox.yrange()

  def _make_grid( self, context, ticks ):
    if ticks is None:
      return
    for tick in ticks:
      self.add( apply(LineY, (tick,), self.grid_style) )

# _BoxLabel -------------------------------------------------------------------

class _BoxLabel( _PlotComponent ):

  kw_rename = {
    'face' : 'fontface',
    'size' : 'fontsize',
  }

  def __init__( self, obj, str, side, offset, **kw ):
    _PlotComponent.__init__( self )
    self.kw_init( kw )
    self.obj = obj
    self.str = str
    self.side = side
    self.offset = offset

  def make( self, context ):
    bb = self.obj.bbox( context )
    offset = _size_relative( self.offset, context.dev_bbox )
    if self.side == 'top':
      p = bb.upperleft()
      q = bb.upperright()
    elif self.side == 'bottom':
      p = bb.lowerleft()
      q = bb.lowerright()
      offset = -offset
    elif self.side == 'left':
      p = bb.lowerleft()
      q = bb.upperleft()
    elif self.side == 'right':
      p = bb.upperright()
      q = bb.lowerright()

    lt = apply( _LineTextObject, (p, q, self.str, offset), \
      self.kw_style )
    self.add( lt )

# _PlotComposite --------------------------------------------------------------

class _PlotComposite( _StyleKeywords ):

  def __init__( self, **kw ):
    self.kw_init( kw )
    self.components = []
    self.dont_clip = 0

  def add( self, *args ):
    for obj in args:
      self.components.append( obj )

  def clear( self ):
    self.components = []

  def empty( self ):
    return len(self.components) == 0

  def limits( self ):
    bb = BoundingBox()
    for obj in self.components:
      bb.union( obj.limits() )
    return bb

  def make( self, context ):
    pass

  def bbox( self, context ):
    self.make( context )
    bb = BoundingBox()
    for obj in self.components:
      bb.union( obj.bbox(context) )
    return bb

  def render( self, context ):
    self.make( context )
    self.kw_predraw( context )
    if not self.dont_clip:
      context.do_clip()
    for obj in self.components:
      obj.render( context )
    self.kw_postdraw( context )

# Frame -----------------------------------------------------------------------

class Frame( _PlotComposite ):

  def __init__( self, labelticks=(0,1,1,0), **kw ):
    apply( _PlotComposite.__init__, (self,), kw )
    self.dont_clip = 1

    self.x2 = _HalfAxisX()
    self.x2.draw_ticklabels = labelticks[0]
    self.x2.ticklabels_dir = 1

    self.x1 = _HalfAxisX()
    self.x1.draw_ticklabels = labelticks[1]
    self.x1.ticklabels_dir = -1
    
    self.y1 = _HalfAxisY()
    self.y1.draw_ticklabels = labelticks[2]
    self.y1.ticklabels_dir = -1

    self.y2 = _HalfAxisY()
    self.y2.draw_ticklabels = labelticks[3]
    self.y2.ticklabels_dir = 1

  def make( self, context ):
    self.clear()
    self.add( self.x1, self.x2, self.y1, self.y2 )

# _PlotContainer --------------------------------------------------------------

def _open_output( filename ):
  if filename == '-':
    import sys
    return sys.stdout
  else:
    # b is for windows
    return open( filename, 'wb' )

def _close_output( file ):
  if 2 < file.fileno():
    file.close()
  else:
    file.flush()

def _draw_text( device, p, str, **kw ):
  device.save_state()
  for key,val in kw.items():
    device.set( key, val )
  device.text( p, str )
  device.restore_state()

def win_temp_path():
  """
  Intended for Windows, returns a valid temp directory, or
  at least the current working directory.
  """
  import os
  if os.environ.has_key('TEMP'):
    if os.path.exists(os.environ['TEMP']):
      return(os.environ['TEMP'])
  else:
    possible_temp_paths = [ '\\temp', '\\winnt\\temp', \
      '\\windows\\temp', '\\tmp' ]
    for i in possible_temp_paths:
      if os.path.exists(i):
        return i
  return os.getcwd()

class _PlotContainer( _ConfAttributes ):

  def __init__( self, **kw ):
    apply( self.conf_setattr, ("_PlotContainer",), kw )

  def empty( self ):
    pass

  def interior( self, device, exterior ):
    TOL = 0.005

    interior = exterior.copy()
    region_diagonal = exterior.diagonal()

    for i in range(10):
      bb = self.exterior( device, interior )

      dll = pt_sub( exterior.lowerleft(), bb.lowerleft() )
      dur = pt_sub( exterior.upperright(), bb.upperright() )

      sll = pt_len(dll) / region_diagonal
      sur = pt_len(dur) / region_diagonal

      if sll < TOL and sur < TOL:
        # XXX:fixme
        if self.aspect_ratio is not None:
          interior.make_aspect_ratio(\
            self.aspect_ratio )
        return interior

      scale = interior.diagonal() / bb.diagonal()
      dll = pt_mul( scale, dll )
      dur = pt_mul( scale, dur )

      interior = BoundingBox(
        pt_add(interior.lowerleft(), dll),
        pt_add(interior.upperright(), dur) )

    raise BigglesError

  def exterior( self, device, interior ):
    return interior.copy()

  def compose_interior( self, device, interior ):
    if self.title is not None:
      offset = _size_relative( self.title_offset, interior )
      exterior = self.exterior( device, interior )
      x = interior.center()[0]
      y = exterior.yrange()[1] + offset
      style = self.title_style.copy()
      style["fontsize"] = _fontsize_relative( \
        self.title_style["fontsize"], interior, device )
      style["texthalign"] = "center"
      style["textvalign"] = "bottom"
      apply( _draw_text, (device, (x,y), self.title), style )

  def compose( self, device, region ):
    if self.empty():
      raise BigglesError( "empty container" )
    exterior = region.copy()
    if self.title is not None:
      offset = _size_relative( self.title_offset, exterior )
      fontsize = _fontsize_relative( \
        self.title_style["fontsize"], exterior, device )
      exterior.deform( -offset-fontsize, 0, 0, 0 )
    interior = self.interior( device, exterior )
    self.compose_interior( device, interior )

  def page_compose( self, device ):
    device.open()
    bb = BoundingBox( device.lowerleft, device.upperright )
    device.bbox = bb.copy()
    for key,val in config.options('default').items():
      device.set( key, val )
    bb.expand( -self.page_margin )
    self.compose( device, bb )
    device.close()

  def show( self, width=None, height=None ):
    import os
    if width is None:
      width = config.value('screen','width')
    if height is None:
      height = config.value('screen','height')
    if os.name == 'posix':
      self.show_x11( width, height )
    elif os.name == 'dos' or os.name == 'nt':
      self.show_win( width, height )
    else:
      _message( "show: system type '%s' not supported" \
        % os.name )

  def show_x11( self, width, height ):
    persistent = config.interactive() and \
      config.bool('screen','persistent')
    device = renderer.ScreenRenderer( persistent, width, height )
    self.page_compose( device )
    device.delete()

  def show_win( self, width, height ):
    """
    Substitute for show() that will work on Windows.
    Generates temporary files somewhere that end with
    '_biggles.png'. These temporary files are not deleted,
    they must be manually cleaned up during normal
    temp directory maintenance.
    """
    import os, tempfile
    #tf = os.path.join( win_temp_path(), 'biggles_graph.png' )
    tf = tempfile.mktemp('_biggles.png')
    self.write_img( width, height, tf )
    os.startfile( tf )

  def psprint( self, printcmd=None, **kw ):
    import os, copy
    if os.name != 'posix':
      _message( "psprint: system type '%s' not supported" \
        % os.name )
    if printcmd is None:
      printcmd = config.value("printer","command")
    opt = copy.copy( config.options("postscript") )
    opt.update( kw )
    _message( 'printing plot with "%s"' % printcmd )
    printer = os.popen( printcmd, 'w' )
    device = apply( renderer.PSRenderer, (printer,), opt )
    self.page_compose( device )
    device.delete()
    printer.close()

   def write_eps( self, filename, **kw ):
    opt = copy.copy( config.options("postscript") )
    opt.update( kw )
    file = _open_output( filename )
    device = apply( renderer.PSRenderer, (file,), opt )
    self.page_compose( device )
    device.delete()
    _close_output( file )

  def write_img( self, *args ):
    if len(args) == 4:
      type,width,height,filename = args
    elif len(args) == 3:
      import string
      width,height,filename = args
      type = string.lower( filename[-3:] )
    file = _open_output( filename )
    device = renderer.ImageRenderer( type, width, height, file )
    self.page_compose( device )
    device.delete()
    _close_output( file )

  save_as_eps = write_eps
  save_as_img = write_img

  def draw_piddle( self, canvastype=None, size=(500,500) ):
    from device.piddle import PiddleRenderer
    device = PiddleRenderer( canvastype, size )
    self.page_compose( device )
    canvas = device.canvas
    device.delete()
    return canvas

  def write_back_png( self, *args ):
    """
    Saves PNG file in temporary file. Returns file contents.
    """
    import tempfile, os
    if len(args) == 2:
      width,height = args
    type = 'png'
    file = tempfile.mktemp('_biggles.png')
    self.write_img(type, width, height, file)
    f = open(file, 'rb')
    output = f.read()
    f.close()
    os.remove(file)
    return output

def multipage( plots, filename, **kw ):
  file = _open_output( filename )
  opt = copy.copy( config.options("postscript") )
  opt.update( kw )
  device = apply( renderer.PSRenderer, (file,), opt )
  for plot in plots:
    plot.page_compose( device )
  device.delete()
  _close_output( file )

# -----------------------------------------------------------------------------

def _limits_axis( content_range, gutter, user_range, log ):

  r0, r1 = 0, 1

  if content_range is not None:
    a, b = content_range
    if a is not None: r0 = a
    if b is not None: r1 = b

  if gutter is not None:
    dx = 0.5 * gutter * (r1 - r0)
    a = r0 - dx
    if not log or a > 0:
      r0 = a
    r1 = r1 + dx

  if user_range is not None:
    a, b = user_range
    if a is not None: r0 = a
    if b is not None: r1 = b

  if r0 == r1:
    r0 = r0 - 1
    r1 = r1 + 1

  return r0, r1

def _limits( content_bbox, gutter, xlog, ylog, xrange, yrange ):

  xr = _limits_axis( content_bbox.xrange(), gutter, xrange, xlog )
  yr = _limits_axis( content_bbox.yrange(), gutter, yrange, ylog )

  return BoundingBox( (xr[0],yr[0]), (xr[1],yr[1]) )

# Plot ------------------------------------------------------------------------

class Plot( _PlotContainer ):

  def __init__( self, **kw ):
    apply( _PlotContainer.__init__, (self,) )
    apply( self.conf_setattr, ("Plot",), kw )
    self.content = _PlotComposite()

  def __iadd__( self, other ):
    self.add( other )

  def empty( self ):
    return self.content.empty()

  def add( self, *args ):
    apply( self.content.add, args )

  def limits( self ):
    return _limits( self.content.limits(), self.gutter, \
      self.xlog, self.ylog, self.xrange, self.yrange )

  def compose_interior( self, device, region, limits=None ):
    if limits is None:
      limits = self.limits()
    context = _PlotContext( device, region, limits,
      xlog=self.xlog, ylog=self.ylog )
    self.content.render( context )

  def compose( self, device, region, limits=None ):
    interior = self.interior( device, region )
    self.compose_interior( device, interior, limits )

# FramedPlot ------------------------------------------------------------------

class FramedPlot( _PlotContainer ):

  def __init__( self, **kw ):
    apply( _PlotContainer.__init__, (self,) )
    self.content1 = _PlotComposite()
    self.content2 = _PlotComposite()
    self.x1 = _HalfAxisX()
    self.x1.ticklabels_dir = -1
    self.y1 = _HalfAxisY()
    self.y1.ticklabels_dir = -1
    self.x2 = _HalfAxisX()
    self.x2.draw_ticklabels = None
    self.y2 = _HalfAxisY()
    self.y2.draw_ticklabels = None
    self.frame = _Alias( self.x1, self.x2, self.y1, self.y2 )
    self.frame1 = _Alias( self.x1, self.y1 )
    self.frame2 = _Alias( self.x2, self.y2 )
    self.x = _Alias( self.x1, self.x2 )
    self.y = _Alias( self.y1, self.y2 )
    apply( self.conf_setattr, ("FramedPlot",), kw )

  _attr_map = {
    "xlabel"  : ("x1", "label"),
    "ylabel"  : ("y1", "label"),
    "xlog"    : ("x1", "log"),
    "ylog"          : ("y1", "log"),
    "xrange"  : ("x1", "range"),
    "yrange"  : ("y1", "range"),
    "xtitle"  : ("x1", "label"),
    "ytitle"  : ("y1", "label"),
  }

  def __repr__( self ):
    return "<biggles.FramedPlot instance>"

  def __getattr__( self, name ):
    if self._attr_map.has_key( name ):
      xs = self._attr_map[ name ]
      obj = self
      for x in xs[:-1]:
        obj = getattr( obj, x )
      return getattr( obj, xs[-1] )
    else:
      return self.__dict__[name]

   def __setattr__( self, name, value ):
    if self._attr_map.has_key( name ):
      xs = self._attr_map[ name ]
      obj = self
      for x in xs[:-1]:
        obj = getattr( obj, x )
      setattr( obj, xs[-1], value )
    else:
      self.__dict__[name] = value

  def empty( self ):
    return self.content1.empty() and self.content2.empty()

  def add( self, *args ):
    apply( self.content1.add, args )

  def add2( self, *args ):
    apply( self.content2.add, args )

  def _xy2log( self ):
    return _first_not_none(self.x2.log, self.x1.log), \
           _first_not_none(self.y2.log, self.y1.log)

  def _limits1( self ):
    return _limits( self.content1.limits(),
      self.gutter, self.x1.log, self.y1.log, \
      self.x1.range, self.y1.range )

  def _context1( self, device, region ):
    return _PlotContext( device, region, self._limits1(),
      xlog=self.x1.log, ylog=self.y1.log )

  def _limits2( self ):
    limits = self.content2.limits()
    if self.content2.empty():
      limits = self.content1.limits()
    xlog, ylog = self._xy2log()
    xrange = _first_not_none( self.x2.range, self.x1.range )
    yrange = _first_not_none( self.y2.range, self.y1.range )
    return _limits( limits, self.gutter, xlog, ylog, \
      xrange, yrange )

  def _context2( self, device, region ):
    xlog, ylog = self._xy2log()
    return _PlotContext( device, region, self._limits2(),
      xlog, ylog )

  def exterior( self, device, region ):
    bbox = BoundingBox()

    context1 = self._context1( device, region )
    bbox.union( self.x1.bbox(context1) )
    bbox.union( self.y1.bbox(context1) )

    context2 = self._context2( device, region )
    bbox.union( self.x2.bbox(context2) )
    bbox.union( self.y2.bbox(context2) )

    return bbox

  def compose_interior( self, device, region ):
    _PlotContainer.compose_interior( self, device, region )

    context1 = self._context1( device, region )
    context2 = self._context2( device, region )

    self.content1.render( context1 )
    self.content2.render( context2 )

    self.y2.render( context2 )
    self.x2.render( context2 )
    self.y1.render( context1 )
    self.x1.render( context1 )

class OldCustomFramedPlot( FramedPlot ):

  def __init__( self, **kw ):
    apply( FramedPlot.__init__, (self,), kw ) 
    self.x = self.x1
    self.y = self.y1

# Table -----------------------------------------------------------------------

class _Grid:

  def __init__( self, nrows, ncols, bbox, cellpadding=0, cellspacing=0 ):
    self.nrows = nrows
    self.ncols = ncols

    w, h = bbox.width(), bbox.height()
    cp = _size_relative( cellpadding, bbox )
    cs = _size_relative( cellspacing, bbox )

    self.origin = pt_add( bbox.lowerleft(), (cp,cp) )
    self.step_x = (w + cs)/ncols
    self.step_y = (h + cs)/nrows
    self.cell_dimen = self.step_x - cs - 2*cp, \
      self.step_y - cs - 2*cp

  def cell( self, i, j ):
    ii = self.nrows-1 - i
    p = pt_add( self.origin, (j*self.step_x,ii*self.step_y) )
    q = pt_add( p, self.cell_dimen )
    return BoundingBox( p, q )

class Table( _PlotContainer ):

  def __init__( self, rows, cols, **kw ):
    apply( _PlotContainer.__init__, (self,) )
    apply( self.conf_setattr, ("Table",), kw )
    self.rows = rows
    self.cols = cols
    self.content = {}

  def __getitem__( self, key ):
    return self.content[key]

  def __setitem__( self, key, value ):
    self.content[key] = value

  def set( self, i, j, obj ):
    self.content[i,j] = obj

  def get( self, i, j ):
    return self.content.get( (i,j), None )

  def exterior( self, device, interior ):
    ext = interior.copy()

    if self.align_interiors:
      g = _Grid( self.rows, self.cols, interior, \
        self.cellpadding, self.cellspacing )

      for key,obj in self.content.items():
        subregion = apply( g.cell, key )
        ext.union( obj.exterior(device, subregion) )
    return ext

  def compose_interior( self, device, interior ):
    _PlotContainer.compose_interior( self, device, interior )

    g = _Grid( self.rows, self.cols, interior, \
      self.cellpadding, self.cellspacing )

    for key,obj in self.content.items():
      subregion = apply( g.cell, key )
      if self.align_interiors:
        obj.compose_interior( device, subregion )
      else:
        obj.compose( device, subregion )

# FramedArray -----------------------------------------------------------------
#
# Hideous, but it works...
#

def _frame_draw( obj, device, region, limits, labelticks=(0,1,1,0) ):
  frame = Frame( labelticks=labelticks )
  context = _PlotContext( device, region, limits,
    xlog=obj.xlog, ylog=obj.ylog )
  frame.render( context )

def _frame_bbox( obj, device, region, limits, labelticks=(0,1,1,0) ):
  frame = Frame( labelticks=labelticks )
  context = _PlotContext( device, region, limits,
    xlog=obj.xlog, ylog=obj.ylog )
  return frame.bbox( context )

def _range_union( a, b ):
  if a is None: return b
  if b is None: return a
  return min(a[0],b[0]), max(a[1],b[1])

class FramedArray( _PlotContainer ):

  def __init__( self, nrows, ncols, **kw ):
    apply( _PlotContainer.__init__, (self,) )
    self.nrows = nrows
    self.ncols = ncols
    self.content = {}
    for i in range(nrows):
      for j in range(ncols):
        self.content[i,j] = Plot()
    apply( self.conf_setattr, ("FramedArray",), kw )

  _attr_distribute = [
    'gutter',
    'xlog',
    'ylog',
    'xrange',
    'yrange',
  ]

  _attr_deprecated = {
    'labeloffset'  : 'label_offset',
    'labelsize'  : 'label_size',
  }

  def __setattr__( self, name, value ):
    if name in self._attr_distribute:
      for obj in self.content.values():
        setattr( obj, name, value )
    else:
      _name = self._attr_deprecated.get( name, name )
      self.__dict__[_name] = value

  def __getitem__( self, key ):
    return self.content[key]

  def _limits( self, i, j ):
    if self.uniform_limits:
      return self._limits_uniform()
    else:
      return self._limits_nonuniform( i, j )

  def _limits_uniform( self ):
    limits = BoundingBox()
    for obj in self.content.values():
      limits.union( obj.limits() )
    return limits

  def _limits_nonuniform( self, i, j ):
    lx = None
    for k in range(self.nrows):
      l = self.content[k,j].limits()
      lx = _range_union( l.xrange(), lx )
    ly = None
    for k in range(self.ncols):
      l = self.content[i,k].limits()
      ly = _range_union( l.yrange(), ly )
    return BoundingBox( (lx[0],ly[0]), (lx[1],ly[1]) )

  def _grid( self, interior ):
    return _Grid( self.nrows, self.ncols, interior,
      cellspacing=self.cellspacing )

  def _frames_bbox( self, device, interior ):
    bb = BoundingBox()
    g = self._grid( interior )
    corners = [(0,0),(self.nrows-1,self.ncols-1)]

    for key in corners:
      obj = self.content[key]
      subregion = apply( g.cell, key )
      limits = apply( self._limits, key )
      axislabels = [0,0,0,0]
      if key[0] == self.nrows-1:
        axislabels[1] = 1
      if key[1] == 0:
        axislabels[2] = 1
      bb.union( _frame_bbox(obj, device, subregion, \
          limits, axislabels) )

    return bb

  def exterior( self, device, interior ):
    bb = self._frames_bbox( device, interior )

    labeloffset = _size_relative( self.label_offset, interior )
    labelsize = _fontsize_relative( \
      self.label_size, interior, device )
    margin = labeloffset + labelsize

    if self.xlabel is not None:
      bb.deform( 0, margin, 0, 0 )
    if self.ylabel is not None:
      bb.deform( 0, 0, margin, 0 )

    return bb

  def _frames_draw( self, device, interior ):
    g = self._grid( interior )

    for key,obj in self.content.items():
      subregion = apply( g.cell, key )
      limits = apply( self._limits, key )
      axislabels = [0,0,0,0]
      if key[0] == self.nrows-1:
        axislabels[1] = 1
      if key[1] == 0:
        axislabels[2] = 1
      _frame_draw( obj, device, subregion, \
         limits, axislabels )

  def _data_draw( self, device, interior ):
    g = self._grid( interior )

    for key,obj in self.content.items():
      subregion = apply( g.cell, key )
      limits = apply( self._limits, key )
      obj.compose_interior( device, subregion, limits )

  def _labels_draw( self, device, interior ):
    bb = self._frames_bbox( device, interior )

    labeloffset = _size_relative( self.label_offset, interior )
    labelsize = _fontsize_relative( \
      self.label_size, interior, device )

    device.save_state()
    device.set( 'fontsize', labelsize )
    device.set( 'texthalign', 'center' )
    if self.xlabel is not None:
      x = interior.center()[0]
      y = bb.yrange()[0] - labeloffset
      device.set( 'textvalign', 'top' )
      device.text( (x,y), self.xlabel )
    if self.ylabel is not None:
      x = bb.xrange()[0] - labeloffset
      y = interior.center()[1]
      device.set( 'textangle', 90. )
      device.set( 'textvalign', 'bottom' )
      device.text( (x,y), self.ylabel )
    device.restore_state()

  def add( self, *args ):
    for obj in self.content.values():
      apply( obj.add, args ) 

  def compose_interior( self, device, interior ):
    _PlotContainer.compose_interior( self, device, interior )
    self._data_draw( device, interior )
    self._frames_draw( device, interior )
    self._labels_draw( device, interior )

# Text ------------------------------------------------------------------------

class Text( _PlotContainer ):

  def __init__( self, text, **kw ):
    apply( _PlotContainer.__init__, (self,) )
    apply( self.conf_setattr, ("Text",), kw )
    import string
    self.lines = string.split( text, "\n" )
    if self.lines[0] == '':
      del self.lines[0]
    if self.lines[-1] == '':
      del self.lines[-1]

  def compose( self, device, region ):
    device.save_state()
    context = _PlotContext( device, region, region )
    fontsize = _fontsize_relative(
      self.fontsize, context.dev_bbox, device )
    device.set( 'fontsize', fontsize )

    block_w = 0
    for line in self.lines:
      block_w = max( block_w, device.textwidth(line) )
    dy = fontsize * self.lineheight
    block_h = fontsize + dy * (len(self.lines)-1)

    x0, y0 = region.center()
    y0 = y0 + block_h/2.

    if self.halign == 'left':
      x0 = x0 - (region.width() - block_w)/2.
    elif self.halign == 'right':
      x0 = x0 + (region.width() - block_w)/2.

    if self.valign == 'top':
      y0 = y0 + (region.height() - block_h)/2.
    elif self.valign == 'bottom':
      y0 = y0 - (region.height() - block_h)/2.

    if self.justify == 'left':
      x0 = x0 - block_w/2
    elif self.justify == 'right':
      x0 = x0 + block_w/2

    ## render
    block = _PlotComposite()

    y = y0
    for line in self.lines:
      block.add( DataLabel(x0, y, line, \
        halign=self.justify, valign='top', \
        fontface=self.fontface, \
        fontsize=self.fontsize) )
      y = y - dy

    block.render( context )
    device.restore_state()

# Inset -----------------------------------------------------------------------

class _Inset:

  def __init__( self, p, q, plot ):
    self.plot_limits = BoundingBox( p, q )
    self.plot = plot

  def render( self, context ):
    region = self.bbox( context )
    self.plot.compose_interior( context.draw, region )

class DataInset( _Inset ):

  def bbox( self, context ):
    p = apply( context.geom, self.plot_limits.lowerleft() )
    q = apply( context.geom, self.plot_limits.upperright() )
    return BoundingBox( p, q )

  def limits( self ):
    return self.plot_limits.copy()

class PlotInset( _Inset ):

  def bbox( self, context ):
    p = apply( context.plot_geom, self.plot_limits.lowerleft() )
    q = apply( context.plot_geom, self.plot_limits.upperright() )
    return BoundingBox( p, q )

  def limits( self ):
    return BoundingBox()

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.