Evolife
Evolife has been developed to study Genetic algorithms, Natural evolution and behavioural ecology.
Plot_Area.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2""" @brief Windows that display Curves or images.
3
4 This module can be used independently.
5
6 Useful classes are:
7 - Image_Area: An area that displays an image
8 function: display(<bitmap>)
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>) )
16
17 Usage:
18 #self.W = QPlot_Area.AreaView(QPlot_Area.Image_Area)
19 self.W = QPlot_Area.AreaView(QPlot_Area.Draw_Area)
20 #self.W = QPlot_Area.AreaView(QPlot_Area.Plot_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))
24"""
25
26#============================================================================#
27# EVOLIFE http://evolife.telecom-paris.fr Jean-Louis Dessalles #
28# Telecom Paris 2022-04-11 www.dessalles.fr #
29# -------------------------------------------------------------------------- #
30# License: Creative Commons BY-NC-SA #
31#============================================================================#
32# Documentation: https://evolife.telecom-paris.fr/Classes #
33#============================================================================#
34
35
36
39
40
41
42
43import sys
44if __name__ == '__main__': sys.path.append('../..') # for tests
45
46
47import os
48import random
49from math import log
50from time import sleep
51
52try:
53 from PyQt5 import QtGui, QtCore, QtWidgets
54 QString = lambda x: x
55except ImportError: # compatibility with PyQt4
56 from PyQt4 import QtGui, QtCore
57 from PyQt4 import QtGui as QtWidgets
58 # compatibility with python 2
59 if sys.version_info >= (3,0): QString = lambda x: x
60 else: QString = QtCore.QString
61
62
63sys.path.append('..')
64
65from Evolife.Graphics.Curves import Curves, Stroke, EvolifeColourID
66from Evolife.Tools.Tools import error, Nb2A0
67
68# scalar array multiplication
69ZoomL = lambda *L: map(lambda x: int(x * L[0]), L[1:])
70
71
72
75
76class Image_Area(QtWidgets.QGraphicsScene):
77 """ Defines a logical Area on which objects are drawn (hidden from screen)
78 """
79
80 def __init__(self, image=None, width=1000, height=1000, EventInterpreter=None, zoom=1):
81 """ Inherits from QGraphicsScene
82 """
83 QtWidgets.QGraphicsScene.__init__(self, 0, 0, width, height) # calling the parent's constructor
84 if image is not None and os.path.exists(str(image)):
85 #print "Loading %s . . ." % image
86 self.ScaledBoard = self.Board = QtGui.QPixmap(image) # loads the image
87 #error("Plot","Unable to find image %s" % image)
88 self.fitSize = True
89 else:
90 # By default, a Scene has a graphic area (pixmap) on which one can draw
91 self.ScaledBoard = self.Board = QtGui.QPixmap(width,height)
92 image_ID = EvolifeColourID(image, default=None)
93 if image_ID is not None:
94 self.Board.fill(QtGui.QColor(image_ID[1]))
95 elif image and image.startswith('#'): # background colour given by code
96 self.Board.fill(QtGui.QColor(image))
97 else: # default
98 # self.Board.fill(QtGui.QColor("#F0B554"))
99 self.Board.fill(QtGui.QColor("#FFFFFF"))
100 self.fitSize = False
101 self.Canvas = self.addPixmap(self.ScaledBoard)
102 self.W = int(self.width())
103 self.H = int(self.height())
104 self.FrameNumber = 0
105 self.EventInterpreter = EventInterpreter
106 # self.changed.connect(self.Changing)
107 self.zoom = zoom
108
109 # # def Changing(self, e):
110 # # print 'Changing', e
111
112 def resize(self, w, h):
113 """ Stores new dimensions and redraws
114 """
115 self.W = int(w)
116 self.H = int(h)
117 #self.ScaledBoard = self.Board.scaled(w, h)
118 self.redraw()
119
120 def dimension():
121 """ returns dimensions
122 """
123 return (self.width(), self.height())
124
125 def redraw(self):
126 """ the whole scene is redrawn when the scale changes
127 """
128 for obj in self.items():
129 self.removeItem(obj)
130 self.ScaledBoard = self.Board.scaled(self.W, self.H)
131 self.Canvas = self.addPixmap(self.ScaledBoard)
132 self.setSceneRect(self.itemsBoundingRect())
133 #self.setSceneRect(0, 0, self.W/10.0, self.H/10.0)
134
135
136 def drawPoints(self):
137 """ Unused ------ just for test
138 """
139 item = QtGui.QGraphicsEllipseItem(20, 10, 40, 20, None, self)
140 qp = QtGui.QPainter()
141 qp.begin(self.Board)
142 pen = QtGui.QPen()
143 pen.setColor(QtCore.Qt.red)
144 pen.setWidth(3)
145 qp.setPen(pen)
146 for i in range(10):
147 x = random.randint(1, self.W-1)
148 y = random.randint(1, self.H-1)
149 qp.drawLine(0,0,x, y)
150 qp.end()
151 self.Canvas.setPixmap(self.ScaledBoard)
152
153
154
157
158
160 """ Draw_Area: Basic drawing Area
161 """
162
163 def __init__(self, image=None, width=400, height=400, EventInterpreter=None, zoom=1):
164 """ Calls Image_Area constructor
165 Initializes Curves
166 """
167 Image_Area.__init__(self, image, width, height, EventInterpreter, zoom=zoom)
168 self.set_margins(*ZoomL(zoom, 6, 6, 6, 6))
169 self.OrigineX = 0
170 self.OrigineY = 0
171 if True:
172 # if not self.fitSize:
173 self.scaleX = 100.0 # current maximal horizontal value
174 self.scaleY = 100.0 # current maximal vertical value
175 # else:
176 # self.scaleX = self.W
177 # self.scaleY = self.H
178 Curves.__init__(self)
179 self.init_Pens() # initializes pens for drawing curves
180
181 def set_margins(self, Left, Right, Bottom, Top):
182 """ surrounding margins (in Image_Area pixels)
183 """
184 self.LeftMargin = Left
185 self.RightMargin = Right
186 self.BottomMargin = Bottom
187 self.TopMargin = Top
188
189 def init_Pens(self):
190 """ Defining one pen and one QPainterPath per curve
191 """
192 self.Pens = dict()
193
195 for Curve in self.Curves:
196 self.Pens[Curve.ID] = QtGui.QPen()
197 self.Pens[Curve.ID].setColor(QtGui.QColor(Curve.colour))
198 self.Pens[Curve.ID].setWidth(3)
199
201
202 def grid(self):
203 """ Initial display - to be overloaded
204 """
205 pass
206
207 def pixel2xy(self, Point):
208 """ converts physical coordinates into logical pixel(between 0 and scaleX)
209 """
210 (px,py) = Point
211 return (px * self.scaleX / (self.W - self.LeftMargin - self.RightMargin),
212 py * self.scaleY / (self.H - self.BottomMargin - self.TopMargin))
213
214 def xy2pixel(self, Point):
215 """ converts logical pixel (between 0 and scaleX) into physical coordinates through rescaling
216 """
217 (x,y) = Point
218 if self.scaleX and self.scaleY:
219 return (((self.W - self.LeftMargin - self.RightMargin) * x) / self.scaleX,
220 ((self.H - self.BottomMargin - self.TopMargin) * y) / self.scaleY )
221 return (0,0)
222
223 def convert(self, Point):
224 """ Conversion of logical coordinates into physical coordinates through scaling and margin translation
225 """
226 (x,y) = self.xy2pixel(Point)
227 return (self.LeftMargin + x, self.H - self.BottomMargin - y )
228
229 def Q_Convert(self, Point):
230 """ Conversion of Point into Qt point
231 """
232 (x,y) = self.convert(Point)
233 #print "(%02f, %02f) converti en (%02f, %02f) avec scale=(%02f, %02f)" % (Point[0], Point[1], x ,y, self.scaleX, self.scaleY)
234 return QtCore.QPointF(x, y)
235
236 def draw(self, oldpoint, newpoint, Width=3, ColorID=0, Tag=''):
237 """ adds a segment on a given curve
238 """
239
241 if Width:
242 # print('drawing from', oldpoint, 'to', newpoint, 'width', Width)
243 self.Pens[ColorID].setWidth(int(Width))
244 self.addLine(QtCore.QLineF(self.Q_Convert(oldpoint), self.Q_Convert(newpoint)), self.Pens[ColorID])
245
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)
248 """
249 #print ">>>>>>>>", newpoint, self.Q_Convert(newpoint)
250
256 self.draw(self.Curves[ColorID].last(), newpoint, Width, ColorID, Tag)
257
258
259 def erase(self):
260 """ erase curves, items and restore grid
261 """
262 for P in self.Curves:
263 P.erase()
264 for obj in self.items():
265 if obj != self.Canvas:
266 self.removeItem(obj)
267 self.init_Pens()
268 self.grid()
269
270 def redraw(self):
271 """ the whole picture is redrawn when the scale changes
272 """
273 Image_Area.redraw(self)
274 self.init_Pens()
275 self.grid()
276 for Curve in self.Curves:
277 for Segment in Curve:
278 # print(Segment)
279 self.draw(Segment[0], Segment[1], Width=Curve.thick, ColorID=Curve.ID)
280
281 def reframe(self, Point, Anticipation=False):
282 """ performs a change of scale when necessary
283 """
284 self.change = False
285 def rescale(Scale, coord):
286 # print(coord, Scale)
287 if coord <= Scale:
288 return Scale
289 self.change = True
290 if Anticipation:
291 ordre = 10 ** (int(log(coord,10))) # order of magnitude
292 Scale = (coord // ordre + 1) * ordre
293 else:
294 Scale = coord
295 return Scale
296
297 if isinstance(Point, Stroke):
298 ws, hs = self.pixel2xy((Point.size, Point.size))
299 # ws, hs = ((Point.size, Point.size))
300 (x,y) = (Point.x + ws, Point.y + hs)
301 else: (x,y) = Point
302 self.scaleX = rescale(self.scaleX, x)
303 self.scaleY = rescale(self.scaleY, y)
304 return self.change
305
306 def plot(self, Curve_designation, newpoint, Width=3):
307 """ draws an additional segment on a curve
308 """
309 # one is tolerant about the meaning of Curve_designation
310 Curve_id = EvolifeColourID(Curve_designation)[0]
311 # print('plotting to', Curve_designation, Curve_id, newpoint)
312 try:
313 # if Width != self.Curves[Curve_id].thick: print('changing thickness of %s to %s' % (Curve_designation, Width))
314 self.Curves[Curve_id].thick = Width # update thickness
315 self.drawTo(newpoint, Width, Curve_id) # action on display
316 self.CurveAddPoint(Curve_id, newpoint) # memory
317 except IndexError:
318 error("Draw_Area: unknown Curve ID")
319
320 def move(self, Curve_designation, newpoint):
321 """ introduces a discontinuity in a curve
322 """
323 # one is tolerant about the meaning of Curve_designation
324 Curve_id = EvolifeColourID(Curve_designation)[0]
325 # print('moving to', Curve_designation, Curve_id, newpoint)
326 try:
327
328 self.CurveAddPoint(Curve_id, newpoint, Draw=False)
329 except IndexError:
330 error("Draw_Area: unknown Curve ID")
331
332 def speck(self, Curve_id, newpoint, Size=3):
333 """ draws a spot
334 """
335 # print('about to draw speck at', Curve_id, newpoint)
336 if self.reframe(newpoint): self.redraw()
337 self.move(Curve_id, newpoint)
338 self.plot(Curve_id, newpoint, Width=Size)
339
340 def mouseLocate(self, MouseEvent):
341 """ convert mouse position into window coordinates
342 """
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))
348 return None
349
350 def mousePressEvent(self, MouseEvent):
351 """ retrieves mouse clicks - added by Gauthier Tallec
352 """
353 Loc = self.mouseLocate(MouseEvent)
354 if Loc is not None:
355 self.EventInterpreter(('MouseClick', Loc))
356
357 def mouseReleaseEvent(self, MouseEvent):
358 """ retrieves mouse clicks
359 """
360 Loc = self.mouseLocate(MouseEvent)
361 if Loc is not None:
362 self.EventInterpreter(('MouseDeClick', Loc))
363
364 def mouseDoubleClickEvent(self, MouseEvent):
365 """ retrieves mouse clicks
366 """
367 Loc = self.mouseLocate(MouseEvent)
368 if Loc is not None:
369 self.EventInterpreter(('MouseDoubleClick', Loc))
370
371 def mouseMoveEvent(self, MouseEvent):
372 """ retrieves mouse movements when button is pressed
373 """
374 Loc = self.mouseLocate(MouseEvent)
375 if Loc is not None:
376 self.EventInterpreter(('MouseMove', Loc))
377
378
379
380
383
385 """ Definition of the drawing area, with grid, legend and curves
386 """
387 def __init__(self, image=None, width=556, height=390, EventInterpreter=None, zoom=1):
388 """ Calls Draw_Area constructor and sets margins
389 """
390 Draw_Area.__init__(self, image, width, height, EventInterpreter, zoom=zoom)
391 self.set_margins(*ZoomL(zoom, 32, 20, 28, 20))
392 #self.grid() # drawing the grid
393 self.init_Pens() # redo it because offsets have changed
394
395 def grid(self):
396 """ Drawing a grid with axes and legend
397 """
398 NbMailles = 5
399 mailleX = (int(self.zoom * 0.5) + self.W - self.RightMargin - self.LeftMargin)/NbMailles
400 mailleY = (int(self.zoom * 0.5) + self.H - self.TopMargin - self.BottomMargin)/NbMailles
401 GridColour = '#A64910'
402 gridPen = QtGui.QPen()
403 gridPen.setColor(QtGui.QColor(GridColour))
404 gridPen.setWidth(int(self.zoom))
405 # drawing the grid
406 for i in range(NbMailles+1):
407 # vertical lines
408 self.addLine( self.LeftMargin + i*mailleX, self.TopMargin,
409 self.LeftMargin + i*mailleX, self.H - self.BottomMargin, gridPen)
410 # horizontal lines
411 self.addLine( self.LeftMargin, self.TopMargin + i*mailleY,
412 self.W - self.RightMargin, self.TopMargin + i*mailleY, gridPen)
413 # drawing axes
414 gridPen.setWidth(int(self.zoom * 2))
415 self.addRect(self.LeftMargin,self.TopMargin,self.W-self.RightMargin+1-self.LeftMargin,
416 self.H-self.BottomMargin+1-self.TopMargin, gridPen)
417
418 # drawing legend
419 PSize = int((13 - 2 * int(log(max(self.scaleX, self.scaleY), 10)))) # size of police
420 for i in range(NbMailles+1):
421 # legend on the x-axis
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),
424 self.H-self.BottomMargin+int(self.zoom*5)))
425 # legend on the y-axis
426 self.addSimpleText(QString(str(int(i*self.scaleY//NbMailles)).rjust(3)),
427 QtGui.QFont(QString("Arial"), PSize)).setPos(QtCore.QPointF(self.LeftMargin-int(self.zoom*25),
428 self.H - self.BottomMargin - i*mailleY-6))
429
430 # drawing colour code
431 for C in self.Curves[1:]:
432 self.addEllipse(self.W - self.RightMargin/2, self.H - C.ID * self.BottomMargin/2 - int(self.zoom*20),
433 int(self.zoom*4),int(self.zoom*4),
434 QtGui.QPen(QtGui.QColor(C.colour)), QtGui.QBrush(QtGui.QColor(C.colour), QtCore.Qt.SolidPattern))
435
436
437
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
442 """
443 if self.reframe(newpoint, Anticipation=True):
444 self.redrawredraw()
445 Draw_Area.plot(self, Curve_id, newpoint, Width=Width)
446
447
448
452 """ Defines a 2-D region where agents are located and may move
453 """
454
455 DEFAULTSHAPE = 'ellipse'
456 # DEFAULTSHAPE = 'rectangle'
457
458 def __init__(self, image=None, width=400, height=300, legend=True, EventInterpreter=None, zoom=1):
459 """ Creates a Draw_Area and initializes agents
460 """
461 self.LegendLegend = legend and (image == None or EvolifeColourID(image)[0])
462 self.Toric = False # says whether right (resp. top) edge
463 # is supposed to touch left (resp. bottom) edge
464 Draw_Area.__init__(self, image, width, height, EventInterpreter, zoom=zoom)
465 self.set_margins(*ZoomL(zoom, 6, 6, 6, 6))
466 #self.Board.fill(QtGui.QColor(QtCore.Qt.white))
467 self.Canvas.setPixmap(self.ScaledBoard)
468 self.positions = dict() # positions of agents
469 self.segments = dict() # line segments pointing out from agents
470 self.shapes = dict() # shape of agents
471 self.GraphicAgents = dict() # Q-references of agents
472 self.KnownShapes = {}
473
474 def setToric(self, Toric=True):
475 """ if toric, edges 'touch' each other
476 """
477 self.Toric = Toric
478
479 def grid(self):
480 """ Writing maximal values for both axes
481 """
482 if self.LegendLegend:
483 self.addSimpleText(QString(str(round(self.scaleY))),
484 QtGui.QFont(QString("Arial"), 8)).setPos(QtCore.QPointF(self.LeftMargin + int(self.zoom*3),
485 int(self.zoom*7) + self.TopMargin))
486 self.addSimpleText(QString(str(round(self.scaleX))),
487 QtGui.QFont(QString("Arial"), 8)).setPos(QtCore.QPointF(self.W - self.RightMargin-int(self.zoom*16) ,
488 self.H-self.BottomMargin - int(self.zoom*8)))
489
490 def coordinates(self, Coord):
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)
497 # 'size' is in pixels and is not resized in case of zoom. However, if negative, it is interpreted in your coordinates and it will be resized/
498 # 'size' may be a fractional number. It is then understood as a fraction of the window size.
499 # The value assigned to 'shape' in the string 'shape=...' can be 'ellipse' (=default) or 'rectangle' or
500 # any image. If it is an image, 'colour' is interpreted as an angle. The image is scaled to fit 'size' (but aspect ratio is preserved)
501 """
502
503 Shape = None
504 # shape is indicated anywhere as a string
505 Coord1 = tuple()
506 for x in Coord:
507 if type(x) == str and x.lower().startswith('shape'): Shape = x[x.rfind('=')+1:].strip()
508 else: Coord1 += (x,)
509 Start = Stroke(Coord1[:4], RefSize=self.W)
510 if len(Coord1) > 4: End = Stroke(Coord1[4:], RefSize=self.W)
511 else: End = Stroke(None) # no segment
512 return (Start, End, Shape)
513
514 def convert(self, Coord):
515 """ process modulo if toric and calls Draw_Area's convert
516 """
517 if self.Toric:
518 Coord = (Coord[0] % self.scaleX, Coord[1] % self.scaleY)
519 return Draw_Area.convert(self, Coord)
520
521 def create_agent(self, Name, Coord):
522 """ creates a dot at some location that represents an agent
523 """
524 self.move_agent(Name, Coord) # move_agent checks for agent's existence and possibly creates it
525
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
528 """
529 def physical_move(AgentRef, Location):
530 """ moves a shape to a location
531 """
532 # We want the origin of a shape to be the lower left corner (contrary to Qt convention)
533 AgentRef.setPos(QtCore.QPointF(Location[0],Location[1])) # performing actual move
534
535 # Coord in analysed as a Position + an optional segment starting from that position,
536 # and missing coordinates are completed
537 if Coord: (Position, Segment, Shape) = self.coordinates(Coord)
538 else: Coord = Position.point() + Segment.point() + (('shape=%s' % Shape,) if Shape else ())
539 Location = self.convertconvert(Position.point()) # Translation into physical coordinates
540 # if not self.Toric and self.reframe(Position): # The window is reframed if necessary
541 # self.redraw() # poses display problems...
542 if Name in self.positions:
543 # print('moving', Name, 'in', Position.Coord, len(self.positions))
544 if self.positions[Name].colour != Position.colour or self.shapes.get(Name) != Shape:
545 # Colour or shape has changed, the agent is destroyed and reborn
546 # print('Changing colour or shape')
547 self.remove_agent(Name)
548 if str(Position.colour).startswith('-'):
549 # print('destroying agent', Name)
550 return # negative colour means the agent is removed
551 self.move_agent(Name, Coord) # false recursive call
552 if self.positions[Name].Coord == Position.Coord \
553 and self.segments[Name].Coord == Segment.Coord \
554 and self.shapes.get(Name) == Shape:
555 return # nothing to do
556
558 self.positions[Name] = Position # recording the agent's new position
559 self.shapes[Name] = Shape # recording the agent's new shape
560 AgentRef = self.GraphicAgents[Name][0]
561 # AgentRef.setPos(QtCore.QPointF(Location[0],Location[1])) # performing actual move
562 physical_move(AgentRef, Location)
563 if self.segments[Name].Coord: # the segment is re-created
564 self.remove_segment(Name)
565 if Segment.Coord:
566 SegmentRef = self.create_graphic_segment(Position, Segment)
567 else: SegmentRef = None
568 self.segments[Name] = Segment
569 self.GraphicAgents[Name] = (AgentRef, SegmentRef) # New Q-reference to graphic objects are memorized
570 else:
572 try:
573 if str(Position.colour).startswith('-'):
574 # print 'Error negative colour in display -->\t\t', Position.Coord
575 return
576 AgentRef = self.create_graphic_agent(Position, Shape) # creating the shape or loading the image
577 # AgentRef.setPos(QtCore.QPointF(Location[0],Location[1])) # translating the shape or image
578 physical_move(AgentRef, Location)
579 if Segment.Coord:
580 # drawing the segment that originates from the shape
581 SegmentRef = self.create_graphic_segment(Position, Segment)
582 else: SegmentRef = None
583 except IndexError:
584 error("Draw_Area: unknown colour ID")
585 self.positions[Name] = Position # recording the agent's new position
586 self.shapes[Name] = Shape # recording the agent's new shape
587 self.segments[Name] = Segment
588 self.GraphicAgents[Name] = (AgentRef, SegmentRef) # Q-reference to graphic objects are memorized
589
590 def create_graphic_agent(self, Position, Shape=None):
591 """ creates a graphic agent and returns the Q-reference
592 """
593 if Shape is None: Shape = self.DEFAULTSHAPE
594 if Position.PixelSize:
595 PhysicalW = PhysicalH = Position.size # sizes are provided in pixels
596 else:
597 PhysicalW, PhysicalH = self.xy2pixel((Position.size, Position.size)) # sizes are provided in logical coordinates and are resized
598 if Shape == 'ellipse':
599 return self.addEllipse(0, -PhysicalH, PhysicalW, PhysicalH,
600 QtGui.QPen(QtGui.QColor(EvolifeColourID(Position.colour)[1])),
601 QtGui.QBrush(QtGui.QColor(EvolifeColourID(Position.colour)[1]), QtCore.Qt.SolidPattern))
602 if Shape == 'rectangle':
603 return self.addRect(0, -PhysicalH, PhysicalW, PhysicalH,
604 QtGui.QPen(QtGui.QColor(EvolifeColourID(Position.colour)[1])),
605 QtGui.QBrush(QtGui.QColor(EvolifeColourID(Position.colour)[1]), QtCore.Qt.SolidPattern))
606 if Shape not in self.KnownShapes:
607 if os.path.exists(Shape):
608 # print('Existing shape: %s' % Shape)
609 self.KnownShapes[Shape] = QtGui.QPixmap(Shape) # loads the image
610 if Shape in self.KnownShapes: # no else
611 # interpreting colour as angle
612 if Position.colour and type(Position.colour) == int:
613 agent = self.addPixmap(self.KnownShapes[Shape].transformed(QtGui.QTransform().rotate(-Position.colour)))
614 else:
615 agent = self.addPixmap(self.KnownShapes[Shape])
616 # currentSize, h = self.pixel2xy((agent.boundingRect().width(), 0))
617 scaleW = float(PhysicalW) / agent.boundingRect().width()
618 scaleH = float(PhysicalH) / agent.boundingRect().width() # to keep aspect ratio
619 agent.setTransform(QtGui.QTransform.fromScale(scaleW, scaleH)) # should be translated as well
620 return agent
621 error("Ground: unknown shape: %s" % Shape)
622 return None
623
624 def create_graphic_segment(self, Position, Segment):
625 """ creates a graphic segment and returns the Q-reference
626 """
627 Colour = EvolifeColourID(Segment.colour)[0]
628 self.Pens[Colour].setWidth(Segment.size)
629 Location1 = self.convertconvert(Position.point()) # Translation into physical coordinates
630 Location2 = self.convertconvert(Segment.point()) # Translation into physical coordinates
631 return self.addLine(Location1[0], Location1[1], Location2[0], Location2[1], self.Pens[Colour])
632
633 def remove_agent(self, Name):
634 """ removes an agent from the ground
635 """
636 self.remove_segment(Name)
637 self.removeItem(self.GraphicAgents[Name][0])
638 del self.positions[Name]
639 del self.GraphicAgents[Name]
640
641 def remove_segment(self, Name):
642 """ removes a segment from the ground
643 """
644 if self.segments[Name].Coord:
645 self.removeItem(self.GraphicAgents[Name][1])
646 del self.segments[Name]
647
648 def on_ground(self):
649 """ Returns the identificiation of all agents on the ground
650 """
651 return self.GraphicAgents.keys()
652
653 def remove_absent(self, Present):
654 """ removes agents that are not in Present
655 """
656 for Name in list(self.GraphicAgents.keys()): # ground copy of keys
657 if Name not in Present: self.remove_agent(Name)
658
659 def scroll(self):
660 """ move agents vertically (not yet used)
661 """
662 for Name in list(self.GraphicAgents.keys()):
663 self.positions[Name].scroll()
664 self.move_agent(Name, Position + Segment) # addition has been redefined
665
666 def draw_tailed_blob(self, Coord):
667 """ draws a blob and a segment, as for an agent,
668 but without agent name and without moving and removing options
669 """
670 (Position, Segment, Shape) = self.coordinates(Coord)
671 # print(Position)
672 # print(Segment)
673 if not self.Toric and self.reframe(Position): # The window is reframed if necessary
674 self.redrawredrawredraw()
675 if Segment.Coord:
676 if Segment.size:
677 self.move(Segment.colour, Position.point())
678 self.plot(Segment.colour, Segment.point(), Width=Segment.size)
679 else: # invisible line
680 self.move(Segment.colour, Segment.point())
681 self.speck(Segment.colour, Segment.point(), Size=0) # drawing invisible point to rescale
682 self.speck(Position.colour, Position.point(), Size=Position.size)
683
684 def erase(self):
685 """ removes agents and erases Draw_Area
686 """
687 for Agent in list(self.GraphicAgents.keys()): # copy because modified by restore_agent
688 self.remove_agent(Agent)
689 Draw_Area.erase(self) # modif 211105
690 # self.redraw() # modif 211105
691
692 def redraw(self):
693 """ the whole picture is redrawn when the scale changes
694 """
695 # First, delete all unnamed objects (named objects are agents)
696 Draw_Area.redraw(self) # destroys all agents in the scene
697 for Agent in list(self.GraphicAgents.keys()): # copy because modified by restore_agent
698 if Agent in self.positions: # the agent is already displayed (and not in the process of being displayed)
699 self.restore_agent(Agent)
700
701 def restore_agent(self, Name):
702 """ display an agent that is still present in 'positions' and in 'segments', but is graphically dead
703 """
704 self.move_agent(Name, Position=self.positions.pop(Name), Segment=self.segments.pop(Name),
705 Shape=self.shapes.get(Name))
706
707
708
709
712
713if __name__ == "__main__":
714
715 print(__doc__)
716
717
718__author__ = 'Dessalles'
Stores a list of 'Curves'.
Definition: Curves.py:273
def Legend(self)
returns tuples (ID, colour, colourname, curvename, legend) representing active curves
Definition: Curves.py:329
def CurveAddPoint(self, Curve_id, Point, Draw=True)
Adds a point to a Curve.
Definition: Curves.py:293
Stroke: drawing element (point or segment) #.
Definition: Curves.py:98
Draw_Area: Basic drawing Area.
Definition: Plot_Area.py:159
def move(self, Curve_designation, newpoint)
introduces a discontinuity in a curve
Definition: Plot_Area.py:320
def __init__(self, image=None, width=400, height=400, EventInterpreter=None, zoom=1)
Calls Image_Area constructor Initializes Curves.
Definition: Plot_Area.py:163
def pixel2xy(self, Point)
converts physical coordinates into logical pixel(between 0 and scaleX)
Definition: Plot_Area.py:207
def grid(self)
Initial display - to be overloaded.
Definition: Plot_Area.py:202
def init_Pens(self)
Defining one pen and one QPainterPath per curve.
Definition: Plot_Area.py:189
def set_margins(self, Left, Right, Bottom, Top)
surrounding margins (in Image_Area pixels)
Definition: Plot_Area.py:181
def convert(self, Point)
Conversion of logical coordinates into physical coordinates through scaling and margin translation.
Definition: Plot_Area.py:223
def xy2pixel(self, Point)
converts logical pixel (between 0 and scaleX) into physical coordinates through rescaling
Definition: Plot_Area.py:214
def Q_Convert(self, Point)
Conversion of Point into Qt point.
Definition: Plot_Area.py:229
def erase(self)
erase curves, items and restore grid
Definition: Plot_Area.py:259
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)
Definition: Plot_Area.py:246
def redraw(self)
the whole picture is redrawn when the scale changes
Definition: Plot_Area.py:270
def plot(self, Curve_designation, newpoint, Width=3)
draws an additional segment on a curve
Definition: Plot_Area.py:306
def reframe(self, Point, Anticipation=False)
performs a change of scale when necessary
Definition: Plot_Area.py:281
def draw(self, oldpoint, newpoint, Width=3, ColorID=0, Tag='')
adds a segment on a given curve
Definition: Plot_Area.py:236
Defines a 2-D region where agents are located and may move.
Definition: Plot_Area.py:451
def convert(self, Coord)
process modulo if toric and calls Draw_Area's convert
Definition: Plot_Area.py:514
def setToric(self, Toric=True)
if toric, edges 'touch' each other
Definition: Plot_Area.py:474
def remove_agent(self, Name)
removes an agent from the ground
Definition: Plot_Area.py:633
def grid(self)
Writing maximal values for both axes.
Definition: Plot_Area.py:479
def erase(self)
removes agents and erases Draw_Area
Definition: Plot_Area.py:684
def move_agent(self, Name, Coord=None, Position=None, Segment=None, Shape=None)
moves an agent's representative dot to some new location
Definition: Plot_Area.py:526
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...
Definition: Plot_Area.py:666
def __init__(self, image=None, width=400, height=300, legend=True, EventInterpreter=None, zoom=1)
Creates a Draw_Area and initializes agents.
Definition: Plot_Area.py:458
def on_ground(self)
Returns the identificiation of all agents on the ground.
Definition: Plot_Area.py:648
def remove_segment(self, Name)
removes a segment from the ground
Definition: Plot_Area.py:641
def remove_absent(self, Present)
removes agents that are not in Present
Definition: Plot_Area.py:653
def create_graphic_agent(self, Position, Shape=None)
creates a graphic agent and returns the Q-reference
Definition: Plot_Area.py:590
def create_agent(self, Name, Coord)
creates a dot at some location that represents an agent
Definition: Plot_Area.py:521
def scroll(self)
move agents vertically (not yet used)
Definition: Plot_Area.py:659
def create_graphic_segment(self, Position, Segment)
creates a graphic segment and returns the Q-reference
Definition: Plot_Area.py:624
def redraw(self)
the whole picture is redrawn when the scale changes
Definition: Plot_Area.py:692
def restore_agent(self, Name)
display an agent that is still present in 'positions' and in 'segments', but is graphically dead
Definition: Plot_Area.py:701
def coordinates(self, Coord)
Coord is a tuple.
Definition: Plot_Area.py:490
Basic graphic area type #.
Definition: Plot_Area.py:76
def dimension()
returns dimensions
Definition: Plot_Area.py:120
def redraw(self)
the whole scene is redrawn when the scale changes
Definition: Plot_Area.py:125
def resize(self, w, h)
Stores new dimensions and redraws.
Definition: Plot_Area.py:112
def __init__(self, image=None, width=1000, height=1000, EventInterpreter=None, zoom=1)
Inherits from QGraphicsScene.
Definition: Plot_Area.py:80
def drawPoints(self)
Unused ---— just for test.
Definition: Plot_Area.py:136
Graphic area for displaying curves #.
Definition: Plot_Area.py:384
def plot(self, Curve_id, newpoint, Width=3)
A version of PLOT that checks whether the new segment remains within the frame.
Definition: Plot_Area.py:438
def __init__(self, image=None, width=556, height=390, EventInterpreter=None, zoom=1)
Calls Draw_Area constructor and sets margins.
Definition: Plot_Area.py:387
def grid(self)
Drawing a grid with axes and legend
Definition: Plot_Area.py:395
def display(self)
calling 'display' for all individuals in the population
Definition: Population.py:200
Stores data that can be used to plot curves and stored into a file.
Definition: Curves.py:1
def EvolifeColourID(Colour_designation, default=(4, 'red'))
Recognizes Colour_designation as a number, a name, a (R,V,B) tuple or a #RRVVBB pattern.
Definition: Curves.py:70
def mouseLocate(self, MouseEvent)
convert mouse position into window coordinates
Definition: Plot_Area.py:340
def mousePressEvent(self, MouseEvent)
retrieves mouse clicks - added by Gauthier Tallec
Definition: Plot_Area.py:350
def speck(self, Curve_id, newpoint, Size=3)
draws a spot
Definition: Plot_Area.py:332
def mouseReleaseEvent(self, MouseEvent)
retrieves mouse clicks
Definition: Plot_Area.py:357
def mouseMoveEvent(self, MouseEvent)
retrieves mouse movements when button is pressed
Definition: Plot_Area.py:371
def mouseDoubleClickEvent(self, MouseEvent)
retrieves mouse clicks
Definition: Plot_Area.py:364
Various functions.
Definition: Tools.py:1
def error(ErrMsg, Explanation='')
Definition: Tools.py:182