2""" @brief  Windows that display Curves or images.
 
    4    This module can be used independently.
 
    7    - Image_Area:   An area that displays an image
 
    9    - Draw_Area:    Basic drawing Area
 
   10        function:   plot(<plot_number>, (<x>, <y>) )
 
   11    - Plot_Area:    Draw_Area + grid + automatic resizing
 
   12        function:   plot(<plot_number>, (<x>, <y>) )
 
   13    - Ground:      Defines a 2-D region where agents are located 
and may move
 
   14        functions:  create_agent(<name>, <colour_number>, (<x>, <y>) )
 
   15                    move_agent(<name>, (<Newx>, <Newy>) )
 
   19        self.W = QPlot_Area.AreaView(QPlot_Area.Draw_Area)
 
   21        self.W.Area.plot(3,(10,10))
 
   22        self.W.Area.plot(3,(20,40))
 
   23        self.W.Area.plot(3,(10,22))
 
   26#============================================================================# 
   27# EVOLIFE  http://evolife.telecom-paris.fr             Jean-Louis Dessalles   
   44if __name__ == 
'__main__':  sys.path.append(
'../..')  
 
   53    from PyQt5 
import QtGui, QtCore, QtWidgets
 
   56    from PyQt4 
import QtGui, QtCore
 
   57    from PyQt4 
import QtGui 
as QtWidgets
 
   59    if sys.version_info >= (3,0):   QString = 
lambda x: x
 
   60    else:                           QString = QtCore.QString
 
   69ZoomL = 
lambda *L: map(
lambda x: int(x * L[0]), L[1:])  
 
   77    """ Defines a logical Area on which objects are drawn (hidden from screen) 
   80    def __init__(self, image=None, width=1000, height=1000, EventInterpreter=None, zoom=1):
 
   81        """ Inherits from QGraphicsScene 
   83        QtWidgets.QGraphicsScene.__init__(self, 0, 0, width, height)    
   84        if image 
is not None and os.path.exists(str(image)):
 
   93            if image_ID 
is not None:
 
   94                self.
Board.fill(QtGui.QColor(image_ID[1]))
 
   95            elif image 
and image.startswith(
'#'):   
 
   96                self.
Board.fill(QtGui.QColor(image))
 
   99                self.
Board.fill(QtGui.QColor(
"#FFFFFF"))
 
  102        self.
W = int(self.width())
 
  103        self.
H = int(self.height())
 
  113        """ Stores new dimensions and redraws 
  121        """ returns dimensions 
  123        return (self.width(), self.height())
 
  126        """ the whole scene is redrawn when the scale changes 
  128        for obj 
in self.items():
 
  132        self.setSceneRect(self.itemsBoundingRect())
 
  137        """ Unused ------ just for test  
  139        item = QtGui.QGraphicsEllipseItem(20, 10, 40, 20, None, self)
 
  140        qp = QtGui.QPainter()
 
  143        pen.setColor(QtCore.Qt.red)
 
  147            x = random.randint(1, self.
W-1)
 
  148            y = random.randint(1, self.
H-1)
 
  149            qp.drawLine(0,0,x, y)    
 
  160    """ Draw_Area: Basic drawing Area 
  163    def __init__(self, image=None, width=400, height=400, EventInterpreter=None, zoom=1):
 
  164        """ Calls Image_Area constructor 
  167        Image_Area.__init__(self, image, width, height, EventInterpreter, zoom=zoom) 
  178        Curves.__init__(self)
 
  182        """ surrounding margins (in Image_Area pixels) 
  190        """ Defining one pen and one QPainterPath per curve 
  196            self.
Pens[Curve.ID] = QtGui.QPen()
 
  197            self.
Pens[Curve.ID].setColor(QtGui.QColor(Curve.colour))
 
  198            self.
Pens[Curve.ID].setWidth(3)
 
  203        """ Initial display - to be overloaded 
  208        """ converts physical coordinates into logical pixel(between 0 and scaleX) 
  215        """ converts logical pixel (between 0 and scaleX) into physical coordinates through rescaling  
  224        """ Conversion of logical coordinates into physical coordinates through scaling and margin translation  
  230        """ Conversion of Point into Qt point 
  234        return QtCore.QPointF(x, y)
 
  236    def draw(self, oldpoint, newpoint, Width=3, ColorID=0, Tag=''):
 
  237        """ adds a segment on a given curve  
  243            self.
Pens[ColorID].setWidth(int(Width))
 
  246    def drawTo(self, newpoint, Width=3, ColorID=0, Drawing=True, Tag=''):
 
  247        """ draws an additional segment on a given curve (from the end of the previous one) 
  256        self.
