def clear(self):

        """ Remove nodes and edges and reset the layout.
        """

        dict.clear(self)
        self.nodes = []
        self.edges = []
        self.root  = None

        self.layout.i = 0
        self.alpha = 0

def clear ( self ) : dict . clear ( self ) self . nodes = [ ] self . edges = [ ] self . root = None self . layout . i = 0 self . alpha = 0

Remove nodes and edges and reset the layout .


def add_node(self, id, radius=8, style=style.DEFAULT, category="", label=None, root=False,
                 properties={}):

        """ Add node from id and return the node object.
        """

        if self.has_key(id):
            return self[id]

        if not isinstance(style, str) and style.__dict__.has_key["name"]:
            style = style.name

        n = node(self, id, radius, style, category, label, properties)
        self[n.id] = n
        self.nodes.append(n)
        if root: self.root = n

        return n

def add_node ( self , id , radius = 8 , style = style . DEFAULT , category = "" , label = None , root = False , properties = { } ) : if self . has_key ( id ) : return self [ id ] if not isinstance ( style , str ) and style . __dict__ . has_key [ "name" ] : style = style . name n = node ( self , id , radius , style , category , label , properties ) self [ n . id ] = n self . nodes . append ( n ) if root : self . root = n return n

Add node from id and return the node object .


def remove_node(self, id):

        """ Remove node with given id.
        """

        if self.has_key(id):
            n = self[id]
            self.nodes.remove(n)
            del self[id]

            # Remove all edges involving id and all links to it.
            for e in list(self.edges):
                if n in (e.node1, e.node2):
                    if n in e.node1.links:
                        e.node1.links.remove(n)
                    if n in e.node2.links:
                        e.node2.links.remove(n)
                    self.edges.remove(e)


def remove_edge(self, id1, id2):

        """ Remove edges between nodes with given id's.
        """

        for e in list(self.edges):
            if id1 in (e.node1.id, e.node2.id) and \
               id2 in (e.node1.id, e.node2.id):
                e.node1.links.remove(e.node2)
                e.node2.links.remove(e.node1)
                self.edges.remove(e)

def remove_edge ( self , id1 , id2 ) : for e in list ( self . edges ) : if id1 in ( e . node1 . id , e . node2 . id ) and id2 in ( e . node1 . id , e . node2 . id ) : e . node1 . links . remove ( e . node2 ) e . node2 . links . remove ( e . node1 ) self . edges . remove ( e )

Remove edges between nodes with given id s .


def edge(self, id1, id2):
        """ Returns the edge between the nodes with given id1 and id2.
        """
        if id1 in self and \
           id2 in self and \
           self[id2] in self[id1].links:
            return self[id1].links.edge(id2)
        return None

def edge ( self , id1 , id2 ) : if id1 in self and id2 in self and self [ id2 ] in self [ id1 ] . links : return self [ id1 ] . links . edge ( id2 ) return None

Returns the edge between the nodes with given id1 and id2 .


def update(self, iterations=10):

        """ Iterates the graph layout and updates node positions.
        """

        # The graph fades in when initially constructed.
        self.alpha += 0.05
        self.alpha = min(self.alpha, 1.0)

        # Iterates over the graph's layout.
        # Each step the graph's bounds are recalculated
        # and a number of iterations are processed,
        # more and more as the layout progresses.
        if self.layout.i == 0:
            self.layout.prepare()
            self.layout.i += 1
        elif self.layout.i == 1:
            self.layout.iterate()
        elif self.layout.i < self.layout.n:
            n = min(iterations, self.layout.i / 10 + 1)
            for i in range(n):
                self.layout.iterate()

        # Calculate the absolute center of the graph.
        min_, max = self.layout.bounds
        self.x = _ctx.WIDTH - max.x*self.d - min_.x*self.d
        self.y = _ctx.HEIGHT - max.y*self.d - min_.y*self.d
        self.x /= 2
        self.y /= 2

        return not self.layout.done

Iterates the graph layout and updates node positions .


def offset(self, node):
        """ Returns the distance from the center to the given node.
        """
        x = self.x + node.x - _ctx.WIDTH/2
        y = self.y + node.y - _ctx.HEIGHT/2
        return x, y

def offset ( self , node ) : x = self . x + node . x - _ctx . WIDTH / 2 y = self . y + node . y - _ctx . HEIGHT / 2 return x , y

Returns the distance from the center to the given node .


def draw(self, dx=0, dy=0, weighted=False, directed=False, highlight=[], traffic=None):

        """ Layout the graph incrementally.

        The graph is drawn at the center of the canvas.
        The weighted and directed parameters visualize edge weight and direction.
        The highlight specifies list of connected nodes.
        The path will be colored according to the "highlight" style.
        Clicking and dragging events are monitored.

        """

        self.update()

        # Draw the graph background.
        s = self.styles.default
        s.graph_background(s)

        # Center the graph on the canvas.
        _ctx.push()
        _ctx.translate(self.x+dx, self.y+dy)

        # Indicate betweenness centrality.
        if traffic:
            if isinstance(traffic, bool):
                traffic = 5
            for n in self.nodes_by_betweenness()[:traffic]:
                try: s = self.styles[n.style]
                except: s = self.styles.default
                if s.graph_traffic:
                    s.graph_traffic(s, n, self.alpha)

        # Draw the edges and their labels.
        s = self.styles.default
        if s.edges:
            s.edges(s, self.edges, self.alpha, weighted, directed)

        # Draw each node in the graph.
        # Apply individual style to each node (or default).
        for n in self.nodes:
            try:  s = self.styles[n.style]
            except: s = self.styles.default
            if s.node:
                s.node(s, n, self.alpha)

        # Highlight the given shortest path.
        try: s = self.styles.highlight
        except: s = self.styles.default
        if s.path:
            s.path(s, self, highlight)

        # Draw node id's as labels on each node.
        for n in self.nodes:
            try:  s = self.styles[n.style]
            except: s = self.styles.default
            if s.node_label:
                s.node_label(s, n, self.alpha)

        # Events for clicked and dragged nodes.
        # Nodes will resist being dragged by attraction and repulsion,
        # put the event listener on top to get more direct feedback.
        #self.events.update()

        _ctx.pop()

Layout the graph incrementally . The graph is drawn at the center of the canvas . The weighted and directed parameters visualize edge weight and direction . The highlight specifies list of connected nodes . The path will be colored according to the highlight style . Clicking and dragging events are monitored .


def prune(self, depth=0):
        """ Removes all nodes with less or equal links than depth.
        """
        for n in list(self.nodes):
            if len(n.links) <= depth:
                self.remove_node(n.id)

def prune ( self , depth = 0 ) : for n in list ( self . nodes ) : if len ( n . links ) <= depth : self . remove_node ( n . id )

Removes all nodes with less or equal links than depth .


def betweenness_centrality(self, normalized=True):
        """ Calculates betweenness centrality and returns an node id -> weight dictionary.
        Node betweenness weights are updated in the process.
        """
        bc = proximity.brandes_betweenness_centrality(self, normalized)
        for id, w in bc.iteritems(): self[id]._betweenness = w
        return bc

def betweenness_centrality ( self , normalized = True ) : bc = proximity . brandes_betweenness_centrality ( self , normalized ) for id , w in bc . iteritems ( ) : self [ id ] . _betweenness = w return bc

Calculates betweenness centrality and returns an node id - > weight dictionary . Node betweenness weights are updated in the process .


def eigenvector_centrality(self, normalized=True, reversed=True, rating={},
                               start=None, iterations=100, tolerance=0.0001):
        """ Calculates eigenvector centrality and returns an node id -> weight dictionary.
        Node eigenvalue weights are updated in the process.
        """
        ec = proximity.eigenvector_centrality(
            self, normalized, reversed, rating, start, iterations, tolerance
        )
        for id, w in ec.iteritems(): self[id]._eigenvalue = w
        return ec

def eigenvector_centrality ( self , normalized = True , reversed = True , rating = { } , start = None , iterations = 100 , tolerance = 0.0001 ) : ec = proximity . eigenvector_centrality ( self , normalized , reversed , rating , start , iterations , tolerance ) for id , w in ec . iteritems ( ) : self [ id ] . _eigenvalue = w return ec

Calculates eigenvector centrality and returns an node id - > weight dictionary . Node eigenvalue weights are updated in the process .


def nodes_by_betweenness(self, treshold=0.0):
        """ Returns nodes sorted by betweenness centrality.
        Nodes with a lot of passing traffic will be at the front of the list.
        """
        nodes = [(n.betweenness, n) for n in self.nodes if n.betweenness > treshold]
        nodes.sort(); nodes.reverse()
        return [n for w, n in nodes]

def nodes_by_betweenness ( self , treshold = 0.0 ) : nodes = [ ( n . betweenness , n ) for n in self . nodes if n . betweenness > treshold ] nodes . sort ( ) nodes . reverse ( ) return [ n for w , n in nodes ]

Returns nodes sorted by betweenness centrality . Nodes with a lot of passing traffic will be at the front of the list .


def nodes_by_eigenvalue(self, treshold=0.0):
        """ Returns nodes sorted by eigenvector centrality.
        Nodes with a lot of incoming traffic will be at the front of the list
        """
        nodes = [(n.eigenvalue, n) for n in self.nodes if n.eigenvalue > treshold]
        nodes.sort(); nodes.reverse()
        return [n for w, n in nodes]

def nodes_by_eigenvalue ( self , treshold = 0.0 ) : nodes = [ ( n . eigenvalue , n ) for n in self . nodes if n . eigenvalue > treshold ] nodes . sort ( ) nodes . reverse ( ) return [ n for w , n in nodes ]

Returns nodes sorted by eigenvector centrality . Nodes with a lot of incoming traffic will be at the front of the list


def nodes_by_category(self, category):
        """ Returns nodes with the given category attribute.
        """
        return [n for n in self.nodes if n.category == category]

def nodes_by_category ( self , category ) : return [ n for n in self . nodes if n . category == category ]

Returns nodes with the given category attribute .


def crown(self, depth=2):
        """ Returns a list of leaves, nodes connected to leaves, etc.
        """
        nodes = []
        for node in self.leaves: nodes += node.flatten(depth-1)
        return cluster.unique(nodes)

def crown ( self , depth = 2 ) : nodes = [ ] for node in self . leaves : nodes += node . flatten ( depth - 1 ) return cluster . unique ( nodes )

Returns a list of leaves nodes connected to leaves etc .


def _density(self):
        """ The number of edges in relation to the total number of possible edges.
        """
        return 2.0*len(self.edges) / (len(self.nodes) * (len(self.nodes)-1))

def _density ( self ) : return 2.0 * len ( self . edges ) / ( len ( self . nodes ) * ( len ( self . nodes ) - 1 ) )

The number of edges in relation to the total number of possible edges .


def load(self, id):

        """ Rebuilds the graph around the given node id.
        """

        self.clear()

        # Root node.
        self.add_node(id, root=True)

        # Directly connected nodes have priority.
        for w, id2 in self.get_links(id):
            self.add_edge(id, id2, weight=w)
            if len(self) > self.max:
                break

        # Now get all the other nodes in the cluster.
        for w, id2, links in self.get_cluster(id):
            for id3 in links:
                self.add_edge(id3, id2, weight=w)
                self.add_edge(id, id3, weight=w)
            #if len(links) == 0:
            #    self.add_edge(id, id2)
            if len(self) > self.max:
                break

        # Provide a backlink to the previous root.
        if self.event.clicked:
            g.add_node(self.event.clicked)

Rebuilds the graph around the given node id .


def click(self, node):

        """ Callback from graph.events when a node is clicked.
        """

        if not self.has_node(node.id): return
        if node == self.root: return

        self._dx, self._dy = self.offset(node)
        self.previous = self.root.id
        self.load(node.id)

def click ( self , node ) : if not self . has_node ( node . id ) : return if node == self . root : return self . _dx , self . _dy = self . offset ( node ) self . previous = self . root . id self . load ( node . id )

Callback from graph . events when a node is clicked .


