# -*- coding: utf-8 -*-
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from numpy import *
import pickle

## FBIZER
## Luke Campagnola   2009.12.14
#
#  Downloads your list of facebook friends, some of their info, and a list of 
#  which of your friends are connected to each other.
#  Animates the network, allowing people who know each other to group.
#  If you have a large network, you'll need a fast computer.

#  Depends on python, pyqt4, numpy, and pyfacebook
#  run with "python -i fbizer.py"


## You will have to get your own FB application API key/secret combo and put them here:
apikey = ''
secret = ''

#  Control
#  Mouse-over dots to see their info
#  Drag dots to manually reshape the network
#  Right-button-drag up/down to zoom
#  middle-button-drag to pam
#
#  From the command line:
#     runLoop = False   # stops animation
#     startLoop()       # starts animation
#     kick()            # randomly jostles points
#     colorBy(...)      # change the coloring based on user data
#           --  try these:  -- 
#        colorBy('sex')
#        colorBy('relationship_status')
#        colorBy('hometown_location')
#        colorBy(['hometown_location', 'state'])
#        colorBy(['hometown_location', 'city'])
#     view.writeSvg()  ## Save an SVG file of everything visible
#     view.writeImage()  ## Save a PNG image file of everything visible


## Todo:
# names on/off
# lines on/off
# hilight selected node's lines


# Delicious Global Variables! Most of these variables can be changed from 
# the python interactive prompt while the program is running.

# set to True, will store FB data to disk so it only needs to 
# download data the first time the program is run.
# (must be set before starting the program)
storeCache = False  

# set to False to stop the animation loop
runLoop = True

# How much to prevent tightly interconnected groups from squeezing together
uncrowdingFactor = 0.2

# How strongly to push all nodes inward. If you have a large network, 
# you probably wand a smaller bounding force.
boundingForce = 0.005

# Time step for each frame. Larger values cause faster simulation, 
# but may become unstable
dt = 0.01

# Maximum distance a point can move for each frame. Slows things 
# down, but also stabilizes the simulation.
moveLimit = 0.3

# UIDs to remove from the set before displaying
#   looks like [1123321, 122321123, 11232131]
removeUIDs = []










class Node(QGraphicsEllipseItem):
    def __init__(self, array, info):
        rad = array[2] * .05
        QGraphicsEllipseItem.__init__(self, QRectF(-rad, -rad, rad*2, rad*2))
        self.array = array
        self.info = info
        
        self.setZValue(10)
        self.held = False
        self.setColor()
    
    def mousePressEvent(self, ev):
        if ev.button() == Qt.LeftButton:
            ev.accept()
            self.held = True
    
    def mouseMoveEvent(self, ev):
        self.setPos(ev.scenePos(), ignoreHold=True)
        self.updateArray()
    
    def mouseReleaseEvent(self, ev):
        self.held = False
        
    def setPos(self, pos, y=None, ignoreHold=False):
        if self.held and not ignoreHold:
            self.updateArray()
            return
        if y is None:
            QGraphicsEllipseItem.setPos(self, pos)
        else:
            QGraphicsEllipseItem.setPos(self, pos, y)
            
    def updateArray(self):
        self.array[0] = self.pos().x()
        self.array[1] = self.pos().y()
        
    def updatePos(self):
        self.setPos(self.array[0], self.array[1])

    def setColor(self, c=None):
        if c is None:
            self.setBrush(QBrush(QColor(70, 70, 160, 200)))
            self.setPen(QPen(QColor(255, 255, 255, 50)))
        else:
            self.setBrush(QBrush(QColor(c)))
            self.setPen(QPen(QColor(255, 255, 255, 50)))
            #self.setPen(QPen(Qt.NoPen))

def updateGraph():
    global nodeItems, linkItems
    global nodes, links, nodeInd
    
    for i in range(len(nodes)):
        node = nodes[i]
        nodeItems[node].updatePos()
        
    for i in range(len(links)):    
        link = links[i]
        p1 = nodeItems[link[0]].pos()
        p2 = nodeItems[link[1]].pos()
        linkItems[link].setLine(p1.x(), p1.y(), p2.x(), p2.y())
        
    #view.setRange(QRectF(x.min()-1, y.min()-1, x.max()-x.min()+2, y.max()-y.min()+2))  
        