draw(self.
Curves[ColorID].last(), newpoint, Width, ColorID, Tag)
 
  260        """ erase curves, items and restore grid 
  264        for obj 
in self.items():
 
  271        """ the whole picture is redrawn when the scale changes 
  273        Image_Area.redraw(self) 
  277            for Segment 
in Curve:
 
  279                self.
draw(Segment[0], Segment[1], Width=Curve.thick, ColorID=Curve.ID)
 
  282        """ performs a change of scale when necessary 
  285        def rescale(Scale, coord):
 
  291                ordre = 10 ** (int(log(coord,10)))  
 
  292                Scale = (coord // ordre + 1) * ordre
 
  297        if isinstance(Point, Stroke):   
 
  298            ws, hs = self.
pixel2xy((Point.size, Point.size))
 
  300            (x,y) = (Point.x + ws, Point.y + hs)
 
  306    def plot(self, Curve_designation, newpoint, Width=3):
 
  307        """ draws an additional segment on a curve 
  314            self.
Curves[Curve_id].thick = Width     
 
  315            self.
drawTo(newpoint, Width, Curve_id)  
 
  318            error(
"Draw_Area: unknown Curve ID")
 
  320    def move(self, Curve_designation, newpoint):
 
  321        """ introduces a discontinuity in a curve 
  328            self.CurveAddPoint(Curve_id, newpoint, Draw=
False)
 
  330            error(
"Draw_Area: unknown Curve ID")
 
  332    def speck(self, Curve_id, newpoint, Size=3):
 
  336        if self.reframe(newpoint):  self.redraw()
 
  337        self.move(Curve_id, newpoint)
 
  338        self.plot(Curve_id, newpoint, Width=Size)
 
  341        """ convert mouse position into window coordinates  
  343        mousePosition = MouseEvent.scenePos() 
  344        x = mousePosition.x() - self.LeftMargin 
  345        y = self.H - mousePosition.y() - self.BottomMargin 
  346        if x >= 0 
and y >= 0 
and self.EventInterpreter:
 
  347            return self.pixel2xy((x,y))
 
  351        """ retrieves mouse clicks - added by Gauthier Tallec  
  353        Loc = self.mouseLocate(MouseEvent) 
  355            self.EventInterpreter((
'MouseClick', Loc))
 
  358        """ retrieves mouse clicks  
  360        Loc = self.mouseLocate(MouseEvent) 
  362            self.EventInterpreter((
'MouseDeClick', Loc))
 
  365        """ retrieves mouse clicks  
  367        Loc = self.mouseLocate(MouseEvent) 
  369            self.EventInterpreter((
'MouseDoubleClick', Loc))
 
  372        """ retrieves mouse movements when button is pressed  
  374        Loc = self.mouseLocate(MouseEvent) 
  376            self.EventInterpreter((
'MouseMove', Loc))
 
  385    """ Definition of the drawing area, with grid, legend and curves 
  387    def __init__(self, image=None, width=556, height=390, EventInterpreter=None, zoom=1):
 
  388        """ Calls Draw_Area constructor and sets margins  
  390        Draw_Area.__init__(self, image, width, height, EventInterpreter, zoom=zoom) 
  396        """ Drawing a grid with axes and legend   
  401        GridColour = '#A64910' 
  402        gridPen = QtGui.QPen()
 
  403        gridPen.setColor(QtGui.QColor(GridColour))
 
  404        gridPen.setWidth(int(self.
zoom))
 
  406        for i 
in range(NbMailles+1): 
 
  414        gridPen.setWidth(int(self.
zoom * 2))
 
  419        PSize = int((13 - 2 * int(log(max(self.
scaleX, self.
scaleY), 10))))     
 
  420        for i 
in range(NbMailles+1):
 
  422            self.addSimpleText(
QString(str(int(i*self.
scaleX//NbMailles)).center(3)),
 
  423                        QtGui.QFont(
QString(
"Arial"), PSize)).setPos(QtCore.QPointF(self.
LeftMargin+i*mailleX-int(self.
zoom*12), 
 
  426            self.addSimpleText(
QString(str(int(i*self.
scaleY//NbMailles)).rjust(3)),
 
  434                              QtGui.QPen(QtGui.QColor(C.colour)), QtGui.QBrush(QtGui.QColor(C.colour), QtCore.Qt.SolidPattern))
 
  438    def plot(self, Curve_id, newpoint, Width=3):
 
  439        """ A version of PLOT that checks whether the new segment 
  440            remains within the frame. If not, the scale 
