diff --git a/samples/floatcanvas/PieChart.py b/samples/floatcanvas/PieChart.py
index eaacb524..b7d89213 100644
--- a/samples/floatcanvas/PieChart.py
+++ b/samples/floatcanvas/PieChart.py
@@ -5,7 +5,7 @@ import wx
## import the installed version
from wx.lib.floatcanvas import NavCanvas, FloatCanvas
-from wx.lib.floatcanvas.SpecialObjects import PieChart
+from wx.lib.floatcanvas.FCObjects import PieChart
## import a local version
#import sys
diff --git a/wx/lib/floatcanvas/Utilities/BBoxTest.py b/unittests/test_lib_floatcanvas_bbox.py
old mode 100755
new mode 100644
similarity index 95%
rename from wx/lib/floatcanvas/Utilities/BBoxTest.py
rename to unittests/test_lib_floatcanvas_bbox.py
index 6b46bacd..ea6e6b95
--- a/wx/lib/floatcanvas/Utilities/BBoxTest.py
+++ b/unittests/test_lib_floatcanvas_bbox.py
@@ -1,27 +1,12 @@
-#!/usr/bin/env python
-#----------------------------------------------------------------------------
-# Name: BBoxTest.py
-# Purpose: Test code for the BBox Object
-#
-# Author:
-#
-# Created:
-# Version:
-# Date:
-# Licence:
-# Tags: phoenix-port
-#----------------------------------------------------------------------------
+import imp_unittest, unittest
+import wtc
+import wx
-"""
-Test code for the BBox Object
+from wx.lib.floatcanvas.Utilities.BBox import *
-"""
+#---------------------------------------------------------------------------
-import unittest
-
-from BBox import *
-
-class testCreator(unittest.TestCase):
+class testCreator(wtc.WidgetTestCase):
def testCreates(self):
B = BBox(((0,0),(5,5)))
self.failUnless(isinstance(B, BBox))
@@ -74,7 +59,7 @@ class testCreator(unittest.TestCase):
# Should catch tiny difference
self.failUnlessRaises(ValueError, BBox, ((0,0), (-1e-20,5)) )
-class testAsBBox(unittest.TestCase):
+class testAsBBox(wtc.WidgetTestCase):
def testPassThrough(self):
B = BBox(((0,0),(5,5)))
@@ -99,7 +84,7 @@ class testAsBBox(unittest.TestCase):
A[0,0] = -10
self.failUnless(C[0,0] == A[0,0])
-class testIntersect(unittest.TestCase):
+class testIntersect(wtc.WidgetTestCase):
def testSame(self):
B = BBox(((-23.5, 456),(56, 532.0)))
@@ -188,7 +173,7 @@ class testIntersect(unittest.TestCase):
-class testEquality(unittest.TestCase):
+class testEquality(wtc.WidgetTestCase):
def testSame(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
@@ -218,7 +203,7 @@ class testEquality(unittest.TestCase):
C = N.array( ( (1.01, 2.0), (5.0, 10.0) ) )
self.failIf(C == B)
-class testInside(unittest.TestCase):
+class testInside(wtc.WidgetTestCase):
def testSame(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
C = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
@@ -274,7 +259,7 @@ class testInside(unittest.TestCase):
C = BBox( ( (17.1, 8),(17.95, 32) ) )
self.failIf(B.Inside(C) )
-class testPointInside(unittest.TestCase):
+class testPointInside(wtc.WidgetTestCase):
def testPointIn(self):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
P = (3.0, 4.0)
@@ -350,7 +335,7 @@ class testPointInside(unittest.TestCase):
P = (-1, -10.0)
self.failUnless(B.PointInside(P))
-class testFromPoints(unittest.TestCase):
+class testFromPoints(wtc.WidgetTestCase):
def testCreate(self):
Pts = N.array( ((5,2),
@@ -398,7 +383,7 @@ class testFromPoints(unittest.TestCase):
B[1,0] == 65.0 and
B[1,1] == 43.2
)
-class testMerge(unittest.TestCase):
+class testMerge(wtc.WidgetTestCase):
A = BBox( ((-23.5, 456), (56, 532.0)) )
B = BBox( ((-20.3, 460), (54, 465 )) )# B should be completely inside A
C = BBox( ((-23.5, 456), (58, 540.0)) )# up and to the right or A
@@ -424,7 +409,7 @@ class testMerge(unittest.TestCase):
A.Merge(self.D)
self.failUnless(A[0] == self.D[0] and A[1] == self.A[1])
-class testWidthHeight(unittest.TestCase):
+class testWidthHeight(wtc.WidgetTestCase):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
def testWidth(self):
self.failUnless(self.B.Width == 4.0)
@@ -444,7 +429,7 @@ class testWidthHeight(unittest.TestCase):
def testSetH(self):
self.failUnlessRaises(AttributeError, self.attemptSetHeight)
-class testCenter(unittest.TestCase):
+class testCenter(wtc.WidgetTestCase):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
def testCenter(self):
self.failUnless( (self.B.Center == (3.0, 6.0)).all() )
@@ -456,7 +441,7 @@ class testCenter(unittest.TestCase):
self.failUnlessRaises(AttributeError, self.attemptSetCenter)
-class testBBarray(unittest.TestCase):
+class testBBarray(wtc.WidgetTestCase):
BBarray = N.array( ( ((-23.5, 456), (56, 532.0)),
((-20.3, 460), (54, 465 )),
((-23.5, 456), (58, 540.0)),
@@ -469,7 +454,7 @@ class testBBarray(unittest.TestCase):
BB = fromBBArray(self.BBarray)
self.failUnless(BB == self.BB, "Wrong BB was created. It was:\n%s \nit should have been:\n%s"%(BB, self.BB))
-class testNullBBox(unittest.TestCase):
+class testNullBBox(wtc.WidgetTestCase):
B1 = NullBBox()
B2 = NullBBox()
B3 = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
@@ -504,7 +489,7 @@ class testNullBBox(unittest.TestCase):
self.failUnless( self.B3.Overlaps(self.B1) == False)
-class testInfBBox(unittest.TestCase):
+class testInfBBox(wtc.WidgetTestCase):
B1 = InfBBox()
B2 = InfBBox()
B3 = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
@@ -556,7 +541,7 @@ class testInfBBox(unittest.TestCase):
self.failUnless( self.NB.Overlaps(self.B1) == True)
-class testSides(unittest.TestCase):
+class testSides(wtc.WidgetTestCase):
B = BBox( ( (1.0, 2.0), (5.0, 10.0) ) )
def testLeft(self):
@@ -568,7 +553,8 @@ class testSides(unittest.TestCase):
def testTop(self):
self.failUnless( self.B.Top == 10.0 )
+
+#---------------------------------------------------------------------------
-
-if __name__ == "__main__":
+if __name__ == '__main__':
unittest.main()
diff --git a/wx/lib/floatcanvas/FCObjects.py b/wx/lib/floatcanvas/FCObjects.py
new file mode 100644
index 00000000..f1095309
--- /dev/null
+++ b/wx/lib/floatcanvas/FCObjects.py
@@ -0,0 +1,2657 @@
+#!/usr/bin/env python
+#----------------------------------------------------------------------------
+# Name: FCObjects.py
+# Purpose: Contains all the drawing objects FloatCanvas supports
+#
+# Author:
+#
+# Created:
+# Version:
+# Date:
+# Licence:
+# Tags: phoenix-port, unittest, documented, py3-port
+#----------------------------------------------------------------------------
+"""
+This is where FloatCanvas defines its drawings objects.
+"""
+
+import sys
+
+import wx
+from wx.lib import six
+
+import numpy as N
+
+from .Utilities import BBox
+from wx.lib.floatcanvas.Utilities import Colors
+
+mac = sys.platform.startswith("darwin")
+
+## A global variable to hold the Pixels per inch that wxWindows thinks is in use
+## This is used for scaling fonts.
+## This can't be computed on module __init__, because a wx.App might not have initialized yet.
+global FontScale
+
+
+def ComputeFontScale():
+ """
+ Compute the font scale.
+
+ A global variable to hold the scaling from pixel size to point size.
+ """
+ global FontScale
+ dc = wx.ScreenDC()
+ dc.SetFont(wx.Font(16, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
+ E = dc.GetTextExtent("X")
+ FontScale = 16/E[1]
+ del dc
+
+ComputeFontScale()
+
+## fixme: This should probably be re-factored into a class
+_testBitmap = None
+_testDC = None
+def _cycleidxs(indexcount, maxvalue, step):
+
+ """
+ Utility function used by _colorGenerator
+ """
+ def colormatch(color):
+ """Return True if the color comes back from the bitmap identically."""
+ if len(color) < 3:
+ return True
+ global _testBitmap, _testDC
+ B = _testBitmap
+ if not mac:
+ dc = _testDC
+ if not B:
+ B = _testBitmap = wx.Bitmap(1, 1)
+ if not mac:
+ dc = _testDC = wx.MemoryDC()
+ if mac:
+ dc = wx.MemoryDC()
+ dc.SelectObject(B)
+ dc.SetBackground(wx.BLACK_BRUSH)
+ dc.Clear()
+ dc.SetPen(wx.Pen(wx.Colour(*color), 4))
+ dc.DrawPoint(0,0)
+ if mac:
+ del dc
+ pdata = wx.AlphaPixelData(B)
+ pacc = pdata.GetPixels()
+ pacc.MoveTo(pdata, 0, 0)
+ outcolor = pacc.Get()[:3]
+ else:
+ outcolor = dc.GetPixel(0,0)
+ return outcolor == color
+
+ if indexcount == 0:
+ yield ()
+ else:
+ for idx in range(0, maxvalue, step):
+ for tail in _cycleidxs(indexcount - 1, maxvalue, step):
+ color = (idx, ) + tail
+ if not colormatch(color):
+ continue
+ yield color
+
+def _colorGenerator():
+
+ """
+ Generates a series of unique colors used to do hit-tests with the Hit
+ Test bitmap
+ """
+ return _cycleidxs(indexcount=3, maxvalue=256, step=1)
+
+
+class DrawObject:
+ """
+ This is the base class for all the objects that can be drawn.
+
+ One must subclass from this (and an assortment of Mixins) to create
+ a new DrawObject, see for example :class:`~lib.floatcanvas.FloatCanvas.Circle`.
+
+ """
+ #This class contains a series of static dictionaries:
+
+ #* BrushList
+ #* PenList
+ #* FillStyleList
+ #* LineStyleList
+
+
+ def __init__(self, InForeground = False, IsVisible = True):
+ """
+ Default class constructor.
+
+ :param boolean `InForeground`: Define if object should be in foreground
+ or not
+ :param boolean `IsVisible`: Define if object should be visible
+
+ """
+ self.InForeground = InForeground
+
+ self._Canvas = None
+
+ self.HitColor = None
+ self.CallBackFuncs = {}
+
+ ## these are the defaults
+ self.HitAble = False
+ self.HitLine = True
+ self.HitFill = True
+ self.MinHitLineWidth = 3
+ self.HitLineWidth = 3 ## this gets re-set by the subclasses if necessary
+
+ self.Brush = None
+ self.Pen = None
+
+ self.FillStyle = "Solid"
+
+ self.Visible = IsVisible
+
+ # I pre-define all these as class variables to provide an easier
+ # interface, and perhaps speed things up by caching all the Pens
+ # and Brushes, although that may not help, as I think wx now
+ # does that on it's own. Send me a note if you know!
+
+ BrushList = {
+ ( None, "Transparent") : wx.TRANSPARENT_BRUSH,
+ ("Blue", "Solid") : wx.BLUE_BRUSH,
+ ("Green", "Solid") : wx.GREEN_BRUSH,
+ ("White", "Solid") : wx.WHITE_BRUSH,
+ ("Black", "Solid") : wx.BLACK_BRUSH,
+ ("Grey", "Solid") : wx.GREY_BRUSH,
+ ("MediumGrey", "Solid") : wx.MEDIUM_GREY_BRUSH,
+ ("LightGrey", "Solid") : wx.LIGHT_GREY_BRUSH,
+ ("Cyan", "Solid") : wx.CYAN_BRUSH,
+ ("Red", "Solid") : wx.RED_BRUSH
+ }
+ PenList = {
+ (None, "Transparent", 1) : wx.TRANSPARENT_PEN,
+ ("Green", "Solid", 1) : wx.GREEN_PEN,
+ ("White", "Solid", 1) : wx.WHITE_PEN,
+ ("Black", "Solid", 1) : wx.BLACK_PEN,
+ ("Grey", "Solid", 1) : wx.GREY_PEN,
+ ("MediumGrey", "Solid", 1) : wx.MEDIUM_GREY_PEN,
+ ("LightGrey", "Solid", 1) : wx.LIGHT_GREY_PEN,
+ ("Cyan", "Solid", 1) : wx.CYAN_PEN,
+ ("Red", "Solid", 1) : wx.RED_PEN
+ }
+
+ FillStyleList = {
+ "Transparent" : wx.BRUSHSTYLE_TRANSPARENT,
+ "Solid" : wx.BRUSHSTYLE_SOLID,
+ "BiDiagonalHatch": wx.BRUSHSTYLE_BDIAGONAL_HATCH,
+ "CrossDiagHatch" : wx.BRUSHSTYLE_CROSSDIAG_HATCH,
+ "FDiagonal_Hatch": wx.BRUSHSTYLE_FDIAGONAL_HATCH,
+ "CrossHatch" : wx.BRUSHSTYLE_CROSS_HATCH,
+ "HorizontalHatch": wx.BRUSHSTYLE_HORIZONTAL_HATCH,
+ "VerticalHatch" : wx.BRUSHSTYLE_VERTICAL_HATCH
+ }
+
+ LineStyleList = {
+ "Solid" : wx.PENSTYLE_SOLID,
+ "Transparent": wx.PENSTYLE_TRANSPARENT,
+ "Dot" : wx.PENSTYLE_DOT,
+ "LongDash" : wx.PENSTYLE_LONG_DASH,
+ "ShortDash" : wx.PENSTYLE_SHORT_DASH,
+ "DotDash" : wx.PENSTYLE_DOT_DASH,
+ }
+
+ def Bind(self, Event, CallBackFun):
+ """
+ Bind an event to the DrawObject
+
+ :param `Event`: see below for supported event types
+
+ - EVT_FC_LEFT_DOWN
+ - EVT_FC_LEFT_UP
+ - EVT_FC_LEFT_DCLICK
+ - EVT_FC_MIDDLE_DOWN
+ - EVT_FC_MIDDLE_UP
+ - EVT_FC_MIDDLE_DCLICK
+ - EVT_FC_RIGHT_DOWN
+ - EVT_FC_RIGHT_UP
+ - EVT_FC_RIGHT_DCLICK
+ - EVT_FC_ENTER_OBJECT
+ - EVT_FC_LEAVE_OBJECT
+
+ :param `CallBackFun`: the call back function for the event
+
+
+ """
+ ##fixme: Way too much Canvas Manipulation here!
+ self.CallBackFuncs[Event] = CallBackFun
+ self.HitAble = True
+ self._Canvas.UseHitTest = True
+ if self.InForeground and self._Canvas._ForegroundHTBitmap is None:
+ self._Canvas.MakeNewForegroundHTBitmap()
+ elif self._Canvas._HTBitmap is None:
+ self._Canvas.MakeNewHTBitmap()
+ if not self.HitColor:
+ if not self._Canvas.HitColorGenerator:
+ # first call to prevent the background color from being used.
+ self._Canvas.HitColorGenerator = _colorGenerator()
+ if six.PY3:
+ next(self._Canvas.HitColorGenerator)
+ else:
+ self._Canvas.HitColorGenerator.next()
+ if six.PY3:
+ self.HitColor = next(self._Canvas.HitColorGenerator)
+ else:
+ self.HitColor = self._Canvas.HitColorGenerator.next()
+ self.SetHitPen(self.HitColor,self.HitLineWidth)
+ self.SetHitBrush(self.HitColor)
+ # put the object in the hit dict, indexed by it's color
+ if not self._Canvas.HitDict:
+ self._Canvas.MakeHitDict()
+ self._Canvas.HitDict[Event][self.HitColor] = (self) # put the object in the hit dict, indexed by its color
+
+ def UnBindAll(self):
+ """
+ Unbind all events
+
+ .. note:: Currently only removes one from each list
+
+ """
+ ## fixme: this only removes one from each list, there could be more.
+ ## + patch by Tim Ansel
+ if self._Canvas.HitDict:
+ for Event in self._Canvas.HitDict.itervalues():
+ try:
+ del Event[self.HitColor]
+ except KeyError:
+ pass
+ self.HitAble = False
+
+
+ def SetBrush(self, FillColor, FillStyle):
+ """
+ Set the brush for this DrawObject
+
+ :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ for valid entries
+ :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
+ for valid entries
+ """
+ if FillColor is None or FillStyle is None:
+ self.Brush = wx.TRANSPARENT_BRUSH
+ ##fixme: should I really re-set the style?
+ self.FillStyle = "Transparent"
+ else:
+ self.Brush = self.BrushList.setdefault(
+ (FillColor, FillStyle),
+ wx.Brush(FillColor, self.FillStyleList[FillStyle]))
+ #print("Setting Brush, BrushList length:", len(self.BrushList))
+
+ def SetPen(self, LineColor, LineStyle, LineWidth):
+ """
+ Set the Pen for this DrawObject
+
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ for valid entries
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ for valid entries
+ :param integer `LineWidth`: the width in pixels
+ """
+ if (LineColor is None) or (LineStyle is None):
+ self.Pen = wx.TRANSPARENT_PEN
+ self.LineStyle = 'Transparent'
+ else:
+ self.Pen = self.PenList.setdefault(
+ (LineColor, LineStyle, LineWidth),
+ wx.Pen(LineColor, LineWidth, self.LineStyleList[LineStyle]))
+
+ def SetHitBrush(self, HitColor):
+ """
+ Set the brush used for hit test, do not call directly.
+
+ :param `HitColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+
+ """
+ if not self.HitFill:
+ self.HitBrush = wx.TRANSPARENT_BRUSH
+ else:
+ self.HitBrush = self.BrushList.setdefault(
+ (HitColor,"solid"),
+ wx.Brush(HitColor, self.FillStyleList["Solid"]))
+
+ def SetHitPen(self, HitColor, LineWidth):
+ """
+ Set the pen used for hit test, do not call directly.
+
+ :param `HitColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param integer `LineWidth`: the line width in pixels
+
+ """
+ if not self.HitLine:
+ self.HitPen = wx.TRANSPARENT_PEN
+ else:
+ self.HitPen = self.PenList.setdefault( (HitColor, "solid", self.HitLineWidth), wx.Pen(HitColor, self.HitLineWidth, self.LineStyleList["Solid"]) )
+
+ ## Just to make sure that they will always be there
+ ## the appropriate ones should be overridden in the subclasses
+ def SetColor(self, Color):
+ """
+ Set the Color - this method is overridden in the subclasses
+
+ :param `Color`: use one of the following values any valid entry from
+ :class:`ColourDatabase`
+
+ - ``Green``
+ - ``White``
+ - ``Black``
+ - ``Grey``
+ - ``MediumGrey``
+ - ``LightGrey``
+ - ``Cyan``
+ - ``Red``
+
+ """
+
+ pass
+
+ def SetLineColor(self, LineColor):
+ """
+ Set the LineColor - this method is overridden in the subclasses
+
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ for valid values
+
+ """
+ pass
+
+ def SetLineStyle(self, LineStyle):
+ """
+ Set the LineStyle - this method is overridden in the subclasses
+
+ :param `LineStyle`: Use one of the following values or ``None`` :
+
+ ===================== =============================
+ Style Description
+ ===================== =============================
+ ``Solid`` Solid line
+ ``Transparent`` A transparent line
+ ``Dot`` Dotted line
+ ``LongDash`` Dashed line (long)
+ ``ShortDash`` Dashed line (short)
+ ``DotDash`` Dash-dot-dash line
+ ===================== =============================
+
+ """
+ pass
+
+ def SetLineWidth(self, LineWidth):
+ """
+ Set the LineWidth
+
+ :param integer `LineWidth`: sets the line width in pixel
+
+ """
+ pass
+
+ def SetFillColor(self, FillColor):
+ """
+ Set the FillColor - this method is overridden in the subclasses
+
+ :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ for valid values
+
+ """
+ pass
+
+ def SetFillStyle(self, FillStyle):
+ """
+ Set the FillStyle - this method is overridden in the subclasses
+
+ :param string `FillStyle`: following is a list of valid values:
+
+ ===================== =========================================
+ Style Description
+ ===================== =========================================
+ ``Transparent`` Transparent fill
+ ``Solid`` Solid fill
+ ``BiDiagonalHatch`` Bi Diagonal hatch fill
+ ``CrossDiagHatch`` Cross Diagonal hatch fill
+ ``FDiagonal_Hatch`` F Diagonal hatch fill
+ ``CrossHatch`` Cross hatch fill
+ ``HorizontalHatch`` Horizontal hatch fill
+ ``VerticalHatch`` Vertical hatch fill
+ ===================== =========================================
+
+ """
+ pass
+
+ def PutInBackground(self):
+ """Put the object in the background."""
+ if self._Canvas and self.InForeground:
+ self._Canvas._ForeDrawList.remove(self)
+ self._Canvas._DrawList.append(self)
+ self._Canvas._BackgroundDirty = True
+ self.InForeground = False
+
+ def PutInForeground(self):
+ """Put the object in the foreground."""
+ if self._Canvas and (not self.InForeground):
+ self._Canvas._ForeDrawList.append(self)
+ self._Canvas._DrawList.remove(self)
+ self._Canvas._BackgroundDirty = True
+ self.InForeground = True
+
+ def Hide(self):
+ """Hide the object."""
+ self.Visible = False
+
+ def Show(self):
+ """Show the object."""
+ self.Visible = True
+
+
+class ColorOnlyMixin:
+ """
+ Mixin class for objects that have just one color, rather than a fill
+ color and line color
+
+ """
+
+ def SetColor(self, Color):
+ """
+ Set the Color
+
+ :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ for valid values
+
+ """
+ self.SetPen(Color,"Solid",1)
+ self.SetBrush(Color,"Solid")
+
+ SetFillColor = SetColor # Just to provide a consistant interface
+
+
+class LineOnlyMixin:
+ """
+ Mixin class for objects that have just a line, rather than a fill
+ color and line color
+
+ """
+
+ def SetLineColor(self, LineColor):
+ """
+ Set the LineColor
+
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ for valid values
+
+ """
+ self.LineColor = LineColor
+ self.SetPen(LineColor,self.LineStyle,self.LineWidth)
+ SetColor = SetLineColor# so that it will do something reasonable
+
+ def SetLineStyle(self, LineStyle):
+ """
+ Set the LineStyle
+
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ for valid values
+
+ """
+ self.LineStyle = LineStyle
+ self.SetPen(self.LineColor,LineStyle,self.LineWidth)
+
+ def SetLineWidth(self, LineWidth):
+ """
+ Set the LineWidth
+
+ :param integer `LineWidth`: line width in pixels
+
+ """
+ self.LineWidth = LineWidth
+ self.SetPen(self.LineColor,self.LineStyle,LineWidth)
+
+
+class LineAndFillMixin(LineOnlyMixin):
+ """
+ Mixin class for objects that have both a line and a fill color and
+ style.
+
+ """
+ def SetFillColor(self, FillColor):
+ """
+ Set the FillColor
+
+ :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ for valid values
+
+ """
+ self.FillColor = FillColor
+ self.SetBrush(FillColor, self.FillStyle)
+
+ def SetFillStyle(self, FillStyle):
+ """
+ Set the FillStyle
+
+ :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
+ for valid values
+
+ """
+ self.FillStyle = FillStyle
+ self.SetBrush(self.FillColor,FillStyle)
+
+ def SetUpDraw(self, dc, WorldToPixel, ScaleWorldToPixel, HTdc):
+ """
+ Setup for draw
+
+ :param `dc`: the dc to draw ???
+ :param `WorldToPixel`: ???
+ :param `ScaleWorldToPixel`: ???
+ :param `HTdc`: ???
+
+ """
+ dc.SetPen(self.Pen)
+ dc.SetBrush(self.Brush)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ return ( WorldToPixel(self.XY),
+ ScaleWorldToPixel(self.WH) )
+
+
+class XYObjectMixin:
+ """
+ This is a mixin class that provides some methods suitable for use
+ with objects that have a single (x,y) coordinate pair.
+ """
+
+ def Move(self, Delta):
+ """
+ Moves the object by delta, where delta is a (dx, dy) pair.
+
+ :param `Delta`: is a (dx, dy) pair ideally a `NumPy `_
+ array of shape (2, )
+
+ """
+ Delta = N.asarray(Delta, N.float)
+ self.XY += Delta
+ self.BoundingBox += Delta
+
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+ def CalcBoundingBox(self):
+ """Calculate the bounding box."""
+ ## This may get overwritten in some subclasses
+ self.BoundingBox = BBox.asBBox((self.XY, self.XY))
+
+ def SetPoint(self, xy):
+ xy = N.array(xy, N.float)
+ xy.shape = (2,)
+
+ self.XY = xy
+ self.CalcBoundingBox()
+
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+class PointsObjectMixin:
+ """
+ A mixin class that provides some methods suitable for use
+ with objects that have a set of (x, y) coordinate pairs.
+
+ """
+
+ def Move(self, Delta):
+ """
+ Moves the object by delta, where delta is a (dx, dy) pair.
+
+ :param `Delta`: is a (dx, dy) pair ideally a `NumPy `_
+ array of shape (2, )
+
+ """
+ Delta = N.asarray(Delta, N.float)
+ Delta.shape = (2,)
+ self.Points += Delta
+ self.BoundingBox += Delta
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+ def CalcBoundingBox(self):
+ """Calculate the bounding box."""
+ self.BoundingBox = BBox.fromPoints(self.Points)
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+ def SetPoints(self, Points, copy=True):
+ """
+ Sets the coordinates of the points of the object to Points (NX2 array).
+
+ :param `Points`: takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param boolean `copy`: By default, a copy is made, if copy is set to
+ ``False``, a reference is used, if Points is a NumPy array of Floats.
+ This allows you to change some or all of the points without making
+ any copies.
+
+ For example::
+
+ Points = Object.Points
+ # shifts the points 5 in the x dir, and 10 in the y dir.
+ Points += (5, 10)
+ # Sets the points to the same array as it was
+ Object.SetPoints(Points, False)
+
+ """
+ if copy:
+ self.Points = N.array(Points, N.float)
+ self.Points.shape = (-1, 2) # Make sure it is a NX2 array, even if there is only one point
+ else:
+ self.Points = N.asarray(Points, N.float)
+ self.CalcBoundingBox()
+
+
+class Polygon(PointsObjectMixin, LineAndFillMixin, DrawObject):
+ """Draws a polygon
+
+ Points is a list of 2-tuples, or a NX2 NumPy array of
+ point coordinates. so that Points[N][0] is the x-coordinate of
+ point N and Points[N][1] is the y-coordinate or Points[N,0] is the
+ x-coordinate of point N and Points[N,1] is the y-coordinate for
+ arrays.
+
+ """
+ def __init__(self,
+ Points,
+ LineColor = "Black",
+ LineStyle = "Solid",
+ LineWidth = 1,
+ FillColor = None,
+ FillStyle = "Solid",
+ InForeground = False):
+ """Default class constructor.
+
+ :param `Points`: start point, takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+ DrawObject.__init__(self, InForeground)
+ self.Points = N.array(Points ,N.float) # this DOES need to make a copy
+ self.CalcBoundingBox()
+
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+ self.LineWidth = LineWidth
+ self.FillColor = FillColor
+ self.FillStyle = FillStyle
+
+ self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
+
+ self.SetPen(LineColor,LineStyle,LineWidth)
+ self.SetBrush(FillColor,FillStyle)
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel = None, HTdc=None):
+ Points = WorldToPixel(self.Points)#.tolist()
+ dc.SetPen(self.Pen)
+ dc.SetBrush(self.Brush)
+ dc.DrawPolygon(Points)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawPolygon(Points)
+
+class Line(PointsObjectMixin, LineOnlyMixin, DrawObject):
+ """Draws a line
+
+ It will draw a straight line if there are two points, and a polyline
+ if there are more than two.
+
+ """
+ def __init__(self, Points,
+ LineColor = "Black",
+ LineStyle = "Solid",
+ LineWidth = 1,
+ InForeground = False):
+ """Default class constructor.
+
+ :param `Points`: takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+ DrawObject.__init__(self, InForeground)
+
+
+ self.Points = N.array(Points,N.float)
+ self.CalcBoundingBox()
+
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+ self.LineWidth = LineWidth
+
+ self.SetPen(LineColor,LineStyle,LineWidth)
+
+ self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
+
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ Points = WorldToPixel(self.Points)
+ dc.SetPen(self.Pen)
+ dc.DrawLines(Points)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.DrawLines(Points)
+
+class Spline(Line):
+ """Draws a spline"""
+ def __init__(self, *args, **kwargs):
+ """Default class constructor.
+
+ see :class:`~lib.floatcanvas.FloatCanvas.Line`
+
+ """
+ Line.__init__(self, *args, **kwargs)
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ Points = WorldToPixel(self.Points)
+ dc.SetPen(self.Pen)
+ dc.DrawSpline(Points)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.DrawSpline(Points)
+
+
+class Arrow(XYObjectMixin, LineOnlyMixin, DrawObject):
+ """Draws an arrow
+
+ It will draw an arrow , starting at the point ``XY`` points at an angle
+ defined by ``Direction``.
+
+ """
+ def __init__(self,
+ XY,
+ Length,
+ Direction,
+ LineColor = "Black",
+ LineStyle = "Solid",
+ LineWidth = 2,
+ ArrowHeadSize = 8,
+ ArrowHeadAngle = 30,
+ InForeground = False):
+ """Default class constructor.
+
+ :param `XY`: the (x, y) coordinate of the starting point, or a 2-tuple,
+ or a (2,) `NumPy `_ array
+ :param integer `Length`: length of arrow in pixels
+ :param integer `Direction`: angle of arrow in degrees, zero is straight
+ up `+` angle is to the right
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param `ArrowHeadSize`: size of arrow head in pixels
+ :param `ArrowHeadAngle`: angle of arrow head in degrees
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+
+ DrawObject.__init__(self, InForeground)
+
+ self.XY = N.array(XY, N.float)
+ self.XY.shape = (2,) # Make sure it is a length 2 vector
+ self.Length = Length
+ self.Direction = float(Direction)
+ self.ArrowHeadSize = ArrowHeadSize
+ self.ArrowHeadAngle = float(ArrowHeadAngle)
+
+ self.CalcArrowPoints()
+ self.CalcBoundingBox()
+
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+ self.LineWidth = LineWidth
+
+ self.SetPen(LineColor,LineStyle,LineWidth)
+
+ ##fixme: How should the HitTest be drawn?
+ self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
+
+ def SetDirection(self, Direction):
+ """Set the direction
+
+ :param integer `Direction`: angle of arrow in degrees, zero is straight
+ up `+` angle is to the right
+
+ """
+ self.Direction = float(Direction)
+ self.CalcArrowPoints()
+
+ def SetLength(self, Length):
+ """Set the length
+
+ :param integer `Length`: length of arrow in pixels
+
+ """
+ self.Length = Length
+ self.CalcArrowPoints()
+
+ def SetLengthDirection(self, Length, Direction):
+ """Set the lenght and direction
+
+ :param integer `Length`: length of arrow in pixels
+ :param integer `Direction`: angle of arrow in degrees, zero is straight
+ up `+` angle is to the right
+
+ """
+ self.Direction = float(Direction)
+ self.Length = Length
+ self.CalcArrowPoints()
+
+## def CalcArrowPoints(self):
+## L = self.Length
+## S = self.ArrowHeadSize
+## phi = self.ArrowHeadAngle * N.pi / 360
+## theta = (self.Direction-90.0) * N.pi / 180
+## ArrowPoints = N.array( ( (0, L, L - S*N.cos(phi),L, L - S*N.cos(phi) ),
+## (0, 0, S*N.sin(phi), 0, -S*N.sin(phi) ) ),
+## N.float )
+## RotationMatrix = N.array( ( ( N.cos(theta), -N.sin(theta) ),
+## ( N.sin(theta), N.cos(theta) ) ),
+## N.float
+## )
+## ArrowPoints = N.matrixmultiply(RotationMatrix, ArrowPoints)
+## self.ArrowPoints = N.transpose(ArrowPoints)
+
+ def CalcArrowPoints(self):
+ """Calculate the arrow points."""
+ L = self.Length
+ S = self.ArrowHeadSize
+ phi = self.ArrowHeadAngle * N.pi / 360
+ theta = (270 - self.Direction) * N.pi / 180
+ AP = N.array( ( (0,0),
+ (0,0),
+ (N.cos(theta - phi), -N.sin(theta - phi) ),
+ (0,0),
+ (N.cos(theta + phi), -N.sin(theta + phi) ),
+ ), N.float )
+ AP *= S
+ shift = (-L*N.cos(theta), L*N.sin(theta) )
+ AP[1:,:] += shift
+ self.ArrowPoints = AP
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ dc.SetPen(self.Pen)
+ xy = WorldToPixel(self.XY)
+ ArrowPoints = xy + self.ArrowPoints
+ dc.DrawLines(ArrowPoints)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.DrawLines(ArrowPoints)
+
+
+class ArrowLine(PointsObjectMixin, LineOnlyMixin, DrawObject):
+ """Draws an arrow line.
+
+ It will draw a set of arrows from point to point.
+
+ It takes a list of 2-tuples, or a NX2 NumPy Float array of point coordinates.
+
+ """
+
+ def __init__(self,
+ Points,
+ LineColor = "Black",
+ LineStyle = "Solid",
+ LineWidth = 1,
+ ArrowHeadSize = 8,
+ ArrowHeadAngle = 30,
+ InForeground = False):
+ """Default class constructor.
+
+ :param `Points`: takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param `ArrowHeadSize`: size of arrow head in pixels
+ :param `ArrowHeadAngle`: angle of arrow head in degrees
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+
+ DrawObject.__init__(self, InForeground)
+
+ self.Points = N.asarray(Points,N.float)
+ self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
+ self.ArrowHeadSize = ArrowHeadSize
+ self.ArrowHeadAngle = float(ArrowHeadAngle)
+
+ self.CalcArrowPoints()
+ self.CalcBoundingBox()
+
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+ self.LineWidth = LineWidth
+
+ self.SetPen(LineColor,LineStyle,LineWidth)
+
+ self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
+
+ def CalcArrowPoints(self):
+ """Calculate the arrow points."""
+ S = self.ArrowHeadSize
+ phi = self.ArrowHeadAngle * N.pi / 360
+ Points = self.Points
+ n = Points.shape[0]
+ self.ArrowPoints = N.zeros((n-1, 3, 2), N.float)
+ for i in range(n-1):
+ dx, dy = self.Points[i] - self.Points[i+1]
+ theta = N.arctan2(dy, dx)
+ AP = N.array( (
+ (N.cos(theta - phi), -N.sin(theta-phi)),
+ (0,0),
+ (N.cos(theta + phi), -N.sin(theta + phi))
+ ),
+ N.float )
+ self.ArrowPoints[i,:,:] = AP
+ self.ArrowPoints *= S
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ Points = WorldToPixel(self.Points)
+ ArrowPoints = Points[1:,N.newaxis,:] + self.ArrowPoints
+ dc.SetPen(self.Pen)
+ dc.DrawLines(Points)
+ for arrow in ArrowPoints:
+ dc.DrawLines(arrow)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.DrawLines(Points)
+ for arrow in ArrowPoints:
+ HTdc.DrawLines(arrow)
+
+
+class PointSet(PointsObjectMixin, ColorOnlyMixin, DrawObject):
+ """Draws a set of points
+
+ If Points is a sequence of tuples: Points[N][0] is the x-coordinate of
+ point N and Points[N][1] is the y-coordinate.
+
+ If Points is a NumPy array: Points[N,0] is the x-coordinate of point
+ N and Points[N,1] is the y-coordinate for arrays.
+
+ Each point will be drawn the same color and Diameter. The Diameter
+ is in screen pixels, not world coordinates.
+
+ The hit-test code does not distingish between the points, you will
+ only know that one of the points got hit, not which one. You can use
+ PointSet.FindClosestPoint(WorldPoint) to find out which one
+
+ In the case of points, the HitLineWidth is used as diameter.
+
+ """
+ def __init__(self, Points, Color="Black", Diameter=1, InForeground=False):
+ """Default class constructor.
+
+ :param `Points`: takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param integer `Diameter`: the points diameter
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+ DrawObject.__init__(self, InForeground)
+
+ self.Points = N.array(Points,N.float)
+ self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
+ self.CalcBoundingBox()
+ self.Diameter = Diameter
+
+ self.HitLineWidth = min(self.MinHitLineWidth, Diameter)
+ self.SetColor(Color)
+
+ def SetDiameter(self, Diameter):
+ """Sets the diameter
+
+ :param integer `Diameter`: the points diameter
+
+ """
+ self.Diameter = Diameter
+
+ def FindClosestPoint(self, XY):
+ """
+
+ Returns the index of the closest point to the point, XY, given
+ in World coordinates. It's essentially random which you get if
+ there are more than one that are the same.
+
+ This can be used to figure out which point got hit in a mouse
+ binding callback, for instance. It's a lot faster that using a
+ lot of separate points.
+
+ :param `XY`: the (x,y) coordinates of the point to look for, it takes a
+ 2-tuple or (2,) numpy array in World coordinates
+
+ """
+ d = self.Points - XY
+ return N.argmin(N.hypot(d[:,0],d[:,1]))
+
+
+ def DrawD2(self, dc, Points):
+ # A Little optimization for a diameter2 - point
+ dc.DrawPointList(Points)
+ dc.DrawPointList(Points + (1,0))
+ dc.DrawPointList(Points + (0,1))
+ dc.DrawPointList(Points + (1,1))
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ dc.SetPen(self.Pen)
+ Points = WorldToPixel(self.Points)
+ if self.Diameter <= 1:
+ dc.DrawPointList(Points)
+ elif self.Diameter <= 2:
+ self.DrawD2(dc, Points)
+ else:
+ dc.SetBrush(self.Brush)
+ radius = int(round(self.Diameter/2))
+ ##fixme: I really should add a DrawCircleList to wxPython
+ if len(Points) > 100:
+ xy = Points
+ xywh = N.concatenate((xy-radius, N.ones(xy.shape) * self.Diameter ), 1 )
+ dc.DrawEllipseList(xywh)
+ else:
+ for xy in Points:
+ dc.DrawCircle(xy[0],xy[1], radius)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ if self.Diameter <= 1:
+ HTdc.DrawPointList(Points)
+ elif self.Diameter <= 2:
+ self.DrawD2(HTdc, Points)
+ else:
+ if len(Points) > 100:
+ xy = Points
+ xywh = N.concatenate((xy-radius, N.ones(xy.shape) * self.Diameter ), 1 )
+ HTdc.DrawEllipseList(xywh)
+ else:
+ for xy in Points:
+ HTdc.DrawCircle(xy[0],xy[1], radius)
+
+class Point(XYObjectMixin, ColorOnlyMixin, DrawObject):
+ """A point DrawObject
+
+ .. note::
+
+ The Bounding box is just the point, and doesn't include the Diameter.
+
+ The HitLineWidth is used as diameter for the Hit Test.
+
+ """
+ def __init__(self, XY, Color="Black", Diameter=1, InForeground=False):
+ """Default class constructor.
+
+ :param `XY`: the (x, y) coordinate of the center of the point, or a
+ 2-tuple, or a (2,) `NumPy `_ array
+ :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param integer `Diameter`: in screen points
+ :param `InForeground`: define if object is in foreground
+
+ """
+
+ DrawObject.__init__(self, InForeground)
+
+ self.XY = N.array(XY, N.float)
+ self.XY.shape = (2,) # Make sure it is a length 2 vector
+ self.CalcBoundingBox()
+ self.SetColor(Color)
+ self.Diameter = Diameter
+
+ self.HitLineWidth = self.MinHitLineWidth
+
+ def SetDiameter(self, Diameter):
+ """Set the diameter of the object.
+
+ :param integer `Diameter`: in screen points
+
+ """
+ self.Diameter = Diameter
+
+ def _Draw(self, dc, WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ dc.SetPen(self.Pen)
+ xy = WorldToPixel(self.XY)
+ if self.Diameter <= 1:
+ dc.DrawPoint(xy[0], xy[1])
+ else:
+ dc.SetBrush(self.Brush)
+ radius = int(round(self.Diameter/2))
+ dc.DrawCircle(xy[0],xy[1], radius)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ if self.Diameter <= 1:
+ HTdc.DrawPoint(xy[0], xy[1])
+ else:
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawCircle(xy[0],xy[1], radius)
+
+class SquarePoint(XYObjectMixin, ColorOnlyMixin, DrawObject):
+ """Draws a square point
+
+ The Size is in screen points, not world coordinates, so the
+ Bounding box is just the point, and doesn't include the Size.
+
+ The HitLineWidth is used as diameter for the Hit Test.
+
+ """
+ def __init__(self, Point, Color="Black", Size=4, InForeground=False):
+ """Default class constructor.
+
+ :param `Point`: takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param integer `Size`: the size of the square point
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+ DrawObject.__init__(self, InForeground)
+
+ self.XY = N.array(Point, N.float)
+ self.XY.shape = (2,) # Make sure it is a length 2 vector
+ self.CalcBoundingBox()
+ self.SetColor(Color)
+ self.Size = Size
+
+ self.HitLineWidth = self.MinHitLineWidth
+
+ def SetSize(self, Size):
+ """Sets the size
+
+ :param integer `Size`: the size of the square point
+
+ """
+ self.Size = Size
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ Size = self.Size
+ dc.SetPen(self.Pen)
+ xc,yc = WorldToPixel(self.XY)
+
+ if self.Size <= 1:
+ dc.DrawPoint(xc, yc)
+ else:
+ x = xc - Size/2.0
+ y = yc - Size/2.0
+ dc.SetBrush(self.Brush)
+ dc.DrawRectangle(x, y, Size, Size)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ if self.Size <= 1:
+ HTdc.DrawPoint(xc, xc)
+ else:
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawRectangle(x, y, Size, Size)
+
+class RectEllipse(XYObjectMixin, LineAndFillMixin, DrawObject):
+ """A RectEllipse draw object."""
+ def __init__(self, XY, WH,
+ LineColor = "Black",
+ LineStyle = "Solid",
+ LineWidth = 1,
+ FillColor = None,
+ FillStyle = "Solid",
+ InForeground = False):
+ """Default class constructor.
+
+ :param `XY`: the (x, y) coordinate of the corner of RectEllipse, or a 2-tuple,
+ or a (2,) `NumPy `_ array
+ :param `WH`: a tuple with the Width and Height for the object
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
+ :param `InForeground`: put object in foreground
+
+ """
+
+ DrawObject.__init__(self,InForeground)
+
+ self.SetShape(XY, WH)
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+ self.LineWidth = LineWidth
+ self.FillColor = FillColor
+ self.FillStyle = FillStyle
+
+ self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
+
+ # these define the behaviour when zooming makes the objects really small.
+ self.MinSize = 1
+ self.DisappearWhenSmall = True
+
+ self.SetPen(LineColor,LineStyle,LineWidth)
+ self.SetBrush(FillColor,FillStyle)
+
+ def SetShape(self, XY, WH):
+ """Set the shape of the object.
+
+ :param `XY`: takes a 2-tuple, or a (2,) `NumPy `_
+ array of point coordinates
+ :param `WH`: a tuple with the Width and Height for the object
+
+ """
+ self.XY = N.array( XY, N.float)
+ self.XY.shape = (2,)
+ self.WH = N.array( WH, N.float)
+ self.WH.shape = (2,)
+ self.CalcBoundingBox()
+
+ def CalcBoundingBox(self):
+ """Calculate the bounding box."""
+ # you need this in case Width or Height are negative
+ corners = N.array((self.XY, (self.XY + self.WH) ), N.float)
+ self.BoundingBox = BBox.fromPoints(corners)
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+
+class Rectangle(RectEllipse):
+ """Draws a rectangle see :class:`~lib.floatcanvas.FloatCanvas.RectEllipse`"""
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ ( XY, WH ) = self.SetUpDraw(dc,
+ WorldToPixel,
+ ScaleWorldToPixel,
+ HTdc)
+ WH[N.abs(WH) < self.MinSize] = self.MinSize
+ if not( self.DisappearWhenSmall and N.abs(WH).min() <= self.MinSize) : # don't try to draw it too tiny
+ dc.DrawRectangle(XY, WH)
+ if HTdc and self.HitAble:
+ HTdc.DrawRectangle(XY, WH)
+
+
+class Ellipse(RectEllipse):
+ """Draws an ellipse see :class:`~lib.floatcanvas.FloatCanvas.RectEllipse`"""
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ ( XY, WH ) = self.SetUpDraw(dc,
+ WorldToPixel,
+ ScaleWorldToPixel,
+ HTdc)
+ WH[N.abs(WH) < self.MinSize] = self.MinSize
+ if not( self.DisappearWhenSmall and N.abs(WH).min() <= self.MinSize) : # don't try to draw it too tiny
+ dc.DrawEllipse(XY, WH)
+ if HTdc and self.HitAble:
+ HTdc.DrawEllipse(XY, WH)
+
+class Circle(XYObjectMixin, LineAndFillMixin, DrawObject):
+ """Draws a circle"""
+ def __init__(self, XY, Diameter,
+ LineColor = "Black",
+ LineStyle = "Solid",
+ LineWidth = 1,
+ FillColor = None,
+ FillStyle = "Solid",
+ InForeground = False):
+ """Default class constructor.
+
+ :param `XY`: the (x, y) coordinate of the center of the circle, or a 2-tuple,
+ or a (2,) `NumPy `_ array
+ :param integer `Diameter`: the diameter for the object
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+ DrawObject.__init__(self, InForeground)
+
+ self.XY = N.array(XY, N.float)
+ self.WH = N.array((Diameter/2, Diameter/2), N.float) # just to keep it compatible with others
+ self.CalcBoundingBox()
+
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+ self.LineWidth = LineWidth
+ self.FillColor = FillColor
+ self.FillStyle = FillStyle
+
+ self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
+
+ # these define the behaviour when zooming makes the objects really small.
+ self.MinSize = 1
+ self.DisappearWhenSmall = True
+
+ self.SetPen(LineColor,LineStyle,LineWidth)
+ self.SetBrush(FillColor,FillStyle)
+
+ def SetDiameter(self, Diameter):
+ """Set the diameter of the object
+
+ :param integer `Diameter`: the diameter for the object
+
+ """
+ self.WH = N.array((Diameter/2, Diameter/2), N.float) # just to keep it compatible with others
+
+ def CalcBoundingBox(self):
+ """Calculate the bounding box of the object."""
+ # you need this in case Width or Height are negative
+ self.BoundingBox = BBox.fromPoints( (self.XY+self.WH, self.XY-self.WH) )
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ ( XY, WH ) = self.SetUpDraw(dc,
+ WorldToPixel,
+ ScaleWorldToPixel,
+ HTdc)
+
+ WH[N.abs(WH) < self.MinSize] = self.MinSize
+ if not( self.DisappearWhenSmall and N.abs(WH).min() <= self.MinSize) : # don't try to draw it too tiny
+ dc.DrawCircle(XY, WH[0])
+ if HTdc and self.HitAble:
+ HTdc.DrawCircle(XY, WH[0])
+
+
+class TextObjectMixin(XYObjectMixin):
+ """
+
+ A mix in class that holds attributes and methods that are needed by
+ the Text objects
+
+ """
+
+ ## I'm caching fonts, because on GTK, getting a new font can take a
+ ## while. However, it gets cleared after every full draw as hanging
+ ## on to a bunch of large fonts takes a massive amount of memory.
+
+ FontList = {}
+
+ LayoutFontSize = 16 # font size used for calculating layout
+
+ def SetFont(self, Size, Family, Style, Weight, Underlined, FaceName):
+ self.Font = self.FontList.setdefault( (Size,
+ Family,
+ Style,
+ Weight,
+ Underlined,
+ FaceName),
+ #wx.FontFromPixelSize((0.45*Size,Size), # this seemed to give a decent height/width ratio on Windows
+ wx.Font(Size,
+ Family,
+ Style,
+ Weight,
+ Underlined,
+ FaceName) )
+
+ def SetColor(self, Color):
+ self.Color = Color
+
+ def SetBackgroundColor(self, BackgroundColor):
+ self.BackgroundColor = BackgroundColor
+
+ def SetText(self, String):
+ """
+ Re-sets the text displayed by the object
+
+ In the case of the ScaledTextBox, it will re-do the layout as appropriate
+
+ Note: only tested with the ScaledTextBox
+
+ """
+
+ self.String = String
+ self.LayoutText()
+
+ def LayoutText(self):
+ """
+ A dummy method to re-do the layout of the text.
+
+ A derived object needs to override this if required.
+
+ """
+ pass
+
+ ## store the function that shift the coords for drawing text. The
+ ## "c" parameter is the correction for world coordinates, rather
+ ## than pixel coords as the y axis is reversed
+ ## pad is the extra space around the text
+ ## if world = 1, the vertical shift is done in y-up coordinates
+ ShiftFunDict = {'tl': lambda x, y, w, h, world=0, pad=0: (x + pad, y + pad - 2*world*pad),
+ 'tc': lambda x, y, w, h, world=0, pad=0: (x - w/2, y + pad - 2*world*pad),
+ 'tr': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y + pad - 2*world*pad),
+ 'cl': lambda x, y, w, h, world=0, pad=0: (x + pad, y - h/2 + world*h),
+ 'cc': lambda x, y, w, h, world=0, pad=0: (x - w/2, y - h/2 + world*h),
+ 'cr': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y - h/2 + world*h),
+ 'bl': lambda x, y, w, h, world=0, pad=0: (x + pad, y - h + 2*world*h - pad + world*2*pad) ,
+ 'bc': lambda x, y, w, h, world=0, pad=0: (x - w/2, y - h + 2*world*h - pad + world*2*pad) ,
+ 'br': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y - h + 2*world*h - pad + world*2*pad)}
+
+class Text(TextObjectMixin, DrawObject):
+ """Draws a text object
+
+ The size is fixed, and does not scale with the drawing.
+
+ The hit-test is done on the entire text extent
+
+ """
+
+ def __init__(self, String, xy,
+ Size = 14,
+ Color = "Black",
+ BackgroundColor = None,
+ Family = wx.FONTFAMILY_MODERN,
+ Style = wx.FONTSTYLE_NORMAL,
+ Weight = wx.FONTWEIGHT_NORMAL,
+ Underlined = False,
+ Position = 'tl',
+ InForeground = False,
+ Font = None):
+ """Default class constructor.
+
+ :param string `string`: the text to draw
+ :param `XY`: the (x, y) coordinate of the corner of the text, or a 2-tuple,
+ or a (2,) `NumPy `_ array
+ :param `Size`: the font size
+ :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `BackgroundColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param FontFamily `Family`: a valid :ref:`FontFamily`
+ :param FontStyle `Style`: a valid :ref:`FontStyle`
+ :param FontWeight `Weight`: a valid :ref:`FontWeight`
+ :param boolean `Underlined`: underline the text
+ :param string `Position`: a two character string indicating where in
+ relation to the coordinates the box should be oriented
+ :param boolean `InForeground`: should object be in foreground
+ :param Font `Font`: alternatively you can define :ref:`Font` and the
+ above will be ignored.
+
+ ============== ==========================
+ 1st character Meaning
+ ============== ==========================
+ ``t`` top
+ ``c`` center
+ ``b`` bottom
+ ============== ==========================
+
+ ============== ==========================
+ 2nd character Meaning
+ ============== ==========================
+ ``l`` left
+ ``c`` center
+ ``r`` right
+ ============== ==========================
+
+ :param Font `Font`: a valid :class:`Font`
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+ DrawObject.__init__(self,InForeground)
+
+ self.String = String
+ # Input size in in Pixels, compute points size from FontScaleinfo.
+ # fixme: for printing, we'll have to do something a little different
+ self.Size = Size * FontScale
+
+ self.Color = Color
+ self.BackgroundColor = BackgroundColor
+
+ if not Font:
+ FaceName = ''
+ else:
+ FaceName = Font.GetFaceName()
+ Family = Font.GetFamily()
+ Size = Font.GetPointSize()
+ Style = Font.GetStyle()
+ Underlined = Font.GetUnderlined()
+ Weight = Font.GetWeight()
+ self.SetFont(Size, Family, Style, Weight, Underlined, FaceName)
+
+ self.BoundingBox = BBox.asBBox((xy, xy))
+
+ self.XY = N.asarray(xy)
+ self.XY.shape = (2,)
+
+ (self.TextWidth, self.TextHeight) = (None, None)
+ self.ShiftFun = self.ShiftFunDict[Position]
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ XY = WorldToPixel(self.XY)
+ dc.SetFont(self.Font)
+ dc.SetTextForeground(self.Color)
+ if self.BackgroundColor:
+ dc.SetBackgroundMode(wx.SOLID)
+ dc.SetTextBackground(self.BackgroundColor)
+ else:
+ dc.SetBackgroundMode(wx.TRANSPARENT)
+ if self.TextWidth is None or self.TextHeight is None:
+ (self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String)
+ XY = self.ShiftFun(XY[0], XY[1], self.TextWidth, self.TextHeight)
+ dc.DrawText(self.String, XY)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawRectangle(XY, (self.TextWidth, self.TextHeight) )
+
+class ScaledText(TextObjectMixin, DrawObject):
+ """
+ ##fixme: this can be depricated and jsut use ScaledTextBox with different defaults.
+
+ This class creates a text object that is scaled when zoomed. It is
+ placed at the coordinates, x,y. the "Position" argument is a two
+ charactor string, indicating where in relation to the coordinates
+ the string should be oriented.
+
+ The first letter is: t, c, or b, for top, center and bottom The
+ second letter is: l, c, or r, for left, center and right The
+ position refers to the position relative to the text itself. It
+ defaults to "tl" (top left).
+
+ Size is the size of the font in world coordinates.
+
+ * Family: Font family, a generic way of referring to fonts without
+ specifying actual facename. One of:
+
+ * wx.FONTFAMILY_DEFAULT: Chooses a default font.
+ * wx.FONTFAMILY_DECORATIVE: A decorative font.
+ * wx.FONTFAMILY_ROMAN: A formal, serif font.
+ * wx.FONTFAMILY_SCRIPT: A handwriting font.
+ * wx.FONTFAMILY_SWISS: A sans-serif font.
+ * wx.FONTFAMILY_MODERN: A fixed pitch font.
+
+ .. note:: these are only as good as the wxWindows defaults, which aren't so good.
+
+ * Style: One of wx.FONTSTYLE_NORMAL, wx.FONTSTYLE_SLANT and wx.FONTSTYLE_ITALIC.
+ * Weight: One of wx.FONTWEIGHT_NORMAL, wx.FONTWEIGHT_LIGHT and wx.FONTWEIGHT_BOLD.
+ * Underlined: The value can be True or False. At present this may have an an
+ effect on Windows only.
+
+
+ Alternatively, you can set the kw arg: Font, to a wx.Font, and the
+ above will be ignored. The size of the font you specify will be
+ ignored, but the rest of its attributes will be preserved.
+
+ The size will scale as the drawing is zoomed.
+
+ Bugs/Limitations:
+
+ As fonts are scaled, the do end up a little different, so you don't
+ get exactly the same picture as you scale up and doen, but it's
+ pretty darn close.
+
+ On wxGTK1 on my Linux system, at least, using a font of over about
+ 3000 pts. brings the system to a halt. It's the Font Server using
+ huge amounts of memory. My work around is to max the font size to
+ 3000 points, so it won't scale past there. GTK2 uses smarter font
+ drawing, so that may not be an issue in future versions, so feel
+ free to test. Another smarter way to do it would be to set a global
+ zoom limit at that point.
+
+ The hit-test is done on the entire text extent. This could be made
+ optional, but I haven't gotten around to it.
+
+ """
+
+ def __init__(self,
+ String,
+ XY,
+ Size,
+ Color = "Black",
+ BackgroundColor = None,
+ Family = wx.FONTFAMILY_MODERN,
+ Style = wx.FONTSTYLE_NORMAL,
+ Weight = wx.FONTWEIGHT_NORMAL,
+ Underlined = False,
+ Position = 'tl',
+ Font = None,
+ InForeground = False):
+
+ DrawObject.__init__(self,InForeground)
+
+ self.String = String
+ self.XY = N.array( XY, N.float)
+ self.XY.shape = (2,)
+ self.Size = Size
+ self.Color = Color
+ self.BackgroundColor = BackgroundColor
+ self.Family = Family
+ self.Style = Style
+ self.Weight = Weight
+ self.Underlined = Underlined
+ if not Font:
+ self.FaceName = ''
+ else:
+ self.FaceName = Font.GetFaceName()
+ self.Family = Font.GetFamily()
+ self.Style = Font.GetStyle()
+ self.Underlined = Font.GetUnderlined()
+ self.Weight = Font.GetWeight()
+
+ # Experimental max font size value on wxGTK2: this works OK on
+ # my system. If it's a lot larger, there is a crash, with the
+ # message:
+ #
+ # The application 'FloatCanvasDemo.py' lost its
+ # connection to the display :0.0; most likely the X server was
+ # shut down or you killed/destroyed the application.
+ #
+ # Windows and OS-X seem to be better behaved in this regard.
+ # They may not draw it, but they don't crash either!
+ self.MaxFontSize = 1000
+ self.MinFontSize = 1 # this can be changed to set a minimum size
+ self.DisappearWhenSmall = True
+ self.ShiftFun = self.ShiftFunDict[Position]
+
+ self.CalcBoundingBox()
+
+ def LayoutText(self):
+ # This will be called when the text is re-set
+ # nothing much to be done here
+ self.CalcBoundingBox()
+
+ def CalcBoundingBox(self):
+ ## this isn't exact, as fonts don't scale exactly.
+ dc = wx.MemoryDC()
+ bitmap = wx.Bitmap(1, 1)
+ dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
+ DrawingSize = 40 # pts This effectively determines the resolution that the BB is computed to.
+ ScaleFactor = float(self.Size) / DrawingSize
+ self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
+ dc.SetFont(self.Font)
+ (w,h) = dc.GetTextExtent(self.String)
+ w = w * ScaleFactor
+ h = h * ScaleFactor
+ x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world = 1)
+ self.BoundingBox = BBox.asBBox(((x, y-h ),(x + w, y)))
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ (X,Y) = WorldToPixel( (self.XY) )
+
+ # compute the font size:
+ Size = abs( ScaleWorldToPixel( (self.Size, self.Size) )[1] ) # only need a y coordinate length
+ ## Check to see if the font size is large enough to blow up the X font server
+ ## If so, limit it. Would it be better just to not draw it?
+ ## note that this limit is dependent on how much memory you have, etc.
+ Size = min(Size, self.MaxFontSize)
+ Size = max(Size, self.MinFontSize) # smallest size you want - default to 0
+
+ # Draw the Text
+ if not( self.DisappearWhenSmall and Size <= self.MinFontSize) : # don't try to draw a zero sized font!
+ self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
+ dc.SetFont(self.Font)
+ dc.SetTextForeground(self.Color)
+ if self.BackgroundColor:
+ dc.SetBackgroundMode(wx.SOLID)
+ dc.SetTextBackground(self.BackgroundColor)
+ else:
+ dc.SetBackgroundMode(wx.TRANSPARENT)
+ (w,h) = dc.GetTextExtent(self.String)
+ # compute the shift, and adjust the coordinates, if neccesary
+ # This had to be put in here, because it changes with Zoom, as
+ # fonts don't scale exactly.
+ xy = self.ShiftFun(X, Y, w, h)
+ dc.DrawText(self.String, xy)
+
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawRectangle(xy, (w, h))
+
+class ScaledTextBox(TextObjectMixin, DrawObject):
+ """Draws a text object
+
+ The object is scaled when zoomed.
+
+ The hit-test is done on the entire text extent
+
+ Bugs/Limitations:
+
+ As fonts are scaled, they do end up a little different, so you don't
+ get exactly the same picture as you scale up and down, but it's
+ pretty darn close.
+
+ On wxGTK1 on my Linux system, at least, using a font of over about
+ 1000 pts. brings the system to a halt. It's the Font Server using
+ huge amounts of memory. My work around is to max the font size to
+ 1000 points, so it won't scale past there. GTK2 uses smarter font
+ drawing, so that may not be an issue in future versions, so feel
+ free to test. Another smarter way to do it would be to set a global
+ zoom limit at that point.
+
+ """
+
+ def __init__(self, String,
+ Point,
+ Size,
+ Color = "Black",
+ BackgroundColor = None,
+ LineColor = 'Black',
+ LineStyle = 'Solid',
+ LineWidth = 1,
+ Width = None,
+ PadSize = None,
+ Family = wx.FONTFAMILY_MODERN,
+ Style = wx.FONTSTYLE_NORMAL,
+ Weight = wx.FONTWEIGHT_NORMAL,
+ Underlined = False,
+ Position = 'tl',
+ Alignment = "left",
+ Font = None,
+ LineSpacing = 1.0,
+ InForeground = False):
+ """Default class constructor.
+
+ :param `Point`: takes a 2-tuple, or a (2,) `NumPy `_
+ array of point coordinates
+ :param integer `Size`: size in World units
+ :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `BackgroundColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param `Width`: width in pixels or ``None``, text will be wrapped to
+ the given width.
+ :param `PadSize`: padding in world units or ``None``, if specified it
+ will creating a space (margin) around the text
+ :param FontFamily `Family`: a valid :ref:`FontFamily`
+ :param FontStyle `Style`: a valid :ref:`FontStyle`
+ :param FontWeight `Weight`: a valid :ref:`FontWeight`
+ :param boolean `Underlined`: underline the text
+ :param string `Position`: a two character string indicating where in
+ relation to the coordinates the box should be oriented
+
+ ============== ==========================
+ 1st character Meaning
+ ============== ==========================
+ ``t`` top
+ ``c`` center
+ ``b`` bottom
+ ============== ==========================
+
+ ============== ==========================
+ 2nd character Meaning
+ ============== ==========================
+ ``l`` left
+ ``c`` center
+ ``r`` right
+ ============== ==========================
+
+ :param `Alignment`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param Font `Font`: alternatively a valid :class:`Font` can be defined
+ in which case the above will be ignored
+ :param float `LineSpacing`: the line space to be used
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+ DrawObject.__init__(self,InForeground)
+
+ self.XY = N.array(Point, N.float)
+ self.Size = Size
+ self.Color = Color
+ self.BackgroundColor = BackgroundColor
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+ self.LineWidth = LineWidth
+ self.Width = Width
+ if PadSize is None: # the default is just a little bit of padding
+ self.PadSize = Size/10.0
+ else:
+ self.PadSize = float(PadSize)
+ self.Family = Family
+ self.Style = Style
+ self.Weight = Weight
+ self.Underlined = Underlined
+ self.Alignment = Alignment.lower()
+ self.LineSpacing = float(LineSpacing)
+ self.Position = Position
+
+ if not Font:
+ self.FaceName = ''
+ else:
+ self.FaceName = Font.GetFaceName()
+ self.Family = Font.GetFamily()
+ self.Style = Font.GetStyle()
+ self.Underlined = Font.GetUnderlined()
+ self.Weight = Font.GetWeight()
+
+ # Experimental max font size value on wxGTK2: this works OK on
+ # my system. If it's a lot larger, there is a crash, with the
+ # message:
+ #
+ # The application 'FloatCanvasDemo.py' lost its
+ # connection to the display :0.0; most likely the X server was
+ # shut down or you killed/destroyed the application.
+ #
+ # Windows and OS-X seem to be better behaved in this regard.
+ # They may not draw it, but they don't crash either!
+
+ self.MaxFontSize = 1000
+ self.MinFontSize = 1 # this can be changed to set a larger minimum size
+ self.DisappearWhenSmall = True
+
+ self.ShiftFun = self.ShiftFunDict[Position]
+
+ self.String = String
+ self.LayoutText()
+ self.CalcBoundingBox()
+
+ self.SetPen(LineColor,LineStyle,LineWidth)
+ self.SetBrush(BackgroundColor, "Solid")
+
+
+ def WrapToWidth(self):
+ dc = wx.MemoryDC()
+ bitmap = wx.Bitmap(1, 1)
+ dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
+ DrawingSize = self.LayoutFontSize # pts This effectively determines the resolution that the BB is computed to.
+ ScaleFactor = float(self.Size) / DrawingSize
+ Width = (self.Width - 2*self.PadSize) / ScaleFactor #Width to wrap to
+ self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
+ dc.SetFont(self.Font)
+ NewStrings = []
+ for s in self.Strings:
+ #beginning = True
+ text = s.split(" ")
+ text.reverse()
+ LineLength = 0
+ NewText = text[-1]
+ del text[-1]
+ while text:
+ w = dc.GetTextExtent(' ' + text[-1])[0]
+ if LineLength + w <= Width:
+ NewText += ' '
+ NewText += text[-1]
+ LineLength = dc.GetTextExtent(NewText)[0]
+ else:
+ NewStrings.append(NewText)
+ NewText = text[-1]
+ LineLength = dc.GetTextExtent(text[-1])[0]
+ del text[-1]
+ NewStrings.append(NewText)
+ self.Strings = NewStrings
+
+ def ReWrap(self, Width):
+ self.Width = Width
+ self.LayoutText()
+
+ def LayoutText(self):
+ """
+
+ Calculates the positions of the words of text.
+
+ This isn't exact, as fonts don't scale exactly.
+ To help this, the position of each individual word
+ is stored separately, so that the general layout stays
+ the same in world coordinates, as the fonts scale.
+
+ """
+ self.Strings = self.String.split("\n")
+ if self.Width:
+ self.WrapToWidth()
+
+ dc = wx.MemoryDC()
+ bitmap = wx.Bitmap(1, 1)
+ dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
+
+ DrawingSize = self.LayoutFontSize # pts This effectively determines the resolution that the BB is computed to.
+ ScaleFactor = float(self.Size) / DrawingSize
+
+ self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
+ dc.SetFont(self.Font)
+ TextHeight = dc.GetTextExtent("X")[1]
+ SpaceWidth = dc.GetTextExtent(" ")[0]
+ LineHeight = TextHeight * self.LineSpacing
+
+ LineWidths = N.zeros((len(self.Strings),), N.float)
+ y = 0
+ Words = []
+ AllLinePoints = []
+
+ for i, s in enumerate(self.Strings):
+ LineWidths[i] = 0
+ LineWords = s.split(" ")
+ LinePoints = N.zeros((len(LineWords),2), N.float)
+ for j, word in enumerate(LineWords):
+ if j > 0:
+ LineWidths[i] += SpaceWidth
+ Words.append(word)
+ LinePoints[j] = (LineWidths[i], y)
+ w = dc.GetTextExtent(word)[0]
+ LineWidths[i] += w
+ y -= LineHeight
+ AllLinePoints.append(LinePoints)
+ TextWidth = N.maximum.reduce(LineWidths)
+ self.Words = Words
+
+ if self.Width is None:
+ BoxWidth = TextWidth * ScaleFactor + 2*self.PadSize
+ else: # use the defined Width
+ BoxWidth = self.Width
+ Points = N.zeros((0,2), N.float)
+
+ for i, LinePoints in enumerate(AllLinePoints):
+ ## Scale to World Coords.
+ LinePoints *= (ScaleFactor, ScaleFactor)
+ if self.Alignment == 'left':
+ LinePoints[:,0] += self.PadSize
+ elif self.Alignment == 'center':
+ LinePoints[:,0] += (BoxWidth - LineWidths[i]*ScaleFactor)/2.0
+ elif self.Alignment == 'right':
+ LinePoints[:,0] += (BoxWidth - LineWidths[i]*ScaleFactor-self.PadSize)
+ Points = N.concatenate((Points, LinePoints))
+
+ BoxHeight = -(Points[-1,1] - (TextHeight * ScaleFactor)) + 2*self.PadSize
+ #(x,y) = self.ShiftFun(self.XY[0], self.XY[1], BoxWidth, BoxHeight, world=1)
+ Points += (0, -self.PadSize)
+ self.Points = Points
+ self.BoxWidth = BoxWidth
+ self.BoxHeight = BoxHeight
+ self.CalcBoundingBox()
+
+ def CalcBoundingBox(self):
+
+ """
+
+ Calculates the Bounding Box
+
+ """
+
+ w, h = self.BoxWidth, self.BoxHeight
+ x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world=1)
+ self.BoundingBox = BBox.asBBox(((x, y-h ),(x + w, y)))
+
+ def GetBoxRect(self):
+ wh = (self.BoxWidth, self.BoxHeight)
+ xy = (self.BoundingBox[0,0], self.BoundingBox[1,1])
+
+ return (xy, wh)
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ xy, wh = self.GetBoxRect()
+
+ Points = self.Points + xy
+ Points = WorldToPixel(Points)
+ xy = WorldToPixel(xy)
+ wh = ScaleWorldToPixel(wh) * (1,-1)
+
+ # compute the font size:
+ Size = abs( ScaleWorldToPixel( (self.Size, self.Size) )[1] ) # only need a y coordinate length
+ ## Check to see if the font size is large enough to blow up the X font server
+ ## If so, limit it. Would it be better just to not draw it?
+ ## note that this limit is dependent on how much memory you have, etc.
+ Size = min(Size, self.MaxFontSize)
+
+ Size = max(Size, self.MinFontSize) # smallest size you want - default to 1
+
+ # Draw The Box
+ if (self.LineStyle and self.LineColor) or self.BackgroundColor:
+ dc.SetBrush(self.Brush)
+ dc.SetPen(self.Pen)
+ dc.DrawRectangle(xy , wh)
+
+ # Draw the Text
+ if not( self.DisappearWhenSmall and Size <= self.MinFontSize) : # don't try to draw a zero sized font!
+ self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
+ dc.SetFont(self.Font)
+ dc.SetTextForeground(self.Color)
+ dc.SetBackgroundMode(wx.TRANSPARENT)
+ dc.DrawTextList(self.Words, Points)
+
+ # Draw the hit box.
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawRectangle(xy, wh)
+
+class Bitmap(TextObjectMixin, DrawObject):
+ """Draws a bitmap
+
+ The size is fixed, and does not scale with the drawing.
+
+ """
+
+ def __init__(self, Bitmap, XY,
+ Position='tl',
+ InForeground=False):
+ """Default class constructor.
+
+ :param Bitmap `Bitmap`: the bitmap to be drawn
+ :param `XY`: the (x, y) coordinate of the corner of the bitmap, or a 2-tuple,
+ or a (2,) `NumPy `_ array
+ :param string `Position`: a two character string indicating where in relation to the coordinates
+ the bitmap should be oriented
+
+ ============== ==========================
+ 1st character Meaning
+ ============== ==========================
+ ``t`` top
+ ``c`` center
+ ``b`` bottom
+ ============== ==========================
+
+ ============== ==========================
+ 2nd character Meaning
+ ============== ==========================
+ ``l`` left
+ ``c`` center
+ ``r`` right
+ ============== ==========================
+
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+
+ DrawObject.__init__(self,InForeground)
+
+ if type(Bitmap) == wx.Bitmap:
+ self.Bitmap = Bitmap
+ elif type(Bitmap) == wx.Image:
+ self.Bitmap = wx.Bitmap(Bitmap)
+
+ # Note the BB is just the point, as the size in World coordinates is not fixed
+ self.BoundingBox = BBox.asBBox( (XY,XY) )
+
+ self.XY = XY
+
+ (self.Width, self.Height) = self.Bitmap.GetWidth(), self.Bitmap.GetHeight()
+ self.ShiftFun = self.ShiftFunDict[Position]
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ XY = WorldToPixel(self.XY)
+ XY = self.ShiftFun(XY[0], XY[1], self.Width, self.Height)
+ dc.DrawBitmap(self.Bitmap, XY, True)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawRectangle(XY, (self.Width, self.Height) )
+
+class ScaledBitmap(TextObjectMixin, DrawObject):
+ """Draws a scaled bitmap
+
+ The size scales with the drawing
+
+ """
+
+ def __init__(self,
+ Bitmap,
+ XY,
+ Height,
+ Position = 'tl',
+ InForeground = False):
+ """Default class constructor.
+
+ :param wx.Bitmap `Bitmap`: the bitmap to be drawn
+ :param `XY`: the (x, y) coordinate of the corner of the scaled bitmap,
+ or a 2-tuple, or a (2,) `NumPy `_ array
+ :param `Height`: height to be used, width is calculated from the aspect ratio of the bitmap
+ :param string `Position`: a two character string indicating where in relation to the coordinates
+ the bitmap should be oriented
+
+ ============== ==========================
+ 1st character Meaning
+ ============== ==========================
+ ``t`` top
+ ``c`` center
+ ``b`` bottom
+ ============== ==========================
+
+ ============== ==========================
+ 2nd character Meaning
+ ============== ==========================
+ ``l`` left
+ ``c`` center
+ ``r`` right
+ ============== ==========================
+
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+
+ DrawObject.__init__(self,InForeground)
+
+ if type(Bitmap) == wx.Bitmap:
+ self.Image = Bitmap.ConvertToImage()
+ elif type(Bitmap) == wx.Image:
+ self.Image = Bitmap
+
+ self.XY = XY
+ self.Height = Height
+ (self.bmpWidth, self.bmpHeight) = self.Image.GetWidth(), self.Image.GetHeight()
+ self.Width = self.bmpWidth / self.bmpHeight * Height
+ self.ShiftFun = self.ShiftFunDict[Position]
+ self.CalcBoundingBox()
+ self.ScaledBitmap = None
+ self.ScaledHeight = None
+
+ def CalcBoundingBox(self):
+ """Calculate the bounding box."""
+ ## this isn't exact, as fonts don't scale exactly.
+ w, h = self.Width, self.Height
+ x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world = 1)
+ self.BoundingBox = BBox.asBBox( ( (x, y-h ), (x + w, y) ) )
+
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ XY = WorldToPixel(self.XY)
+ H = ScaleWorldToPixel(self.Height)[0]
+ W = H * (self.bmpWidth / self.bmpHeight)
+ if (self.ScaledBitmap is None) or (H != self.ScaledHeight) :
+ self.ScaledHeight = H
+ Img = self.Image.Scale(W, H)
+ self.ScaledBitmap = wx.Bitmap(Img)
+
+ XY = self.ShiftFun(XY[0], XY[1], W, H)
+ dc.DrawBitmap(self.ScaledBitmap, XY, True)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawRectangle(XY, (W, H) )
+
+class ScaledBitmap2(TextObjectMixin, DrawObject, ):
+ """Draws a scaled bitmap
+
+ An alternative scaled bitmap that only scaled the required amount of
+ the main bitmap when zoomed in: EXPERIMENTAL!
+
+ """
+
+ def __init__(self,
+ Bitmap,
+ XY,
+ Height,
+ Width=None,
+ Position = 'tl',
+ InForeground = False):
+ """Default class constructor.
+
+ :param wx.Bitmap `Bitmap`: the bitmap to be drawn
+ :param `XY`: the (x, y) coordinate of the corner of the scaled bitmap,
+ or a 2-tuple, or a (2,) `NumPy `_ array
+ :param `Height`: height to be used
+ :param `Width`: width to be used, if ``None`` width is calculated from the aspect ratio of the bitmap
+ :param string `Position`: a two character string indicating where in relation to the coordinates
+ the bitmap should be oriented
+
+ ============== ==========================
+ 1st character Meaning
+ ============== ==========================
+ ``t`` top
+ ``c`` center
+ ``b`` bottom
+ ============== ==========================
+
+ ============== ==========================
+ 2nd character Meaning
+ ============== ==========================
+ ``l`` left
+ ``c`` center
+ ``r`` right
+ ============== ==========================
+
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+
+ DrawObject.__init__(self,InForeground)
+
+ if type(Bitmap) == wx.Bitmap:
+ self.Image = Bitmap.ConvertToImage()
+ elif type(Bitmap) == wx.Image:
+ self.Image = Bitmap
+
+ self.XY = N.array(XY, N.float)
+ self.Height = Height
+ (self.bmpWidth, self.bmpHeight) = self.Image.GetWidth(), self.Image.GetHeight()
+ self.bmpWH = N.array((self.bmpWidth, self.bmpHeight), N.int32)
+ ## fixme: this should all accommodate different scales for X and Y
+ if Width is None:
+ self.BmpScale = float(self.bmpHeight) / Height
+ self.Width = self.bmpWidth / self.BmpScale
+ self.WH = N.array((self.Width, Height), N.float)
+ ##fixme: should this have a y = -1 to shift to y-up?
+ self.BmpScale = self.bmpWH / self.WH
+
+ #print("bmpWH:", self.bmpWH)
+ #print("Width, Height:", self.WH)
+ #print("self.BmpScale", self.BmpScale)
+ self.ShiftFun = self.ShiftFunDict[Position]
+ self.CalcBoundingBox()
+ self.ScaledBitmap = None # cache of the last existing scaled bitmap
+
+ def CalcBoundingBox(self):
+ """Calculate the bounding box."""
+ ## this isn't exact, as fonts don't scale exactly.
+ w,h = self.Width, self.Height
+ x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world = 1)
+ self.BoundingBox = BBox.asBBox( ((x, y-h ), (x + w, y)) )
+
+ def WorldToBitmap(self, Pw):
+ """Computes the bitmap coords from World coords."""
+ delta = Pw - self.XY
+ Pb = delta * self.BmpScale
+ Pb *= (1, -1) ##fixme: this may only works for Yup projection!
+ ## and may only work for top left position
+
+ return Pb.astype(N.int_)
+
+ def _DrawEntireBitmap(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc):
+ """
+ this is pretty much the old code
+
+ Scales and Draws the entire bitmap.
+
+ """
+ XY = WorldToPixel(self.XY)
+ H = ScaleWorldToPixel(self.Height)[0]
+ W = H * (self.bmpWidth / self.bmpHeight)
+ if (self.ScaledBitmap is None) or (self.ScaledBitmap[0] != (0, 0, self.bmpWidth, self.bmpHeight, W, H) ):
+ #if True: #fixme: (self.ScaledBitmap is None) or (H != self.ScaledHeight) :
+ self.ScaledHeight = H
+ #print("Scaling to:", W, H)
+ Img = self.Image.Scale(W, H)
+ bmp = wx.Bitmap(Img)
+ self.ScaledBitmap = ((0, 0, self.bmpWidth, self.bmpHeight , W, H), bmp)# this defines the cached bitmap
+ else:
+ #print("Using Cached bitmap")
+ bmp = self.ScaledBitmap[1]
+ XY = self.ShiftFun(XY[0], XY[1], W, H)
+ dc.DrawBitmap(bmp, XY, True)
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawRectangle(XY, (W, H) )
+
+ def _DrawSubBitmap(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc):
+ """
+ Subsets just the part of the bitmap that is visible
+ then scales and draws that.
+
+ """
+ BBworld = BBox.asBBox(self._Canvas.ViewPortBB)
+ BBbitmap = BBox.fromPoints(self.WorldToBitmap(BBworld))
+
+ XYs = WorldToPixel(self.XY)
+ # figure out subimage:
+ # fixme: this should be able to be done more succinctly!
+
+ if BBbitmap[0,0] < 0:
+ Xb = 0
+ elif BBbitmap[0,0] > self.bmpWH[0]: # off the bitmap
+ Xb = 0
+ else:
+ Xb = BBbitmap[0,0]
+ XYs[0] = 0 # draw at origin
+
+ if BBbitmap[0,1] < 0:
+ Yb = 0
+ elif BBbitmap[0,1] > self.bmpWH[1]: # off the bitmap
+ Yb = 0
+ ShouldDraw = False
+ else:
+ Yb = BBbitmap[0,1]
+ XYs[1] = 0 # draw at origin
+
+ if BBbitmap[1,0] < 0:
+ #off the screen -- This should never happen!
+ Wb = 0
+ elif BBbitmap[1,0] > self.bmpWH[0]:
+ Wb = self.bmpWH[0] - Xb
+ else:
+ Wb = BBbitmap[1,0] - Xb
+
+ if BBbitmap[1,1] < 0:
+ # off the screen -- This should never happen!
+ Hb = 0
+ ShouldDraw = False
+ elif BBbitmap[1,1] > self.bmpWH[1]:
+ Hb = self.bmpWH[1] - Yb
+ else:
+ Hb = BBbitmap[1,1] - Yb
+
+ FullHeight = ScaleWorldToPixel(self.Height)[0]
+ scale = FullHeight / self.bmpWH[1]
+ Ws = int(scale * Wb + 0.5) # add the 0.5 to round
+ Hs = int(scale * Hb + 0.5)
+ if (self.ScaledBitmap is None) or (self.ScaledBitmap[0] != (Xb, Yb, Wb, Hb, Ws, Ws) ):
+ Img = self.Image.GetSubImage(wx.Rect(Xb, Yb, Wb, Hb))
+ print("rescaling with High quality")
+ Img.Rescale(Ws, Hs, quality=wx.IMAGE_QUALITY_HIGH)
+ bmp = wx.Bitmap(Img)
+ self.ScaledBitmap = ((Xb, Yb, Wb, Hb, Ws, Ws), bmp)# this defines the cached bitmap
+ #XY = self.ShiftFun(XY[0], XY[1], W, H)
+ #fixme: get the shiftfun working!
+ else:
+ #print("Using cached bitmap")
+ ##fixme: The cached bitmap could be used if the one needed is the same scale, but
+ ## a subset of the cached one.
+ bmp = self.ScaledBitmap[1]
+ dc.DrawBitmap(bmp, XYs, True)
+
+ if HTdc and self.HitAble:
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawRectangle(XYs, (Ws, Hs) )
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ BBworld = BBox.asBBox(self._Canvas.ViewPortBB)
+ ## first see if entire bitmap is displayed:
+ if BBworld.Inside(self.BoundingBox):
+ #print("Drawing entire bitmap with old code")
+ self._DrawEntireBitmap(dc , WorldToPixel, ScaleWorldToPixel, HTdc)
+ return None
+ elif BBworld.Overlaps(self.BoundingBox):
+ #BBbitmap = BBox.fromPoints(self.WorldToBitmap(BBworld))
+ #print("Drawing a sub-bitmap")
+ self._DrawSubBitmap(dc , WorldToPixel, ScaleWorldToPixel, HTdc)
+ else:
+ #print("Not Drawing -- no part of image is showing")
+ pass
+
+class DotGrid:
+ """
+ An example of a Grid Object -- it is set on the FloatCanvas with one of:
+
+ FloatCanvas.GridUnder = Grid
+ FloatCanvas.GridOver = Grid
+
+ It will be drawn every time, regardless of the viewport.
+
+ In its _Draw method, it computes what to draw, given the ViewPortBB
+ of the Canvas it's being drawn on.
+
+ """
+ def __init__(self, Spacing, Size = 2, Color = "Black", Cross=False, CrossThickness = 1):
+
+ self.Spacing = N.array(Spacing, N.float)
+ self.Spacing.shape = (2,)
+ self.Size = Size
+ self.Color = Color
+ self.Cross = Cross
+ self.CrossThickness = CrossThickness
+
+ def CalcPoints(self, Canvas):
+ ViewPortBB = Canvas.ViewPortBB
+
+ Spacing = self.Spacing
+
+ minx, miny = N.floor(ViewPortBB[0] / Spacing) * Spacing
+ maxx, maxy = N.ceil(ViewPortBB[1] / Spacing) * Spacing
+
+ ##fixme: this could use vstack or something with numpy
+ x = N.arange(minx, maxx+Spacing[0], Spacing[0]) # making sure to get the last point
+ y = N.arange(miny, maxy+Spacing[1], Spacing[1]) # an extra is OK
+ Points = N.zeros((len(y), len(x), 2), N.float)
+ x.shape = (1,-1)
+ y.shape = (-1,1)
+ Points[:,:,0] += x
+ Points[:,:,1] += y
+ Points.shape = (-1,2)
+
+ return Points
+
+ def _Draw(self, dc, Canvas):
+ Points = self.CalcPoints(Canvas)
+
+ Points = Canvas.WorldToPixel(Points)
+
+ dc.SetPen(wx.Pen(self.Color,self.CrossThickness))
+
+ if self.Cross: # Use cross shaped markers
+ #Horizontal lines
+ LinePoints = N.concatenate((Points + (self.Size,0),Points + (-self.Size,0)),1)
+ dc.DrawLineList(LinePoints)
+ # Vertical Lines
+ LinePoints = N.concatenate((Points + (0,self.Size),Points + (0,-self.Size)),1)
+ dc.DrawLineList(LinePoints)
+ pass
+ else: # use dots
+ ## Note: this code borrowed from Pointset -- it really shouldn't be repeated here!.
+ if self.Size <= 1:
+ dc.DrawPointList(Points)
+ elif self.Size <= 2:
+ dc.DrawPointList(Points + (0,-1))
+ dc.DrawPointList(Points + (0, 1))
+ dc.DrawPointList(Points + (1, 0))
+ dc.DrawPointList(Points + (-1,0))
+ else:
+ dc.SetBrush(wx.Brush(self.Color))
+ radius = int(round(self.Size/2))
+ ##fixme: I really should add a DrawCircleList to wxPython
+ if len(Points) > 100:
+ xy = Points
+ xywh = N.concatenate((xy-radius, N.ones(xy.shape) * self.Size ), 1 )
+ dc.DrawEllipseList(xywh)
+ else:
+ for xy in Points:
+ dc.DrawCircle(xy[0],xy[1], radius)
+
+class Arc(XYObjectMixin, LineAndFillMixin, DrawObject):
+ """Draws an arc of a circle, centered on point ``CenterXY``, from
+ the first point ``StartXY`` to the second ``EndXY``.
+
+ The arc is drawn in an anticlockwise direction from the start point to
+ the end point.
+
+ """
+ def __init__(self,
+ StartXY,
+ EndXY,
+ CenterXY,
+ LineColor = "Black",
+ LineStyle = "Solid",
+ LineWidth = 1,
+ FillColor = None,
+ FillStyle = "Solid",
+ InForeground = False):
+ """Default class constructor.
+
+ :param `StartXY`: start point, takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param `EndXY`: end point, takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param `CenterXY`: center point, takes a 2-tuple, or a (2,)
+ `NumPy `_ array of point coordinates
+ :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
+ :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
+ :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
+ :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
+ :param boolean `InForeground`: should object be in foreground
+
+ """
+
+ DrawObject.__init__(self, InForeground)
+
+ # There is probably a more elegant way to do this next section
+ # The bounding box just gets set to the WH of a circle, with center at CenterXY
+ # This is suitable for a pie chart as it will be a circle anyway
+ radius = N.sqrt( (StartXY[0]-CenterXY[0])**2 + (StartXY[1]-CenterXY[1])**2 )
+ minX = CenterXY[0]-radius
+ minY = CenterXY[1]-radius
+ maxX = CenterXY[0]+radius
+ maxY = CenterXY[1]+radius
+ XY = [minX,minY]
+ WH = [maxX-minX,maxY-minY]
+
+ self.XY = N.asarray( XY, N.float).reshape((2,))
+ self.WH = N.asarray( WH, N.float).reshape((2,))
+
+ self.StartXY = N.asarray(StartXY, N.float).reshape((2,))
+ self.CenterXY = N.asarray(CenterXY, N.float).reshape((2,))
+ self.EndXY = N.asarray(EndXY, N.float).reshape((2,))
+
+ #self.BoundingBox = array((self.XY, (self.XY + self.WH)), Float)
+ self.CalcBoundingBox()
+
+ #Finish the setup; allocate color,style etc.
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+ self.LineWidth = LineWidth
+ self.FillColor = FillColor
+ self.FillStyle = FillStyle
+
+ self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
+
+ self.SetPen(LineColor, LineStyle, LineWidth)
+ self.SetBrush(FillColor, FillStyle) #Why isn't this working ???
+
+ def Move(self, Delta):
+ """Move the object by delta
+
+ :param `Delta`: delta is a (dx, dy) pair. Ideally a `NumPy `_
+ array of shape (2,)
+
+ """
+
+ Delta = N.asarray(Delta, N.float)
+ self.XY += Delta
+ self.StartXY += Delta
+ self.CenterXY += Delta
+ self.EndXY += Delta
+ self.BoundingBox += Delta
+
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ self.SetUpDraw(dc , WorldToPixel, ScaleWorldToPixel, HTdc)
+ StartXY = WorldToPixel(self.StartXY)
+ EndXY = WorldToPixel(self.EndXY)
+ CenterXY = WorldToPixel(self.CenterXY)
+
+ dc.DrawArc(StartXY, EndXY, CenterXY)
+ if HTdc and self.HitAble:
+ HTdc.DrawArc(StartXY, EndXY, CenterXY)
+
+ def CalcBoundingBox(self):
+ """Calculate the bounding box."""
+ self.BoundingBox = BBox.asBBox( N.array((self.XY, (self.XY + self.WH) ),
+ N.float) )
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+
+class PieChart(XYObjectMixin, LineOnlyMixin, DrawObject):
+ """
+ This is DrawObject for a pie chart
+
+ You can pass in a bunch of values, and it will draw a pie chart for
+ you, and it will make the chart, scaling the size of each "slice" to
+ match your values.
+
+ The parameters are:
+
+ XY : The (x,y) coords of the center of the chart
+ Diameter : The diamter of the chart in worls coords, unless you set
+ "Scaled" to False, in which case it's in pixel coords.
+ Values : sequence of values you want to make the chart of.
+ FillColors=None : sequence of colors you want the slices. If
+ None, it will choose (no guarantee youll like them!)
+ FillStyles=None : Fill style you want ("Solid", "Hash", etc)
+ LineColor = None : Color of lines separating the slices
+ LineStyle = "Solid" : style of lines separating the slices
+ LineWidth = 1 : With of lines separating the slices
+ Scaled = True : Do you want the pie to scale when zooming? or stay the same size in pixels?
+ InForeground = False: Should it be on the foreground?
+
+
+ """
+
+
+ ##fixme: this should be a longer and better designed set.
+ ## Maybe one from: http://geography.uoregon.edu/datagraphics/color_scales.htm
+ DefaultColorList = Colors.CategoricalColor1
+ #["Red", "Green", "Blue", "Purple", "Yellow", "Cyan"]
+
+ def __init__(self,
+ XY,
+ Diameter,
+ Values,
+ FillColors=None,
+ FillStyles=None,
+ LineColor = None,
+ LineStyle = "Solid",
+ LineWidth = 1,
+ Scaled = True,
+ InForeground = False):
+ DrawObject.__init__(self, InForeground)
+
+ self.XY = N.asarray(XY, N.float).reshape( (2,) )
+ self.Diameter = Diameter
+ self.Values = N.asarray(Values, dtype=N.float).reshape((-1,1))
+ if FillColors is None:
+ FillColors = self.DefaultColorList[:len(Values)]
+ if FillStyles is None:
+ FillStyles = ['Solid'] * len(FillColors)
+ self.FillColors = FillColors
+ self.FillStyles = FillStyles
+ self.LineColor = LineColor
+ self.LineStyle = LineStyle
+
+ self.Scaled = Scaled
+ self.InForeground = InForeground
+
+ self.SetPen(LineColor, LineStyle, LineWidth)
+ self.SetBrushes()
+ self.CalculatePoints()
+
+ def SetFillColors(self, FillColors):
+ self.FillColors = FillColors
+ self.SetBrushes()
+
+ def SetFillStyles(self, FillStyles):
+ self.FillStyles = FillStyles
+ self.SetBrushed()
+
+ def SetValues(self, Values):
+ Values = N.asarray(Values, dtype=N.float).reshape((-1,1))
+ self.Values = Values
+ self.CalculatePoints()
+
+ def CalculatePoints(self):
+ # add the zero point to start
+ Values = N.vstack( ( (0,), self.Values) )
+ self.Angles = 360. * Values.cumsum()/Values.sum()
+ self.CalcBoundingBox()
+
+ def SetBrushes(self):
+ self.Brushes = []
+ for FillColor, FillStyle in zip(self.FillColors, self.FillStyles):
+ if FillColor is None or FillStyle is None:
+ self.Brush = wx.TRANSPARENT_BRUSH
+ else:
+ self.Brushes.append(self.BrushList.setdefault( (FillColor, FillStyle),
+ wx.Brush( FillColor, self.FillStyleList[FillStyle] )
+ )
+ )
+ def CalcBoundingBox(self):
+ if self.Scaled:
+ self.BoundingBox = BBox.asBBox( ((self.XY-self.Diameter),(self.XY+self.Diameter)) )
+ else:
+ self.BoundingBox = BBox.asBBox((self.XY, self.XY))
+ if self._Canvas:
+ self._Canvas.BoundingBoxDirty = True
+
+ def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+ CenterXY = WorldToPixel(self.XY)
+ if self.Scaled:
+ Diameter = ScaleWorldToPixel( (self.Diameter,self.Diameter) )[0]
+ else:
+ Diameter = self.Diameter
+ WH = N.array((Diameter,Diameter), dtype = N.float)
+ Corner = CenterXY - (WH / 2)
+ dc.SetPen(self.Pen)
+ for i, brush in enumerate(self.Brushes):
+ dc.SetBrush( brush )
+ dc.DrawEllipticArc(Corner[0], Corner[1], WH[0], WH[1], self.Angles[i], self.Angles[i+1])
+ if HTdc and self.HitAble:
+ if self.Scaled:
+ radius = (ScaleWorldToPixel(self.Diameter)/2)[0]# just the x-coord
+ else:
+ radius = self.Diameter/2
+ HTdc.SetPen(self.HitPen)
+ HTdc.SetBrush(self.HitBrush)
+ HTdc.DrawCircle(CenterXY, radius)
diff --git a/wx/lib/floatcanvas/FloatCanvas.py b/wx/lib/floatcanvas/FloatCanvas.py
index 04b3752d..a698df66 100644
--- a/wx/lib/floatcanvas/FloatCanvas.py
+++ b/wx/lib/floatcanvas/FloatCanvas.py
@@ -53,15 +53,12 @@ from time import clock
import wx
from wx.lib import six
+from .FCObjects import *
+
from .Utilities import BBox
from . import GUIMode
-## A global variable to hold the Pixels per inch that wxWindows thinks is in use
-## This is used for scaling fonts.
-## This can't be computed on module __init__, because a wx.App might not have initialized yet.
-global FontScale
-
## Custom Exceptions:
class FloatCanvasError(Exception):
@@ -107,404 +104,6 @@ class _MouseEvent(wx.PyCommandEvent):
return getattr(self._NativeEvent, name)
-## fixme: This should probably be re-factored into a class
-_testBitmap = None
-_testDC = None
-def _cycleidxs(indexcount, maxvalue, step):
-
- """
- Utility function used by _colorGenerator
- """
- def colormatch(color):
- """Return True if the color comes back from the bitmap identically."""
- if len(color) < 3:
- return True
- global _testBitmap, _testDC
- B = _testBitmap
- if not mac:
- dc = _testDC
- if not B:
- B = _testBitmap = wx.Bitmap(1, 1)
- if not mac:
- dc = _testDC = wx.MemoryDC()
- if mac:
- dc = wx.MemoryDC()
- dc.SelectObject(B)
- dc.SetBackground(wx.BLACK_BRUSH)
- dc.Clear()
- dc.SetPen(wx.Pen(wx.Colour(*color), 4))
- dc.DrawPoint(0,0)
- if mac:
- del dc
- pdata = wx.AlphaPixelData(B)
- pacc = pdata.GetPixels()
- pacc.MoveTo(pdata, 0, 0)
- outcolor = pacc.Get()[:3]
- else:
- outcolor = dc.GetPixel(0,0)
- return outcolor == color
-
- if indexcount == 0:
- yield ()
- else:
- for idx in range(0, maxvalue, step):
- for tail in _cycleidxs(indexcount - 1, maxvalue, step):
- color = (idx, ) + tail
- if not colormatch(color):
- continue
- yield color
-
-def _colorGenerator():
-
- """
- Generates a series of unique colors used to do hit-tests with the Hit
- Test bitmap
- """
- return _cycleidxs(indexcount=3, maxvalue=256, step=1)
-
-class DrawObject:
- """
- This is the base class for all the objects that can be drawn.
-
- One must subclass from this (and an assortment of Mixins) to create
- a new DrawObject, see for example :class:`~lib.floatcanvas.FloatCanvas.Circle`.
-
- """
- #This class contains a series of static dictionaries:
-
- #* BrushList
- #* PenList
- #* FillStyleList
- #* LineStyleList
-
-
- def __init__(self, InForeground = False, IsVisible = True):
- """
- Default class constructor.
-
- :param boolean `InForeground`: Define if object should be in foreground
- or not
- :param boolean `IsVisible`: Define if object should be visible
-
- """
- self.InForeground = InForeground
-
- self._Canvas = None
-
- self.HitColor = None
- self.CallBackFuncs = {}
-
- ## these are the defaults
- self.HitAble = False
- self.HitLine = True
- self.HitFill = True
- self.MinHitLineWidth = 3
- self.HitLineWidth = 3 ## this gets re-set by the subclasses if necessary
-
- self.Brush = None
- self.Pen = None
-
- self.FillStyle = "Solid"
-
- self.Visible = IsVisible
-
- # I pre-define all these as class variables to provide an easier
- # interface, and perhaps speed things up by caching all the Pens
- # and Brushes, although that may not help, as I think wx now
- # does that on it's own. Send me a note if you know!
-
- BrushList = {
- ( None, "Transparent") : wx.TRANSPARENT_BRUSH,
- ("Blue", "Solid") : wx.BLUE_BRUSH,
- ("Green", "Solid") : wx.GREEN_BRUSH,
- ("White", "Solid") : wx.WHITE_BRUSH,
- ("Black", "Solid") : wx.BLACK_BRUSH,
- ("Grey", "Solid") : wx.GREY_BRUSH,
- ("MediumGrey", "Solid") : wx.MEDIUM_GREY_BRUSH,
- ("LightGrey", "Solid") : wx.LIGHT_GREY_BRUSH,
- ("Cyan", "Solid") : wx.CYAN_BRUSH,
- ("Red", "Solid") : wx.RED_BRUSH
- }
- PenList = {
- (None, "Transparent", 1) : wx.TRANSPARENT_PEN,
- ("Green", "Solid", 1) : wx.GREEN_PEN,
- ("White", "Solid", 1) : wx.WHITE_PEN,
- ("Black", "Solid", 1) : wx.BLACK_PEN,
- ("Grey", "Solid", 1) : wx.GREY_PEN,
- ("MediumGrey", "Solid", 1) : wx.MEDIUM_GREY_PEN,
- ("LightGrey", "Solid", 1) : wx.LIGHT_GREY_PEN,
- ("Cyan", "Solid", 1) : wx.CYAN_PEN,
- ("Red", "Solid", 1) : wx.RED_PEN
- }
-
- FillStyleList = {
- "Transparent" : wx.BRUSHSTYLE_TRANSPARENT,
- "Solid" : wx.BRUSHSTYLE_SOLID,
- "BiDiagonalHatch": wx.BRUSHSTYLE_BDIAGONAL_HATCH,
- "CrossDiagHatch" : wx.BRUSHSTYLE_CROSSDIAG_HATCH,
- "FDiagonal_Hatch": wx.BRUSHSTYLE_FDIAGONAL_HATCH,
- "CrossHatch" : wx.BRUSHSTYLE_CROSS_HATCH,
- "HorizontalHatch": wx.BRUSHSTYLE_HORIZONTAL_HATCH,
- "VerticalHatch" : wx.BRUSHSTYLE_VERTICAL_HATCH
- }
-
- LineStyleList = {
- "Solid" : wx.PENSTYLE_SOLID,
- "Transparent": wx.PENSTYLE_TRANSPARENT,
- "Dot" : wx.PENSTYLE_DOT,
- "LongDash" : wx.PENSTYLE_LONG_DASH,
- "ShortDash" : wx.PENSTYLE_SHORT_DASH,
- "DotDash" : wx.PENSTYLE_DOT_DASH,
- }
-
- def Bind(self, Event, CallBackFun):
- """
- Bind an event to the DrawObject
-
- :param `Event`: see below for supported event types
-
- - EVT_FC_LEFT_DOWN
- - EVT_FC_LEFT_UP
- - EVT_FC_LEFT_DCLICK
- - EVT_FC_MIDDLE_DOWN
- - EVT_FC_MIDDLE_UP
- - EVT_FC_MIDDLE_DCLICK
- - EVT_FC_RIGHT_DOWN
- - EVT_FC_RIGHT_UP
- - EVT_FC_RIGHT_DCLICK
- - EVT_FC_ENTER_OBJECT
- - EVT_FC_LEAVE_OBJECT
-
- :param `CallBackFun`: the call back function for the event
-
-
- """
- ##fixme: Way too much Canvas Manipulation here!
- self.CallBackFuncs[Event] = CallBackFun
- self.HitAble = True
- self._Canvas.UseHitTest = True
- if self.InForeground and self._Canvas._ForegroundHTBitmap is None:
- self._Canvas.MakeNewForegroundHTBitmap()
- elif self._Canvas._HTBitmap is None:
- self._Canvas.MakeNewHTBitmap()
- if not self.HitColor:
- if not self._Canvas.HitColorGenerator:
- # first call to prevent the background color from being used.
- self._Canvas.HitColorGenerator = _colorGenerator()
- if six.PY3:
- next(self._Canvas.HitColorGenerator)
- else:
- self._Canvas.HitColorGenerator.next()
- if six.PY3:
- self.HitColor = next(self._Canvas.HitColorGenerator)
- else:
- self.HitColor = self._Canvas.HitColorGenerator.next()
- self.SetHitPen(self.HitColor,self.HitLineWidth)
- self.SetHitBrush(self.HitColor)
- # put the object in the hit dict, indexed by it's color
- if not self._Canvas.HitDict:
- self._Canvas.MakeHitDict()
- self._Canvas.HitDict[Event][self.HitColor] = (self) # put the object in the hit dict, indexed by its color
-
- def UnBindAll(self):
- """
- Unbind all events
-
- .. note:: Currently only removes one from each list
-
- """
- ## fixme: this only removes one from each list, there could be more.
- ## + patch by Tim Ansel
- if self._Canvas.HitDict:
- for Event in self._Canvas.HitDict.itervalues():
- try:
- del Event[self.HitColor]
- except KeyError:
- pass
- self.HitAble = False
-
-
- def SetBrush(self, FillColor, FillStyle):
- """
- Set the brush for this DrawObject
-
- :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- for valid entries
- :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
- for valid entries
- """
- if FillColor is None or FillStyle is None:
- self.Brush = wx.TRANSPARENT_BRUSH
- ##fixme: should I really re-set the style?
- self.FillStyle = "Transparent"
- else:
- self.Brush = self.BrushList.setdefault(
- (FillColor, FillStyle),
- wx.Brush(FillColor, self.FillStyleList[FillStyle]))
- #print("Setting Brush, BrushList length:", len(self.BrushList))
-
- def SetPen(self, LineColor, LineStyle, LineWidth):
- """
- Set the Pen for this DrawObject
-
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- for valid entries
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- for valid entries
- :param integer `LineWidth`: the width in pixels
- """
- if (LineColor is None) or (LineStyle is None):
- self.Pen = wx.TRANSPARENT_PEN
- self.LineStyle = 'Transparent'
- else:
- self.Pen = self.PenList.setdefault(
- (LineColor, LineStyle, LineWidth),
- wx.Pen(LineColor, LineWidth, self.LineStyleList[LineStyle]))
-
- def SetHitBrush(self, HitColor):
- """
- Set the brush used for hit test, do not call directly.
-
- :param `HitColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
-
- """
- if not self.HitFill:
- self.HitBrush = wx.TRANSPARENT_BRUSH
- else:
- self.HitBrush = self.BrushList.setdefault(
- (HitColor,"solid"),
- wx.Brush(HitColor, self.FillStyleList["Solid"]))
-
- def SetHitPen(self, HitColor, LineWidth):
- """
- Set the pen used for hit test, do not call directly.
-
- :param `HitColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param integer `LineWidth`: the line width in pixels
-
- """
- if not self.HitLine:
- self.HitPen = wx.TRANSPARENT_PEN
- else:
- self.HitPen = self.PenList.setdefault( (HitColor, "solid", self.HitLineWidth), wx.Pen(HitColor, self.HitLineWidth, self.LineStyleList["Solid"]) )
-
- ## Just to make sure that they will always be there
- ## the appropriate ones should be overridden in the subclasses
- def SetColor(self, Color):
- """
- Set the Color - this method is overridden in the subclasses
-
- :param `Color`: use one of the following values any valid entry from
- :class:`ColourDatabase`
-
- - ``Green``
- - ``White``
- - ``Black``
- - ``Grey``
- - ``MediumGrey``
- - ``LightGrey``
- - ``Cyan``
- - ``Red``
-
- """
-
- pass
-
- def SetLineColor(self, LineColor):
- """
- Set the LineColor - this method is overridden in the subclasses
-
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- for valid values
-
- """
- pass
-
- def SetLineStyle(self, LineStyle):
- """
- Set the LineStyle - this method is overridden in the subclasses
-
- :param `LineStyle`: Use one of the following values or ``None`` :
-
- ===================== =============================
- Style Description
- ===================== =============================
- ``Solid`` Solid line
- ``Transparent`` A transparent line
- ``Dot`` Dotted line
- ``LongDash`` Dashed line (long)
- ``ShortDash`` Dashed line (short)
- ``DotDash`` Dash-dot-dash line
- ===================== =============================
-
- """
- pass
-
- def SetLineWidth(self, LineWidth):
- """
- Set the LineWidth
-
- :param integer `LineWidth`: sets the line width in pixel
-
- """
- pass
-
- def SetFillColor(self, FillColor):
- """
- Set the FillColor - this method is overridden in the subclasses
-
- :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- for valid values
-
- """
- pass
-
- def SetFillStyle(self, FillStyle):
- """
- Set the FillStyle - this method is overridden in the subclasses
-
- :param string `FillStyle`: following is a list of valid values:
-
- ===================== =========================================
- Style Description
- ===================== =========================================
- ``Transparent`` Transparent fill
- ``Solid`` Solid fill
- ``BiDiagonalHatch`` Bi Diagonal hatch fill
- ``CrossDiagHatch`` Cross Diagonal hatch fill
- ``FDiagonal_Hatch`` F Diagonal hatch fill
- ``CrossHatch`` Cross hatch fill
- ``HorizontalHatch`` Horizontal hatch fill
- ``VerticalHatch`` Vertical hatch fill
- ===================== =========================================
-
- """
- pass
-
- def PutInBackground(self):
- """Put the object in the background."""
- if self._Canvas and self.InForeground:
- self._Canvas._ForeDrawList.remove(self)
- self._Canvas._DrawList.append(self)
- self._Canvas._BackgroundDirty = True
- self.InForeground = False
-
- def PutInForeground(self):
- """Put the object in the foreground."""
- if self._Canvas and (not self.InForeground):
- self._Canvas._ForeDrawList.append(self)
- self._Canvas._DrawList.remove(self)
- self._Canvas._BackgroundDirty = True
- self.InForeground = True
-
- def Hide(self):
- """Hide the object."""
- self.Visible = False
-
- def Show(self):
- """Show the object."""
- self.Visible = True
-
class Group(DrawObject):
"""
A group of other FloatCanvas Objects
@@ -693,2092 +292,6 @@ class Group(DrawObject):
obj._Draw(dc, WorldToPixel, ScaleWorldToPixel, HTdc)
-class ColorOnlyMixin:
- """
- Mixin class for objects that have just one color, rather than a fill
- color and line color
-
- """
-
- def SetColor(self, Color):
- """
- Set the Color
-
- :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- for valid values
-
- """
- self.SetPen(Color,"Solid",1)
- self.SetBrush(Color,"Solid")
-
- SetFillColor = SetColor # Just to provide a consistant interface
-
-
-class LineOnlyMixin:
- """
- Mixin class for objects that have just a line, rather than a fill
- color and line color
-
- """
-
- def SetLineColor(self, LineColor):
- """
- Set the LineColor
-
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- for valid values
-
- """
- self.LineColor = LineColor
- self.SetPen(LineColor,self.LineStyle,self.LineWidth)
- SetColor = SetLineColor# so that it will do something reasonable
-
- def SetLineStyle(self, LineStyle):
- """
- Set the LineStyle
-
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- for valid values
-
- """
- self.LineStyle = LineStyle
- self.SetPen(self.LineColor,LineStyle,self.LineWidth)
-
- def SetLineWidth(self, LineWidth):
- """
- Set the LineWidth
-
- :param integer `LineWidth`: line width in pixels
-
- """
- self.LineWidth = LineWidth
- self.SetPen(self.LineColor,self.LineStyle,LineWidth)
-
-
-class LineAndFillMixin(LineOnlyMixin):
- """
- Mixin class for objects that have both a line and a fill color and
- style.
-
- """
- def SetFillColor(self, FillColor):
- """
- Set the FillColor
-
- :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- for valid values
-
- """
- self.FillColor = FillColor
- self.SetBrush(FillColor, self.FillStyle)
-
- def SetFillStyle(self, FillStyle):
- """
- Set the FillStyle
-
- :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
- for valid values
-
- """
- self.FillStyle = FillStyle
- self.SetBrush(self.FillColor,FillStyle)
-
- def SetUpDraw(self, dc, WorldToPixel, ScaleWorldToPixel, HTdc):
- """
- Setup for draw
-
- :param `dc`: the dc to draw ???
- :param `WorldToPixel`: ???
- :param `ScaleWorldToPixel`: ???
- :param `HTdc`: ???
-
- """
- dc.SetPen(self.Pen)
- dc.SetBrush(self.Brush)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- return ( WorldToPixel(self.XY),
- ScaleWorldToPixel(self.WH) )
-
-
-class XYObjectMixin:
- """
- This is a mixin class that provides some methods suitable for use
- with objects that have a single (x,y) coordinate pair.
- """
-
- def Move(self, Delta):
- """
- Moves the object by delta, where delta is a (dx, dy) pair.
-
- :param `Delta`: is a (dx, dy) pair ideally a `NumPy `_
- array of shape (2, )
-
- """
- Delta = N.asarray(Delta, N.float)
- self.XY += Delta
- self.BoundingBox += Delta
-
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
- def CalcBoundingBox(self):
- """Calculate the bounding box."""
- ## This may get overwritten in some subclasses
- self.BoundingBox = BBox.asBBox((self.XY, self.XY))
-
- def SetPoint(self, xy):
- xy = N.array(xy, N.float)
- xy.shape = (2,)
-
- self.XY = xy
- self.CalcBoundingBox()
-
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
-class PointsObjectMixin:
- """
- A mixin class that provides some methods suitable for use
- with objects that have a set of (x, y) coordinate pairs.
-
- """
-
- def Move(self, Delta):
- """
- Moves the object by delta, where delta is a (dx, dy) pair.
-
- :param `Delta`: is a (dx, dy) pair ideally a `NumPy `_
- array of shape (2, )
-
- """
- Delta = N.asarray(Delta, N.float)
- Delta.shape = (2,)
- self.Points += Delta
- self.BoundingBox += Delta
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
- def CalcBoundingBox(self):
- """Calculate the bounding box."""
- self.BoundingBox = BBox.fromPoints(self.Points)
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
- def SetPoints(self, Points, copy=True):
- """
- Sets the coordinates of the points of the object to Points (NX2 array).
-
- :param `Points`: takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param boolean `copy`: By default, a copy is made, if copy is set to
- ``False``, a reference is used, if Points is a NumPy array of Floats.
- This allows you to change some or all of the points without making
- any copies.
-
- For example::
-
- Points = Object.Points
- # shifts the points 5 in the x dir, and 10 in the y dir.
- Points += (5, 10)
- # Sets the points to the same array as it was
- Object.SetPoints(Points, False)
-
- """
- if copy:
- self.Points = N.array(Points, N.float)
- self.Points.shape = (-1, 2) # Make sure it is a NX2 array, even if there is only one point
- else:
- self.Points = N.asarray(Points, N.float)
- self.CalcBoundingBox()
-
-
-class Polygon(PointsObjectMixin, LineAndFillMixin, DrawObject):
- """Draws a polygon
-
- Points is a list of 2-tuples, or a NX2 NumPy array of
- point coordinates. so that Points[N][0] is the x-coordinate of
- point N and Points[N][1] is the y-coordinate or Points[N,0] is the
- x-coordinate of point N and Points[N,1] is the y-coordinate for
- arrays.
-
- """
- def __init__(self,
- Points,
- LineColor = "Black",
- LineStyle = "Solid",
- LineWidth = 1,
- FillColor = None,
- FillStyle = "Solid",
- InForeground = False):
- """Default class constructor.
-
- :param `Points`: start point, takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
- :param boolean `InForeground`: should object be in foreground
-
- """
- DrawObject.__init__(self, InForeground)
- self.Points = N.array(Points ,N.float) # this DOES need to make a copy
- self.CalcBoundingBox()
-
- self.LineColor = LineColor
- self.LineStyle = LineStyle
- self.LineWidth = LineWidth
- self.FillColor = FillColor
- self.FillStyle = FillStyle
-
- self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
-
- self.SetPen(LineColor,LineStyle,LineWidth)
- self.SetBrush(FillColor,FillStyle)
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel = None, HTdc=None):
- Points = WorldToPixel(self.Points)#.tolist()
- dc.SetPen(self.Pen)
- dc.SetBrush(self.Brush)
- dc.DrawPolygon(Points)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawPolygon(Points)
-
-class Line(PointsObjectMixin, LineOnlyMixin, DrawObject):
- """Draws a line
-
- It will draw a straight line if there are two points, and a polyline
- if there are more than two.
-
- """
- def __init__(self, Points,
- LineColor = "Black",
- LineStyle = "Solid",
- LineWidth = 1,
- InForeground = False):
- """Default class constructor.
-
- :param `Points`: takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param boolean `InForeground`: should object be in foreground
-
- """
- DrawObject.__init__(self, InForeground)
-
-
- self.Points = N.array(Points,N.float)
- self.CalcBoundingBox()
-
- self.LineColor = LineColor
- self.LineStyle = LineStyle
- self.LineWidth = LineWidth
-
- self.SetPen(LineColor,LineStyle,LineWidth)
-
- self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
-
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- Points = WorldToPixel(self.Points)
- dc.SetPen(self.Pen)
- dc.DrawLines(Points)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.DrawLines(Points)
-
-class Spline(Line):
- """Draws a spline"""
- def __init__(self, *args, **kwargs):
- """Default class constructor.
-
- see :class:`~lib.floatcanvas.FloatCanvas.Line`
-
- """
- Line.__init__(self, *args, **kwargs)
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- Points = WorldToPixel(self.Points)
- dc.SetPen(self.Pen)
- dc.DrawSpline(Points)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.DrawSpline(Points)
-
-
-class Arrow(XYObjectMixin, LineOnlyMixin, DrawObject):
- """Draws an arrow
-
- It will draw an arrow , starting at the point ``XY`` points at an angle
- defined by ``Direction``.
-
- """
- def __init__(self,
- XY,
- Length,
- Direction,
- LineColor = "Black",
- LineStyle = "Solid",
- LineWidth = 2,
- ArrowHeadSize = 8,
- ArrowHeadAngle = 30,
- InForeground = False):
- """Default class constructor.
-
- :param `XY`: the (x, y) coordinate of the starting point, or a 2-tuple,
- or a (2,) `NumPy `_ array
- :param integer `Length`: length of arrow in pixels
- :param integer `Direction`: angle of arrow in degrees, zero is straight
- up `+` angle is to the right
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param `ArrowHeadSize`: size of arrow head in pixels
- :param `ArrowHeadAngle`: angle of arrow head in degrees
- :param boolean `InForeground`: should object be in foreground
-
- """
-
- DrawObject.__init__(self, InForeground)
-
- self.XY = N.array(XY, N.float)
- self.XY.shape = (2,) # Make sure it is a length 2 vector
- self.Length = Length
- self.Direction = float(Direction)
- self.ArrowHeadSize = ArrowHeadSize
- self.ArrowHeadAngle = float(ArrowHeadAngle)
-
- self.CalcArrowPoints()
- self.CalcBoundingBox()
-
- self.LineColor = LineColor
- self.LineStyle = LineStyle
- self.LineWidth = LineWidth
-
- self.SetPen(LineColor,LineStyle,LineWidth)
-
- ##fixme: How should the HitTest be drawn?
- self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
-
- def SetDirection(self, Direction):
- """Set the direction
-
- :param integer `Direction`: angle of arrow in degrees, zero is straight
- up `+` angle is to the right
-
- """
- self.Direction = float(Direction)
- self.CalcArrowPoints()
-
- def SetLength(self, Length):
- """Set the length
-
- :param integer `Length`: length of arrow in pixels
-
- """
- self.Length = Length
- self.CalcArrowPoints()
-
- def SetLengthDirection(self, Length, Direction):
- """Set the lenght and direction
-
- :param integer `Length`: length of arrow in pixels
- :param integer `Direction`: angle of arrow in degrees, zero is straight
- up `+` angle is to the right
-
- """
- self.Direction = float(Direction)
- self.Length = Length
- self.CalcArrowPoints()
-
-## def CalcArrowPoints(self):
-## L = self.Length
-## S = self.ArrowHeadSize
-## phi = self.ArrowHeadAngle * N.pi / 360
-## theta = (self.Direction-90.0) * N.pi / 180
-## ArrowPoints = N.array( ( (0, L, L - S*N.cos(phi),L, L - S*N.cos(phi) ),
-## (0, 0, S*N.sin(phi), 0, -S*N.sin(phi) ) ),
-## N.float )
-## RotationMatrix = N.array( ( ( N.cos(theta), -N.sin(theta) ),
-## ( N.sin(theta), N.cos(theta) ) ),
-## N.float
-## )
-## ArrowPoints = N.matrixmultiply(RotationMatrix, ArrowPoints)
-## self.ArrowPoints = N.transpose(ArrowPoints)
-
- def CalcArrowPoints(self):
- """Calculate the arrow points."""
- L = self.Length
- S = self.ArrowHeadSize
- phi = self.ArrowHeadAngle * N.pi / 360
- theta = (270 - self.Direction) * N.pi / 180
- AP = N.array( ( (0,0),
- (0,0),
- (N.cos(theta - phi), -N.sin(theta - phi) ),
- (0,0),
- (N.cos(theta + phi), -N.sin(theta + phi) ),
- ), N.float )
- AP *= S
- shift = (-L*N.cos(theta), L*N.sin(theta) )
- AP[1:,:] += shift
- self.ArrowPoints = AP
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- dc.SetPen(self.Pen)
- xy = WorldToPixel(self.XY)
- ArrowPoints = xy + self.ArrowPoints
- dc.DrawLines(ArrowPoints)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.DrawLines(ArrowPoints)
-
-
-class ArrowLine(PointsObjectMixin, LineOnlyMixin, DrawObject):
- """Draws an arrow line.
-
- It will draw a set of arrows from point to point.
-
- It takes a list of 2-tuples, or a NX2 NumPy Float array of point coordinates.
-
- """
-
- def __init__(self,
- Points,
- LineColor = "Black",
- LineStyle = "Solid",
- LineWidth = 1,
- ArrowHeadSize = 8,
- ArrowHeadAngle = 30,
- InForeground = False):
- """Default class constructor.
-
- :param `Points`: takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param `ArrowHeadSize`: size of arrow head in pixels
- :param `ArrowHeadAngle`: angle of arrow head in degrees
- :param boolean `InForeground`: should object be in foreground
-
- """
-
- DrawObject.__init__(self, InForeground)
-
- self.Points = N.asarray(Points,N.float)
- self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
- self.ArrowHeadSize = ArrowHeadSize
- self.ArrowHeadAngle = float(ArrowHeadAngle)
-
- self.CalcArrowPoints()
- self.CalcBoundingBox()
-
- self.LineColor = LineColor
- self.LineStyle = LineStyle
- self.LineWidth = LineWidth
-
- self.SetPen(LineColor,LineStyle,LineWidth)
-
- self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
-
- def CalcArrowPoints(self):
- """Calculate the arrow points."""
- S = self.ArrowHeadSize
- phi = self.ArrowHeadAngle * N.pi / 360
- Points = self.Points
- n = Points.shape[0]
- self.ArrowPoints = N.zeros((n-1, 3, 2), N.float)
- for i in range(n-1):
- dx, dy = self.Points[i] - self.Points[i+1]
- theta = N.arctan2(dy, dx)
- AP = N.array( (
- (N.cos(theta - phi), -N.sin(theta-phi)),
- (0,0),
- (N.cos(theta + phi), -N.sin(theta + phi))
- ),
- N.float )
- self.ArrowPoints[i,:,:] = AP
- self.ArrowPoints *= S
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- Points = WorldToPixel(self.Points)
- ArrowPoints = Points[1:,N.newaxis,:] + self.ArrowPoints
- dc.SetPen(self.Pen)
- dc.DrawLines(Points)
- for arrow in ArrowPoints:
- dc.DrawLines(arrow)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.DrawLines(Points)
- for arrow in ArrowPoints:
- HTdc.DrawLines(arrow)
-
-
-class PointSet(PointsObjectMixin, ColorOnlyMixin, DrawObject):
- """Draws a set of points
-
- If Points is a sequence of tuples: Points[N][0] is the x-coordinate of
- point N and Points[N][1] is the y-coordinate.
-
- If Points is a NumPy array: Points[N,0] is the x-coordinate of point
- N and Points[N,1] is the y-coordinate for arrays.
-
- Each point will be drawn the same color and Diameter. The Diameter
- is in screen pixels, not world coordinates.
-
- The hit-test code does not distingish between the points, you will
- only know that one of the points got hit, not which one. You can use
- PointSet.FindClosestPoint(WorldPoint) to find out which one
-
- In the case of points, the HitLineWidth is used as diameter.
-
- """
- def __init__(self, Points, Color="Black", Diameter=1, InForeground=False):
- """Default class constructor.
-
- :param `Points`: takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param integer `Diameter`: the points diameter
- :param boolean `InForeground`: should object be in foreground
-
- """
- DrawObject.__init__(self, InForeground)
-
- self.Points = N.array(Points,N.float)
- self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
- self.CalcBoundingBox()
- self.Diameter = Diameter
-
- self.HitLineWidth = min(self.MinHitLineWidth, Diameter)
- self.SetColor(Color)
-
- def SetDiameter(self, Diameter):
- """Sets the diameter
-
- :param integer `Diameter`: the points diameter
-
- """
- self.Diameter = Diameter
-
- def FindClosestPoint(self, XY):
- """
-
- Returns the index of the closest point to the point, XY, given
- in World coordinates. It's essentially random which you get if
- there are more than one that are the same.
-
- This can be used to figure out which point got hit in a mouse
- binding callback, for instance. It's a lot faster that using a
- lot of separate points.
-
- :param `XY`: the (x,y) coordinates of the point to look for, it takes a
- 2-tuple or (2,) numpy array in World coordinates
-
- """
- d = self.Points - XY
- return N.argmin(N.hypot(d[:,0],d[:,1]))
-
-
- def DrawD2(self, dc, Points):
- # A Little optimization for a diameter2 - point
- dc.DrawPointList(Points)
- dc.DrawPointList(Points + (1,0))
- dc.DrawPointList(Points + (0,1))
- dc.DrawPointList(Points + (1,1))
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- dc.SetPen(self.Pen)
- Points = WorldToPixel(self.Points)
- if self.Diameter <= 1:
- dc.DrawPointList(Points)
- elif self.Diameter <= 2:
- self.DrawD2(dc, Points)
- else:
- dc.SetBrush(self.Brush)
- radius = int(round(self.Diameter/2))
- ##fixme: I really should add a DrawCircleList to wxPython
- if len(Points) > 100:
- xy = Points
- xywh = N.concatenate((xy-radius, N.ones(xy.shape) * self.Diameter ), 1 )
- dc.DrawEllipseList(xywh)
- else:
- for xy in Points:
- dc.DrawCircle(xy[0],xy[1], radius)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- if self.Diameter <= 1:
- HTdc.DrawPointList(Points)
- elif self.Diameter <= 2:
- self.DrawD2(HTdc, Points)
- else:
- if len(Points) > 100:
- xy = Points
- xywh = N.concatenate((xy-radius, N.ones(xy.shape) * self.Diameter ), 1 )
- HTdc.DrawEllipseList(xywh)
- else:
- for xy in Points:
- HTdc.DrawCircle(xy[0],xy[1], radius)
-
-class Point(XYObjectMixin, ColorOnlyMixin, DrawObject):
- """A point DrawObject
-
- .. note::
-
- The Bounding box is just the point, and doesn't include the Diameter.
-
- The HitLineWidth is used as diameter for the Hit Test.
-
- """
- def __init__(self, XY, Color="Black", Diameter=1, InForeground=False):
- """Default class constructor.
-
- :param `XY`: the (x, y) coordinate of the center of the point, or a
- 2-tuple, or a (2,) `NumPy `_ array
- :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param integer `Diameter`: in screen points
- :param `InForeground`: define if object is in foreground
-
- """
-
- DrawObject.__init__(self, InForeground)
-
- self.XY = N.array(XY, N.float)
- self.XY.shape = (2,) # Make sure it is a length 2 vector
- self.CalcBoundingBox()
- self.SetColor(Color)
- self.Diameter = Diameter
-
- self.HitLineWidth = self.MinHitLineWidth
-
- def SetDiameter(self, Diameter):
- """Set the diameter of the object.
-
- :param integer `Diameter`: in screen points
-
- """
- self.Diameter = Diameter
-
- def _Draw(self, dc, WorldToPixel, ScaleWorldToPixel, HTdc=None):
- dc.SetPen(self.Pen)
- xy = WorldToPixel(self.XY)
- if self.Diameter <= 1:
- dc.DrawPoint(xy[0], xy[1])
- else:
- dc.SetBrush(self.Brush)
- radius = int(round(self.Diameter/2))
- dc.DrawCircle(xy[0],xy[1], radius)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- if self.Diameter <= 1:
- HTdc.DrawPoint(xy[0], xy[1])
- else:
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawCircle(xy[0],xy[1], radius)
-
-class SquarePoint(XYObjectMixin, ColorOnlyMixin, DrawObject):
- """Draws a square point
-
- The Size is in screen points, not world coordinates, so the
- Bounding box is just the point, and doesn't include the Size.
-
- The HitLineWidth is used as diameter for the Hit Test.
-
- """
- def __init__(self, Point, Color="Black", Size=4, InForeground=False):
- """Default class constructor.
-
- :param `Point`: takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param integer `Size`: the size of the square point
- :param boolean `InForeground`: should object be in foreground
-
- """
- DrawObject.__init__(self, InForeground)
-
- self.XY = N.array(Point, N.float)
- self.XY.shape = (2,) # Make sure it is a length 2 vector
- self.CalcBoundingBox()
- self.SetColor(Color)
- self.Size = Size
-
- self.HitLineWidth = self.MinHitLineWidth
-
- def SetSize(self, Size):
- """Sets the size
-
- :param integer `Size`: the size of the square point
-
- """
- self.Size = Size
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- Size = self.Size
- dc.SetPen(self.Pen)
- xc,yc = WorldToPixel(self.XY)
-
- if self.Size <= 1:
- dc.DrawPoint(xc, yc)
- else:
- x = xc - Size/2.0
- y = yc - Size/2.0
- dc.SetBrush(self.Brush)
- dc.DrawRectangle(x, y, Size, Size)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- if self.Size <= 1:
- HTdc.DrawPoint(xc, xc)
- else:
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawRectangle(x, y, Size, Size)
-
-class RectEllipse(XYObjectMixin, LineAndFillMixin, DrawObject):
- """A RectEllipse draw object."""
- def __init__(self, XY, WH,
- LineColor = "Black",
- LineStyle = "Solid",
- LineWidth = 1,
- FillColor = None,
- FillStyle = "Solid",
- InForeground = False):
- """Default class constructor.
-
- :param `XY`: the (x, y) coordinate of the corner of RectEllipse, or a 2-tuple,
- or a (2,) `NumPy `_ array
- :param `WH`: a tuple with the Width and Height for the object
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
- :param `InForeground`: put object in foreground
-
- """
-
- DrawObject.__init__(self,InForeground)
-
- self.SetShape(XY, WH)
- self.LineColor = LineColor
- self.LineStyle = LineStyle
- self.LineWidth = LineWidth
- self.FillColor = FillColor
- self.FillStyle = FillStyle
-
- self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
-
- # these define the behaviour when zooming makes the objects really small.
- self.MinSize = 1
- self.DisappearWhenSmall = True
-
- self.SetPen(LineColor,LineStyle,LineWidth)
- self.SetBrush(FillColor,FillStyle)
-
- def SetShape(self, XY, WH):
- """Set the shape of the object.
-
- :param `XY`: takes a 2-tuple, or a (2,) `NumPy `_
- array of point coordinates
- :param `WH`: a tuple with the Width and Height for the object
-
- """
- self.XY = N.array( XY, N.float)
- self.XY.shape = (2,)
- self.WH = N.array( WH, N.float)
- self.WH.shape = (2,)
- self.CalcBoundingBox()
-
- def CalcBoundingBox(self):
- """Calculate the bounding box."""
- # you need this in case Width or Height are negative
- corners = N.array((self.XY, (self.XY + self.WH) ), N.float)
- self.BoundingBox = BBox.fromPoints(corners)
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
-
-class Rectangle(RectEllipse):
- """Draws a rectangle see :class:`~lib.floatcanvas.FloatCanvas.RectEllipse`"""
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- ( XY, WH ) = self.SetUpDraw(dc,
- WorldToPixel,
- ScaleWorldToPixel,
- HTdc)
- WH[N.abs(WH) < self.MinSize] = self.MinSize
- if not( self.DisappearWhenSmall and N.abs(WH).min() <= self.MinSize) : # don't try to draw it too tiny
- dc.DrawRectangle(XY, WH)
- if HTdc and self.HitAble:
- HTdc.DrawRectangle(XY, WH)
-
-
-class Ellipse(RectEllipse):
- """Draws an ellipse see :class:`~lib.floatcanvas.FloatCanvas.RectEllipse`"""
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- ( XY, WH ) = self.SetUpDraw(dc,
- WorldToPixel,
- ScaleWorldToPixel,
- HTdc)
- WH[N.abs(WH) < self.MinSize] = self.MinSize
- if not( self.DisappearWhenSmall and N.abs(WH).min() <= self.MinSize) : # don't try to draw it too tiny
- dc.DrawEllipse(XY, WH)
- if HTdc and self.HitAble:
- HTdc.DrawEllipse(XY, WH)
-
-class Circle(XYObjectMixin, LineAndFillMixin, DrawObject):
- """Draws a circle"""
- def __init__(self, XY, Diameter,
- LineColor = "Black",
- LineStyle = "Solid",
- LineWidth = 1,
- FillColor = None,
- FillStyle = "Solid",
- InForeground = False):
- """Default class constructor.
-
- :param `XY`: the (x, y) coordinate of the center of the circle, or a 2-tuple,
- or a (2,) `NumPy `_ array
- :param integer `Diameter`: the diameter for the object
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
- :param boolean `InForeground`: should object be in foreground
-
- """
- DrawObject.__init__(self, InForeground)
-
- self.XY = N.array(XY, N.float)
- self.WH = N.array((Diameter/2, Diameter/2), N.float) # just to keep it compatible with others
- self.CalcBoundingBox()
-
- self.LineColor = LineColor
- self.LineStyle = LineStyle
- self.LineWidth = LineWidth
- self.FillColor = FillColor
- self.FillStyle = FillStyle
-
- self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
-
- # these define the behaviour when zooming makes the objects really small.
- self.MinSize = 1
- self.DisappearWhenSmall = True
-
- self.SetPen(LineColor,LineStyle,LineWidth)
- self.SetBrush(FillColor,FillStyle)
-
- def SetDiameter(self, Diameter):
- """Set the diameter of the object
-
- :param integer `Diameter`: the diameter for the object
-
- """
- self.WH = N.array((Diameter/2, Diameter/2), N.float) # just to keep it compatible with others
-
- def CalcBoundingBox(self):
- """Calculate the bounding box of the object."""
- # you need this in case Width or Height are negative
- self.BoundingBox = BBox.fromPoints( (self.XY+self.WH, self.XY-self.WH) )
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- ( XY, WH ) = self.SetUpDraw(dc,
- WorldToPixel,
- ScaleWorldToPixel,
- HTdc)
-
- WH[N.abs(WH) < self.MinSize] = self.MinSize
- if not( self.DisappearWhenSmall and N.abs(WH).min() <= self.MinSize) : # don't try to draw it too tiny
- dc.DrawCircle(XY, WH[0])
- if HTdc and self.HitAble:
- HTdc.DrawCircle(XY, WH[0])
-
-
-class TextObjectMixin(XYObjectMixin):
- """
-
- A mix in class that holds attributes and methods that are needed by
- the Text objects
-
- """
-
- ## I'm caching fonts, because on GTK, getting a new font can take a
- ## while. However, it gets cleared after every full draw as hanging
- ## on to a bunch of large fonts takes a massive amount of memory.
-
- FontList = {}
-
- LayoutFontSize = 16 # font size used for calculating layout
-
- def SetFont(self, Size, Family, Style, Weight, Underlined, FaceName):
- self.Font = self.FontList.setdefault( (Size,
- Family,
- Style,
- Weight,
- Underlined,
- FaceName),
- #wx.FontFromPixelSize((0.45*Size,Size), # this seemed to give a decent height/width ratio on Windows
- wx.Font(Size,
- Family,
- Style,
- Weight,
- Underlined,
- FaceName) )
-
- def SetColor(self, Color):
- self.Color = Color
-
- def SetBackgroundColor(self, BackgroundColor):
- self.BackgroundColor = BackgroundColor
-
- def SetText(self, String):
- """
- Re-sets the text displayed by the object
-
- In the case of the ScaledTextBox, it will re-do the layout as appropriate
-
- Note: only tested with the ScaledTextBox
-
- """
-
- self.String = String
- self.LayoutText()
-
- def LayoutText(self):
- """
- A dummy method to re-do the layout of the text.
-
- A derived object needs to override this if required.
-
- """
- pass
-
- ## store the function that shift the coords for drawing text. The
- ## "c" parameter is the correction for world coordinates, rather
- ## than pixel coords as the y axis is reversed
- ## pad is the extra space around the text
- ## if world = 1, the vertical shift is done in y-up coordinates
- ShiftFunDict = {'tl': lambda x, y, w, h, world=0, pad=0: (x + pad, y + pad - 2*world*pad),
- 'tc': lambda x, y, w, h, world=0, pad=0: (x - w/2, y + pad - 2*world*pad),
- 'tr': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y + pad - 2*world*pad),
- 'cl': lambda x, y, w, h, world=0, pad=0: (x + pad, y - h/2 + world*h),
- 'cc': lambda x, y, w, h, world=0, pad=0: (x - w/2, y - h/2 + world*h),
- 'cr': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y - h/2 + world*h),
- 'bl': lambda x, y, w, h, world=0, pad=0: (x + pad, y - h + 2*world*h - pad + world*2*pad) ,
- 'bc': lambda x, y, w, h, world=0, pad=0: (x - w/2, y - h + 2*world*h - pad + world*2*pad) ,
- 'br': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y - h + 2*world*h - pad + world*2*pad)}
-
-class Text(TextObjectMixin, DrawObject):
- """Draws a text object
-
- The size is fixed, and does not scale with the drawing.
-
- The hit-test is done on the entire text extent
-
- """
-
- def __init__(self, String, xy,
- Size = 14,
- Color = "Black",
- BackgroundColor = None,
- Family = wx.FONTFAMILY_MODERN,
- Style = wx.FONTSTYLE_NORMAL,
- Weight = wx.FONTWEIGHT_NORMAL,
- Underlined = False,
- Position = 'tl',
- InForeground = False,
- Font = None):
- """Default class constructor.
-
- :param string `string`: the text to draw
- :param `XY`: the (x, y) coordinate of the corner of the text, or a 2-tuple,
- or a (2,) `NumPy `_ array
- :param `Size`: the font size
- :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `BackgroundColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param FontFamily `Family`: a valid :ref:`FontFamily`
- :param FontStyle `Style`: a valid :ref:`FontStyle`
- :param FontWeight `Weight`: a valid :ref:`FontWeight`
- :param boolean `Underlined`: underline the text
- :param string `Position`: a two character string indicating where in
- relation to the coordinates the box should be oriented
- :param boolean `InForeground`: should object be in foreground
- :param Font `Font`: alternatively you can define :ref:`Font` and the
- above will be ignored.
-
- ============== ==========================
- 1st character Meaning
- ============== ==========================
- ``t`` top
- ``c`` center
- ``b`` bottom
- ============== ==========================
-
- ============== ==========================
- 2nd character Meaning
- ============== ==========================
- ``l`` left
- ``c`` center
- ``r`` right
- ============== ==========================
-
- :param Font `Font`: a valid :class:`Font`
- :param boolean `InForeground`: should object be in foreground
-
- """
- DrawObject.__init__(self,InForeground)
-
- self.String = String
- # Input size in in Pixels, compute points size from FontScaleinfo.
- # fixme: for printing, we'll have to do something a little different
- self.Size = Size * FontScale
-
- self.Color = Color
- self.BackgroundColor = BackgroundColor
-
- if not Font:
- FaceName = ''
- else:
- FaceName = Font.GetFaceName()
- Family = Font.GetFamily()
- Size = Font.GetPointSize()
- Style = Font.GetStyle()
- Underlined = Font.GetUnderlined()
- Weight = Font.GetWeight()
- self.SetFont(Size, Family, Style, Weight, Underlined, FaceName)
-
- self.BoundingBox = BBox.asBBox((xy, xy))
-
- self.XY = N.asarray(xy)
- self.XY.shape = (2,)
-
- (self.TextWidth, self.TextHeight) = (None, None)
- self.ShiftFun = self.ShiftFunDict[Position]
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- XY = WorldToPixel(self.XY)
- dc.SetFont(self.Font)
- dc.SetTextForeground(self.Color)
- if self.BackgroundColor:
- dc.SetBackgroundMode(wx.SOLID)
- dc.SetTextBackground(self.BackgroundColor)
- else:
- dc.SetBackgroundMode(wx.TRANSPARENT)
- if self.TextWidth is None or self.TextHeight is None:
- (self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String)
- XY = self.ShiftFun(XY[0], XY[1], self.TextWidth, self.TextHeight)
- dc.DrawText(self.String, XY)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawRectangle(XY, (self.TextWidth, self.TextHeight) )
-
-class ScaledText(TextObjectMixin, DrawObject):
- """
- ##fixme: this can be depricated and jsut use ScaledTextBox with different defaults.
-
- This class creates a text object that is scaled when zoomed. It is
- placed at the coordinates, x,y. the "Position" argument is a two
- charactor string, indicating where in relation to the coordinates
- the string should be oriented.
-
- The first letter is: t, c, or b, for top, center and bottom The
- second letter is: l, c, or r, for left, center and right The
- position refers to the position relative to the text itself. It
- defaults to "tl" (top left).
-
- Size is the size of the font in world coordinates.
-
- * Family: Font family, a generic way of referring to fonts without
- specifying actual facename. One of:
-
- * wx.FONTFAMILY_DEFAULT: Chooses a default font.
- * wx.FONTFAMILY_DECORATIVE: A decorative font.
- * wx.FONTFAMILY_ROMAN: A formal, serif font.
- * wx.FONTFAMILY_SCRIPT: A handwriting font.
- * wx.FONTFAMILY_SWISS: A sans-serif font.
- * wx.FONTFAMILY_MODERN: A fixed pitch font.
-
- .. note:: these are only as good as the wxWindows defaults, which aren't so good.
-
- * Style: One of wx.FONTSTYLE_NORMAL, wx.FONTSTYLE_SLANT and wx.FONTSTYLE_ITALIC.
- * Weight: One of wx.FONTWEIGHT_NORMAL, wx.FONTWEIGHT_LIGHT and wx.FONTWEIGHT_BOLD.
- * Underlined: The value can be True or False. At present this may have an an
- effect on Windows only.
-
-
- Alternatively, you can set the kw arg: Font, to a wx.Font, and the
- above will be ignored. The size of the font you specify will be
- ignored, but the rest of its attributes will be preserved.
-
- The size will scale as the drawing is zoomed.
-
- Bugs/Limitations:
-
- As fonts are scaled, the do end up a little different, so you don't
- get exactly the same picture as you scale up and doen, but it's
- pretty darn close.
-
- On wxGTK1 on my Linux system, at least, using a font of over about
- 3000 pts. brings the system to a halt. It's the Font Server using
- huge amounts of memory. My work around is to max the font size to
- 3000 points, so it won't scale past there. GTK2 uses smarter font
- drawing, so that may not be an issue in future versions, so feel
- free to test. Another smarter way to do it would be to set a global
- zoom limit at that point.
-
- The hit-test is done on the entire text extent. This could be made
- optional, but I haven't gotten around to it.
-
- """
-
- def __init__(self,
- String,
- XY,
- Size,
- Color = "Black",
- BackgroundColor = None,
- Family = wx.FONTFAMILY_MODERN,
- Style = wx.FONTSTYLE_NORMAL,
- Weight = wx.FONTWEIGHT_NORMAL,
- Underlined = False,
- Position = 'tl',
- Font = None,
- InForeground = False):
-
- DrawObject.__init__(self,InForeground)
-
- self.String = String
- self.XY = N.array( XY, N.float)
- self.XY.shape = (2,)
- self.Size = Size
- self.Color = Color
- self.BackgroundColor = BackgroundColor
- self.Family = Family
- self.Style = Style
- self.Weight = Weight
- self.Underlined = Underlined
- if not Font:
- self.FaceName = ''
- else:
- self.FaceName = Font.GetFaceName()
- self.Family = Font.GetFamily()
- self.Style = Font.GetStyle()
- self.Underlined = Font.GetUnderlined()
- self.Weight = Font.GetWeight()
-
- # Experimental max font size value on wxGTK2: this works OK on
- # my system. If it's a lot larger, there is a crash, with the
- # message:
- #
- # The application 'FloatCanvasDemo.py' lost its
- # connection to the display :0.0; most likely the X server was
- # shut down or you killed/destroyed the application.
- #
- # Windows and OS-X seem to be better behaved in this regard.
- # They may not draw it, but they don't crash either!
- self.MaxFontSize = 1000
- self.MinFontSize = 1 # this can be changed to set a minimum size
- self.DisappearWhenSmall = True
- self.ShiftFun = self.ShiftFunDict[Position]
-
- self.CalcBoundingBox()
-
- def LayoutText(self):
- # This will be called when the text is re-set
- # nothing much to be done here
- self.CalcBoundingBox()
-
- def CalcBoundingBox(self):
- ## this isn't exact, as fonts don't scale exactly.
- dc = wx.MemoryDC()
- bitmap = wx.Bitmap(1, 1)
- dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
- DrawingSize = 40 # pts This effectively determines the resolution that the BB is computed to.
- ScaleFactor = float(self.Size) / DrawingSize
- self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
- dc.SetFont(self.Font)
- (w,h) = dc.GetTextExtent(self.String)
- w = w * ScaleFactor
- h = h * ScaleFactor
- x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world = 1)
- self.BoundingBox = BBox.asBBox(((x, y-h ),(x + w, y)))
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- (X,Y) = WorldToPixel( (self.XY) )
-
- # compute the font size:
- Size = abs( ScaleWorldToPixel( (self.Size, self.Size) )[1] ) # only need a y coordinate length
- ## Check to see if the font size is large enough to blow up the X font server
- ## If so, limit it. Would it be better just to not draw it?
- ## note that this limit is dependent on how much memory you have, etc.
- Size = min(Size, self.MaxFontSize)
- Size = max(Size, self.MinFontSize) # smallest size you want - default to 0
-
- # Draw the Text
- if not( self.DisappearWhenSmall and Size <= self.MinFontSize) : # don't try to draw a zero sized font!
- self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
- dc.SetFont(self.Font)
- dc.SetTextForeground(self.Color)
- if self.BackgroundColor:
- dc.SetBackgroundMode(wx.SOLID)
- dc.SetTextBackground(self.BackgroundColor)
- else:
- dc.SetBackgroundMode(wx.TRANSPARENT)
- (w,h) = dc.GetTextExtent(self.String)
- # compute the shift, and adjust the coordinates, if neccesary
- # This had to be put in here, because it changes with Zoom, as
- # fonts don't scale exactly.
- xy = self.ShiftFun(X, Y, w, h)
- dc.DrawText(self.String, xy)
-
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawRectangle(xy, (w, h))
-
-class ScaledTextBox(TextObjectMixin, DrawObject):
- """Draws a text object
-
- The object is scaled when zoomed.
-
- The hit-test is done on the entire text extent
-
- Bugs/Limitations:
-
- As fonts are scaled, they do end up a little different, so you don't
- get exactly the same picture as you scale up and down, but it's
- pretty darn close.
-
- On wxGTK1 on my Linux system, at least, using a font of over about
- 1000 pts. brings the system to a halt. It's the Font Server using
- huge amounts of memory. My work around is to max the font size to
- 1000 points, so it won't scale past there. GTK2 uses smarter font
- drawing, so that may not be an issue in future versions, so feel
- free to test. Another smarter way to do it would be to set a global
- zoom limit at that point.
-
- """
-
- def __init__(self, String,
- Point,
- Size,
- Color = "Black",
- BackgroundColor = None,
- LineColor = 'Black',
- LineStyle = 'Solid',
- LineWidth = 1,
- Width = None,
- PadSize = None,
- Family = wx.FONTFAMILY_MODERN,
- Style = wx.FONTSTYLE_NORMAL,
- Weight = wx.FONTWEIGHT_NORMAL,
- Underlined = False,
- Position = 'tl',
- Alignment = "left",
- Font = None,
- LineSpacing = 1.0,
- InForeground = False):
- """Default class constructor.
-
- :param `Point`: takes a 2-tuple, or a (2,) `NumPy `_
- array of point coordinates
- :param integer `Size`: size in World units
- :param `Color`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `BackgroundColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param `Width`: width in pixels or ``None``, text will be wrapped to
- the given width.
- :param `PadSize`: padding in world units or ``None``, if specified it
- will creating a space (margin) around the text
- :param FontFamily `Family`: a valid :ref:`FontFamily`
- :param FontStyle `Style`: a valid :ref:`FontStyle`
- :param FontWeight `Weight`: a valid :ref:`FontWeight`
- :param boolean `Underlined`: underline the text
- :param string `Position`: a two character string indicating where in
- relation to the coordinates the box should be oriented
-
- ============== ==========================
- 1st character Meaning
- ============== ==========================
- ``t`` top
- ``c`` center
- ``b`` bottom
- ============== ==========================
-
- ============== ==========================
- 2nd character Meaning
- ============== ==========================
- ``l`` left
- ``c`` center
- ``r`` right
- ============== ==========================
-
- :param `Alignment`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param Font `Font`: alternatively a valid :class:`Font` can be defined
- in which case the above will be ignored
- :param float `LineSpacing`: the line space to be used
- :param boolean `InForeground`: should object be in foreground
-
- """
- DrawObject.__init__(self,InForeground)
-
- self.XY = N.array(Point, N.float)
- self.Size = Size
- self.Color = Color
- self.BackgroundColor = BackgroundColor
- self.LineColor = LineColor
- self.LineStyle = LineStyle
- self.LineWidth = LineWidth
- self.Width = Width
- if PadSize is None: # the default is just a little bit of padding
- self.PadSize = Size/10.0
- else:
- self.PadSize = float(PadSize)
- self.Family = Family
- self.Style = Style
- self.Weight = Weight
- self.Underlined = Underlined
- self.Alignment = Alignment.lower()
- self.LineSpacing = float(LineSpacing)
- self.Position = Position
-
- if not Font:
- self.FaceName = ''
- else:
- self.FaceName = Font.GetFaceName()
- self.Family = Font.GetFamily()
- self.Style = Font.GetStyle()
- self.Underlined = Font.GetUnderlined()
- self.Weight = Font.GetWeight()
-
- # Experimental max font size value on wxGTK2: this works OK on
- # my system. If it's a lot larger, there is a crash, with the
- # message:
- #
- # The application 'FloatCanvasDemo.py' lost its
- # connection to the display :0.0; most likely the X server was
- # shut down or you killed/destroyed the application.
- #
- # Windows and OS-X seem to be better behaved in this regard.
- # They may not draw it, but they don't crash either!
-
- self.MaxFontSize = 1000
- self.MinFontSize = 1 # this can be changed to set a larger minimum size
- self.DisappearWhenSmall = True
-
- self.ShiftFun = self.ShiftFunDict[Position]
-
- self.String = String
- self.LayoutText()
- self.CalcBoundingBox()
-
- self.SetPen(LineColor,LineStyle,LineWidth)
- self.SetBrush(BackgroundColor, "Solid")
-
-
- def WrapToWidth(self):
- dc = wx.MemoryDC()
- bitmap = wx.Bitmap(1, 1)
- dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
- DrawingSize = self.LayoutFontSize # pts This effectively determines the resolution that the BB is computed to.
- ScaleFactor = float(self.Size) / DrawingSize
- Width = (self.Width - 2*self.PadSize) / ScaleFactor #Width to wrap to
- self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
- dc.SetFont(self.Font)
- NewStrings = []
- for s in self.Strings:
- #beginning = True
- text = s.split(" ")
- text.reverse()
- LineLength = 0
- NewText = text[-1]
- del text[-1]
- while text:
- w = dc.GetTextExtent(' ' + text[-1])[0]
- if LineLength + w <= Width:
- NewText += ' '
- NewText += text[-1]
- LineLength = dc.GetTextExtent(NewText)[0]
- else:
- NewStrings.append(NewText)
- NewText = text[-1]
- LineLength = dc.GetTextExtent(text[-1])[0]
- del text[-1]
- NewStrings.append(NewText)
- self.Strings = NewStrings
-
- def ReWrap(self, Width):
- self.Width = Width
- self.LayoutText()
-
- def LayoutText(self):
- """
-
- Calculates the positions of the words of text.
-
- This isn't exact, as fonts don't scale exactly.
- To help this, the position of each individual word
- is stored separately, so that the general layout stays
- the same in world coordinates, as the fonts scale.
-
- """
- self.Strings = self.String.split("\n")
- if self.Width:
- self.WrapToWidth()
-
- dc = wx.MemoryDC()
- bitmap = wx.Bitmap(1, 1)
- dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
-
- DrawingSize = self.LayoutFontSize # pts This effectively determines the resolution that the BB is computed to.
- ScaleFactor = float(self.Size) / DrawingSize
-
- self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
- dc.SetFont(self.Font)
- TextHeight = dc.GetTextExtent("X")[1]
- SpaceWidth = dc.GetTextExtent(" ")[0]
- LineHeight = TextHeight * self.LineSpacing
-
- LineWidths = N.zeros((len(self.Strings),), N.float)
- y = 0
- Words = []
- AllLinePoints = []
-
- for i, s in enumerate(self.Strings):
- LineWidths[i] = 0
- LineWords = s.split(" ")
- LinePoints = N.zeros((len(LineWords),2), N.float)
- for j, word in enumerate(LineWords):
- if j > 0:
- LineWidths[i] += SpaceWidth
- Words.append(word)
- LinePoints[j] = (LineWidths[i], y)
- w = dc.GetTextExtent(word)[0]
- LineWidths[i] += w
- y -= LineHeight
- AllLinePoints.append(LinePoints)
- TextWidth = N.maximum.reduce(LineWidths)
- self.Words = Words
-
- if self.Width is None:
- BoxWidth = TextWidth * ScaleFactor + 2*self.PadSize
- else: # use the defined Width
- BoxWidth = self.Width
- Points = N.zeros((0,2), N.float)
-
- for i, LinePoints in enumerate(AllLinePoints):
- ## Scale to World Coords.
- LinePoints *= (ScaleFactor, ScaleFactor)
- if self.Alignment == 'left':
- LinePoints[:,0] += self.PadSize
- elif self.Alignment == 'center':
- LinePoints[:,0] += (BoxWidth - LineWidths[i]*ScaleFactor)/2.0
- elif self.Alignment == 'right':
- LinePoints[:,0] += (BoxWidth - LineWidths[i]*ScaleFactor-self.PadSize)
- Points = N.concatenate((Points, LinePoints))
-
- BoxHeight = -(Points[-1,1] - (TextHeight * ScaleFactor)) + 2*self.PadSize
- #(x,y) = self.ShiftFun(self.XY[0], self.XY[1], BoxWidth, BoxHeight, world=1)
- Points += (0, -self.PadSize)
- self.Points = Points
- self.BoxWidth = BoxWidth
- self.BoxHeight = BoxHeight
- self.CalcBoundingBox()
-
- def CalcBoundingBox(self):
-
- """
-
- Calculates the Bounding Box
-
- """
-
- w, h = self.BoxWidth, self.BoxHeight
- x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world=1)
- self.BoundingBox = BBox.asBBox(((x, y-h ),(x + w, y)))
-
- def GetBoxRect(self):
- wh = (self.BoxWidth, self.BoxHeight)
- xy = (self.BoundingBox[0,0], self.BoundingBox[1,1])
-
- return (xy, wh)
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- xy, wh = self.GetBoxRect()
-
- Points = self.Points + xy
- Points = WorldToPixel(Points)
- xy = WorldToPixel(xy)
- wh = ScaleWorldToPixel(wh) * (1,-1)
-
- # compute the font size:
- Size = abs( ScaleWorldToPixel( (self.Size, self.Size) )[1] ) # only need a y coordinate length
- ## Check to see if the font size is large enough to blow up the X font server
- ## If so, limit it. Would it be better just to not draw it?
- ## note that this limit is dependent on how much memory you have, etc.
- Size = min(Size, self.MaxFontSize)
-
- Size = max(Size, self.MinFontSize) # smallest size you want - default to 1
-
- # Draw The Box
- if (self.LineStyle and self.LineColor) or self.BackgroundColor:
- dc.SetBrush(self.Brush)
- dc.SetPen(self.Pen)
- dc.DrawRectangle(xy , wh)
-
- # Draw the Text
- if not( self.DisappearWhenSmall and Size <= self.MinFontSize) : # don't try to draw a zero sized font!
- self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underlined, self.FaceName)
- dc.SetFont(self.Font)
- dc.SetTextForeground(self.Color)
- dc.SetBackgroundMode(wx.TRANSPARENT)
- dc.DrawTextList(self.Words, Points)
-
- # Draw the hit box.
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawRectangle(xy, wh)
-
-class Bitmap(TextObjectMixin, DrawObject):
- """Draws a bitmap
-
- The size is fixed, and does not scale with the drawing.
-
- """
-
- def __init__(self, Bitmap, XY,
- Position='tl',
- InForeground=False):
- """Default class constructor.
-
- :param Bitmap `Bitmap`: the bitmap to be drawn
- :param `XY`: the (x, y) coordinate of the corner of the bitmap, or a 2-tuple,
- or a (2,) `NumPy `_ array
- :param string `Position`: a two character string indicating where in relation to the coordinates
- the bitmap should be oriented
-
- ============== ==========================
- 1st character Meaning
- ============== ==========================
- ``t`` top
- ``c`` center
- ``b`` bottom
- ============== ==========================
-
- ============== ==========================
- 2nd character Meaning
- ============== ==========================
- ``l`` left
- ``c`` center
- ``r`` right
- ============== ==========================
-
- :param boolean `InForeground`: should object be in foreground
-
- """
-
- DrawObject.__init__(self,InForeground)
-
- if type(Bitmap) == wx.Bitmap:
- self.Bitmap = Bitmap
- elif type(Bitmap) == wx.Image:
- self.Bitmap = wx.Bitmap(Bitmap)
-
- # Note the BB is just the point, as the size in World coordinates is not fixed
- self.BoundingBox = BBox.asBBox( (XY,XY) )
-
- self.XY = XY
-
- (self.Width, self.Height) = self.Bitmap.GetWidth(), self.Bitmap.GetHeight()
- self.ShiftFun = self.ShiftFunDict[Position]
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- XY = WorldToPixel(self.XY)
- XY = self.ShiftFun(XY[0], XY[1], self.Width, self.Height)
- dc.DrawBitmap(self.Bitmap, XY, True)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawRectangle(XY, (self.Width, self.Height) )
-
-class ScaledBitmap(TextObjectMixin, DrawObject):
- """Draws a scaled bitmap
-
- The size scales with the drawing
-
- """
-
- def __init__(self,
- Bitmap,
- XY,
- Height,
- Position = 'tl',
- InForeground = False):
- """Default class constructor.
-
- :param wx.Bitmap `Bitmap`: the bitmap to be drawn
- :param `XY`: the (x, y) coordinate of the corner of the scaled bitmap,
- or a 2-tuple, or a (2,) `NumPy `_ array
- :param `Height`: height to be used, width is calculated from the aspect ratio of the bitmap
- :param string `Position`: a two character string indicating where in relation to the coordinates
- the bitmap should be oriented
-
- ============== ==========================
- 1st character Meaning
- ============== ==========================
- ``t`` top
- ``c`` center
- ``b`` bottom
- ============== ==========================
-
- ============== ==========================
- 2nd character Meaning
- ============== ==========================
- ``l`` left
- ``c`` center
- ``r`` right
- ============== ==========================
-
- :param boolean `InForeground`: should object be in foreground
-
- """
-
- DrawObject.__init__(self,InForeground)
-
- if type(Bitmap) == wx.Bitmap:
- self.Image = Bitmap.ConvertToImage()
- elif type(Bitmap) == wx.Image:
- self.Image = Bitmap
-
- self.XY = XY
- self.Height = Height
- (self.bmpWidth, self.bmpHeight) = self.Image.GetWidth(), self.Image.GetHeight()
- self.Width = self.bmpWidth / self.bmpHeight * Height
- self.ShiftFun = self.ShiftFunDict[Position]
- self.CalcBoundingBox()
- self.ScaledBitmap = None
- self.ScaledHeight = None
-
- def CalcBoundingBox(self):
- """Calculate the bounding box."""
- ## this isn't exact, as fonts don't scale exactly.
- w, h = self.Width, self.Height
- x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world = 1)
- self.BoundingBox = BBox.asBBox( ( (x, y-h ), (x + w, y) ) )
-
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- XY = WorldToPixel(self.XY)
- H = ScaleWorldToPixel(self.Height)[0]
- W = H * (self.bmpWidth / self.bmpHeight)
- if (self.ScaledBitmap is None) or (H != self.ScaledHeight) :
- self.ScaledHeight = H
- Img = self.Image.Scale(W, H)
- self.ScaledBitmap = wx.Bitmap(Img)
-
- XY = self.ShiftFun(XY[0], XY[1], W, H)
- dc.DrawBitmap(self.ScaledBitmap, XY, True)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawRectangle(XY, (W, H) )
-
-class ScaledBitmap2(TextObjectMixin, DrawObject, ):
- """Draws a scaled bitmap
-
- An alternative scaled bitmap that only scaled the required amount of
- the main bitmap when zoomed in: EXPERIMENTAL!
-
- """
-
- def __init__(self,
- Bitmap,
- XY,
- Height,
- Width=None,
- Position = 'tl',
- InForeground = False):
- """Default class constructor.
-
- :param wx.Bitmap `Bitmap`: the bitmap to be drawn
- :param `XY`: the (x, y) coordinate of the corner of the scaled bitmap,
- or a 2-tuple, or a (2,) `NumPy `_ array
- :param `Height`: height to be used
- :param `Width`: width to be used, if ``None`` width is calculated from the aspect ratio of the bitmap
- :param string `Position`: a two character string indicating where in relation to the coordinates
- the bitmap should be oriented
-
- ============== ==========================
- 1st character Meaning
- ============== ==========================
- ``t`` top
- ``c`` center
- ``b`` bottom
- ============== ==========================
-
- ============== ==========================
- 2nd character Meaning
- ============== ==========================
- ``l`` left
- ``c`` center
- ``r`` right
- ============== ==========================
-
- :param boolean `InForeground`: should object be in foreground
-
- """
-
- DrawObject.__init__(self,InForeground)
-
- if type(Bitmap) == wx.Bitmap:
- self.Image = Bitmap.ConvertToImage()
- elif type(Bitmap) == wx.Image:
- self.Image = Bitmap
-
- self.XY = N.array(XY, N.float)
- self.Height = Height
- (self.bmpWidth, self.bmpHeight) = self.Image.GetWidth(), self.Image.GetHeight()
- self.bmpWH = N.array((self.bmpWidth, self.bmpHeight), N.int32)
- ## fixme: this should all accommodate different scales for X and Y
- if Width is None:
- self.BmpScale = float(self.bmpHeight) / Height
- self.Width = self.bmpWidth / self.BmpScale
- self.WH = N.array((self.Width, Height), N.float)
- ##fixme: should this have a y = -1 to shift to y-up?
- self.BmpScale = self.bmpWH / self.WH
-
- #print("bmpWH:", self.bmpWH)
- #print("Width, Height:", self.WH)
- #print("self.BmpScale", self.BmpScale)
- self.ShiftFun = self.ShiftFunDict[Position]
- self.CalcBoundingBox()
- self.ScaledBitmap = None # cache of the last existing scaled bitmap
-
- def CalcBoundingBox(self):
- """Calculate the bounding box."""
- ## this isn't exact, as fonts don't scale exactly.
- w,h = self.Width, self.Height
- x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world = 1)
- self.BoundingBox = BBox.asBBox( ((x, y-h ), (x + w, y)) )
-
- def WorldToBitmap(self, Pw):
- """Computes the bitmap coords from World coords."""
- delta = Pw - self.XY
- Pb = delta * self.BmpScale
- Pb *= (1, -1) ##fixme: this may only works for Yup projection!
- ## and may only work for top left position
-
- return Pb.astype(N.int_)
-
- def _DrawEntireBitmap(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc):
- """
- this is pretty much the old code
-
- Scales and Draws the entire bitmap.
-
- """
- XY = WorldToPixel(self.XY)
- H = ScaleWorldToPixel(self.Height)[0]
- W = H * (self.bmpWidth / self.bmpHeight)
- if (self.ScaledBitmap is None) or (self.ScaledBitmap[0] != (0, 0, self.bmpWidth, self.bmpHeight, W, H) ):
- #if True: #fixme: (self.ScaledBitmap is None) or (H != self.ScaledHeight) :
- self.ScaledHeight = H
- #print("Scaling to:", W, H)
- Img = self.Image.Scale(W, H)
- bmp = wx.Bitmap(Img)
- self.ScaledBitmap = ((0, 0, self.bmpWidth, self.bmpHeight , W, H), bmp)# this defines the cached bitmap
- else:
- #print("Using Cached bitmap")
- bmp = self.ScaledBitmap[1]
- XY = self.ShiftFun(XY[0], XY[1], W, H)
- dc.DrawBitmap(bmp, XY, True)
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawRectangle(XY, (W, H) )
-
- def _DrawSubBitmap(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc):
- """
- Subsets just the part of the bitmap that is visible
- then scales and draws that.
-
- """
- BBworld = BBox.asBBox(self._Canvas.ViewPortBB)
- BBbitmap = BBox.fromPoints(self.WorldToBitmap(BBworld))
-
- XYs = WorldToPixel(self.XY)
- # figure out subimage:
- # fixme: this should be able to be done more succinctly!
-
- if BBbitmap[0,0] < 0:
- Xb = 0
- elif BBbitmap[0,0] > self.bmpWH[0]: # off the bitmap
- Xb = 0
- else:
- Xb = BBbitmap[0,0]
- XYs[0] = 0 # draw at origin
-
- if BBbitmap[0,1] < 0:
- Yb = 0
- elif BBbitmap[0,1] > self.bmpWH[1]: # off the bitmap
- Yb = 0
- ShouldDraw = False
- else:
- Yb = BBbitmap[0,1]
- XYs[1] = 0 # draw at origin
-
- if BBbitmap[1,0] < 0:
- #off the screen -- This should never happen!
- Wb = 0
- elif BBbitmap[1,0] > self.bmpWH[0]:
- Wb = self.bmpWH[0] - Xb
- else:
- Wb = BBbitmap[1,0] - Xb
-
- if BBbitmap[1,1] < 0:
- # off the screen -- This should never happen!
- Hb = 0
- ShouldDraw = False
- elif BBbitmap[1,1] > self.bmpWH[1]:
- Hb = self.bmpWH[1] - Yb
- else:
- Hb = BBbitmap[1,1] - Yb
-
- FullHeight = ScaleWorldToPixel(self.Height)[0]
- scale = FullHeight / self.bmpWH[1]
- Ws = int(scale * Wb + 0.5) # add the 0.5 to round
- Hs = int(scale * Hb + 0.5)
- if (self.ScaledBitmap is None) or (self.ScaledBitmap[0] != (Xb, Yb, Wb, Hb, Ws, Ws) ):
- Img = self.Image.GetSubImage(wx.Rect(Xb, Yb, Wb, Hb))
- print("rescaling with High quality")
- Img.Rescale(Ws, Hs, quality=wx.IMAGE_QUALITY_HIGH)
- bmp = wx.Bitmap(Img)
- self.ScaledBitmap = ((Xb, Yb, Wb, Hb, Ws, Ws), bmp)# this defines the cached bitmap
- #XY = self.ShiftFun(XY[0], XY[1], W, H)
- #fixme: get the shiftfun working!
- else:
- #print("Using cached bitmap")
- ##fixme: The cached bitmap could be used if the one needed is the same scale, but
- ## a subset of the cached one.
- bmp = self.ScaledBitmap[1]
- dc.DrawBitmap(bmp, XYs, True)
-
- if HTdc and self.HitAble:
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawRectangle(XYs, (Ws, Hs) )
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- BBworld = BBox.asBBox(self._Canvas.ViewPortBB)
- ## first see if entire bitmap is displayed:
- if BBworld.Inside(self.BoundingBox):
- #print("Drawing entire bitmap with old code")
- self._DrawEntireBitmap(dc , WorldToPixel, ScaleWorldToPixel, HTdc)
- return None
- elif BBworld.Overlaps(self.BoundingBox):
- #BBbitmap = BBox.fromPoints(self.WorldToBitmap(BBworld))
- #print("Drawing a sub-bitmap")
- self._DrawSubBitmap(dc , WorldToPixel, ScaleWorldToPixel, HTdc)
- else:
- #print("Not Drawing -- no part of image is showing")
- pass
-
-class DotGrid:
- """
- An example of a Grid Object -- it is set on the FloatCanvas with one of:
-
- FloatCanvas.GridUnder = Grid
- FloatCanvas.GridOver = Grid
-
- It will be drawn every time, regardless of the viewport.
-
- In its _Draw method, it computes what to draw, given the ViewPortBB
- of the Canvas it's being drawn on.
-
- """
- def __init__(self, Spacing, Size = 2, Color = "Black", Cross=False, CrossThickness = 1):
-
- self.Spacing = N.array(Spacing, N.float)
- self.Spacing.shape = (2,)
- self.Size = Size
- self.Color = Color
- self.Cross = Cross
- self.CrossThickness = CrossThickness
-
- def CalcPoints(self, Canvas):
- ViewPortBB = Canvas.ViewPortBB
-
- Spacing = self.Spacing
-
- minx, miny = N.floor(ViewPortBB[0] / Spacing) * Spacing
- maxx, maxy = N.ceil(ViewPortBB[1] / Spacing) * Spacing
-
- ##fixme: this could use vstack or something with numpy
- x = N.arange(minx, maxx+Spacing[0], Spacing[0]) # making sure to get the last point
- y = N.arange(miny, maxy+Spacing[1], Spacing[1]) # an extra is OK
- Points = N.zeros((len(y), len(x), 2), N.float)
- x.shape = (1,-1)
- y.shape = (-1,1)
- Points[:,:,0] += x
- Points[:,:,1] += y
- Points.shape = (-1,2)
-
- return Points
-
- def _Draw(self, dc, Canvas):
- Points = self.CalcPoints(Canvas)
-
- Points = Canvas.WorldToPixel(Points)
-
- dc.SetPen(wx.Pen(self.Color,self.CrossThickness))
-
- if self.Cross: # Use cross shaped markers
- #Horizontal lines
- LinePoints = N.concatenate((Points + (self.Size,0),Points + (-self.Size,0)),1)
- dc.DrawLineList(LinePoints)
- # Vertical Lines
- LinePoints = N.concatenate((Points + (0,self.Size),Points + (0,-self.Size)),1)
- dc.DrawLineList(LinePoints)
- pass
- else: # use dots
- ## Note: this code borrowed from Pointset -- it really shouldn't be repeated here!.
- if self.Size <= 1:
- dc.DrawPointList(Points)
- elif self.Size <= 2:
- dc.DrawPointList(Points + (0,-1))
- dc.DrawPointList(Points + (0, 1))
- dc.DrawPointList(Points + (1, 0))
- dc.DrawPointList(Points + (-1,0))
- else:
- dc.SetBrush(wx.Brush(self.Color))
- radius = int(round(self.Size/2))
- ##fixme: I really should add a DrawCircleList to wxPython
- if len(Points) > 100:
- xy = Points
- xywh = N.concatenate((xy-radius, N.ones(xy.shape) * self.Size ), 1 )
- dc.DrawEllipseList(xywh)
- else:
- for xy in Points:
- dc.DrawCircle(xy[0],xy[1], radius)
-
-class Arc(XYObjectMixin, LineAndFillMixin, DrawObject):
- """Draws an arc of a circle, centered on point ``CenterXY``, from
- the first point ``StartXY`` to the second ``EndXY``.
-
- The arc is drawn in an anticlockwise direction from the start point to
- the end point.
-
- """
- def __init__(self,
- StartXY,
- EndXY,
- CenterXY,
- LineColor = "Black",
- LineStyle = "Solid",
- LineWidth = 1,
- FillColor = None,
- FillStyle = "Solid",
- InForeground = False):
- """Default class constructor.
-
- :param `StartXY`: start point, takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param `EndXY`: end point, takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param `CenterXY`: center point, takes a 2-tuple, or a (2,)
- `NumPy `_ array of point coordinates
- :param `LineColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `LineStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineStyle`
- :param `LineWidth`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetLineWidth`
- :param `FillColor`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetColor`
- :param `FillStyle`: see :meth:`~lib.floatcanvas.FloatCanvas.DrawObject.SetFillStyle`
- :param boolean `InForeground`: should object be in foreground
-
- """
-
- DrawObject.__init__(self, InForeground)
-
- # There is probably a more elegant way to do this next section
- # The bounding box just gets set to the WH of a circle, with center at CenterXY
- # This is suitable for a pie chart as it will be a circle anyway
- radius = N.sqrt( (StartXY[0]-CenterXY[0])**2 + (StartXY[1]-CenterXY[1])**2 )
- minX = CenterXY[0]-radius
- minY = CenterXY[1]-radius
- maxX = CenterXY[0]+radius
- maxY = CenterXY[1]+radius
- XY = [minX,minY]
- WH = [maxX-minX,maxY-minY]
-
- self.XY = N.asarray( XY, N.float).reshape((2,))
- self.WH = N.asarray( WH, N.float).reshape((2,))
-
- self.StartXY = N.asarray(StartXY, N.float).reshape((2,))
- self.CenterXY = N.asarray(CenterXY, N.float).reshape((2,))
- self.EndXY = N.asarray(EndXY, N.float).reshape((2,))
-
- #self.BoundingBox = array((self.XY, (self.XY + self.WH)), Float)
- self.CalcBoundingBox()
-
- #Finish the setup; allocate color,style etc.
- self.LineColor = LineColor
- self.LineStyle = LineStyle
- self.LineWidth = LineWidth
- self.FillColor = FillColor
- self.FillStyle = FillStyle
-
- self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
-
- self.SetPen(LineColor, LineStyle, LineWidth)
- self.SetBrush(FillColor, FillStyle) #Why isn't this working ???
-
- def Move(self, Delta):
- """Move the object by delta
-
- :param `Delta`: delta is a (dx, dy) pair. Ideally a `NumPy `_
- array of shape (2,)
-
- """
-
- Delta = N.asarray(Delta, N.float)
- self.XY += Delta
- self.StartXY += Delta
- self.CenterXY += Delta
- self.EndXY += Delta
- self.BoundingBox += Delta
-
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- self.SetUpDraw(dc , WorldToPixel, ScaleWorldToPixel, HTdc)
- StartXY = WorldToPixel(self.StartXY)
- EndXY = WorldToPixel(self.EndXY)
- CenterXY = WorldToPixel(self.CenterXY)
-
- dc.DrawArc(StartXY, EndXY, CenterXY)
- if HTdc and self.HitAble:
- HTdc.DrawArc(StartXY, EndXY, CenterXY)
-
- def CalcBoundingBox(self):
- """Calculate the bounding box."""
- self.BoundingBox = BBox.asBBox( N.array((self.XY, (self.XY + self.WH) ),
- N.float) )
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
-
#---------------------------------------------------------------------------
class FloatCanvas(wx.Panel):
"""
@@ -2815,7 +328,6 @@ class FloatCanvas(wx.Panel):
wx.Panel.__init__( self, parent, id, wx.DefaultPosition, size, **kwargs)
- self.ComputeFontScale()
self.InitAll()
self.BackgroundBrush = wx.Brush(BackgroundColor, wx.SOLID)
@@ -2857,18 +369,6 @@ class FloatCanvas(wx.Panel):
# self.CreateCursors()
- def ComputeFontScale(self):
- """
- Compute the font scale.
-
- A global variable to hold the scaling from pixel size to point size.
- """
- global FontScale
- dc = wx.ScreenDC()
- dc.SetFont(wx.Font(16, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
- E = dc.GetTextExtent("X")
- FontScale = 16/E[1]
- del dc
def InitAll(self):
"""
diff --git a/wx/lib/floatcanvas/SpecialObjects/PieChart.py b/wx/lib/floatcanvas/SpecialObjects/PieChart.py
deleted file mode 100644
index 2325b0c4..00000000
--- a/wx/lib/floatcanvas/SpecialObjects/PieChart.py
+++ /dev/null
@@ -1,138 +0,0 @@
-import wx
-
-## import a local version of FloatCanvas
-
-
-from wx.lib.floatcanvas import FloatCanvas
-from wx.lib.floatcanvas.Utilities import BBox
-from wx.lib.floatcanvas.Utilities import Colors
-
-import numpy as N
-
-XYObjectMixin = FloatCanvas.XYObjectMixin
-LineOnlyMixin = FloatCanvas.LineOnlyMixin
-DrawObject = FloatCanvas.DrawObject
-
-class PieChart(XYObjectMixin, LineOnlyMixin, DrawObject):
- """
- This is DrawObject for a pie chart
-
- You can pass in a bunch of values, and it will draw a pie chart for
- you, and it will make the chart, scaling the size of each "slice" to
- match your values.
-
- The parameters are:
-
- XY : The (x,y) coords of the center of the chart
- Diameter : The diamter of the chart in worls coords, unless you set
- "Scaled" to False, in which case it's in pixel coords.
- Values : sequence of values you want to make the chart of.
- FillColors=None : sequence of colors you want the slices. If
- None, it will choose (no guarantee youll like them!)
- FillStyles=None : Fill style you want ("Solid", "Hash", etc)
- LineColor = None : Color of lines separating the slices
- LineStyle = "Solid" : style of lines separating the slices
- LineWidth = 1 : With of lines separating the slices
- Scaled = True : Do you want the pie to scale when zooming? or stay the same size in pixels?
- InForeground = False: Should it be on the foreground?
-
-
- """
-
-
- ##fixme: this should be a longer and better designed set.
- ## Maybe one from: http://geography.uoregon.edu/datagraphics/color_scales.htm
- DefaultColorList = Colors.CategoricalColor1
- #["Red", "Green", "Blue", "Purple", "Yellow", "Cyan"]
-
- def __init__(self,
- XY,
- Diameter,
- Values,
- FillColors=None,
- FillStyles=None,
- LineColor = None,
- LineStyle = "Solid",
- LineWidth = 1,
- Scaled = True,
- InForeground = False):
- DrawObject.__init__(self, InForeground)
-
- self.XY = N.asarray(XY, N.float).reshape( (2,) )
- self.Diameter = Diameter
- self.Values = N.asarray(Values, dtype=N.float).reshape((-1,1))
- if FillColors is None:
- FillColors = self.DefaultColorList[:len(Values)]
- if FillStyles is None:
- FillStyles = ['Solid'] * len(FillColors)
- self.FillColors = FillColors
- self.FillStyles = FillStyles
- self.LineColor = LineColor
- self.LineStyle = LineStyle
-
- self.Scaled = Scaled
- self.InForeground = InForeground
-
- self.SetPen(LineColor, LineStyle, LineWidth)
- self.SetBrushes()
- self.CalculatePoints()
-
- def SetFillColors(self, FillColors):
- self.FillColors = FillColors
- self.SetBrushes()
-
- def SetFillStyles(self, FillStyles):
- self.FillStyles = FillStyles
- self.SetBrushed()
-
- def SetValues(self, Values):
- Values = N.asarray(Values, dtype=N.float).reshape((-1,1))
- self.Values = Values
- self.CalculatePoints()
-
- def CalculatePoints(self):
- # add the zero point to start
- Values = N.vstack( ( (0,), self.Values) )
- self.Angles = 360. * Values.cumsum()/Values.sum()
- self.CalcBoundingBox()
-
- def SetBrushes(self):
- self.Brushes = []
- for FillColor, FillStyle in zip(self.FillColors, self.FillStyles):
- if FillColor is None or FillStyle is None:
- self.Brush = wx.TRANSPARENT_BRUSH
- else:
- self.Brushes.append(self.BrushList.setdefault( (FillColor, FillStyle),
- wx.Brush( FillColor, self.FillStyleList[FillStyle] )
- )
- )
- def CalcBoundingBox(self):
- if self.Scaled:
- self.BoundingBox = BBox.asBBox( ((self.XY-self.Diameter),(self.XY+self.Diameter)) )
- else:
- self.BoundingBox = BBox.asBBox((self.XY, self.XY))
- if self._Canvas:
- self._Canvas.BoundingBoxDirty = True
-
- def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
- CenterXY = WorldToPixel(self.XY)
- if self.Scaled:
- Diameter = ScaleWorldToPixel( (self.Diameter,self.Diameter) )[0]
- else:
- Diameter = self.Diameter
- WH = N.array((Diameter,Diameter), dtype = N.float)
- Corner = CenterXY - (WH / 2)
- dc.SetPen(self.Pen)
- for i, brush in enumerate(self.Brushes):
- dc.SetBrush( brush )
- dc.DrawEllipticArc(Corner[0], Corner[1], WH[0], WH[1], self.Angles[i], self.Angles[i+1])
- if HTdc and self.HitAble:
- if self.Scaled:
- radius = (ScaleWorldToPixel(self.Diameter)/2)[0]# just the x-coord
- else:
- radius = self.Diameter/2
- HTdc.SetPen(self.HitPen)
- HTdc.SetBrush(self.HitBrush)
- HTdc.DrawCircle(CenterXY, radius)
-
-
diff --git a/wx/lib/floatcanvas/SpecialObjects/__init__.py b/wx/lib/floatcanvas/SpecialObjects/__init__.py
deleted file mode 100644
index 14cf0fa3..00000000
--- a/wx/lib/floatcanvas/SpecialObjects/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""
-SpecialObjects Package
-
-Various special objects -- to specific for inclusion in the main FloatCanvas
-
-"""
-from PieChart import PieChart
diff --git a/wx/lib/floatcanvas/Utilities/Colors.py b/wx/lib/floatcanvas/Utilities/Colors.py
index 939e3b54..ea8b5831 100755
--- a/wx/lib/floatcanvas/Utilities/Colors.py
+++ b/wx/lib/floatcanvas/Utilities/Colors.py
@@ -1,4 +1,16 @@
#!/usr/bin/env python
+#----------------------------------------------------------------------------
+# Name: Colors.py
+# Purpose: Contains color lists used in FloatCanvas
+#
+# Author:
+#
+# Created:
+# Version:
+# Date:
+# Licence:
+# Tags: phoenix-port, unittest, documented, py3-port
+#----------------------------------------------------------------------------
"""
Colors.py
diff --git a/wx/lib/floatcanvas/Utilities/GUI.py b/wx/lib/floatcanvas/Utilities/GUI.py
index 075c4492..44df6c4d 100644
--- a/wx/lib/floatcanvas/Utilities/GUI.py
+++ b/wx/lib/floatcanvas/Utilities/GUI.py
@@ -1,3 +1,16 @@
+#!/usr/bin/env python
+#----------------------------------------------------------------------------
+# Name: GUI.py
+# Purpose: Contains GUI related utilities for FloatCanvas
+#
+# Author:
+#
+# Created:
+# Version:
+# Date:
+# Licence:
+# Tags: phoenix-port, unittest, documented, py3-port
+#----------------------------------------------------------------------------
"""
Part of the floatcanvas.Utilities package.