def iterate(dt, debug=False):
    global data, linkMap, unlinkMap, uncrowdingFactor, boundingForce
    x = data[0]
    y = data[1]
    r = data[2]
    diag = eye(len(x), dtype=bool)
    
    xr = atleast_2d(x)
    xc = xr.transpose()
    yr = atleast_2d(y)
    yc = yr.transpose()
    rr = atleast_2d(r)
    rc = rr.transpose()
    
    # matrix of position differences between pairs of nodes
    xd = xr - xc
    yd = yr - yc
    
    rtot = rr + rc  ## matrix of sum of radii between pairs of nodes
    
    if debug:
        print "diffs:"
        print xd
        print yd
    
    
    ## matrix of distance^2 between nodes
    d2 = xd*xd + yd*yd
    d = sqrt(d2)
    if debug:
        print "distance"
        print d
    
    ## unit vectors between nodes
    xu = xd / d
    yu = yd / d
    #if debug:
        #print "units"
        #print xu
        #print yu
        
        
    ## matrix of forces between nodes
    f = - rtot / d2  ## all nodes push away
    
    ## link weighting factor -- nodes surrounded by many siblings should pull less.
    dinv = 1.0/d
    dinv[diag] = 0
    wr = atleast_2d(1.0 / ((uncrowdingFactor * (dinv*linkMap).sum(axis=0)) + 1))  ## represents 1 / (1+ (sum of 1/d)) for each node
    wc = wr.transpose()
    weights = wr * wc
    if debug:
        print "weights"
        print weights
    
    f += 1.0 * d2 * linkMap * weights  ## connected nodes pull toward
    
    ## split into x/y components
    fx = f * xu
    fy = f * yu
    
    ## kill diagonal
    fx[diag] = 0
    fy[diag] = 0
    
    
    if debug:
        print "links"
        print linkMap
        
        print "force"
        print f
        print fx
        print fy
    
    ## sum all forces
    fx = fx.sum(axis=0)
    fy = fy.sum(axis=0)
    
    ## inward force
    fx += boundingForce * x**3
    fy += boundingForce * y**3
    
    ## clip to prevent integration errors
    global moveLimit
    dx = clip(-fx*dt, -moveLimit, moveLimit)
    dy = clip(-fy*dt, -moveLimit, moveLimit)
    
    if debug:
        print "summed force"
        print fx
        print fy
    
    ## update positions
    x += dx
    y += dy
        
def run(iter=10):
    global dt
    for i in range(iter):
        d = False
        #if i == iter-1:
            #d = True
        iterate(dt, debug=d)
    updateGraph()
    
    
#updateGraph()
def loop():
    run(1)
    global runLoop
    if runLoop:
        t = QTimer.singleShot(10, loop)
    
    
def kick(v=0.5):
    global data
    data[0:2] += (random.random((2, data.shape[1]))-0.5) * v
    
def getFacebookData():
    import facebook, codecs
    global apikey, secret

    fb = facebook.Facebook(apikey, secret)
    fb.auth.createToken()
    fb.login()
    print 'After logging in, press enter...'
    raw_input()

    fb.auth.getSession()

    print "Looking up all friend info.."
    info = fb.fql.query('select name, affiliations, religion, sex, hometown_location, relationship_status, significant_other_id, uid from user where uid in (select uid2 from friend where uid1=%d)' % fb.uid)
    friendInfo = dict([(f['uid'], f) for f in info])

    print "Looking up all connections.."
            
    arefriends = fb.fql.query('select uid1, uid2 from friend where uid1 in (select uid2 from friend where uid1=%d) and uid2 in (select uid2 from friend where uid1=%d)' % (fb.uid, fb.uid))

    def sortpair(x,y):
        if x > y:
            return (y, x)
        else:
            return (x, y)

    friendset = set([sortpair(int(f['uid1']), int(f['uid2'])) for f in arefriends])
    return {'friendInfo': friendInfo, 'connect': list(friendset)}
    