def bezier_arc(x1, y1, x2, y2, start_angle=0, extent=90):

    """ Compute a cubic Bezier approximation of an elliptical arc.

    (x1, y1) and (x2, y2) are the corners of the enclosing rectangle.
    The coordinate system has coordinates that increase to the right and down.
    Angles, measured in degress, start with 0 to the right (the positive X axis)
    and increase counter-clockwise.
    The arc extends from start_angle to start_angle+extent.
    I.e. start_angle=0 and extent=180 yields an openside-down semi-circle.

    The resulting coordinates are of the form (x1,y1, x2,y2, x3,y3, x4,y4)
    such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and
    (x3, y3) as their respective Bezier control points.
    """

    x1,y1, x2,y2 = min(x1,x2), max(y1,y2), max(x1,x2), min(y1,y2)

    if abs(extent) <= 90:
        frag_angle = float(extent)
        nfrag = 1
    else:
        nfrag = int(ceil(abs(extent)/90.))
        if nfrag == 0:
            warnings.warn('Invalid value for extent: %r' % extent)
            return []
        frag_angle = float(extent) / nfrag

    x_cen = (x1+x2)/2.
    y_cen = (y1+y2)/2.
    rx = (x2-x1)/2.
    ry = (y2-y1)/2.
    half_angle = radians(frag_angle) / 2
    kappa = abs(4. / 3. * (1. - cos(half_angle)) / sin(half_angle))

    if frag_angle < 0:
        sign = -1
    else:
        sign = 1

    point_list = []

    for i in range(nfrag):
        theta0 = radians(start_angle + i*frag_angle)
        theta1 = radians(start_angle + (i+1)*frag_angle)
        c0 = cos(theta0)
        c1 = cos(theta1)
        s0 = sin(theta0)
        s1 = sin(theta1)
        if frag_angle > 0:
            signed_kappa = -kappa
        else:
            signed_kappa = kappa
        point_list.append((x_cen + rx * c0,
                          y_cen - ry * s0,
                          x_cen + rx * (c0 + signed_kappa * s0),
                          y_cen - ry * (s0 - signed_kappa * c0),
                          x_cen + rx * (c1 - signed_kappa * s1),
                          y_cen - ry * (s1 + signed_kappa * c1),
                          x_cen + rx * c1,
                          y_cen - ry * s1))

    return point_list

def
bezier_arc
(
x1
,
y1
,
x2
,
y2
,
start_angle
=
0
,
extent
=
90
)
:
x1
,
y1
,
x2
,
y2
=
min
(
x1
,
x2
)
,
max
(
y1
,
y2
)
,
max
(
x1
,
x2
)
,
min
(
y1
,
y2
)
if
abs
(
extent
)
<=
90
:
frag_angle
=
float
(
extent
)
nfrag
=
1
else
:
nfrag
=
int
(
ceil
(
abs
(
extent
)
/
90.
)
)
if
nfrag
==
0
:
warnings
.
warn
(
'Invalid value for extent: %r'
%
extent
)
return
[
]
frag_angle
=
float
(
extent
)
/
nfrag
x_cen
=
(
x1
+
x2
)
/
2.
y_cen
=
(
y1
+
y2
)
/
2.
rx
=
(
x2
x1
)
/
2.
ry
=
(
y2
y1
)
/
2.
half_angle
=
radians
(
frag_angle
)
/
2
kappa
=
abs
(
4.
/
3.
*
(
1.
cos
(
half_angle
)
)
/
sin
(
half_angle
)
)
if
frag_angle
<
0
:
sign
=
1
else
:
sign
=
1
point_list
=
[
]
for
i
in
range
(
nfrag
)
:
theta0
=
radians
(
start_angle
+
i
*
frag_angle
)
theta1
=
radians
(
start_angle
+
(
i
+
1
)
*
frag_angle
)
c0
=
cos
(
theta0
)
c1
=
cos
(
theta1
)
s0
=
sin
(
theta0
)
s1
=
sin
(
theta1
)
if
frag_angle
>
0
:
signed_kappa
=
kappa
else
:
signed_kappa
=
kappa
point_list
.
append
(
(
x_cen
+
rx
*
c0
,
y_cen
ry
*
s0
,
x_cen
+
rx
*
(
c0
+
signed_kappa
*
s0
)
,
y_cen
ry
*
(
s0
signed_kappa
*
c0
)
,
x_cen
+
rx
*
(
c1
signed_kappa
*
s1
)
,
y_cen
ry
*
(
s1
+
signed_kappa
*
c1
)
,
x_cen
+
rx
*
c1
,
y_cen
ry
*
s1
)
)
return
point_list

Compute a cubic Bezier approximation of an elliptical arc .


def angle(x1, y1, x2, y2):
    """ The angle in degrees between two vectors.
    """
    sign = 1.0
    usign = (x1*y2 - y1*x2)
    if usign < 0:
        sign = -1.0
    num = x1*x2 + y1*y2
    den = hypot(x1,y1) * hypot(x2,y2)
    ratio = min(max(num/den, -1.0), 1.0)
    return sign * degrees(acos(ratio))

def angle ( x1 , y1 , x2 , y2 ) : sign = 1.0 usign = ( x1 * y2 - y1 * x2 ) if usign < 0 : sign = - 1.0 num = x1 * x2 + y1 * y2 den = hypot ( x1 , y1 ) * hypot ( x2 , y2 ) ratio = min ( max ( num / den , - 1.0 ) , 1.0 ) return sign * degrees ( acos ( ratio ) )

The angle in degrees between two vectors .


def transform_from_local(xp, yp, cphi, sphi, mx, my):
    """ Transform from the local frame to absolute space.
    """
    x = xp * cphi - yp * sphi + mx
    y = xp * sphi + yp * cphi + my
    return (x,y)

def transform_from_local ( xp , yp , cphi , sphi , mx , my ) : x = xp * cphi - yp * sphi + mx y = xp * sphi + yp * cphi + my return ( x , y )

Transform from the local frame to absolute space .


def set_bot(self, bot):
        ''' Bot must be set before running '''
        self.bot = bot
        self.sink.set_bot(bot)

def set_bot ( self , bot ) : self . bot = bot self . sink . set_bot ( bot )


def settings(self, **kwargs):
        '''
        Pass a load of settings into the canvas
        '''
        for k, v in kwargs.items():
            setattr(self, k, v)

def settings ( self , * * kwargs ) : for k , v in kwargs . items ( ) : setattr ( self , k , v )


def size_or_default(self):
        '''
        If size is not set, otherwise set size to DEFAULT_SIZE
        and return it.

        This means, only the first call to size() is valid.
        '''
        if not self.size:
            self.size = self.DEFAULT_SIZE
        return self.size

def size_or_default ( self ) : if not self . size : self . size = self . DEFAULT_SIZE return self . size

If size is not set otherwise set size to DEFAULT_SIZE and return it .


def set_size(self, size):
        '''
        Size is only set the first time it is called

        Size that is set is returned
        '''
        if self.size is None:
            self.size = size
            return size
        else:
            return self.size

def set_size ( self , size ) : if self . size is None : self . size = size return size else : return self . size


def snapshot(self, target, defer=True, file_number=None):
        '''
        Ask the drawqueue to output to target.

        target can be anything supported by the combination
        of canvas implementation and drawqueue implmentation.

        If target is not supported then an exception is thrown.
        '''
        output_func = self.output_closure(target, file_number)
        if defer:
            self._drawqueue.append(output_func)
        else:
            self._drawqueue.append_immediate(output_func)

def snapshot ( self , target , defer = True , file_number = None ) : output_func = self . output_closure ( target , file_number ) if defer : self . _drawqueue . append ( output_func ) else : self . _drawqueue . append_immediate ( output_func )


def flush(self, frame):
        '''
        Passes the drawqueue to the sink for rendering
        '''
        self.sink.render(self.size_or_default(), frame, self._drawqueue)
        self.reset_drawqueue()

def flush ( self , frame ) : self . sink . render ( self . size_or_default ( ) , frame , self . _drawqueue ) self . reset_drawqueue ( )


def overlap(self, x1, y1, x2, y2, r=5):

        """ Returns True when point 1 and point 2 overlap.

        There is an r treshold in which point 1 and point 2
        are considered to overlap.

        """

        if abs(x2-x1) < r and abs(y2-y1) < r:
            return True
        else:
            return False

def overlap ( self , x1 , y1 , x2 , y2 , r = 5 ) : if abs ( x2 - x1 ) < r and abs ( y2 - y1 ) < r : return True else : return False

Returns True when point 1 and point 2 overlap . There is an r treshold in which point 1 and point 2 are considered to overlap .


def reflect(self, x0, y0, x, y):

        """ Reflects the point x, y through origin x0, y0.
        """

        rx = x0 - (x-x0)
        ry = y0 - (y-y0)
        return rx, ry

def reflect ( self , x0 , y0 , x , y ) : rx = x0 - ( x - x0 ) ry = y0 - ( y - y0 ) return rx , ry

Reflects the point x y through origin x0 y0 .


def angle(self, x0, y0, x1, y1):

        """ Calculates the angle between two points.
        """

        a = degrees( atan((y1-y0) / (x1-x0+0.00001)) ) + 360
        if x1-x0 < 0: a += 180
        return a

Calculates the angle between two points .


def coordinates(self, x0, y0, distance, angle):

        """ Calculates the coordinates of a point from the origin.
        """

        x = x0 + cos(radians(angle)) * distance
        y = y0 + sin(radians(angle)) * distance
        return Point(x, y)

def coordinates ( self , x0 , y0 , distance , angle ) : x = x0 + cos ( radians ( angle ) ) * distance y = y0 + sin ( radians ( angle ) ) * distance return Point ( x , y )

Calculates the coordinates of a point from the origin .


def contains_point(self, x, y, d=2):

        """ Returns true when x, y is on the path stroke outline.
        """

        if self.path != None and len(self.path) > 1 \
        and self.path.contains(x, y):
            # If all points around the mouse are also part of the path,
            # this means we are somewhere INSIDE the path.
            # Only points near the edge (i.e. on the outline stroke)
            # should propagate.
            if not self.path.contains(x+d, y) \
            or not self.path.contains(x, y+d) \
            or not self.path.contains(x-d, y) \
            or not self.path.contains(x, y-d) \
            or not self.path.contains(x+d, y+d) \
            or not self.path.contains(x-d, y-d) \
            or not self.path.contains(x+d, y-d) \
            or not self.path.contains(x-d, y+d):
                return True

        return False

Returns true when x y is on the path stroke outline .


def insert_point(self, x, y):

        """ Inserts a point on the path at the mouse location.

        We first need to check if the mouse location is on the path.
        Inserting point is time intensive and experimental.

        """

        try:
            bezier = _ctx.ximport("bezier")
        except:
            from nodebox.graphics import bezier

        # Do a number of checks distributed along the path.
        # Keep the one closest to the actual mouse location.
        # Ten checks works fast but leads to imprecision in sharp corners
        # and curves closely located next to each other.
        # I prefer the slower but more stable approach.
        n = 100
        closest = None
        dx0 = float("inf")
        dy0 = float("inf")
        for i in range(n):
            t = float(i)/n
            pt = self.path.point(t)
            dx = abs(pt.x-x)
            dy = abs(pt.y-y)
            if dx+dy <= dx0+dy0:
                dx0 = dx
                dy0 = dy
                closest = t

        # Next, scan the area around the approximation.
        # If the closest point is located at 0.2 on the path,
        # we need to scan between 0.1 and 0.3 for a better
        # approximation. If 1.5 was the best guess, scan
        # 1.40, 1.41 ... 1.59 and so on.
        # Each decimal precision takes 20 iterations.
        decimals = [3,4]
        for d in decimals:
            d = 1.0/pow(10,d)

            for i in range(20):
                t = closest-d + float(i)*d*0.1
                if t < 0.0: t = 1.0+t
                if t > 1.0: t = t-1.0
                pt = self.path.point(t)
                dx = abs(pt.x-x)
                dy = abs(pt.y-y)
                if dx <= dx0 and dy <= dy0:
                    dx0 = dx
                    dy0 = dy
                    closest_precise = t

            closest = closest_precise

        # Update the points list with the inserted point.
        p = bezier.insert_point(self.path, closest_precise)
        i, t, pt = bezier._locate(self.path, closest_precise)
        i += 1
        pt = PathElement()
        pt.cmd = p[i].cmd
        pt.x = p[i].x
        pt.y = p[i].y
        pt.ctrl1 = Point(p[i].ctrl1.x, p[i].ctrl1.y)
        pt.ctrl2 = Point(p[i].ctrl2.x, p[i].ctrl2.y)
        pt.freehand = False
        self._points.insert(i, pt)
        self._points[i-1].ctrl1 = Point(p[i-1].ctrl1.x, p[i-1].ctrl1.y)
        self._points[i+1].ctrl1 = Point(p[i+1].ctrl1.x, p[i+1].ctrl1.y)
        self._points[i+1].ctrl2 = Point(p[i+1].ctrl2.x, p[i+1].ctrl2.y)

