d3_collision.pyΒΆ

open in new tab
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"""
Example using 3D. Adapted from a demo by Mike Bostock.
"""

from flexx import flx
from pscript import RawJS
from pscript.stubs import Math, d3, window

flx.assets.associate_asset(__name__, 'https://d3js.org/d3.v3.min.js')


class CollisionWidget(flx.Widget):
    """ A widget showing a collision demo based on D3.
    """

    CSS = """
    .flx-CollisionWidget {
        background: #fff;
        border: 1px solid black;
        border-radius: 6px;
    }
    """

    def init(self):
        self.node.id = self.id
        window.setTimeout(self.load_viz, 500)

    @flx.reaction
    def _resize(self):
        w, h = self.size
        if len(self.node.children) > 0:
            svg = self.node.children[0]
            svg.setAttribute('width', w)
            svg.setAttribute('height', h)

    def load_viz(self):

        w, h = self.size

        nodes = d3.range(200).map(lambda: {'radius': Math.random() * 12 + 4})
        color = d3.scale.category10()

        force = d3.layout.force().gravity(0.05).charge(lambda d, i: 0 if i else -2000)\
            .nodes(nodes).size([w, h])

        root = nodes[0]
        root.radius = 0
        root.fixed = True

        force.start()

        x = d3.select('#' + self.id)
        print(x, self.id)
        svg = RawJS('x.append("svg").attr("width", w).attr("height", h)')

        x = RawJS(
            'svg.selectAll("circle").data(nodes.slice(1)).enter().append("circle")')
        x.attr("r", lambda d: d.radius).style("fill", lambda d, i: color(i % 3))

        def on_tick(e):
            q = d3.geom.quadtree(nodes)
            i = 0
            n = nodes.length
            while i < n-1:
                i += 1
                q.visit(collide(nodes[i]))
            svg.selectAll("circle").attr("cx", lambda d: d.x).attr("cy", lambda d: d.y)

        force.on("tick", on_tick)

        def on_mousemove():
            p1 = d3.mouse(self.node)
            root.px = p1[0]
            root.py = p1[1]
            force.resume()

        svg.on("mousemove", on_mousemove)

        def collide(node):
            r = node.radius + 16
            nx1 = node.x - r
            nx2 = node.x + r
            ny1 = node.y - r
            ny2 = node.y + r

            def func(quad, x1, y1, x2, y2):
                if quad.point and quad.point is not node:
                    x = node.x - quad.point.x
                    y = node.y - quad.point.y
                    s = Math.sqrt(x * x + y * y)
                    r = node.radius + quad.point.radius
                    if (s < r):
                        s = (s - r) / s * .5
                        x *= s
                        y *= s
                        node.x -= x
                        node.y -= y
                        quad.point.x += x
                        quad.point.y += y
                return x1 > nx2 or x2 < nx1 or y1 > ny2 or y2 < ny1
            return func


class CollisionDemo(flx.Widget):

    def init(self):
        with flx.VSplit():
            with flx.HSplit():
                CollisionWidget()
                CollisionWidget()
            with flx.HSplit():
                CollisionWidget()
                CollisionWidget()


if __name__ == '__main__':
    flx.launch(CollisionDemo, 'app')
    flx.run()