|
|
|
@ -1,11 +1,12 @@
|
|
|
|
|
from math import floor |
|
|
|
|
|
|
|
|
|
from .colors import Color |
|
|
|
|
from .exceptions import ExceededWindowBoundsError |
|
|
|
|
from kmeans.clustering.point import Point as BasePoint |
|
|
|
|
|
|
|
|
|
# TODO: THIS WILL NEED TO BE MODIFIED TO INHEIRIT THE KMEANS POINT CLASS. |
|
|
|
|
from clusterview2.colors import Color |
|
|
|
|
from clusterview2.exceptions import ExceededWindowBoundsError |
|
|
|
|
|
|
|
|
|
class Point: |
|
|
|
|
|
|
|
|
|
class Point(BasePoint): |
|
|
|
|
""" |
|
|
|
|
A class representing a point. A point |
|
|
|
|
has a point_size bounding box around |
|
|
|
@ -33,71 +34,71 @@ class Point:
|
|
|
|
|
raise ValueError("Point must be initialized with a color of " + |
|
|
|
|
"type Color.") |
|
|
|
|
|
|
|
|
|
self.__point_size = point_size |
|
|
|
|
self.__x = x |
|
|
|
|
self.__y = y |
|
|
|
|
self._point_size = point_size |
|
|
|
|
self._x = x |
|
|
|
|
self._y = y |
|
|
|
|
|
|
|
|
|
self.__color = color |
|
|
|
|
self._color = color |
|
|
|
|
|
|
|
|
|
self.__viewport_width = viewport_width |
|
|
|
|
self.__viewport_height = viewport_height |
|
|
|
|
self._viewport_width = viewport_width |
|
|
|
|
self._viewport_height = viewport_height |
|
|
|
|
|
|
|
|
|
self.__calculate_hitbox() |
|
|
|
|
self._calculate_hitbox() |
|
|
|
|
|
|
|
|
|
self.__check_window_bounds(x, y) |
|
|
|
|
self._check_window_bounds(x, y) |
|
|
|
|
|
|
|
|
|
self.__selected = False |
|
|
|
|
self._selected = False |
|
|
|
|
|
|
|
|
|
self.__attributes = [] |
|
|
|
|
self._attributes = [] |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def x(self): |
|
|
|
|
return self.__x |
|
|
|
|
return self._x |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def y(self): |
|
|
|
|
return self.__y |
|
|
|
|
return self._y |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def point_size(self): |
|
|
|
|
return self.__point_size |
|
|
|
|
return self._point_size |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def selected(self): |
|
|
|
|
return self.__selected |
|
|
|
|
return self._selected |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def color(self): |
|
|
|
|
return self.__color |
|
|
|
|
return self._color |
|
|
|
|
|
|
|
|
|
@color.setter |
|
|
|
|
def color(self, color): |
|
|
|
|
if not isinstance(color, Color): |
|
|
|
|
raise ValueError('Point color must be of type Color.') |
|
|
|
|
|
|
|
|
|
self.__color = color |
|
|
|
|
self._color = color |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def attributes(self): |
|
|
|
|
return self.__attributes |
|
|
|
|
return self._attributes |
|
|
|
|
|
|
|
|
|
def add_attribute(self, attr): |
|
|
|
|
self.__attributes.append(attr) |
|
|
|
|
self._attributes.append(attr) |
|
|
|
|
|
|
|
|
|
def __calculate_hitbox(self): |
|
|
|
|
def _calculate_hitbox(self): |
|
|
|
|
""" |
|
|
|
|
Calculates the hit box for the point given the current |
|
|
|
|
position (center) and the point size. |
|
|
|
|
""" |
|
|
|
|
half_point = floor(self.point_size / 2.0) |
|
|
|
|
|
|
|
|
|
self.__top_left_corner = (self.__x - half_point, |
|
|
|
|
self.__y + half_point) |
|
|
|
|
self._top_left_corner = (self._x - half_point, |
|
|
|
|
self._y + half_point) |
|
|
|
|
|
|
|
|
|
self.__bottom_right_corner = (self.__x + half_point, |
|
|
|
|
self.__y - half_point) |
|
|
|
|
self._bottom_right_corner = (self._x + half_point, |
|
|
|
|
self._y - half_point) |
|
|
|
|
|
|
|
|
|
def __check_window_bounds(self, x, y): |
|
|
|
|
def _check_window_bounds(self, x, y): |
|
|
|
|
""" |
|
|
|
|
Simple window bound check that raises an exception when |
|
|
|
|
the point (x, y) exceeds the known viewport bounds. |
|
|
|
@ -112,8 +113,8 @@ class Point:
|
|
|
|
|
# We need to include the half point here because |
|
|
|
|
# the (x, y) for a point is the center of the square and we |
|
|
|
|
# do not want the EDGES to exceed the viewport bounds. |
|
|
|
|
if (x > self.__viewport_width - half_point or |
|
|
|
|
y > self.__viewport_height - half_point or |
|
|
|
|
if (x > self._viewport_width - half_point or |
|
|
|
|
y > self._viewport_height - half_point or |
|
|
|
|
x < half_point or |
|
|
|
|
y < half_point): |
|
|
|
|
|
|
|
|
@ -127,14 +128,14 @@ class Point:
|
|
|
|
|
@param dy The delta in the y direction. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
self.__check_window_bounds(self.__x + dx, self.__y + dy) |
|
|
|
|
self._check_window_bounds(self._x + dx, self._y + dy) |
|
|
|
|
|
|
|
|
|
self.__x += dx |
|
|
|
|
self.__y += dy |
|
|
|
|
self._x += dx |
|
|
|
|
self._y += dy |
|
|
|
|
|
|
|
|
|
# It's important to note as we move the point we need to |
|
|
|
|
# make sure we are constantly updating it's hitbox. |
|
|
|
|
self.__calculate_hitbox() |
|
|
|
|
self._calculate_hitbox() |
|
|
|
|
|
|
|
|
|
def __eq__(self, other): |
|
|
|
|
""" |
|
|
|
@ -142,22 +143,22 @@ class Point:
|
|
|
|
|
|
|
|
|
|
@param other The other object. |
|
|
|
|
""" |
|
|
|
|
return (self.__x == other.x and |
|
|
|
|
self.__y == other.y and |
|
|
|
|
self.__color == other.color and |
|
|
|
|
self.__attributes == other.attributes and |
|
|
|
|
self.__point_size == other.point_size) |
|
|
|
|
return (self._x == other.x and |
|
|
|
|
self._y == other.y and |
|
|
|
|
self._color == other.color and |
|
|
|
|
self._attributes == other.attributes and |
|
|
|
|
self._point_size == other.point_size) |
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
|
|
|
|
|
# For some reason I had to split this instead of using one giant |
|
|
|
|
# string chained with `+` inside of `()`. |
|
|
|
|
s = "<POINT " |
|
|
|
|
s += f"X: {self.__x} | Y: {self.__y} | " |
|
|
|
|
s += f"SIZE: {self.__point_size} | " |
|
|
|
|
s += f"COLOR: {self.__color} | " |
|
|
|
|
s += f"VIEWPORT_WIDTH: {self.__viewport_width} | " |
|
|
|
|
s += f"VIEWPORT_HEIGHT: {self.__viewport_height}" |
|
|
|
|
s += f"X: {self._x} | Y: {self._y} | " |
|
|
|
|
s += f"SIZE: {self._point_size} | " |
|
|
|
|
s += f"COLOR: {self._color} | " |
|
|
|
|
s += f"VIEWPORT_WIDTH: {self._viewport_width} | " |
|
|
|
|
s += f"VIEWPORT_HEIGHT: {self._viewport_height}" |
|
|
|
|
s += ">" |
|
|
|
|
|
|
|
|
|
return s |
|
|
|
@ -166,13 +167,13 @@ class Point:
|
|
|
|
|
""" |
|
|
|
|
Selects the point. |
|
|
|
|
""" |
|
|
|
|
self.__selected = True |
|
|
|
|
self._selected = True |
|
|
|
|
|
|
|
|
|
def unselect(self): |
|
|
|
|
""" |
|
|
|
|
Unselects the point. |
|
|
|
|
""" |
|
|
|
|
self.__selected = False |
|
|
|
|
self._selected = False |
|
|
|
|
|
|
|
|
|
def hit(self, x, y): |
|
|
|
|
""" |
|
|
|
@ -196,10 +197,10 @@ class Point:
|
|
|
|
|
This function is necessary for properly deleting and selecting points. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
return (x >= self.__top_left_corner[0] and |
|
|
|
|
x <= self.__bottom_right_corner[0] and |
|
|
|
|
y <= self.__top_left_corner[1] and |
|
|
|
|
y >= self.__bottom_right_corner[1]) |
|
|
|
|
return (x >= self._top_left_corner[0] and |
|
|
|
|
x <= self._bottom_right_corner[0] and |
|
|
|
|
y <= self._top_left_corner[1] and |
|
|
|
|
y >= self._bottom_right_corner[1]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Attribute: |
|
|
|
@ -208,8 +209,8 @@ class Attribute:
|
|
|
|
|
""" |
|
|
|
|
Initializes an attribute. |
|
|
|
|
""" |
|
|
|
|
self.__name = name |
|
|
|
|
self.__value = value |
|
|
|
|
self._name = name |
|
|
|
|
self._value = value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PointSet: |
|
|
|
@ -230,29 +231,29 @@ class PointSet:
|
|
|
|
|
@param viewport_height The height of the viewport for bounds |
|
|
|
|
calculations. |
|
|
|
|
""" |
|
|
|
|
self.__points = [] |
|
|
|
|
self.__point_size = point_size |
|
|
|
|
self.__viewport_width = viewport_width |
|
|
|
|
self.__viewport_height = viewport_height |
|
|
|
|
self._points = [] |
|
|
|
|
self._point_size = point_size |
|
|
|
|
self._viewport_width = viewport_width |
|
|
|
|
self._viewport_height = viewport_height |
|
|
|
|
|
|
|
|
|
def __eq__(self, other): |
|
|
|
|
other_points = list(other.points) |
|
|
|
|
|
|
|
|
|
return (self.__points == other_points and |
|
|
|
|
self.__point_size == other.point_size and |
|
|
|
|
self.__viewport_width == other.viewport_width and |
|
|
|
|
self.__viewport_height == other.viewport_height) |
|
|
|
|
return (self._points == other_points and |
|
|
|
|
self._point_size == other.point_size and |
|
|
|
|
self._viewport_width == other.viewport_width and |
|
|
|
|
self._viewport_height == other.viewport_height) |
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
s = [] |
|
|
|
|
|
|
|
|
|
for p in self.__points: |
|
|
|
|
for p in self._points: |
|
|
|
|
s.append(str(p)) |
|
|
|
|
|
|
|
|
|
return ",".join(s) |
|
|
|
|
|
|
|
|
|
def clear(self): |
|
|
|
|
self.__points = [] |
|
|
|
|
self._points = [] |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def points(self): |
|
|
|
@ -260,37 +261,37 @@ class PointSet:
|
|
|
|
|
Getter for points. Returns a generator for |
|
|
|
|
looping. |
|
|
|
|
""" |
|
|
|
|
for point in self.__points: |
|
|
|
|
for point in self._points: |
|
|
|
|
yield point |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def point_size(self): |
|
|
|
|
return self.__point_size |
|
|
|
|
return self._point_size |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def viewport_height(self): |
|
|
|
|
return self.__viewport_height |
|
|
|
|
return self._viewport_height |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def viewport_width(self): |
|
|
|
|
return self.__viewport_width |
|
|
|
|
return self._viewport_width |
|
|
|
|
|
|
|
|
|
@viewport_height.setter |
|
|
|
|
def viewport_height(self, height): |
|
|
|
|
self.__viewport_height = height |
|
|
|
|
self._viewport_height = height |
|
|
|
|
|
|
|
|
|
@viewport_width.setter |
|
|
|
|
def viewport_width(self, width): |
|
|
|
|
self.__viewport_width = width |
|
|
|
|
self._viewport_width = width |
|
|
|
|
|
|
|
|
|
def empty(self): |
|
|
|
|
return len(self.__points) == 0 |
|
|
|
|
return len(self._points) == 0 |
|
|
|
|
|
|
|
|
|
def clear_selection(self): |
|
|
|
|
""" |
|
|
|
|
Handy helper function to clear all selected points. |
|
|
|
|
""" |
|
|
|
|
for p in self.__points: |
|
|
|
|
for p in self._points: |
|
|
|
|
p.unselect() |
|
|
|
|
|
|
|
|
|
def add_point(self, x, y, color, attrs=[]): |
|
|
|
@ -314,17 +315,17 @@ class PointSet:
|
|
|
|
|
if not isinstance(color, Color): |
|
|
|
|
raise ValueError("Point color must be a Color enum.") |
|
|
|
|
|
|
|
|
|
point = Point(x, y, color, self.__point_size, |
|
|
|
|
self.__viewport_width, self.__viewport_height) |
|
|
|
|
point = Point(x, y, color, self._point_size, |
|
|
|
|
self._viewport_width, self._viewport_height) |
|
|
|
|
|
|
|
|
|
for attr in attrs: |
|
|
|
|
point.add_attribute(attr) |
|
|
|
|
|
|
|
|
|
if point in self.__points: |
|
|
|
|
if point in self._points: |
|
|
|
|
# Silently reject a duplicate point (same center). |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
self.__points.append(point) |
|
|
|
|
self._points.append(point) |
|
|
|
|
|
|
|
|
|
def remove_point(self, x, y): |
|
|
|
|
""" |
|
|
|
@ -342,9 +343,9 @@ class PointSet:
|
|
|
|
|
@param x The x-coordinate. |
|
|
|
|
@param y The y-coordinate. |
|
|
|
|
""" |
|
|
|
|
for p in self.__points: |
|
|
|
|
for p in self._points: |
|
|
|
|
if p.hit(x, y): |
|
|
|
|
self.__points.remove(p) |
|
|
|
|
self._points.remove(p) |
|
|
|
|
|
|
|
|
|
def groups(self): |
|
|
|
|
""" |
|
|
|
@ -353,7 +354,7 @@ class PointSet:
|
|
|
|
|
""" |
|
|
|
|
g = {} |
|
|
|
|
|
|
|
|
|
for p in self.__points: |
|
|
|
|
for p in self._points: |
|
|
|
|
if p.color not in g: |
|
|
|
|
# Create the key for the group color since it does |
|
|
|
|
# not exist. |
|
|
|
|