Inserts a point on the path at the mouse location . We first need to check if the mouse location is on the path . Inserting point is time intensive and experimental .


def draw_freehand(self):

        """ Freehand sketching.
        """

        if _ctx._ns["mousedown"]:

            x, y = mouse()
            if self.show_grid:
                x, y = self.grid.snap(x, y)

            if self.freehand_move == True:
                cmd = MOVETO
                self.freehand_move = False
            else:
                cmd = LINETO

            # Add a new LINETO to the path,
            # except when starting to draw,
            # then a MOVETO is added to the path.
            pt = PathElement()
            if cmd != MOVETO:
                pt.freehand = True # Used when mixed with curve drawing.
            else:
                pt.freehand = False
            pt.cmd = cmd
            pt.x = x
            pt.y = y
            pt.ctrl1 = Point(x,y)
            pt.ctrl2 = Point(x,y)
            self._points.append(pt)

            # Draw the current location of the cursor.
            r = 4
            _ctx.nofill()
            _ctx.stroke(self.handle_color)
            _ctx.oval(pt.x-r, pt.y-r, r*2, r*2)
            _ctx.fontsize(9)
            _ctx.fill(self.handle_color)
            _ctx.text(" ("+str(int(pt.x))+", "+str(int(pt.y))+")", pt.x+r, pt.y)

            self._dirty = True

        else:

            # Export the updated drawing,
            # remember to do a MOVETO on the next interaction.
            self.freehand_move = True
            if self._dirty:
                self._points[-1].freehand = False
                self.export_svg()
                self._dirty = False


def export_svg(self):

        """ Exports the path as SVG.

        Uses the filename given when creating this object.
        The file is automatically updated to reflect
        changes to the path.

        """

        d = ""
        if len(self._points) > 0:
            d += "M "+str(self._points[0].x)+" "+str(self._points[0].y)+" "
            for pt in self._points:
                if pt.cmd == MOVETO:
                    d += "M "+str(pt.x)+" "+str(pt.y)+" "
                elif pt.cmd == LINETO:
                    d += "L "+str(pt.x)+" "+str(pt.y)+" "
                elif pt.cmd == CURVETO:
                    d += "C "
                    d += str(pt.ctrl1.x)+" "+str(pt.ctrl1.y)+" "
                    d += str(pt.ctrl2.x)+" "+str(pt.ctrl2.y)+" "
                    d += str(pt.x)+" "+str(pt.y)+" "

        c = "rgb("
        c += str(int(self.path_color.r*255)) + ","
        c += str(int(self.path_color.g*255)) + ","
        c += str(int(self.path_color.b*255)) + ")"

        s  = '<?xml version="1.0"?>\n'
        s += '<svg width="'+str(_ctx.WIDTH)+'pt" height="'+str(_ctx.HEIGHT)+'pt">\n'
        s += '<g>\n'
        s += '<path d="'+d+'" fill="none" stroke="'+c+'" stroke-width="'+str(self.strokewidth)+'" />\n'
        s += '</g>\n'
        s += '</svg>\n'

        f = open(self.file+".svg", "w")
        f.write(s)
        f.close()

def
export_svg
(
self
)
:
d
=
""
if
len
(
self
.
_points
)
>
0
:
d
+=
"M "
+
str
(
self
.
_points
[
0
]
.
x
)
+
" "
+
str
(
self
.
_points
[
0
]
.
y
)
+
" "
for
pt
in
self
.
_points
:
if
pt
.
cmd
==
MOVETO
:
d
+=
"M "
+
str
(
pt
.
x
)
+
" "
+
str
(
pt
.
y
)
+
" "
elif
pt
.
cmd
==
LINETO
:
d
+=
"L "
+
str
(
pt
.
x
)
+
" "
+
str
(
pt
.
y
)
+
" "
elif
pt
.
cmd
==
CURVETO
:
d
+=
"C "
d
+=
str
(
pt
.
ctrl1
.
x
)
+
" "
+
str
(
pt
.
ctrl1
.
y
)
+
" "
d
+=
str
(
pt
.
ctrl2
.
x
)
+
" "
+
str
(
pt
.
ctrl2
.
y
)
+
" "
d
+=
str
(
pt
.
x
)
+
" "
+
str
(
pt
.
y
)
+
" "
c
=
"rgb("
c
+=
str
(
int
(
self
.
path_color
.
r
*
255
)
)
+
","
c
+=
str
(
int
(
self
.
path_color
.
g
*
255
)
)
+
","
c
+=
str
(
int
(
self
.
path_color
.
b
*
255
)
)
+
")"
s
=
'<?xml version="1.0"?>\n'
s
+=
'<svg width="'
+
str
(
_ctx
.
WIDTH
)
+
'pt" height="'
+
str
(
_ctx
.
HEIGHT
)
+
'pt">\n'
s
+=
'<g>\n'
s
+=
'<path d="'
+
d
+
'" fill="none" stroke="'
+
c
+
'" stroke-width="'
+
str
(
self
.
strokewidth
)
+
'" />\n'
s
+=
'</g>\n'
s
+=
'</svg>\n'
f
=
open
(
self
.
file
+
".svg"
,
"w"
)
f
.
write
(
s
)
f
.
close
(
)

Exports the path as SVG . Uses the filename given when creating this object . The file is automatically updated to reflect changes to the path .


def gtk_mouse_button_down(self, widget, event):
        ''' Handle right mouse button clicks '''
        if self.menu_enabled and event.button == 3:
            menu = self.uimanager.get_widget('/Save as')
            menu.popup(None, None, None, None, event.button, event.time)
        else:
            super(ShoebotWindow, self).gtk_mouse_button_down(widget, event)

def
gtk_mouse_button_down
(
self
,
widget
,
event
)
:
if
self
.
menu_enabled
and
event
.
button
==
3
:
menu
=
self
.
uimanager
.
get_widget
(
'/Save as'
)
menu
.
popup
(
,
,
,
,
event
.
button
,
event
.
time
)
else
:
super
(
ShoebotWindow
,
self
)
.
gtk_mouse_button_down
(
widget
,
event
)


def show_variables_window(self):
        """
        Show the variables window.
        """
        if self.var_window is None and self.bot._vars:
            self.var_window = VarWindow(self, self.bot, '%s variables' % (self.title or 'Shoebot'))
            self.var_window.window.connect("destroy", self.var_window_closed)

def
show_variables_window
(
self
)
:
if
self
.
var_window
is
and
self
.
bot
.
_vars
:
self
.
var_window
=
VarWindow
(
self
,
self
.
bot
,
'%s variables'
%
(
self
.
title
or
'Shoebot'
)
)
self
.
var_window
.
window
.
connect
(
"destroy"
,
self
.
var_window_closed
)


def hide_variables_window(self):
        """
        Hide the variables window
        """
        if self.var_window is not None:
            self.var_window.window.destroy()
            self.var_window = None

def hide_variables_window ( self ) : if self . var_window is not None : self . var_window . window . destroy ( ) self . var_window = None


def trigger_fullscreen_action(self, fullscreen):
        """
        Toggle fullscreen from outside the GUI,
        causes the GUI to updated and run all its actions.
        """
        action = self.action_group.get_action('fullscreen')
        action.set_active(fullscreen)

def trigger_fullscreen_action ( self , fullscreen ) : action = self . action_group . get_action ( 'fullscreen' ) action . set_active ( fullscreen )

Toggle fullscreen from outside the GUI causes the GUI to updated and run all its actions .


def do_fullscreen(self, widget):
        """
        Widget Action to Make the window fullscreen and update the bot.
        """
        self.fullscreen()
        self.is_fullscreen = True
        # next lines seem to be needed for window switching really to
        # fullscreen mode before reading it's size values
        while Gtk.events_pending():
            Gtk.main_iteration()
        # we pass informations on full-screen size to bot
        self.bot._screen_width = Gdk.Screen.width()
        self.bot._screen_height = Gdk.Screen.height()
        self.bot._screen_ratio = self.bot._screen_width / self.bot._screen_height

Widget Action to Make the window fullscreen and update the bot .


def do_unfullscreen(self, widget):
        """
        Widget Action to set Windowed Mode.
        """
        self.unfullscreen()
        self.is_fullscreen = False
        self.bot._screen_ratio = None

def do_unfullscreen ( self , widget ) : self . unfullscreen ( ) self . is_fullscreen = False self . bot . _screen_ratio = None


def do_window_close(self, widget, data=None):
        """
        Widget Action to Close the window, triggering the quit event.
        """
        publish_event(QUIT_EVENT)

        if self.has_server:
            self.sock.close()

        self.hide_variables_window()

        self.destroy()
        self.window_open = False

def do_window_close ( self , widget , data = None ) : publish_event ( QUIT_EVENT ) if self . has_server : self . sock . close ( ) self . hide_variables_window ( ) self . destroy ( ) self . window_open = False

Widget Action to Close the window triggering the quit event .


def do_toggle_fullscreen(self, action):
        """
        Widget Action to Toggle fullscreen from the GUI
        """
        is_fullscreen = action.get_active()
        if is_fullscreen:
            self.fullscreen()
        else:
            self.unfullscreen()

def do_toggle_fullscreen ( self , action ) : is_fullscreen = action . get_active ( ) if is_fullscreen : self . fullscreen ( ) else : self . unfullscreen ( )


def do_toggle_variables(self, action):
        """
        Widget Action to toggle showing the variables window.
        """
        self.show_vars = action.get_active()
        if self.show_vars:
            self.show_variables_window()
        else:
            self.hide_variables_window()

def do_toggle_variables ( self , action ) : self . show_vars = action . get_active ( ) if self . show_vars : self . show_variables_window ( ) else : self . hide_variables_window ( )

Widget Action to toggle showing the variables window .


def main_iteration(self):
        """
        Called from main loop, if your sink needs to handle GUI events
        do it here.

        Check any GUI flags then call Gtk.main_iteration to update things.
        """
        if self.show_vars:
            self.show_variables_window()
        else:
            self.hide_variables_window()

        for snapshot_f in self.scheduled_snapshots:
            fn = snapshot_f(self.last_draw_ctx)
            print("Saved snapshot: %s" % fn)
        else:
            self.scheduled_snapshots = deque()

        while Gtk.events_pending():
            Gtk.main_iteration()

def
main_iteration
(
self
)
:
if
self
.
show_vars
:
self
.
show_variables_window
(
)
else
:
self
.
hide_variables_window
(
)
for
snapshot_f
in
self
.
scheduled_snapshots
:
fn
=
snapshot_f
(
self
.
last_draw_ctx
)
print
(
"Saved snapshot: %s"
%
fn
)
else
:
self
.
scheduled_snapshots
=
deque
(
)
while
Gtk
.
events_pending
(
)
:
Gtk
.
main_iteration
(
)

Called from main loop if your sink needs to handle GUI events do it here .


def _mouse_pointer_moved(self, x, y):
        '''GUI callback for mouse moved'''
        self._namespace['MOUSEX'] = x
        self._namespace['MOUSEY'] = y

def _mouse_pointer_moved ( self , x , y ) : self . _namespace [ 'MOUSEX' ] = x self . _namespace [ 'MOUSEY' ] = y


def _key_pressed(self, key, keycode):
        '''GUI callback for key pressed'''
        self._namespace['key'] = key
        self._namespace['keycode'] = keycode
        self._namespace['keydown'] = True

def _key_pressed ( self , key , keycode ) : self . _namespace [ 'key' ] = key self . _namespace [ 'keycode' ] = keycode self . _namespace [ 'keydown' ] = True


def _makeInstance(self, clazz, args, kwargs):
        '''Creates an instance of a class defined in this document.
           This method sets the context of the object to the current context.'''
        inst = clazz(self, *args, **kwargs)
        return inst

