"""
Create complex shapes and murals
................................
"""
from reiz._visual.colors import get_color, ColorType
import pyglet
from pathlib import Path
from reiz._visual._primitives import Polygon as _Polygon
from reiz._visual._primitives import Circle as _Circle
from reiz._visual._primitives import Line as _Line
from typing import Tuple, NewType, List
# : Tuple[float, float], x-y coordinates scaled from -1 to 1 with 0 indicating the center of the screen
XY = NewType("XY", Tuple[float, float])
# %%
class Visual():
"The base class for all visual cues to inherit from"
def adapt(self, window):
pass
def draw(self, canvas=None):
if canvas is not None and not canvas.window.has_exit:
self.adapt(canvas)
try:
for v in self.visual:
v.draw()
except TypeError:
self.visual.draw()
def set_color(self, color: str):
self.color = get_color(color)
def __iter__(self):
yield self
def __next__(self):
yield self
raise StopIteration
# Complex parametric visualisations
# ------------------------------------------------------------------------------
[docs]class Background(Visual):
"""Fills the whole screen with a single color
args
----
color:str
the color to fill the screen. see also :data:`reiz.visual.colors.COLORS`
"""
def __init__(self, color='white'):
self.color = [int(c*255) for c in get_color(color)]
def adapt(self, window):
img = pyglet.image.SolidColorImagePattern(color=self.color)
img = img.create_image(window.width, window.height)
self.visual = pyglet.sprite.Sprite(img=img, x=0, y=0,
usage='static')
# ------------------------------------------------------------------------------
[docs]class Mural(Visual):
"""A text on the screen
args
----
text:str
the text to show
font:str
the font-type (needs to be installed on the system)
fontsize: float
the normalized size of the letters
position: XY
where on the screen the text should be located. Coordinates mark the center of the text
color: ColorType
the desired color
"""
def __init__(self, text: str = 'Hello World', font='Times New Roman',
fontsize: float = 1, position: Tuple[float, float] = (0, 0), color=(1, 1, 1),
anchor_x='center', anchor_y='center'):
self.scale = 0.05 * fontsize
self.text = text
self.font = font
self.pos = position
self.set_color(color)
self.anchor_x = anchor_x
self.anchor_y = anchor_y
def adapt(self, canvas):
color = tuple(int(c*255) for c in self.color)
x0 = canvas.width//2
y0 = canvas.height//2
x = (x0*self.pos[0]) + x0
y = (y0*self.pos[1]) + y0
fontsize = int(canvas.width*self.scale)
self.visual = pyglet.text.Label(self.text, font_name=self.font,
font_size=fontsize, color=color, x=x, y=y,
anchor_x=self.anchor_x, anchor_y=self.anchor_y)
def __repr__(self):
return f"Mural('{self.text}')"
# ------------------------------------------------------------------------------
[docs]class Line(Visual):
"""A line from a to b
args
----
a: XY
x-y coordinates of the start point
b: XY
x-y coordinates of the end point
color: ColorType
the desired color
linewidth:float
"""
def __init__(self, a: XY = (0, 0), b: XY = (0, 0),
color: ColorType = 'white', linewidth: float = 1):
self.a = a
self.b = b
self.color = get_color(color)
self.linewidth = linewidth
def adapt(self, window):
x0 = window.width//2
y0 = window.height//2
ax = (x0*self.a[0]) + x0
ay = (y0*self.a[1]) + y0
bx = (x0*self.b[0]) + x0
by = (y0*self.b[1]) + y0
self.visual = _Line(a=(ax, ay), b=(bx, by), z=0,
color=self.color, stroke=self.linewidth)
[docs]class Polygon(Visual):
"""An arbitrary shape defined by the coordinates of its nodes
args
----
positions: List[XY, ]
a list of x-y-coordinates of each node
color: ColorType
the desired color
"""
def __init__(self, positions: List[XY, ] = [(0, 0), (.1, .2), (.3, .1)], color='white'):
self.positions = positions
self.color = get_color(color)
def adapt(self, window):
x0 = window.width//2
y0 = window.height//2
v = []
for pos in self.positions:
x = (x0*pos[0]) + x0
y = (y0*pos[1]) + y0
v.append((x, y))
self.visual = _Polygon(
v=v, z=0, color=self.color, stroke=0, rotation=0)
[docs]class Bar(Visual):
"""A rectangle starting from the bottom centre of the screen
args
----
height:float
height in normalized units from 0 to 1 (bottom to top of screen)
width:float
width in normalized units from 0 to 1 (nothing to whole screen)
color: ColorType
the desired color
"""
def __init__(self, height: float = .5, width: float = .25, color='white'):
self.height = height
self.width = width
self.color = get_color(color)
def adapt(self, window):
x0 = window.width//2
y0 = window.height*3/4
h = self.height
w = self.width
positions = [(-w, 0), (-w, h), (w, h), (w, 0)]
v = []
for pos in positions:
x = (x0*pos[0]) + x0
y = (y0*pos[1]) + 0
v.append((x, y))
self.visual = _Polygon(
v=v, z=0, color=self.color, stroke=0, rotation=0)
[docs]class Circle(Visual):
"""A circle, i.e. the outline of a ball
args
----
zoom: float
size of the circle
color: ColorType
the desired color
position: XY
position of the center of the circle
opacity: float
how opaque the circle is supposed to be from 0 to 1
stroke: float
thickness of the outline
"""
def __init__(self, zoom: float = 1, color: ColorType = 'red',
position: XY = (0, 0), opacity: float = 1, stroke: float = 0):
self.pos = position
self.color = get_color(color, opacity)
self.zoom = zoom
self.stroke = stroke
def adapt(self, window):
x0 = window.width//2
y0 = window.height//2
width = int(0.1*min([window.height, window.width]) * self.zoom)
x = (x0*self.pos[0]) + x0
y = (y0*self.pos[1]) + y0
self.visual = _Circle(x=x, y=y, z=0, width=width, color=self.color,
stroke=self.stroke)
def __repr__(self):
return (f"Circle(zoom={self.zoom}, color={self.color}, " +
f"position={self.pos})")
[docs]class Cylinder(Visual):
"""A rectangular shape which can be rotated around its center
args
----
position: XY
position of the center of the circle
angle:float
rotation angle around its center
thickness:float
width of the cylinder
length:float
length of the cylinder
color: ColorType
the desired color
"""
def __init__(self, position=(0, 0), angle: float = 0,
thickness: float = .05, length: float = .75,
color: ColorType = 'brown'):
self.color = get_color(color)
self.length = length
self.pos = position
self.angle = angle
self.thickness = thickness
def adapt(self, canvas):
x0 = canvas.width//2
y0 = canvas.height//2
x = (x0*self.pos[0]) + x0
y = (y0*self.pos[1]) + y0
thickness = int(canvas.height * self.thickness)
length = int(canvas.width * self.length)
vertices = ((x-length//2, y-thickness//2),
(x-length//2, y+thickness//2),
(x+length//2, y+thickness//2),
(x+length//2, y-thickness//2))
self.visual = _Polygon(v=vertices, z=0, color=self.color,
stroke=0, rotation=self.angle)
# ------------------------------------------------------------------------------
[docs]class Cross(Visual):
"""A fixation cross in the center of the screen
args
----
zoom: float
size of the cross
color: ColorType
the desired color
"""
def __init__(self, zoom=1, color='white'):
self.color = get_color(color)
self.zoom = zoom
def adapt(self, window):
x0 = window.width//2
y0 = window.height//2
armlen = int(self.zoom * 100)
armwid = int(self.zoom * 15)
y1 = y0-armlen
y2 = y0-armwid
y3 = y0+armwid
y4 = y0+armlen
x1 = x0-armlen
x2 = x0-armwid
x3 = x0+armwid
x4 = x0+armlen
v = [(x1, y2), (x1, y3), (x4, y3), (x4, y2)]
self.ho = _Polygon(v=v, z=0, color=self.color, stroke=0, rotation=0)
v = [(x2, y1), (x2, y4), (x3, y4), (x3, y1)]
self.ve = _Polygon(v=v, z=0, color=self.color, stroke=0, rotation=0)
self.visual = [self.ho, self.ve]
def __repr__(self):
return f"Cross(zoom={self.zoom}, color={self.color[:-1]})"
[docs]class Trapezoid(Visual):
"""A trapezoid spanned between four nodes
args
----
xpos: Tuple[float, float, float, float]
a tuple of x coordinates of the nodes
ypos: Tuple[float, float]
a tuple of y coordinates of the nodes
the first entry is the ypos of the first two xpos
color: ColorType
the desired color
"""
def __init__(self, xpos=(-.33, -.25, .25, .33),
ypos=(-.25, .25), color='white'):
self.xpos = xpos
self.ypos = ypos
self.color = get_color(color)
def adapt(self, window):
x0 = window.width//2
y0 = window.height//2
# generate vertex coordinates
positions = []
for x, y in zip(self.xpos[0:2], self.ypos):
positions.append((x, y))
for x, y in zip(self.xpos[2:], reversed(self.ypos)):
positions.append((x, y))
# create the trapezoid in the first color (default white)
vertex = []
for pos in positions:
x = (x0*pos[0]) + x0
y = (y0*pos[1]) + y0
vertex.append((x, y))
self.visual = _Polygon(
v=vertex, z=0, color=self.color, stroke=0, rotation=0)
# ------------------------------------------------------------------------------
# %% File-based visualisations
[docs]class Image(Visual):
"""Show an image loaded from a file
args
----
imgpath:str
path to the imagefile
position:XY
x-y-coordinate of the center of the image
scale:float
size of the figure relative to the screen size
"""
def __init__(self, imgpath: str,
position: Tuple[float, float] = (0, 0),
scale: float = .5):
self.imgpath = str(Path(imgpath).expanduser().absolute())
self.img = pyglet.image.load(imgpath)
self.scale = scale
self.pos = position
def adapt(self, window):
base_scale = min(window.width/self.img.width,
window.height/self.img.height)
scale = self.scale*base_scale
self.img.scale = scale
x0 = window.width//2 - (scale * self.img.width//2)
y0 = window.height//2 - (scale * self.img.height//2)
x = (x0*self.pos[0]) + x0
y = (y0*self.pos[1]) + y0
self.visual = pyglet.sprite.Sprite(img=self.img, x=x, y=y,
usage='static')
self.visual.scale = scale
def __repr__(self):
return f"Image(imgpath='{self.imgpath}')"
# ------------------------------------------------------------------------------