class ClusteredEventList: """ implements a variant of k-means. same API as EventList, with the difference that ClusteredEventList will simulate a maximum of 2 cursors (if more actual cursors are on the node, they are clustered. In contrast to EventList, the callbacks provide no EventCursors. """ def __init__(self, node, source, onDown = lambda x: None, onUp = lambda x: None, onMotion = lambda x: None, resetMotion = lambda: None, captureEvents = True): self.__centroids = [] self.__centroidByEvent = {} self.__doNewMotion = False self.__callback = { 'onDown': onDown, 'onUp': onUp, 'onMotion': onMotion, 'resetMotion': resetMotion, } self.__eventList = EventList( node = node, source = source, onDown = self.__onDown, onUp = self.__onUp, onMotion = self.__onMotion, resetMotion = self.__resetMotion, captureEvents = captureEvents) def handleInitialDown(self,event): self.__eventList.handleInitialDown(event) def __onDown(self, eventCursor): #self.__callback['onDown'](eventCursor) centroids = list(self.__centroids) # copy self.calcClusters() self.__resetMotion() if len(centroids) != len(self.__centroids): if len(centroids) and self.__centroids[0] == centroids[0]: newCentroid = self.__centroids[1] else: newCentroid = self.__centroids[0] self.__callback['onDown']() def __onUp(self, eventCursor): assert eventCursor in self.__centroidByEvent centroid = self.__centroidByEvent[eventCursor] centroid.removeMember(eventCursor) del self.__centroidByEvent[eventCursor] self.calcClusters() self.__resetMotion() if len(centroid) == 0: self.__callback['onUp']() def __onMotion(self, eventCursor): oldPositions = {} for centroid in self.__centroids: oldPositions[centroid] = centroid.getPos() self.calcClusters() self.__callback['onMotion']() def __resetMotion(self): for centroid in self.__centroids: centroid.resetMotion() self.__callback['resetMotion']() def delete(self): self.__centroids = [] self.__centroidByEvent = {} self.__eventList.delete() self.__callback = { 'onDown': lambda x: None, 'onUp': lambda x: None, 'onMotion': lambda x: None, 'resetMotion': lambda x: None, } def __calcMemberships (self): """ returns True if a membership changed, else False.""" changed = False if not len(self.__centroids): return changed for point in self.__eventList.getCursors(): closestCentroid = self.__centroids[0] minDist = getDistSquared(point.getPos(), closestCentroid.getPos()) for centroid in self.__centroids: distance = getDistSquared (point.getPos(), centroid.getPos()) if distance < minDist: minDist = distance closestCentroid = centroid if not closestCentroid.hasMember(point): self.__doNewMotion = True if point in self.__centroidByEvent: self.__centroidByEvent[point].removeMember(point) self.__centroidByEvent[point] = closestCentroid closestCentroid.addMember(point) changed = True return changed def __tryCalcClusters (self): def __calcInitialCentroids(): self.__centroidByEvent = {} self.__centroids = [] def createCentroid(point): centroid = Centroid() centroid.addMember(point) self.__centroidByEvent[point] = centroid self.__centroids.append(centroid) maxDist = 0 points = None if len(self.__eventList)>1: cursors = self.__eventList.getCursors() for p in cursors: for q in cursors: dist = getDistSquared(p.getPos(),q.getPos()) if dist >= maxDist and p != q: points = p,q maxDist = dist assert(points) for point in points: createCentroid(point) elif len(self.__eventList) == 1: createCentroid(self.__eventList.getCursors()[0]) def __setCentroids(): changed = False for centroid in self.__centroids: if centroid.reposition(): changed = True return changed if not len(self.__centroids): __calcInitialCentroids() self.__calcMemberships() changed = True iterations = 0 while changed: changed = False if __setCentroids(): changed = True if self.__calcMemberships(): changed = True iterations+=1 if iterations>MAX_ITERATIONS: #print "too many iterations(%u), aborting" % iterations __setCentroids() break def calcClusters (self): def __hasBrokenCentroids(): if len(self.__eventList)>1 and len(self.__centroids)!=2: return True for centroid in self.__centroids: if centroid.isBroken(): return True if len(self.__centroids)==2: if self.__centroids[0].getPos() == self.__centroids[1].getPos(): return True return False self.__tryCalcClusters() if __hasBrokenCentroids(): self.__centroids=[] self.__tryCalcClusters() # if __hasBrokenCentroids(): # g_Log.trace(g_Log.APP, # "Cannot fix broken centroids: %s" % self.__centroids) if self.__doNewMotion: self.__doNewMotion = False self.__resetMotion() def getCursors (self): return self.__centroids def __len__(self): return min(len(self.__eventList), len(self.__centroids))