def _makeInstance ( self , clazz , args , kwargs ) : inst = clazz ( self , * args , * * kwargs ) return inst

Creates an instance of a class defined in this document . This method sets the context of the object to the current context .


def _makeColorableInstance(self, clazz, args, kwargs):
        """
        Create an object, if fill, stroke or strokewidth
        is not specified, get them from the _canvas

        :param clazz:
        :param args:
        :param kwargs:
        :return:
        """
        kwargs = dict(kwargs)

        fill = kwargs.get('fill', self._canvas.fillcolor)
        if not isinstance(fill, Color):
            fill = Color(fill, mode='rgb', color_range=1)
        kwargs['fill'] = fill

        stroke = kwargs.get('stroke', self._canvas.strokecolor)
        if not isinstance(stroke, Color):
            stroke = Color(stroke, mode='rgb', color_range=1)
        kwargs['stroke'] = stroke

        kwargs['strokewidth'] = kwargs.get('strokewidth', self._canvas.strokewidth)
        inst = clazz(self, *args, **kwargs)
        return inst

def _makeColorableInstance ( self , clazz , args , kwargs ) : kwargs = dict ( kwargs ) fill = kwargs . get ( 'fill' , self . _canvas . fillcolor ) if not isinstance ( fill , Color ) : fill = Color ( fill , mode = 'rgb' , color_range = 1 ) kwargs [ 'fill' ] = fill stroke = kwargs . get ( 'stroke' , self . _canvas . strokecolor ) if not isinstance ( stroke , Color ) : stroke = Color ( stroke , mode = 'rgb' , color_range = 1 ) kwargs [ 'stroke' ] = stroke kwargs [ 'strokewidth' ] = kwargs . get ( 'strokewidth' , self . _canvas . strokewidth ) inst = clazz ( self , * args , * * kwargs ) return inst

Create an object if fill stroke or strokewidth is not specified get them from the _canvas


def show(self, format='png', as_data=False):
        '''Returns an Image object of the current surface. Used for displaying
        output in Jupyter notebooks. Adapted from the cairo-jupyter project.'''

        from io import BytesIO

        b = BytesIO()

        if format == 'png':
            from IPython.display import Image
            surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.WIDTH, self.HEIGHT)
            self.snapshot(surface)
            surface.write_to_png(b)
            b.seek(0)
            data = b.read()
            if as_data:
                return data
            else:
                return Image(data)
        elif format == 'svg':
            from IPython.display import SVG
            surface = cairo.SVGSurface(b, self.WIDTH, self.HEIGHT)
            surface.finish()
            b.seek(0)
            data = b.read()
            if as_data:
                return data
            else:
                return SVG(data)

def show ( self , format = 'png' , as_data = False ) : from io import BytesIO b = BytesIO ( ) if format == 'png' : from IPython . display import Image surface = cairo . ImageSurface ( cairo . FORMAT_ARGB32 , self . WIDTH , self . HEIGHT ) self . snapshot ( surface ) surface . write_to_png ( b ) b . seek ( 0 ) data = b . read ( ) if as_data : return data else : return Image ( data ) elif format == 'svg' : from IPython . display import SVG surface = cairo . SVGSurface ( b , self . WIDTH , self . HEIGHT ) surface . finish ( ) b . seek ( 0 ) data = b . read ( ) if as_data : return data else : return SVG ( data )

Returns an Image object of the current surface . Used for displaying output in Jupyter notebooks . Adapted from the cairo - jupyter project .


def ximport(self, libName):
        '''
        Import Nodebox libraries.

        The libraries get _ctx, which provides
        them with the nodebox API.

        :param libName: Library name to import
        '''
        # from Nodebox
        lib = __import__(libName)
        self._namespace[libName] = lib
        lib._ctx = self
        return lib

def
ximport
(
self
,
libName
)
:
# from Nodebox
lib
=
__import__
(
libName
)
self
.
_namespace
[
libName
]
=
lib
lib
.
_ctx
=
self
return
lib


def size(self, w=None, h=None):
        '''Set the canvas size

        Only the first call will actually be effective.

        :param w: Width
        :param h: height
        '''

        if not w:
            w = self._canvas.width
        if not h:
            h = self._canvas.height
        if not w and not h:
            return (self._canvas.width, self._canvas.height)

        # FIXME: Updating in all these places seems a bit hacky
        w, h = self._canvas.set_size((w, h))
        self._namespace['WIDTH'] = w
        self._namespace['HEIGHT'] = h
        self.WIDTH = w  # Added to make evolution example work
        self.HEIGHT = h


def speed(self, framerate=None):
        '''Set animation framerate.

        :param framerate: Frames per second to run bot.
        :return: Current framerate of animation.
        '''
        if framerate is not None:
            self._speed = framerate
            self._dynamic = True
        else:
            return self._speed

def speed ( self , framerate = None ) : if framerate is not None : self . _speed = framerate self . _dynamic = True else : return self . _speed


def set_callbacks(self, **kwargs):
        ''' Set callbacks for input events '''
        for name in self.SUPPORTED_CALLBACKS:
            func = kwargs.get(name, getattr(self, name))
            setattr(self, name, func)

def set_callbacks ( self , * * kwargs ) : for name in self . SUPPORTED_CALLBACKS : func = kwargs . get ( name , getattr ( self , name ) ) setattr ( self , name , func )


def complement(clr):
    """
    Returns the color and its complement in a list.
    """
    clr = color(clr)
    colors = colorlist(clr)
    colors.append(clr.complement)

    return colors

def complement ( clr ) : clr = color ( clr ) colors = colorlist ( clr ) colors . append ( clr . complement ) return colors

Returns the color and its complement in a list .


def complementary(clr):
    """
    Returns a list of complementary colors.

    The complement is the color 180 degrees across
    the artistic RYB color wheel.
    The list contains darker and softer contrasting
    and complementing colors.
    """
    clr = color(clr)
    colors = colorlist(clr)

    # A contrasting color: much darker or lighter than the original.
    c = clr.copy()
    if clr.brightness > 0.4:
        c.brightness = 0.1 + c.brightness * 0.25
    else:
        c.brightness = 1.0 - c.brightness * 0.25
    colors.append(c)

    # A soft supporting color: lighter and less saturated.
    c = clr.copy()
    c.brightness = 0.3 + c.brightness
    c.saturation = 0.1 + c.saturation * 0.3
    colors.append(c)

    # A contrasting complement: very dark or very light.
    clr = clr.complement
    c = clr.copy()
    if clr.brightness > 0.3:
        c.brightness = 0.1 + clr.brightness * 0.25
    else:
        c.brightness = 1.0 - c.brightness * 0.25
    colors.append(c)

    # The complement and a light supporting variant.
    colors.append(clr)

    c = clr.copy()
    c.brightness = 0.3 + c.brightness
    c.saturation = 0.1 + c.saturation * 0.25
    colors.append(c)

    return colors

Returns a list of complementary colors .


def split_complementary(clr):
    """
    Returns a list with the split complement of the color.

    The split complement are the two colors to the left and right
    of the color's complement.
    """
    clr = color(clr)
    colors = colorlist(clr)
    clr = clr.complement
    colors.append(clr.rotate_ryb(-30).lighten(0.1))
    colors.append(clr.rotate_ryb(30).lighten(0.1))

    return colors

def split_complementary ( clr ) : clr = color ( clr ) colors = colorlist ( clr ) clr = clr . complement colors . append ( clr . rotate_ryb ( - 30 ) . lighten ( 0.1 ) ) colors . append ( clr . rotate_ryb ( 30 ) . lighten ( 0.1 ) ) return colors

Returns a list with the split complement of the color .


def left_complement(clr):
    """
    Returns the left half of the split complement.

    A list is returned with the same darker and softer colors
    as in the complementary list, but using the hue of the
    left split complement instead of the complement itself.
    """
    left = split_complementary(clr)[1]
    colors = complementary(clr)
    colors[3].h = left.h
    colors[4].h = left.h
    colors[5].h = left.h

    colors = colorlist(
        colors[0], colors[2], colors[1], colors[3], colors[4], colors[5]
    )

    return colors

def left_complement ( clr ) : left = split_complementary ( clr ) [ 1 ] colors = complementary ( clr ) colors [ 3 ] . h = left . h colors [ 4 ] . h = left . h colors [ 5 ] . h = left . h colors = colorlist ( colors [ 0 ] , colors [ 2 ] , colors [ 1 ] , colors [ 3 ] , colors [ 4 ] , colors [ 5 ] ) return colors

Returns the left half of the split complement .


def right_complement(clr):
    """
    Returns the right half of the split complement.
    """
    right = split_complementary(clr)[2]
    colors = complementary(clr)
    colors[3].h = right.h
    colors[4].h = right.h
    colors[5].h = right.h

    colors = colorlist(
        colors[0], colors[2], colors[1], colors[5], colors[4], colors[3]
    )

    return colors

def right_complement ( clr ) : right = split_complementary ( clr ) [ 2 ] colors = complementary ( clr ) colors [ 3 ] . h = right . h colors [ 4 ] . h = right . h colors [ 5 ] . h = right . h colors = colorlist ( colors [ 0 ] , colors [ 2 ] , colors [ 1 ] , colors [ 5 ] , colors [ 4 ] , colors [ 3 ] ) return colors

Returns the right half of the split complement .


def analogous(clr, angle=10, contrast=0.25):
    """
    Returns colors that are next to each other on the wheel.

    These yield natural color schemes (like shades of water or sky).
    The angle determines how far the colors are apart,
    making it bigger will introduce more variation.
    The contrast determines the darkness/lightness of
    the analogue colors in respect to the given colors.
    """
    contrast = max(0, min(contrast, 1.0))

    clr = color(clr)
    colors = colorlist(clr)

    for i, j in [(1, 2.2), (2, 1), (-1, -0.5), (-2, 1)]:
        c = clr.rotate_ryb(angle * i)
        t = 0.44 - j * 0.1
        if clr.brightness - contrast * j < t:
            c.brightness = t
        else:
            c.brightness = clr.brightness - contrast * j
        c.saturation -= 0.05
        colors.append(c)

    return colors

def analogous ( clr , angle = 10 , contrast = 0.25 ) : contrast = max ( 0 , min ( contrast , 1.0 ) ) clr = color ( clr ) colors = colorlist ( clr ) for i , j in [ ( 1 , 2.2 ) , ( 2 , 1 ) , ( - 1 , - 0.5 ) , ( - 2 , 1 ) ] : c = clr . rotate_ryb ( angle * i ) t = 0.44 - j * 0.1 if clr . brightness - contrast * j < t : c . brightness = t else : c . brightness = clr . brightness - contrast * j c . saturation -= 0.05 colors . append ( c ) return colors

Returns colors that are next to each other on the wheel .


def triad(clr, angle=120):
    """
    Returns a triad of colors.

    The triad is made up of this color and two other colors
    that together make up an equilateral triangle on
    the artistic color wheel.
    """
    clr = color(clr)
    colors = colorlist(clr)
    colors.append(clr.rotate_ryb(angle).lighten(0.1))
    colors.append(clr.rotate_ryb(-angle).lighten(0.1))

    return colors

def triad ( clr , angle = 120 ) : clr = color ( clr ) colors = colorlist ( clr ) colors . append ( clr . rotate_ryb ( angle ) . lighten ( 0.1 ) ) colors . append ( clr . rotate_ryb ( - angle ) . lighten ( 0.1 ) ) return colors


def tetrad(clr, angle=90):
    """
    Returns a tetrad of colors.

    The tetrad is made up of this color and three other colors
    that together make up a cross on the artistic color wheel.
    """
    clr = color(clr)
    colors = colorlist(clr)

    c = clr.rotate_ryb(angle)
    if clr.brightness < 0.5:
        c.brightness += 0.2
    else:
        c.brightness -= -0.2
    colors.append(c)

    c = clr.rotate_ryb(angle * 2)
    if clr.brightness < 0.5:
        c.brightness += 0.1
    else:
        c.brightness -= -0.1
    colors.append(c)

    colors.append(clr.rotate_ryb(angle * 3).lighten(0.1))

    return colors

