コード例 #1
0
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))