def intColor(ind, colors=9, values=3, maxValue=255, minValue=150, sat=255):
    """Creates a QColor from a single index. Useful for stepping through a predefined list of colors."""
    colors = int(colors)
    values = int(values)
    ind = int(ind) % (colors * values)
    indh = ind % colors
    indv = ind / colors
    v = minValue + indv * ((maxValue-minValue) / (values-1))
    h = (indh * 360) / colors
    
    c = QColor()
    c.setHsv(h, sat, v)
    return c    

def multiIndex(data, keys):
    if not isinstance(keys, list):
        keys = [keys]
    for k in keys:
        try:
            data = data[k]
        except:
            return None
    if type(data) is dict:
        return tuple(data.items())
    return data

def colorBy(key):
    global nodeInfo, nodeItems
    if key is None:
        for n in nodeItems:
            nodeItems[n].setColor(None)
        return
    
    vals = [multiIndex(nodeInfo[n], key) for n in nodeInfo]
    vals = list(set(vals))
    colors = {}
    c = 0
    for v in vals:
        if v is None or v == '':
            colors[v] = QColor(100, 100, 100, 100)
        else:
            colors[v] = intColor(c, colors=len(vals))
        c += 1
    for n in nodeItems:
        val = multiIndex(nodeInfo[n], key)
        nodeItems[n].setColor(colors[val])


### Begin codes copied from the great elsewhere to ease distribution.

from math import acos
import types

def pclip(x, mn, mx):
    if x > mx:
        return mx
    if x < mn:
        return mn
    return x

class Point(QPointF):
    """Extension of QPointF which adds a few missing methods."""
    
    def __init__(self, *args):
        if len(args) == 1:
            if hasattr(args[0], '__getitem__'):
                QPointF.__init__(self, float(args[0][0]), float(args[0][1]))
                return
            elif type(args[0]) in [types.FloatType, types.IntType]:
                QPointF.__init__(self, float(args[0]), float(args[0]))
                return
        elif len(args) == 2:
            QPointF.__init__(self, args[0], args[1])
            return
        QPointF.__init__(self, *args)
        
    def __getitem__(self, i):
        if i == 0:
            return self.x()
        elif i == 1:
            return self.y()
        else:
            raise IndexError("Point has no index %d" % i)
        
    def __setitem__(self, i, x):
        if i == 0:
            return self.setX(x)
        elif i == 1:
            return self.setY(x)
        else:
            raise IndexError("Point has no index %d" % i)
        
    def __radd__(self, a):
        return self._math_('__radd__', a)
    
    def __add__(self, a):
        return self._math_('__add__', a)
    
    def __rsub__(self, a):
        return self._math_('__rsub__', a)
    
    def __sub__(self, a):
        return self._math_('__sub__', a)
    
    def __rmul__(self, a):
        return self._math_('__rmul__', a)
    
    def __mul__(self, a):
        return self._math_('__mul__', a)
    
    def __rdiv__(self, a):
        return self._math_('__rdiv__', a)
    
    def __div__(self, a):
        return self._math_('__div__', a)
    
    def __rpow__(self, a):
        return self._math_('__rpow__', a)
    
    def __pow__(self, a):
        return self._math_('__pow__', a)
    
    def _math_(self, op, x):
        #print "point math:", op
        try:
            return Point(getattr(QPointF, op)(self, x))
        except:
            x = Point(x)
            return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1]))
    
    def length(self):
        return (self[0]**2 + self[1]**2) ** 0.5
    
    def angle(self, a):
        n1 = self.length()
        n2 = a.length()
        if n1 == 0. or n2 == 0.:
            return None
        ang = acos(pclip(self.dot(a) / (n1 * n2), -1.0, 1.0))
        c = self.cross(a)
        if c > 0:
            ang *= -1.
        return ang
    
    def dot(self, a):
        a = Point(a)
        return self[0]*a[0] + self[1]*a[1]
    
    def cross(self, a):
        a = Point(a)
        return self[0]*a[1] - self[1]*a[0]
    
    def __repr__(self):
        return "Point(%f, %f)" % (self[0], self[1])
    
    
    def min(self):
        return min(self[0], self[1])
    
    def max(self):
        return max(self[0], self[1])