def tetrad ( clr , angle = 90 ) : clr = color ( clr ) colors = colorlist ( clr ) c = clr . rotate_ryb ( angle ) if clr . brightness < 0.5 : c . brightness += 0.2 else : c . brightness -= - 0.2 colors . append ( c ) c = clr . rotate_ryb ( angle * 2 ) if clr . brightness < 0.5 : c . brightness += 0.1 else : c . brightness -= - 0.1 colors . append ( c ) colors . append ( clr . rotate_ryb ( angle * 3 ) . lighten ( 0.1 ) ) return colors


def compound(clr, flip=False):
    """
    Roughly the complement and some far analogs.
    """
    def _wrap(x, min, threshold, plus):
        if x - min < threshold:
            return x + plus
        else:
            return x - min

    d = 1
    if flip: d = -1

    clr = color(clr)
    colors = colorlist(clr)

    c = clr.rotate_ryb(30 * d)
    c.brightness = _wrap(clr.brightness, 0.25, 0.6, 0.25)
    colors.append(c)

    c = clr.rotate_ryb(30 * d)
    c.saturation = _wrap(clr.saturation, 0.4, 0.1, 0.4)
    c.brightness = _wrap(clr.brightness, 0.4, 0.2, 0.4)
    colors.append(c)

    c = clr.rotate_ryb(160 * d)
    c.saturation = _wrap(clr.saturation, 0.25, 0.1, 0.25)
    c.brightness = max(0.2, clr.brightness)
    colors.append(c)

    c = clr.rotate_ryb(150 * d)
    c.saturation = _wrap(clr.saturation, 0.1, 0.8, 0.1)
    c.brightness = _wrap(clr.brightness, 0.3, 0.6, 0.3)
    colors.append(c)

    c = clr.rotate_ryb(150 * d)
    c.saturation = _wrap(clr.saturation, 0.1, 0.8, 0.1)
    c.brightness = _wrap(clr.brightness, 0.4, 0.2, 0.4)
    # colors.append(c)

    return colors

def
compound
(
clr
,
flip
=
False
)
:
def
_wrap
(
x
,
min
,
threshold
,
plus
)
:
if
x
min
<
threshold
:
return
x
+
plus
else
:
return
x
min
d
=
1
if
flip
:
d
=
1
clr
=
color
(
clr
)
colors
=
colorlist
(
clr
)
c
=
clr
.
rotate_ryb
(
30
*
d
)
c
.
brightness
=
_wrap
(
clr
.
brightness
,
0.25
,
0.6
,
0.25
)
colors
.
append
(
c
)
c
=
clr
.
rotate_ryb
(
30
*
d
)
c
.
saturation
=
_wrap
(
clr
.
saturation
,
0.4
,
0.1
,
0.4
)
c
.
brightness
=
_wrap
(
clr
.
brightness
,
0.4
,
0.2
,
0.4
)
colors
.
append
(
c
)
c
=
clr
.
rotate_ryb
(
160
*
d
)
c
.
saturation
=
_wrap
(
clr
.
saturation
,
0.25
,
0.1
,
0.25
)
c
.
brightness
=
max
(
0.2
,
clr
.
brightness
)
colors
.
append
(
c
)
c
=
clr
.
rotate_ryb
(
150
*
d
)
c
.
saturation
=
_wrap
(
clr
.
saturation
,
0.1
,
0.8
,
0.1
)
c
.
brightness
=
_wrap
(
clr
.
brightness
,
0.3
,
0.6
,
0.3
)
colors
.
append
(
c
)
c
=
clr
.
rotate_ryb
(
150
*
d
)
c
.
saturation
=
_wrap
(
clr
.
saturation
,
0.1
,
0.8
,
0.1
)
c
.
brightness
=
_wrap
(
clr
.
brightness
,
0.4
,
0.2
,
0.4
)
# colors.append(c)
return
colors

Roughly the complement and some far analogs .


def outline(path, colors, precision=0.4, continuous=True):
    """
    Outlines each contour in a path with the colors in the list.

    Each contour starts with the first color in the list,
    and ends with the last color in the list.

    Because each line segment is drawn separately,
    works only with corner-mode transforms.
    """
    # The count of points in a given path/contour.
    def _point_count(path, precision):
        return max(int(path.length * precision * 0.5), 10)

    # The total count of points in the path.
    n = sum([_point_count(contour, precision) for contour in path.contours])

    # For a continuous gradient,
    # we need to calculate a subrange in the list of colors
    # for each contour to draw colors from.
    contour_i = 0
    contour_n = len(path.contours) - 1
    if contour_n == 0: continuous = False

    i = 0
    for contour in path.contours:

        if not continuous: i = 0

        # The number of points for each contour.
        j = _point_count(contour, precision)

        first = True
        for pt in contour.points(j):
            if first:
                first = False
            else:
                if not continuous:
                    # If we have a list of 100 colors and 50 points,
                    # point i maps to color i*2.
                    clr = float(i) / j * len(colors)
                else:
                    # In a continuous gradient of 100 colors,
                    # the 2nd contour in a path with 10 contours
                    # draws colors between 10-20
                    clr = float(i) / n * len(colors) - 1 * contour_i / contour_n
                _ctx.stroke(colors[int(clr)])
                _ctx.line(x0, y0, pt.x, pt.y)
            x0 = pt.x
            y0 = pt.y
            i += 1

        pt = contour.point(0.9999999)  # Fix in pathmatics!
        _ctx.line(x0, y0, pt.x, pt.y)
        contour_i += 1

Outlines each contour in a path with the colors in the list .


def guess_name(clr):
    """
    Guesses the shade and hue name of a color.

    If the given color is named in the named_colors list, return that name.
    Otherwise guess its nearest hue and shade range.
    """
    clr = Color(clr)

    if clr.is_transparent: return "transparent"
    if clr.is_black: return "black"
    if clr.is_white: return "white"
    if clr.is_black: return "black"

    for name in named_colors:
        try:
            r, g, b = named_colors[name]
        except:
            continue
        if r == clr.r and g == clr.g and b == clr.b:
            return name

    for shade in shades:
        if clr in shade:
            return shade.name + " " + clr.nearest_hue()
            break

    return clr.nearest_hue()

def
guess_name
(
clr
)
:
clr
=
Color
(
clr
)
if
clr
.
is_transparent
:
return
"transparent"
if
clr
.
is_black
:
return
"black"
if
clr
.
is_white
:
return
"white"
if
clr
.
is_black
:
return
"black"
for
name
in
named_colors
:
try
:
r
,
g
,
b
=
named_colors
[
name
]
except
:
continue
if
r
==
clr
.
r
and
g
==
clr
.
g
and
b
==
clr
.
b
:
return
name
for
shade
in
shades
:
if
clr
in
shade
:
return
shade
.
name
+
" "
+
clr
.
nearest_hue
(
)
break
return
clr
.
nearest_hue
(
)

Guesses the shade and hue name of a color .


def shader(x, y, dx, dy, radius=300, angle=0, spread=90):
    """
    Returns a 0.0 - 1.0 brightness adjusted to a light source.

    The light source is positioned at dx, dy.
    The returned float is calculated for x, y position
    (e.g. an oval at x, y should have this brightness).

    The radius influences the strength of the light,
    angle and spread control the direction of the light.
    """
    if angle != None:
        radius *= 2

    # Get the distance and angle between point and light source.
    d = sqrt((dx - x) ** 2 + (dy - y) ** 2)
    a = degrees(atan2(dy - y, dx - x)) + 180

    # If no angle is defined,
    # light is emitted evenly in all directions
    # and carries as far as the defined radius
    # (e.g. like a radial gradient).
    if d <= radius:
        d1 = 1.0 * d / radius
    else:
        d1 = 1.0
    if angle is None:
        return 1 - d1

    # Normalize the light's direction and spread
    # between 0 and 360.
    angle = 360 - angle % 360
    spread = max(0, min(spread, 360))
    if spread == 0:
        return 0.0

    # Objects that fall within the spreaded direction
    # of the light are illuminated.
    d = abs(a - angle)
    if d <= spread / 2:
        d2 = d / spread + d1
    else:
        d2 = 1.0

    # Wrapping from 0 to 360:
    # a light source with a direction of 10 degrees
    # and a spread of 45 degrees illuminates
    # objects between 0 and 35 degrees and 350 and 360 degrees.
    if 360 - angle <= spread / 2:
        d = abs(360 - angle + a)
        if d <= spread / 2:
            d2 = d / spread + d1
    # Wrapping from 360 to 0.
    if angle < spread / 2:
        d = abs(360 + angle - a)
        if d <= spread / 2:
            d2 = d / spread + d1

    return 1 - max(0, min(d2, 1))

Returns a 0 . 0 - 1 . 0 brightness adjusted to a light source .


def aggregated(cache=DEFAULT_CACHE):
    """
    A dictionary of all aggregated words.

    They keys in the dictionary correspond to subfolders in the aggregated cache.
    Each key has a list of words. Each of these words is the name of an XML-file
    in the subfolder. The XML-file contains color information harvested from the web
    (or handmade).
    """
    global _aggregated_name, _aggregated_dict
    if _aggregated_name != cache:
        _aggregated_name = cache
        _aggregated_dict = {}
        for path in glob(os.path.join(cache, "*")):
            if os.path.isdir(path):
                p = os.path.basename(path)
                _aggregated_dict[p] = glob(os.path.join(path, "*"))
                _aggregated_dict[p] = [os.path.basename(f)[:-4] for f in _aggregated_dict[p]]

    return _aggregated_dict

def aggregated ( cache = DEFAULT_CACHE ) : global _aggregated_name , _aggregated_dict if _aggregated_name != cache : _aggregated_name = cache _aggregated_dict = { } for path in glob ( os . path . join ( cache , "*" ) ) : if os . path . isdir ( path ) : p = os . path . basename ( path ) _aggregated_dict [ p ] = glob ( os . path . join ( path , "*" ) ) _aggregated_dict [ p ] = [ os . path . basename ( f ) [ : - 4 ] for f in _aggregated_dict [ p ] ] return _aggregated_dict


def morguefile(query, n=10, top=10):
    """
    Returns a list of colors drawn from a morgueFile image.

    With the Web library installed,
    downloads a thumbnail from morgueFile and retrieves pixel colors.
    """

    from web import morguefile
    images = morguefile.search(query)[:top]
    path = choice(images).download(thumbnail=True, wait=10)

    return ColorList(path, n, name=query)

def morguefile ( query , n = 10 , top = 10 ) : from web import morguefile images = morguefile . search ( query ) [ : top ] path = choice ( images ) . download ( thumbnail = True , wait = 10 ) return ColorList ( path , n , name = query )

Returns a list of colors drawn from a morgueFile image .


def str_to_rgb(self, str):

        """ Returns RGB values based on a descriptive string.

        If the given str is a named color, return its RGB values.
        Otherwise, return a random named color that has str
        in its name, or a random named color which name appears in str.

        Specific suffixes (-ish, -ed, -y and -like) are recognised
        as well, for example, if you need a random variation of "red"
        you can use reddish (or greenish, yellowy, etc.)

        """

        str = str.lower()
        for ch in "_- ":
            str = str.replace(ch, "")

        # if named_hues.has_key(str):
        #    clr = color(named_hues[str], 1, 1, mode="hsb")
        #    return clr.r, clr.g, clr.b

        if named_colors.has_key(str):
            return named_colors[str]

        for suffix in ["ish", "ed", "y", "like"]:
            str = re.sub("(.*?)" + suffix + "$", "\\1", str)
        str = re.sub("(.*?)dd$", "\\1d", str)

        matches = []
        for name in named_colors:
            if name in str or str in name:
                matches.append(named_colors[name])
        if len(matches) > 0:
            return choice(matches)

        return named_colors["transparent"]

Returns RGB values based on a descriptive string .