is changed
 
  441            and all curves are replotted
 
  443        if self.
reframe(newpoint, Anticipation=
True):
 
  445        Draw_Area.plot(self, Curve_id, newpoint, Width=Width)
 
  452    """ Defines a 2-D region where agents are located and may move 
  455    DEFAULTSHAPE = 
'ellipse' 
  458    def __init__(self, image=None, width=400, height=300, legend=True, EventInterpreter=None, zoom=1):
 
  459        """ Creates a Draw_Area and initializes agents 
  464        Draw_Area.__init__(self, image, width, height, EventInterpreter, zoom=zoom)
 
  475        """ if toric, edges 'touch' each other 
  480        """ Writing maximal values for both axes  
  491        """ Coord is a tuple. 
  492            It has the following form: 
  493            (x, y, colour, size, ToX, ToY, segmentColour, segmentThickness, 'shape=<form>')
 
  494            (shorter tuples are automatically continued 
with default values - 
'shape=...' can be inserted anywhere)
 
  495            The effect 
is that an object of size 
'size' is drawn at location (x,y) (your coordinates, 
not pixels)
 
  496            and a segment starting 
from that blob 
is drawn to (ToX, ToY) (
if these values are given)
 
  507            if type(x) == str 
and x.lower().startswith(
'shape'):    Shape = x[x.rfind(
'=')+1:].strip()
 
  509        Start = 
Stroke(Coord1[:4], RefSize=self.
W)
 
  510        if len(Coord1) > 4: End = 
Stroke(Coord1[4:], RefSize=self.
W)
 
  512        return (Start, End, Shape)
 
  515        """ process modulo if toric and calls Draw_Area's convert 
  518            Coord = (Coord[0] % self.
scaleX, Coord[1] % self.
scaleY)
 
  519        return Draw_Area.convert(self, Coord)
 
  522        """ creates a dot at some location that represents an agent  
  526    def move_agent(self, Name, Coord=None, Position=None, Segment=None, Shape=None):
 
  527        """ moves an agent's representative dot to some new location  
  529        def physical_move(AgentRef, Location):
 
  530            """ moves a shape to a location  
  533            AgentRef.setPos(QtCore.QPointF(Location[0],Location[1]))    
 
  537        if Coord:   (Position, Segment, Shape) = self.
coordinates(Coord)
 
  538        else:   Coord = Position.point() + Segment.point() + ((
'shape=%s' % Shape,) 
if Shape 
else ())
 
  544            if self.
positions[Name].colour != Position.colour 
or self.
shapes.get(Name) != Shape:
 
  548                if str(Position.colour).startswith(
'-'):
 
  552            if self.
positions[Name].Coord == Position.Coord \
 
  553                    and self.
segments[Name].Coord == Segment.Coord \
 
  554                    and self.
shapes.get(Name) == Shape:
 
  562            physical_move(AgentRef, Location)
 
  567            else:   SegmentRef = 
None 
  573                if str(Position.colour).startswith(
'-'):
 
  578                physical_move(AgentRef, Location)
 
  582                else:   SegmentRef = 
None 
  584                error(
"Draw_Area: unknown colour ID")
 
  591        """ creates a graphic agent and returns the Q-reference  
  594        if Position.PixelSize:
 
  595            PhysicalW = PhysicalH = Position.size   
 
  597            PhysicalW, PhysicalH = self.