class GraphicsView(QGraphicsView):
    def __init__(self, *args):
        """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the 
        viewed coordinate range. Also automatically creates a QGraphicsScene and a central QGraphicsWidget
        that is automatically scaled to the full view geometry.
        
        By default, the view coordinate system matches the widget's pixel coordinates and 
        automatically updates when the view is resized. This can be overridden by setting 
        autoPixelRange=False. The exact visible range can be set with setRange().
        
        The view can be panned using the left mouse button and scaled using the right mouse button unless
        disabled via enableMouse(False)."""
        
        QGraphicsView.__init__(self, *args)
        from PyQt4 import QtOpenGL
        self.setViewport(QtOpenGL.QGLWidget())
        palette = QPalette()
        brush = QBrush(QColor(0,0,0))
        brush.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active,QPalette.Base,brush)
        brush = QBrush(QColor(0,0,0))
        brush.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive,QPalette.Base,brush)
        brush = QBrush(QColor(244,244,244))
        brush.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Disabled,QPalette.Base,brush)
        self.setPalette(palette)
        self.setProperty("cursor",QVariant(Qt.ArrowCursor))
        self.setFocusPolicy(Qt.StrongFocus)
        self.setFrameShape(QFrame.NoFrame)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setTransformationAnchor(QGraphicsView.NoAnchor)
        self.setResizeAnchor(QGraphicsView.AnchorViewCenter)
        #self.setResizeAnchor(QGraphicsView.NoAnchor)
        self.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate)
        self.setSceneRect(QRectF(-1e100, -1e100, 1e100, 1e100))
        #self.setInteractive(False)
        self.lockedViewports = []
        self.lastMousePos = None
        #self.setMouseTracking(False)
        self.aspectLocked = False
        self.yInverted = True
        self.range = QRectF(0, 0, 1, 1)
        self.autoPixelRange = True
        self.currentItem = None
        self.clearMouse()
        self.updateMatrix()
        self.sceneObj = QGraphicsScene()
        self.setScene(self.sceneObj)
        self.centralWidget = None
        self.setCentralItem(QGraphicsWidget())
        self.mouseEnabled = False
        
    def setCentralItem(self, item):
        if self.centralWidget is not None:
            self.scene().removeItem(self.centralWidget)
        self.centralWidget = item
        self.sceneObj.addItem(item)
        
    def addItem(self, *args):
        return self.scene().addItem(*args)
        
    def enableMouse(self, b=True):
        self.mouseEnabled = b
        self.autoPixelRange = (not b)
        
    def clearMouse(self):
        self.mouseTrail = []
        self.lastButtonReleased = None
    
    def resizeEvent(self, ev):
        if self.autoPixelRange:
            self.range = QRectF(0, 0, self.size().width(), self.size().height())
        self.setRange(self.range, padding=0)
        self.updateMatrix()
    
    def updateMatrix(self, propagate=True):
        #print "udpateMatrix:"
        translate = Point(self.range.center())
        if self.range.width() == 0 or self.range.height() == 0:
            return
        scale = Point(self.size().width()/self.range.width(), self.size().height()/self.range.height())
        
        m = QMatrix()
        
        ## First center the viewport at 0
        self.resetMatrix()
        center = self.viewportTransform().inverted()[0].map(Point(self.width()/2., self.height()/2.))
        if self.yInverted:
            m.translate(center.x(), center.y())
            #print "  inverted; translate", center.x(), center.y()
        else:
            m.translate(center.x(), -center.y())
            #print "  not inverted; translate", center.x(), -center.y()
            
        ## Now scale and translate properly
        if self.aspectLocked:
            scale = Point(scale.min())
        if not self.yInverted:
            scale = scale * Point(1, -1)
        m.scale(scale[0], scale[1])
        #print "  scale:", scale
        st = translate
        m.translate(-st[0], -st[1])
        #print "  translate:", st
        self.setMatrix(m)
        self.currentScale = scale
        
        if propagate:
            for v in self.lockedViewports:
                v.setXRange(self.range, padding=0)
        
    def visibleRange(self):
        r = QRectF(self.rect())
        return self.viewportTransform().inverted()[0].mapRect(r)

    def translate(self, dx, dy):
        self.range.adjust(dx, dy, dx, dy)
        self.updateMatrix()
    
    def scale(self, sx, sy):
        scale = [sx, sy]
        if self.aspectLocked:
            scale[0] = scale[1]
        adj = (self.range.width()*0.5*(1.0-(1.0/scale[0])), self.range.height()*0.5*(1.0-(1.0/scale[1])))
        #print "======\n", scale, adj
        #print self.range
        self.range.adjust(adj[0], adj[1], -adj[0], -adj[1])
        #print self.range
        
        self.updateMatrix()

    def setRange(self, newRect=None, padding=0.05, lockAspect=None, propagate=True):
        if newRect is None:
            newRect = self.visibleRange()
            padding = 0
        padding = Point(padding)
        newRect = QRectF(newRect)
        pw = newRect.width() * padding[0]
        ph = newRect.height() * padding[1]
        self.range = newRect.adjusted(-pw, -ph, pw, ph)
        #print "New Range:", self.range
        self.centralWidget.setGeometry(self.range)
        self.updateMatrix(propagate)
        self.emit(SIGNAL('viewChanged'), self.range)
        
        
    def lockXRange(self, v1):
        if not v1 in self.lockedViewports:
            self.lockedViewports.append(v1)
        
    def setXRange(self, r, padding=0.05):
        r1 = QRectF(self.range)
        r1.setLeft(r.left())
        r1.setRight(r.right())
        self.setRange(r1, padding=[padding, 0], propagate=False)
        
    def setYRange(self, r, padding=0.05):
        r1 = QRectF(self.range)
        r1.setTop(r.top())
        r1.setBottom(r.bottom())
        self.setRange(r1, padding=[0, padding], propagate=False)
        
    def invertY(self, invert=True):
        #if self.yInverted != invert:
            #self.scale[1] *= -1.
        self.yInverted = invert
        self.updateMatrix()
    
    
    def wheelEvent(self, ev):
        if not self.mouseEnabled:
            return
        QGraphicsView.wheelEvent(self, ev)
        sc = 1.001 ** ev.delta()
        #self.scale *= sc
        #self.updateMatrix()
        self.scale(sc, sc)
        
        
    def setAspectLocked(self, s):
        self.aspectLocked = s
        
    #def mouseDoubleClickEvent(self, ev):
        #QGraphicsView.mouseDoubleClickEvent(self, ev)
        #pass
        
    ## This function is here because interactive mode is disabled due to bugs.
    def graphicsSceneEvent(self, ev, pev=None, fev=None):
        ev1 = GraphicsSceneMouseEvent()
        ev1.setPos(QPointF(ev.pos().x(), ev.pos().y()))
        ev1.setButtons(ev.buttons())
        ev1.setButton(ev.button())
        ev1.setModifiers(ev.modifiers())
        ev1.setScenePos(self.mapToScene(QPoint(ev.pos())))
        if pev is not None:
            ev1.setLastPos(pev.pos())
            ev1.setLastScenePos(pev.scenePos())
            ev1.setLastScreenPos(pev.screenPos())
        if fev is not None:
            ev1.setButtonDownPos(fev.pos())
            ev1.setButtonDownScenePos(fev.scenePos())
            ev1.setButtonDownScreenPos(fev.screenPos())
        return ev1
        
    def mousePressEvent(self, ev):
        QGraphicsView.mousePressEvent(self, ev)
        #print "Press over:"
        #for i in self.items(ev.pos()):
            #print i.zValue(), int(i.acceptedMouseButtons()), i, i.scenePos()
        #print "Grabber:", self.scene().mouseGrabberItem()
        if not self.mouseEnabled:
            return
        self.lastMousePos = Point(ev.pos())
        
        self.currentItem = None
        maxZ = None
        for i in self.items(ev.pos()):
            if maxZ is None or maxZ < i.zValue():
                self.currentItem = i
                maxZ = i.zValue()
        #items = self.items(ev.pos())
        #items.sort(lambda a,b: cmp(a.zValue(), b.zValue())
        self.pev = self.graphicsSceneEvent(ev)
        self.fev = self.pev
        if self.currentItem is not None:
            self.currentItem.mousePressEvent(self.pev)
        #self.clearMouse()
        #self.mouseTrail.append(Point(self.mapToScene(ev.pos())))
        self.emit(SIGNAL("mousePressed(PyQt_PyObject)"), self.mouseTrail)
                
    def mouseReleaseEvent(self, ev):
        QGraphicsView.mouseReleaseEvent(self, ev)
        if not self.mouseEnabled:
            return
            
        #self.mouseTrail.append(Point(self.mapToScene(ev.pos())))
        self.emit(SIGNAL("mouseReleased(PyQt_PyObject)"), self.mouseTrail)
        if self.currentItem is not None:
            pev = self.graphicsSceneEvent(ev, self.pev, self.fev)
            self.pev = pev
            self.currentItem.mouseReleaseEvent(pev)
            self.currentItem = None
        self.lastButtonReleased = ev.button()

    def mouseMoveEvent(self, ev):
        QGraphicsView.mouseMoveEvent(self, ev)
        if not self.mouseEnabled:
            return
        self.emit(SIGNAL("sceneMouseMoved(PyQt_PyObject)"), self.mapToScene(ev.pos()))
        #print "moved. Grabber:", self.scene().mouseGrabberItem()
        
        #self.mouseTrail.append(Point(self.mapToScene(ev.pos())))
        if self.currentItem is not None:
            pev = self.graphicsSceneEvent(ev, self.pev, self.fev)
            self.pev = pev
            self.currentItem.mouseMoveEvent(pev)
        
        if self.lastMousePos is None:
            self.lastMousePos = Point(ev.pos())
        delta = Point(ev.pos()) - self.lastMousePos
        
        self.lastMousePos = Point(ev.pos())
        
        if ev.buttons() == Qt.RightButton:
            delta = Point(pclip(delta[0], -50, 50), pclip(-delta[1], -50, 50))
            scale = 1.01 ** delta
            #if self.yInverted:
                #scale[0] = 1. / scale[0]
            self.scale(scale[0], scale[1])
            self.emit(SIGNAL('regionChanged(QRectF)'), self.range)
        elif ev.buttons() == Qt.MidButton:
            tr = -delta / self.currentScale
            
            self.translate(tr[0], tr[1])
            self.emit(SIGNAL('regionChanged(QRectF)'), self.range)
    
        
    def writeSvg(self, fileName=None):
        if fileName is None:
            fileName = str(QFileDialog.getSaveFileName())
        from PyQt4 import QtSvg
        self.svg = QtSvg.QSvgGenerator()
        self.svg.setFileName(fileName)
        self.svg.setSize(self.size())
        self.svg.setResolution(600)
        painter = QPainter(self.svg)
        self.render(painter)
        
    def writeImage(self, fileName=None):
        if fileName is None:
            fileName = str(QFileDialog.getSaveFileName())
        self.png = QImage(self.size(), QImage.Format_ARGB32)
        painter = QPainter(self.png)
        rh = self.renderHints()
        self.setRenderHints(QPainter.Antialiasing)
        self.render(painter)
        self.setRenderHints(rh)
        self.png.save(fileName)
        
    def getFreehandLine(self):
        
        # Wait for click
        self.clearMouse()
        while self.lastButtonReleased != Qt.LeftButton:
            qApp.sendPostedEvents()
            qApp.processEvents()
            time.sleep(0.01)
        fl = vstack(self.mouseTrail)
        return fl
    
    def getClick(self):
        fl = self.getFreehandLine()
        return fl[-1]
    