def rotate_ryb(self, angle=180):

        """ Returns a color rotated on the artistic RYB color wheel.

        An artistic color wheel has slightly different opposites
        (e.g. purple-yellow instead of purple-lime).
        It is mathematically incorrect but generally assumed
        to provide better complementary colors.

        http://en.wikipedia.org/wiki/RYB_color_model

        """

        h = self.h * 360
        angle = angle % 360

        # Approximation of Itten's RYB color wheel.
        # In HSB, colors hues range from 0-360.
        # However, on the artistic color wheel these are not evenly distributed.
        # The second tuple value contains the actual distribution.
        wheel = [
            (0, 0), (15, 8),
            (30, 17), (45, 26),
            (60, 34), (75, 41),
            (90, 48), (105, 54),
            (120, 60), (135, 81),
            (150, 103), (165, 123),
            (180, 138), (195, 155),
            (210, 171), (225, 187),
            (240, 204), (255, 219),
            (270, 234), (285, 251),
            (300, 267), (315, 282),
            (330, 298), (345, 329),
            (360, 0)
        ]

        # Given a hue, find out under what angle it is
        # located on the artistic color wheel.
        for i in _range(len(wheel) - 1):
            x0, y0 = wheel[i]
            x1, y1 = wheel[i + 1]
            if y1 < y0:
                y1 += 360
            if y0 <= h <= y1:
                a = 1.0 * x0 + (x1 - x0) * (h - y0) / (y1 - y0)
                break

        # And the user-given angle (e.g. complement).
        a = (a + angle) % 360

        # For the given angle, find out what hue is
        # located there on the artistic color wheel.
        for i in _range(len(wheel) - 1):
            x0, y0 = wheel[i]
            x1, y1 = wheel[i + 1]
            if y1 < y0:
                y1 += 360
            if x0 <= a <= x1:
                h = 1.0 * y0 + (y1 - y0) * (a - x0) / (x1 - x0)
                break

        h = h % 360
        return Color(h / 360, self.s, self.brightness, self.a, mode="hsb", name="")

Returns a color rotated on the artistic RYB color wheel .


def nearest_hue(self, primary=False):

        """ Returns the name of the nearest named hue.

        For example,
        if you supply an indigo color (a color between blue and violet),
        the return value is "violet". If primary is set  to True,
        the return value is "purple".

        Primary colors leave out the fuzzy lime, teal,
        cyan, azure and violet hues.

        """
        if self.is_black:
            return "black"
        elif self.is_white:
            return "white"
        elif self.is_grey:
            return "grey"

        if primary:
            hues = primary_hues
        else:
            hues = named_hues.keys()
        nearest, d = "", 1.0
        for hue in hues:
            if abs(self.hue - named_hues[hue]) % 1 < d:
                nearest, d = hue, abs(self.hue - named_hues[hue]) % 1

        return nearest

def nearest_hue ( self , primary = False ) : if self . is_black : return "black" elif self . is_white : return "white" elif self . is_grey : return "grey" if primary : hues = primary_hues else : hues = named_hues . keys ( ) nearest , d = "" , 1.0 for hue in hues : if abs ( self . hue - named_hues [ hue ] ) % 1 < d : nearest , d = hue , abs ( self . hue - named_hues [ hue ] ) % 1 return nearest

Returns the name of the nearest named hue .


def blend(self, clr, factor=0.5):
        """
        Returns a mix of two colors.
        """
        r = self.r * (1 - factor) + clr.r * factor
        g = self.g * (1 - factor) + clr.g * factor
        b = self.b * (1 - factor) + clr.b * factor
        a = self.a * (1 - factor) + clr.a * factor
        return Color(r, g, b, a, mode="rgb")

def blend ( self , clr , factor = 0.5 ) : r = self . r * ( 1 - factor ) + clr . r * factor g = self . g * ( 1 - factor ) + clr . g * factor b = self . b * ( 1 - factor ) + clr . b * factor a = self . a * ( 1 - factor ) + clr . a * factor return Color ( r , g , b , a , mode = "rgb" )


def swatch(self, x, y, w=35, h=35, roundness=0):
        """
        Rectangle swatch for this color.
        """
        _ctx.fill(self)
        _ctx.rect(x, y, w, h, roundness)

def swatch ( self , x , y , w = 35 , h = 35 , roundness = 0 ) : _ctx . fill ( self ) _ctx . rect ( x , y , w , h , roundness )


def image_to_rgb(self, path, n=10):
        """
        Returns a list of colors based on pixel values in the image.

        The Core Image library must be present to determine pixel colors.
        F. Albers: http://nodebox.net/code/index.php/shared_2007-06-11-11-37-05

        """
        from PIL import Image
        img = Image.open(path)
        p = img.getdata()
        f = lambda p: choice(p)

        for i in _range(n):
            rgba = f(p)
            rgba = _list(rgba)
            if len(rgba) == 3:
                rgba.append(255)
            r, g, b, a = [v / 255.0 for v in rgba]
            clr = color(r, g, b, a, mode="rgb")
            self.append(clr)

def image_to_rgb ( self , path , n = 10 ) : from PIL import Image img = Image . open ( path ) p = img . getdata ( ) f = lambda p : choice ( p ) for i in _range ( n ) : rgba = f ( p ) rgba = _list ( rgba ) if len ( rgba ) == 3 : rgba . append ( 255 ) r , g , b , a = [ v / 255.0 for v in rgba ] clr = color ( r , g , b , a , mode = "rgb" ) self . append ( clr )

Returns a list of colors based on pixel values in the image .


def context_to_rgb(self, str):
        """ Returns the colors that have the given word in their context.

        For example, the word "anger" appears
        in black, orange and red contexts,
        so the list will contain those three colors.

        """
        matches = []
        for clr in context:
            tags = context[clr]
            for tag in tags:
                if tag.startswith(str) \
                        or str.startswith(tag):
                    matches.append(clr)
                    break

        matches = [color(name) for name in matches]
        return matches

def context_to_rgb ( self , str ) : matches = [ ] for clr in context : tags = context [ clr ] for tag in tags : if tag . startswith ( str ) or str . startswith ( tag ) : matches . append ( clr ) break matches = [ color ( name ) for name in matches ] return matches

Returns the colors that have the given word in their context .


def _context(self):
        """
        Returns the intersection of each color's context.

        Get the nearest named hue of each color,
        and finds overlapping tags in each hue's colors.
        For example, a list containing yellow, deeppink and olive
        yields: femininity, friendship, happiness, joy.

        """
        tags1 = None
        for clr in self:
            overlap = []
            if clr.is_black:
                name = "black"
            elif clr.is_white:
                name = "white"
            elif clr.is_grey:
                name = "grey"
            else:
                name = clr.nearest_hue(primary=True)
            if name == "orange" and clr.brightness < 0.6:
                name = "brown"
            tags2 = context[name]
            if tags1 is None:
                tags1 = tags2
            else:
                for tag in tags2:
                    if tag in tags1:
                        if tag not in overlap:
                            overlap.append(tag)
                tags1 = overlap

        overlap.sort()
        return overlap

def _context ( self ) : tags1 = None for clr in self : overlap = [ ] if clr . is_black : name = "black" elif clr . is_white : name = "white" elif clr . is_grey : name = "grey" else : name = clr . nearest_hue ( primary = True ) if name == "orange" and clr . brightness < 0.6 : name = "brown" tags2 = context [ name ] if tags1 is None : tags1 = tags2 else : for tag in tags2 : if tag in tags1 : if tag not in overlap : overlap . append ( tag ) tags1 = overlap overlap . sort ( ) return overlap

Returns the intersection of each color s context .


def copy(self):

        """ Returns a deep copy of the list.
        """

        return ColorList(
            [color(clr.r, clr.g, clr.b, clr.a, mode="rgb") for clr in self],
            name=self.name,
            tags=self.tags
        )

def copy ( self ) : return ColorList ( [ color ( clr . r , clr . g , clr . b , clr . a , mode = "rgb" ) for clr in self ] , name = self . name , tags = self . tags )


def _darkest(self):
        """
        Returns the darkest color from the list.

        Knowing the contrast between a light and a dark swatch
        can help us decide how to display readable typography.

        """
        min, n = (1.0, 1.0, 1.0), 3.0
        for clr in self:
            if clr.r + clr.g + clr.b < n:
                min, n = clr, clr.r + clr.g + clr.b

        return min

def _darkest ( self ) : min , n = ( 1.0 , 1.0 , 1.0 ) , 3.0 for clr in self : if clr . r + clr . g + clr . b < n : min , n = clr , clr . r + clr . g + clr . b return min

Returns the darkest color from the list .


def _average(self):
        """
        Returns one average color for the colors in the list.
        """
        r, g, b, a = 0, 0, 0, 0
        for clr in self:
            r += clr.r
            g += clr.g
            b += clr.b
            a += clr.alpha

        r /= len(self)
        g /= len(self)
        b /= len(self)
        a /= len(self)

        return color(r, g, b, a, mode="rgb")

def _average ( self ) : r , g , b , a = 0 , 0 , 0 , 0 for clr in self : r += clr . r g += clr . g b += clr . b a += clr . alpha r /= len ( self ) g /= len ( self ) b /= len ( self ) a /= len ( self ) return color ( r , g , b , a , mode = "rgb" )

Returns one average color for the colors in the list .


def sort_by_distance(self, reversed=False):
        """
        Returns a list with the smallest distance between two neighboring colors.
        The algorithm has a factorial complexity so it may run slow.
        """
        if len(self) == 0: return ColorList()

        # Find the darkest color in the list.
        root = self[0]
        for clr in self[1:]:
            if clr.brightness < root.brightness:
                root = clr

        # Remove the darkest color from the stack,
        # put it in the sorted list as starting element.
        stack = [clr for clr in self]
        stack.remove(root)
        sorted = [root]

        # Now find the color in the stack closest to that color.
        # Take this color from the stack and add it to the sorted list.
        # Now find the color closest to that color, etc.
        while len(stack) > 1:
            closest, distance = stack[0], stack[0].distance(sorted[-1])
            for clr in stack[1:]:
                d = clr.distance(sorted[-1])
                if d < distance:
                    closest, distance = clr, d
            stack.remove(closest)
            sorted.append(closest)
        sorted.append(stack[0])

        if reversed: _list.reverse(sorted)
        return ColorList(sorted)

Returns a list with the smallest distance between two neighboring colors . The algorithm has a factorial complexity so it may run slow .


def _sorted_copy(self, comparison, reversed=False):
        """
        Returns a sorted copy with the colors arranged according to the given comparison.
        """
        sorted = self.copy()
        _list.sort(sorted, comparison)
        if reversed:
            _list.reverse(sorted)
        return sorted

def _sorted_copy ( self , comparison , reversed = False ) : sorted = self . copy ( ) _list . sort ( sorted , comparison ) if reversed : _list . reverse ( sorted ) return sorted

Returns a sorted copy with the colors arranged according to the given comparison .


def cluster_sort(self, cmp1="hue", cmp2="brightness", reversed=False, n=12):
        """
        Sorts the list by cmp1, then cuts it into n pieces which are sorted by cmp2.

        If you want to cluster by hue, use n=12 (since there are 12 primary/secondary hues).
        The resulting list will not contain n even slices:
        n is used rather to slice up the cmp1 property of the colors,
        e.g. cmp1=brightness and n=3 will cluster colors by brightness >= 0.66, 0.33, 0.0
        """
        sorted = self.sort(cmp1)
        clusters = ColorList()

        d = 1.0
        i = 0
        for j in _range(len(sorted)):
            if getattr(sorted[j], cmp1) < d:
                clusters.extend(sorted[i:j].sort(cmp2))
                d -= 1.0 / n
                i = j
        clusters.extend(sorted[i:].sort(cmp2))
        if reversed: _list.reverse(clusters)
        return clusters

def cluster_sort ( self , cmp1 = "hue" , cmp2 = "brightness" , reversed = False , n = 12 ) : sorted = self . sort ( cmp1 ) clusters = ColorList ( ) d = 1.0 i = 0 for j in _range ( len ( sorted ) ) : if getattr ( sorted [ j ] , cmp1 ) < d : clusters . extend ( sorted [ i : j ] . sort ( cmp2 ) ) d -= 1.0 / n i = j clusters . extend ( sorted [ i : ] . sort ( cmp2 ) ) if reversed : _list . reverse ( clusters ) return clusters

Sorts the list by cmp1 then cuts it into n pieces which are sorted by cmp2 .


def reverse(self):
        """
        Returns a reversed copy of the list.
        """
        colors = ColorList.copy(self)
        _list.reverse(colors)
        return colors

def reverse ( self ) : colors = ColorList . copy ( self ) _list . reverse ( colors ) return colors


def repeat(self, n=2, oscillate=False, callback=None):
        """
        Returns a list that is a repetition of the given list.

        When oscillate is True,
        moves from the end back to the beginning,
        and then from the beginning to the end, and so on.
        """
        colorlist = ColorList()
        colors = ColorList.copy(self)
        for i in _range(n):
            colorlist.extend(colors)
            if oscillate: colors = colors.reverse()
            if callback: colors = callback(colors)

        return colorlist