xy2pixel((Position.size, Position.size))    
 
  598        if Shape == 
'ellipse':
 
  599            return self.addEllipse(0, -PhysicalH, PhysicalW, PhysicalH,
 
  601                                 QtGui.QBrush(QtGui.QColor(
EvolifeColourID(Position.colour)[1]), QtCore.Qt.SolidPattern))
 
  602        if Shape == 
'rectangle':
 
  603            return self.addRect(0, -PhysicalH, PhysicalW, PhysicalH,
 
  605                                 QtGui.QBrush(QtGui.QColor(
EvolifeColourID(Position.colour)[1]), QtCore.Qt.SolidPattern))
 
  607            if os.path.exists(Shape):
 
  612            if Position.colour 
and type(Position.colour) == int:
 
  613                agent = self.addPixmap(self.
KnownShapes[Shape].transformed(QtGui.QTransform().rotate(-Position.colour)))
 
  617            scaleW = float(PhysicalW) / agent.boundingRect().width()
 
  618            scaleH = float(PhysicalH) / agent.boundingRect().width()    
 
  619            agent.setTransform(QtGui.QTransform.fromScale(scaleW, scaleH))  
 
  621        error(
"Ground: unknown shape: %s" % Shape)
 
  625        """ creates a graphic segment and returns the Q-reference  
  628        self.Pens[Colour].setWidth(Segment.size) 
  631        return self.addLine(Location1[0], Location1[1], Location2[0], Location2[1], self.
Pens[Colour])
 
  634        """ removes an agent from the ground 
  642        """ removes a segment from the ground 
  649        """ Returns the identificiation of all agents on the ground  
  654        """ removes agents that are not in Present  
  660        """ move agents vertically (not yet used) 
  667        """ draws a blob and a segment, as for an agent,  
  668            but without agent name and without moving 
and removing options 
 
  670        (Position, Segment, Shape) = self.coordinates(Coord)     
  677                self.
move(Segment.colour, Position.point())
 
  678                self.
plot(Segment.colour, Segment.point(), Width=Segment.size)
 
  680                self.
move(Segment.colour, Segment.point())
 
  681                self.speck(Segment.colour, Segment.point(), Size=0) 
 
  682        self.speck(Position.colour, Position.point(), Size=Position.size)
 
  685        """ removes agents and erases Draw_Area 
  689        Draw_Area.erase(self)   
 
  693        """ the whole picture is redrawn when the scale changes  
  696        Draw_Area.redraw(self)  
 
  702        """ display an agent that is still present in 'positions' and in 'segments', but is graphically dead  
  705                Shape=self.shapes.get(Name)) 
  713if __name__ == 
"__main__":
 
  718__author__ = 
'Dessalles' 
Stores a list of 'Curves'.
 
def Legend(self)
returns tuples (ID, colour, colourname, curvename, legend) representing active curves
 
def CurveAddPoint(self, Curve_id, Point, Draw=True)
Adds a point to a Curve.
 
Stroke: drawing element (point or segment) #.
 
Draw_Area: Basic drawing Area.
 
def move(self, Curve_designation, newpoint)
introduces a discontinuity in a curve
 
def __init__(self, image=None, width=400, height=400, EventInterpreter=None, zoom=1)
Calls Image_Area constructor Initializes Curves.
 
def pixel2xy(self, Point)
converts physical coordinates into logical pixel(between 0 and scaleX)
 
def grid(self)
Initial display - to be overloaded.
 
def init_Pens(self)
Defining one pen and one QPainterPath per curve.
 
def set_margins(self, Left, Right, Bottom, Top)
surrounding margins (in Image_Area pixels)
 