class GraphicsSceneMouseEvent(QGraphicsSceneMouseEvent):
    """Stand-in class for QGraphicsSceneMouseEvent"""
    def __init__(self):
        QGraphicsSceneMouseEvent.__init__(self)
            
    def setPos(self, p):
        self.vpos = p
    def setButtons(self, p):
        self.vbuttons = p
    def setButton(self, p):
        self.vbutton = p
    def setModifiers(self, p):
        self.vmodifiers = p
    def setScenePos(self, p):
        self.vscenePos = p
    def setLastPos(self, p):
        self.vlastPos = p
    def setLastScenePos(self, p):
        self.vlastScenePos = p
    def setLastScreenPos(self, p):
        self.vlastScreenPos = p
    def setButtonDownPos(self, p):
        self.vbuttonDownPos = p
    def setButtonDownScenePos(self, p):
        self.vbuttonDownScenePos = p
    def setButtonDownScreenPos(self, p):
        self.vbuttonDownScreenPos = p
    
    def pos(self):
        return self.vpos
    def buttons(self):
        return self.vbuttons
    def button(self):
        return self.vbutton
    def modifiers(self):
        return self.vmodifiers
    def scenePos(self):
        return self.vscenePos
    def lastPos(self):
        return self.vlastPos
    def lastScenePos(self):
        return self.vlastScenePos
    def lastScreenPos(self):
        return self.vlastScreenPos
    def buttonDownPos(self):
        return self.vbuttonDownPos
    def buttonDownScenePos(self):
        return self.vbuttonDownScenePos
    def buttonDownScreenPos(self):
        return self.vbuttonDownScreenPos
    