def repeat ( self , n = 2 , oscillate = False , callback = None ) : colorlist = ColorList ( ) colors = ColorList . copy ( self ) for i in _range ( n ) : colorlist . extend ( colors ) if oscillate : colors = colors . reverse ( ) if callback : colors = callback ( colors ) return colorlist

Returns a list that is a repetition of the given list .


def swatch(self, x, y, w=35, h=35, padding=0, roundness=0):
        """
        Rectangle swatches for all the colors in the list.
        """
        for clr in self:
            clr.swatch(x, y, w, h, roundness)
            y += h + padding

def swatch ( self , x , y , w = 35 , h = 35 , padding = 0 , roundness = 0 ) : for clr in self : clr . swatch ( x , y , w , h , roundness ) y += h + padding

Rectangle swatches for all the colors in the list .


def swarm(self, x, y, r=100):
        """
        Fancy random ovals for all the colors in the list.
        """
        sc = _ctx.stroke(0, 0, 0, 0)
        sw = _ctx.strokewidth(0)

        _ctx.push()
        _ctx.transform(_ctx.CORNER)
        _ctx.translate(x, y)

        for i in _range(r * 3):
            clr = choice(self).copy()
            clr.alpha -= 0.5 * random()
            _ctx.fill(clr)
            clr = choice(self)
            _ctx.stroke(clr)
            _ctx.strokewidth(10 * random())

            _ctx.rotate(360 * random())

            r2 = r * 0.5 * random()
            _ctx.oval(r * random(), 0, r2, r2)
        _ctx.pop()

        _ctx.strokewidth(sw)
        if sc is None:
            _ctx.nostroke()
        else:
            _ctx.stroke(sc)

def swarm ( self , x , y , r = 100 ) : sc = _ctx . stroke ( 0 , 0 , 0 , 0 ) sw = _ctx . strokewidth ( 0 ) _ctx . push ( ) _ctx . transform ( _ctx . CORNER ) _ctx . translate ( x , y ) for i in _range ( r * 3 ) : clr = choice ( self ) . copy ( ) clr . alpha -= 0.5 * random ( ) _ctx . fill ( clr ) clr = choice ( self ) _ctx . stroke ( clr ) _ctx . strokewidth ( 10 * random ( ) ) _ctx . rotate ( 360 * random ( ) ) r2 = r * 0.5 * random ( ) _ctx . oval ( r * random ( ) , 0 , r2 , r2 ) _ctx . pop ( ) _ctx . strokewidth ( sw ) if sc is None : _ctx . nostroke ( ) else : _ctx . stroke ( sc )

Fancy random ovals for all the colors in the list .


def _interpolate(self, colors, n=100):

        """ Returns intermediary colors for given list of colors.
        """

        gradient = []
        for i in _range(n):
            l = len(colors) - 1
            x = int(1.0 * i / n * l)
            x = min(x + 0, l)
            y = min(x + 1, l)

            base = 1.0 * n / l * x
            d = (i - base) / (1.0 * n / l)
            r = colors[x].r * (1 - d) + colors[y].r * d
            g = colors[x].g * (1 - d) + colors[y].g * d
            b = colors[x].b * (1 - d) + colors[y].b * d
            a = colors[x].a * (1 - d) + colors[y].a * d

            gradient.append(color(r, g, b, a, mode="rgb"))

        gradient.append(colors[-1])
        return gradient

def _interpolate ( self , colors , n = 100 ) : gradient = [ ] for i in _range ( n ) : l = len ( colors ) - 1 x = int ( 1.0 * i / n * l ) x = min ( x + 0 , l ) y = min ( x + 1 , l ) base = 1.0 * n / l * x d = ( i - base ) / ( 1.0 * n / l ) r = colors [ x ] . r * ( 1 - d ) + colors [ y ] . r * d g = colors [ x ] . g * ( 1 - d ) + colors [ y ] . g * d b = colors [ x ] . b * ( 1 - d ) + colors [ y ] . b * d a = colors [ x ] . a * ( 1 - d ) + colors [ y ] . a * d gradient . append ( color ( r , g , b , a , mode = "rgb" ) ) gradient . append ( colors [ - 1 ] ) return gradient

Returns intermediary colors for given list of colors .


def _cache(self):
        """
        Populates the list with a number of gradient colors.

        The list has Gradient.steps colors that interpolate between
        the fixed base Gradient.colors.

        The spread parameter controls the midpoint of the gradient,
        you can shift it right and left. A separate gradient is
        calculated for each half and then glued together.
        """
        n = self.steps

        # Only one color in base list.
        if len(self._colors) == 1:
            ColorList.__init__(self, [self._colors[0] for i in _range(n)])
            return

        # Expand the base list so we can chop more accurately.
        colors = self._interpolate(self._colors, 40)

        # Chop into left half and right half.
        # Make sure their ending and beginning match colors.
        left = colors[:len(colors) / 2]
        right = colors[len(colors) / 2:]
        left.append(right[0])
        right.insert(0, left[-1])

        # Calculate left and right gradient proportionally to spread.
        gradient = self._interpolate(left, int(n * self.spread))[:-1]
        gradient.extend(
            self._interpolate(right, n - int(n * self.spread))[1:]
        )

        if self.spread > 1: gradient = gradient[:n]
        if self.spread < 0: gradient = gradient[-n:]
        ColorList.__init__(self, gradient)

Populates the list with a number of gradient colors .


def copy(self, clr=None, d=0.0):
        """
        Returns a copy of the range.

        Optionally, supply a color to get a range copy
        limited to the hue of that color.
        """
        cr = ColorRange()
        cr.name = self.name

        cr.h = deepcopy(self.h)
        cr.s = deepcopy(self.s)
        cr.b = deepcopy(self.b)
        cr.a = deepcopy(self.a)

        cr.grayscale = self.grayscale
        if not self.grayscale:
            cr.black = self.black.copy()
            cr.white = self.white.copy()

        if clr != None:
            cr.h, cr.a = clr.h + d * (random() * 2 - 1), clr.a

        return cr

def copy ( self , clr = None , d = 0.0 ) : cr = ColorRange ( ) cr . name = self . name cr . h = deepcopy ( self . h ) cr . s = deepcopy ( self . s ) cr . b = deepcopy ( self . b ) cr . a = deepcopy ( self . a ) cr . grayscale = self . grayscale if not self . grayscale : cr . black = self . black . copy ( ) cr . white = self . white . copy ( ) if clr != None : cr . h , cr . a = clr . h + d * ( random ( ) * 2 - 1 ) , clr . a return cr


def color(self, clr=None, d=0.035):
        """
        Returns a color with random values in the defined h, s b, a ranges.

        If a color is given, use that color's hue and alpha,
        and generate its saturation and brightness from the shade.
        The hue is varied with the given d.

        In this way you could have a "warm" color range
        that returns all kinds of warm colors.
        When a red color is given as parameter it would generate
        all kinds of warm red colors.
        """
        # Revert to grayscale for black, white and grey hues.
        if clr != None and not isinstance(clr, Color):
            clr = color(clr)
        if clr != None and not self.grayscale:
            if clr.is_black: return self.black.color(clr, d)
            if clr.is_white: return self.white.color(clr, d)
            if clr.is_grey: return choice(
                (self.black.color(clr, d), self.white.color(clr, d))
            )

        h, s, b, a = self.h, self.s, self.b, self.a
        if clr != None:
            h, a = clr.h + d * (random() * 2 - 1), clr.a

        hsba = []
        for v in [h, s, b, a]:
            if isinstance(v, _list):
                min, max = choice(v)
            elif isinstance(v, tuple):
                min, max = v
            else:
                min, max = v, v
            hsba.append(min + (max - min) * random())

        h, s, b, a = hsba
        return color(h, s, b, a, mode="hsb")

Returns a color with random values in the defined h s b a ranges .


def contains(self, clr):
        """
        Returns True if the given color is part of this color range.

        Check whether each h, s, b, a component of the color
        falls within the defined range for that component.

        If the given color is grayscale,
        checks against the definitions for black and white.
        """
        if not isinstance(clr, Color):
            return False

        if not isinstance(clr, _list):
            clr = [clr]

        for clr in clr:

            if clr.is_grey and not self.grayscale:
                return (self.black.contains(clr) or \
                        self.white.contains(clr))

            for r, v in [(self.h, clr.h), (self.s, clr.s), (self.b, clr.brightness), (self.a, clr.a)]:
                if isinstance(r, _list):
                    pass
                elif isinstance(r, tuple):
                    r = [r]
                else:
                    r = [(r, r)]
                for min, max in r:
                    if not (min <= v <= max):
                        return False

        return True

def contains ( self , clr ) : if not isinstance ( clr , Color ) : return False if not isinstance ( clr , _list ) : clr = [ clr ] for clr in clr : if clr . is_grey and not self . grayscale : return ( self . black . contains ( clr ) or self . white . contains ( clr ) ) for r , v in [ ( self . h , clr . h ) , ( self . s , clr . s ) , ( self . b , clr . brightness ) , ( self . a , clr . a ) ] : if isinstance ( r , _list ) : pass elif isinstance ( r , tuple ) : r = [ r ] else : r = [ ( r , r ) ] for min , max in r : if not ( min <= v <= max ) : return False return True

Returns True if the given color is part of this color range .


def _xml(self):
        """
        Returns the color information as XML.

        The XML has the following structure:
        <colors query="">
            <color name="" weight="" />
                <rgb r="" g="" b="" />
                <shade name="" weight="" />
            </color>
        </colors>

        Notice that ranges are stored by name and retrieved in the _load()
        method with the shade() command - and are thus expected to be
        shades (e.g. intense, warm, ...) unless the shade() command would
        return any custom ranges as well. This can be done by appending custom
        ranges to the shades list.
        """
        grouped = self._weight_by_hue()

        xml = "<colors query=\"" + self.name + "\" tags=\"" + ", ".join(self.tags) + "\">\n\n"
        for total_weight, normalized_weight, hue, ranges in grouped:
            if hue == self.blue: hue = "blue"
            clr = color(hue)
            xml += "\t<color name=\"" + clr.name + "\" weight=\"" + str(normalized_weight) + "\">\n "
            xml += "\t\t<rgb r=\"" + str(clr.r) + "\" g=\"" + str(clr.g) + "\" "
            xml += "b=\"" + str(clr.b) + "\" a=\"" + str(clr.a) + "\" />\n "
            for clr, rng, wgt in ranges:
                xml += "\t\t<shade name=\"" + str(rng) + "\" weight=\"" + str(wgt / total_weight) + "\" />\n "
            xml = xml.rstrip(" ") + "\t</color>\n\n"
        xml += "</colors>"

        return xml

def
_xml
(
self
)
:
grouped
=
self
.
_weight_by_hue
(
)
xml
=
"<colors query=\""
+
self
.
name
+
"\" tags=\""
+
", "
.
join
(
self
.
tags
)
+
"\">\n\n"
for
total_weight
,
normalized_weight
,
hue
,
ranges
in
grouped
:
if
hue
==
self
.
blue
:
hue
=
"blue"
clr
=
color
(
hue
)
xml
+=
"\t<color name=\""
+
clr
.
name
+
"\" weight=\""
+
str
(
normalized_weight
)
+
"\">\n "
xml
+=
"\t\t<rgb r=\""
+
str
(
clr
.
r
)
+
"\" g=\""
+
str
(
clr
.
g
)
+
"\" "
xml
+=
"b=\""
+
str
(
clr
.
b
)
+
"\" a=\""
+
str
(
clr
.
a
)
+
"\" />\n "
for
clr
,
rng
,
wgt
in
ranges
:
xml
+=
"\t\t<shade name=\""
+
str
(
rng
)
+
"\" weight=\""
+
str
(
wgt
/
total_weight
)
+
"\" />\n "
xml
=
xml
.
rstrip
(
" "
)
+
"\t</color>\n\n"
xml
+=
"</colors>"
return
xml


def _save(self):
        """
        Saves the color information in the cache as XML.
        """
        if not os.path.exists(self.cache):
            os.makedirs(self.cache)

        path = os.path.join(self.cache, self.name + ".xml")
        f = open(path, "w")
        f.write(self.xml)
        f.close()

