         .outerRadius(outerRadius)
       force
         .linkDistance((l) -> l.distance)
-        .charge((n) -> if n.active then -150 else -30) # fush nodes away from selected node
-        # TODO try friction based on mouse distance
         .nodes(nodes)
         .links(links)
         .start()
 
+      xScale.domain(d3.extent(nodes, (n) -> n.x))
+      yScale.domain(d3.extent(nodes, (n) -> n.y))
+
       # voronoi
-      voronoi = d3.geom.voronoi().x((n) -> n.x).y((n) -> n.y)
+      voronoi = d3.geom.voronoi().x(xScaleD).y(yScaleD)
       calculateVoronoi = ->
         _.zipWith(nodes, voronoi(nodes), (node, patch) -> node.voronoiArea = patch)
       calculateVoronoi()
 
       # hulls
       hulls = svg.selectAll('.hull').data(_.pairs(byParty)).enter().append('path').attr('class', (d) -> [party, articles] = d; "hull #{party}")
-      hull = d3.geom.hull().x((node) -> node.x).y((node) -> node.y)
-      hullArea = d3.svg.line().x((n) -> n.x).y((n) -> n.y).interpolate('linear-closed')
+      hull = d3.geom.hull().x(xScaleD).y(yScaleD)
+      hullArea = d3.svg.line().x(xScaleD).y(yScaleD).interpolate('linear-closed')
       updateActive = ->
-        node.sort((d, o) -> +d.active * 2 - 1) # map true, false to 1 and -1
+        # node.sort((d, o) -> +d.active * 2 - 1) # map true, false to 1 and -1
         voronoiPatches.classed('active', (d) -> d.active)
         _.delay -> node.classed('active', (d) -> d.active) # delay to ensure css animations still work
 
       # link = svg.selectAll('.link').data(links).enter().append('line').attr('class', 'link')
-      node = svg.selectAll('.node').data(nodes).enter().append('g').attr('class', (d) -> 'node ' + d.predictedLabel).call(force.drag)
+      node = svg.selectAll('.node').data(nodes, (a) -> a.url).enter().append('g').attr('class', (d) -> 'node ' + d.predictedLabel).call(force.drag)
       node.append('circle').attr('class', 'selectionIndicator').attr('cx', 0).attr('cy', 0).attr('r', circleSize * 4)
       titles = node.append('g').attr('class', 'title')
       titles

    height = 800
    diag = Math.sqrt(width**2+height**2)
    minSide = Math.min(width, height)

    svg = d3.select(elem[0]).append('svg')
      .attr('width', width)
      .attr('height', height)
    force = d3.layout.force()
      .gravity(.05)
      .charge(-100)
      .size([width, height])
    # preparing the pies
    pie = d3.layout.pie()

      voronoiPatches
        .on('mouseover', (d) ->
          d.active = true

          scaleD = (other) ->
            selectedPos = [xScale(d.x), yScale(d.y)]
            pos = [xScale(other.x), yScale(other.y)]
            diff = vSub(pos, selectedPos)
            dist = vNorm(diff)
            theShift = outerRadius*shift(outerRadius/2)(dist/outerRadius)
            _.object ['x','y'], vAdd(pos, vScale(theShift/dist, diff))

          xScaleD = (d) -> scaleD(d).x
          yScaleD = (d) -> scaleD(d).y

          duration = 500
          n = nodes.length

          angle = (doc, other) ->
            diff = vSub([doc.x, doc.y], [other.x, other.y])
            unitDiff = vScale(1/vNorm(diff), diff)
            alpha = Math.acos(vDot([0, 1], unitDiff)) / Math.PI * 180
            alpha = if diff[0] <= 0 then alpha else 360 - alpha

          node
            .sort((doc, other) ->
              # sort by distance to selected
              d3.descending(euclideanDistance([d.x, d.y], [doc.x, doc.y]), euclideanDistance([d.x, d.y], [other.x, other.y])))
            .transition()
              .duration((doc, i) ->
                duration)
              .delay((doc, i) -> # calculate angle to basis vector
                return 0 if doc == d
                dist = euclideanDistance([xScale(d.x), yScale(d.y)], [xScale(doc.x), yScale(doc.y)])
                # distDelay = d3.scale.linear().domain([0, minSide]).range([0, duration])(dist)
                console.log dist, gaussian(outerRadius*3, 2)(dist)*1000
                gaussian(outerRadius*3, 2)(dist)*1000 # works pretty well... FIXME to many magic numbers
              )
              .attr 'transform', (d) ->
                'translate(' + xScaleD(d) + ',' + yScaleD(d) + ')'

          # force.start()
          updateActive()
        )
        .on('mouseout', (d) ->
          d.active = false
          # d.fixed = false
          updateActive()
        )

      # x + sign(x)*2(1-sigmoid(|x|))
      shift = (offset) -> (x) ->
        if x < 1e-10
          0
        else
          Math.sign(x)*2*(1-sigmoid(Math.abs(x)-offset))

      calculateVoronoiThrottled = _.throttle(calculateVoronoi, 200)

      force.on 'tick', ->
        console.log 'tick'
        # smooth the layout change with inertia
        rate = 0.8
        smooth = (previous, next) -> (1 - rate) * previous + rate * next
        xScale.domain(_.zipWith(xScale.domain(), d3.extent(nodes, (n) -> n.x), smooth))
        yScale.domain(_.zipWith(yScale.domain(), d3.extent(nodes, (n) -> n.y), smooth))

        # link.attr('x1', (d) ->
        #   xScale d.source.x
        # ).attr('y1', (d) ->
        #   yScale d.source.y
        # ).attr('x2', (d) ->
        #   xScale d.target.x
        # ).attr 'y2', (d) ->
        #   yScale d.target.y

        node.attr 'transform', (d) ->
          'translate(' + xScaleD(d) + ',' + yScaleD(d) + ')'

        hulls.attr "d", (d) ->
          [party, articles] = d