class View(GraphicsView):
    def __init__(self):
        GraphicsView.__init__(self)
        self.setMouseTracking(True)

    def mouseMoveEvent(self, ev):
        GraphicsView.mouseMoveEvent(self, ev)
        items = self.items(ev.pos())
        for i in items:
            if isinstance(i, Node):
                global status
                exkeys = ['sex', 'religion', 'hometown_location', 'relationship_status']
                extra = ','.join(["%s: %s" % (k, i.info[k]) for k in exkeys])
                status.showMessage("%d: %s   %s" % (i.info['uid'],i.info['name'],extra))
    



try:
    fd = open('facebook_data.pickle')
    data = pickle.load(fd)
    fd.close()
except:
    data = getFacebookData()
    if storeCache:
        fd = open('facebook_data.pickle', 'w')
        pickle.dump(data, fd)
        fd.close()
    
links = data['connect']
nodeInfo = data['friendInfo']
nodes = nodeInfo.keys()

## remove specific uids from list
for k in nodeInfo:
    if k in removeUIDs:
        nodes.remove(k)
        links = [x for x in links if x[0] != k and x[1] != k]
        break


## count connections
for k in nodeInfo:
    n = len([1 for x in links if (k in x)])
    nodeInfo[k]['count'] = n

nodeInd = {}
for i in range(len(nodes)):
    nodeInd[nodes[i]] = i