def _save ( self ) : if not os . path . exists ( self . cache ) : os . makedirs ( self . cache ) path = os . path . join ( self . cache , self . name + ".xml" ) f = open ( path , "w" ) f . write ( self . xml ) f . close ( )

Saves the color information in the cache as XML .


def _load(self, top=5, blue="blue", archive=None, member=None):
        """
        Loads a theme from aggregated web data.

        The data must be old-style Prism XML: <color>s consisting of <shade>s.
        Colors named "blue" will be overridden with the blue parameter.

        archive can be a file like object (e.g. a ZipFile)
        and will be used along with 'member' if specified.
        """
        if archive is None:
            path = os.path.join(self.cache, self.name + ".xml")
            xml = open(path).read()
        else:
            assert member is not None
            xml = archive.read(member)
        dom = parseString(xml).documentElement

        attr = lambda e, a: e.attributes[a].value

        for e in dom.getElementsByTagName("color")[:top]:
            w = float(attr(e, "weight"))
            try:
                rgb = e.getElementsByTagName("rgb")[0]
                clr = color(
                    float(attr(rgb, "r")),
                    float(attr(rgb, "g")),
                    float(attr(rgb, "b")),
                    float(attr(rgb, "a")),
                    mode="rgb"
                )
                try:
                    clr.name = attr(e, "name")
                    if clr.name == "blue": clr = color(blue)
                except:
                    pass
            except:
                name = attr(e, "name")
                if name == "blue": name = blue
                clr = color(name)

            for s in e.getElementsByTagName("shade"):
                self.ranges.append((
                    clr,
                    shade(attr(s, "name")),
                    w * float(attr(s, "weight"))
                ))

def _load ( self , top = 5 , blue = "blue" , archive = None , member = None ) : if archive is None : path = os . path . join ( self . cache , self . name + ".xml" ) xml = open ( path ) . read ( ) else : assert member is not None xml = archive . read ( member ) dom = parseString ( xml ) . documentElement attr = lambda e , a : e . attributes [ a ] . value for e in dom . getElementsByTagName ( "color" ) [ : top ] : w = float ( attr ( e , "weight" ) ) try : rgb = e . getElementsByTagName ( "rgb" ) [ 0 ] clr = color ( float ( attr ( rgb , "r" ) ) , float ( attr ( rgb , "g" ) ) , float ( attr ( rgb , "b" ) ) , float ( attr ( rgb , "a" ) ) , mode = "rgb" ) try : clr . name = attr ( e , "name" ) if clr . name == "blue" : clr = color ( blue ) except : pass except : name = attr ( e , "name" ) if name == "blue" : name = blue clr = color ( name ) for s in e . getElementsByTagName ( "shade" ) : self . ranges . append ( ( clr , shade ( attr ( s , "name" ) ) , w * float ( attr ( s , "weight" ) ) ) )

Loads a theme from aggregated web data .


def color(self, d=0.035):
        """
        Returns a random color within the theme.

        Fetches a random range (the weight is taken into account,
        so ranges with a bigger weight have a higher chance of propagating)
        and hues it with the associated color.
        """
        s = sum([w for clr, rng, w in self.ranges])
        r = random()
        for clr, rng, weight in self.ranges:
            if weight / s >= r: break
            r -= weight / s

        return rng(clr, d)

def color ( self , d = 0.035 ) : s = sum ( [ w for clr , rng , w in self . ranges ] ) r = random ( ) for clr , rng , weight in self . ranges : if weight / s >= r : break r -= weight / s return rng ( clr , d )

Returns a random color within the theme .


def colors(self, n=10, d=0.035):
        """
        Returns a number of random colors from the theme.
        """
        s = sum([w for clr, rng, w in self.ranges])
        colors = colorlist()
        for i in _range(n):
            r = random()
            for clr, rng, weight in self.ranges:
                if weight / s >= r: break
                r -= weight / s
            colors.append(rng(clr, d))

        return colors

def colors ( self , n = 10 , d = 0.035 ) : s = sum ( [ w for clr , rng , w in self . ranges ] ) colors = colorlist ( ) for i in _range ( n ) : r = random ( ) for clr , rng , weight in self . ranges : if weight / s >= r : break r -= weight / s colors . append ( rng ( clr , d ) ) return colors

Returns a number of random colors from the theme .


def recombine(self, other, d=0.7):
        """
        Genetic recombination of two themes using cut and splice technique.
        """
        a, b = self, other
        d1 = max(0, min(d, 1))
        d2 = d1

        c = ColorTheme(
            name=a.name[:int(len(a.name) * d1)] +
                 b.name[int(len(b.name) * d2):],
            ranges=a.ranges[:int(len(a.ranges) * d1)] +
                   b.ranges[int(len(b.ranges) * d2):],
            top=a.top,
            cache=os.path.join(DEFAULT_CACHE, "recombined"),
            blue=a.blue,
            length=a.length * d1 + b.length * d2
        )
        c.tags = a.tags[:int(len(a.tags) * d1)]
        c.tags += b.tags[int(len(b.tags) * d2):]
        return c

def recombine ( self , other , d = 0.7 ) : a , b = self , other d1 = max ( 0 , min ( d , 1 ) ) d2 = d1 c = ColorTheme ( name = a . name [ : int ( len ( a . name ) * d1 ) ] + b . name [ int ( len ( b . name ) * d2 ) : ] , ranges = a . ranges [ : int ( len ( a . ranges ) * d1 ) ] + b . ranges [ int ( len ( b . ranges ) * d2 ) : ] , top = a . top , cache = os . path . join ( DEFAULT_CACHE , "recombined" ) , blue = a . blue , length = a . length * d1 + b . length * d2 ) c . tags = a . tags [ : int ( len ( a . tags ) * d1 ) ] c . tags += b . tags [ int ( len ( b . tags ) * d2 ) : ] return c

Genetic recombination of two themes using cut and splice technique .


def swatch(self, x, y, w=35, h=35, padding=4, roundness=0, n=12, d=0.035, grouped=None):
        """
        Draws a weighted swatch with approximately n columns and rows.

        When the grouped parameter is True, colors are grouped in blocks of the same hue
        (also see the _weight_by_hue() method).
        """
        if grouped is None:  # should be True or False
            grouped = self.group_swatches

        # If we dont't need to make groups,
        # just display an individual column for each weight
        # in the (color, range, weight) tuples.
        if not grouped:
            s = sum([wgt for clr, rng, wgt in self.ranges])
            for clr, rng, wgt in self.ranges:
                cols = max(1, int(wgt / s * n))
                for i in _range(cols):
                    rng.colors(clr, n=n, d=d).swatch(x, y, w, h, padding=padding, roundness=roundness)
                    x += w + padding

            return x, y + n * (h + padding)

        # When grouped, combine hues and display them
        # in batches of rows, then moving on to the next hue.
        grouped = self._weight_by_hue()
        for total_weight, normalized_weight, hue, ranges in grouped:
            dy = y
            rc = 0
            for clr, rng, weight in ranges:
                dx = x
                cols = int(normalized_weight * n)
                cols = max(1, min(cols, n - len(grouped)))
                if clr.name == "black": rng = rng.black
                if clr.name == "white": rng = rng.white
                for i in _range(cols):
                    rows = int(weight / total_weight * n)
                    rows = max(1, rows)
                    # Each column should add up to n rows,
                    # if not due to rounding errors, add a row at the bottom.
                    if (clr, rng, weight) == ranges[-1] and rc + rows < n: rows += 1
                    rng.colors(clr, n=rows, d=d).swatch(dx, dy, w, h, padding=padding, roundness=roundness)
                    dx += w + padding
                dy += (w + padding) * rows  # + padding
                rc = rows
            x += (w + padding) * cols + padding

        return x, dy

Draws a weighted swatch with approximately n columns and rows .


def fseq(self, client, message):
        """
        fseq messages associate a unique frame id with a set of set
        and alive messages
        """
        client.last_frame = client.current_frame
        client.current_frame = message[3]

def fseq ( self , client , message ) : client . last_frame = client . current_frame client . current_frame = message [ 3 ]


def objs(self):
        """
        Returns a generator list of tracked objects which are recognized with
        this profile and are in the current session.
        """
        for obj in self.objects.itervalues():
            if obj.sessionid in self.sessions:
                yield obj

def objs ( self ) : for obj in self . objects . itervalues ( ) : if obj . sessionid in self . sessions : yield obj

Returns a generator list of tracked objects which are recognized with this profile and are in the current session .


def _append_element(self, render_func, pe):
        '''
        Append a render function and the parameters to pass
        an equivilent PathElement, or the PathElement itself.
        '''
        self._render_funcs.append(render_func)
        self._elements.append(pe)

def _append_element ( self , render_func , pe ) : self . _render_funcs . append ( render_func ) self . _elements . append ( pe )

Append a render function and the parameters to pass an equivilent PathElement or the PathElement itself .


def _render_closure(self):
        '''Use a closure so that draw attributes can be saved'''
        fillcolor = self.fill
        strokecolor = self.stroke
        strokewidth = self.strokewidth

        def _render(cairo_ctx):
            '''
            At the moment this is based on cairo.

            TODO: Need to work out how to move the cairo specific
                  bits somewhere else.
            '''
            # Go to initial point (CORNER or CENTER):
            transform = self._call_transform_mode(self._transform)

            if fillcolor is None and strokecolor is None:
                # Fixes _bug_FillStrokeNofillNostroke.bot
                return

            cairo_ctx.set_matrix(transform)
            # Run the path commands on the cairo context:
            self._traverse(cairo_ctx)
            # Matrix affects stroke, so we need to reset it:
            cairo_ctx.set_matrix(cairo.Matrix())

            if fillcolor is not None and strokecolor is not None:
                if strokecolor[3] < 1:
                    # Draw onto intermediate surface so that stroke
                    # does not overlay fill
                    cairo_ctx.push_group()

                    cairo_ctx.set_source_rgba(*fillcolor)
                    cairo_ctx.fill_preserve()

                    e = cairo_ctx.stroke_extents()
                    cairo_ctx.set_source_rgba(*strokecolor)
                    cairo_ctx.set_operator(cairo.OPERATOR_SOURCE)
                    cairo_ctx.set_line_width(strokewidth)
                    cairo_ctx.stroke()

                    cairo_ctx.pop_group_to_source()
                    cairo_ctx.paint()
                else:
                    # Fast path if no alpha in stroke
                    cairo_ctx.set_source_rgba(*fillcolor)
                    cairo_ctx.fill_preserve()

                    cairo_ctx.set_source_rgba(*strokecolor)
                    cairo_ctx.set_line_width(strokewidth)
                    cairo_ctx.stroke()
            elif fillcolor is not None:
                cairo_ctx.set_source_rgba(*fillcolor)
                cairo_ctx.fill()
            elif strokecolor is not None:
                cairo_ctx.set_source_rgba(*strokecolor)
                cairo_ctx.set_line_width(strokewidth)
                cairo_ctx.stroke()

        return _render


def _linepoint(self, t, x0, y0, x1, y1):
        """ Returns coordinates for point at t on the line.
            Calculates the coordinates of x and y for a point at t on a straight line.
            The t parameter is a number between 0.0 and 1.0,
            x0 and y0 define the starting point of the line,
            x1 and y1 the ending point of the line.
        """
        # Originally from nodebox-gl
        out_x = x0 + t * (x1 - x0)
        out_y = y0 + t * (y1 - y0)
        return (out_x, out_y)

Returns coordinates for point at t on the line . Calculates the coordinates of x and y for a point at t on a straight line . The t parameter is a number between 0 . 0 and 1 . 0 x0 and y0 define the starting point of the line x1 and y1 the ending point of the line .


def _linelength(self, x0, y0, x1, y1):
        """ Returns the length of the line.
        """
        # Originally from nodebox-gl
        a = pow(abs(x0 - x1), 2)
        b = pow(abs(y0 - y1), 2)
        return sqrt(a + b)

def
_linelength
(
self
,
x0
,
y0
,
x1
,
y1
)
:
# Originally from nodebox-gl
a
=
pow
(
abs
(
x0
x1
)
,
2
)
b
=
pow
(
abs
(
y0
y1
)
,
2
)
return
sqrt
(
a
+
b
)