def convert(self, Point)
Conversion of logical coordinates into physical coordinates through scaling and margin translation.
 
def xy2pixel(self, Point)
converts logical pixel (between 0 and scaleX) into physical coordinates through rescaling
 
def Q_Convert(self, Point)
Conversion of Point into Qt point.
 
def erase(self)
erase curves, items and restore grid
 
def drawTo(self, newpoint, Width=3, ColorID=0, Drawing=True, Tag='')
draws an additional segment on a given curve (from the end of the previous one)
 
def redraw(self)
the whole picture is redrawn when the scale changes
 
def plot(self, Curve_designation, newpoint, Width=3)
draws an additional segment on a curve
 
def reframe(self, Point, Anticipation=False)
performs a change of scale when necessary
 
def draw(self, oldpoint, newpoint, Width=3, ColorID=0, Tag='')
adds a segment on a given curve
 
Defines a 2-D region where agents are located and may move.
 
def convert(self, Coord)
process modulo if toric and calls Draw_Area's convert
 
def setToric(self, Toric=True)
if toric, edges 'touch' each other
 
def remove_agent(self, Name)
removes an agent from the ground
 
def grid(self)
Writing maximal values for both axes.
 
def erase(self)
removes agents and erases Draw_Area
 
def move_agent(self, Name, Coord=None, Position=None, Segment=None, Shape=None)
moves an agent's representative dot to some new location
 
def draw_tailed_blob(self, Coord)
draws a blob and a segment, as for an agent, but without agent name and without moving and removing o...
 
def __init__(self, image=None, width=400, height=300, legend=True, EventInterpreter=None, zoom=1)
Creates a Draw_Area and initializes agents.
 
def on_ground(self)
Returns the identificiation of all agents on the ground.
 
def remove_segment(self, Name)
removes a segment from the ground
 
def remove_absent(self, Present)
removes agents that are not in Present
 
def create_graphic_agent(self, Position, Shape=None)
creates a graphic agent and returns the Q-reference
 
def create_agent(self, Name, Coord)
creates a dot at some location that represents an agent
 
def scroll(self)
move agents vertically (not yet used)
 
def create_graphic_segment(self, Position, Segment)
creates a graphic segment and returns the Q-reference
 
def redraw(self)
the whole picture is redrawn when the scale changes
 
def restore_agent(self, Name)
display an agent that is still present in 'positions' and in 'segments', but is graphically dead
 
def coordinates(self, Coord)
Coord is a tuple.
 
Basic graphic area type #.
 
def dimension()
returns dimensions
 
def redraw(self)
the whole scene is redrawn when the scale changes
 
def resize(self, w, h)
Stores new dimensions and redraws.
 
def __init__(self, image=None, width=1000, height=1000, EventInterpreter=None, zoom=1)
Inherits from QGraphicsScene.
 
def drawPoints(self)
Unused ---— just for test.
 
Graphic area for displaying curves #.
 
def plot(self, Curve_id, newpoint, Width=3)
A version of PLOT that checks whether the new segment remains within the frame.
 
def __init__(self, image=None, width=556, height=390, EventInterpreter=None, zoom=1)
Calls Draw_Area constructor and sets margins.
 
def grid(self)
Drawing a grid with axes and legend
 
def display(self)
calling 'display' for all individuals in the population
 
Stores data that can be used to plot curves and stored into a file.
 
def EvolifeColourID(Colour_designation, default=(4, 'red'))
Recognizes Colour_designation as a number, a name, a (R,V,B) tuple or a #RRVVBB pattern.
 
def mouseLocate(self, MouseEvent)
convert mouse position into window coordinates
 
def mousePressEvent(self, MouseEvent)
retrieves mouse clicks - added by Gauthier Tallec
 
def speck(self, Curve_id, newpoint, Size=3)
draws a spot
 
def mouseReleaseEvent(self, MouseEvent)
retrieves mouse clicks
 
def mouseMoveEvent(self, MouseEvent)
retrieves mouse movements when button is pressed
 
def mouseDoubleClickEvent(self, MouseEvent)
retrieves mouse clicks