linkMap = zeros((len(nodes), len(nodes)), dtype=bool)
for l in links:
    i1 = nodeInd[l[0]]
    i2 = nodeInd[l[1]]
    linkMap[i1, i2] = 1
    linkMap[i2, i1] = 1
unlinkMap = 1-linkMap


app = QApplication([])
win = QMainWindow()
win.resize(600, 600)
view = View()
win.setCentralWidget(view)
scene = QGraphicsScene()
view.setScene(scene)
win.show()
view.setAspectLocked(True)
view.enableMouse()
view.setRange(QRectF(-20, -20, 40, 40))
status = QStatusBar()
win.setStatusBar(status)

data = random.random((3, len(nodes)))-0.5 ## columns are x, y, radius
#x = random.random(len(nodes))-0.5
#y = random.random(len(nodes))-0.5
data[2] = 5 + sqrt(array([nodeInfo[k]['count'] for k in nodes]))


nodeItems = {}
for i in range(len(nodes)):
    n = nodes[i]
    item = Node(data[:, i], nodeInfo[n])
    scene.addItem(item)
    nodeItems[n] = item
    
    
linkItems = {}
for l in links:
    i = QGraphicsLineItem(0, 0, 1, 1)
    linkItems[l] = i
    scene.addItem(i)
    so1 = nodeInfo[l[0]]['significant_other_id']
    so2 = nodeInfo[l[1]]['significant_other_id']
    if so1 == l[1] or so2 == l[0]:
        i.setPen(QPen(QColor(255, 255, 0, 100)))
    else:
        i.setPen(QPen(QColor(255, 255, 255, 50)))

def startLoop():
    global runLoop
    runLoop = True
    loop()

startLoop()
