class MagicCloud::Layouter::Place

Incapsulating place lookup process

  1. find initial random place

  2. at each step, shift in spiral from the previous place

  3. always knows, if the place “ready” for shape (empty and inside board)

Public Class Methods

new(layouter, shape) click to toggle source
# File lib/magic_cloud/layouter/place.rb, line 12
def initialize(layouter, shape)
  @layouter, @shape = layouter, shape

  # initial position
  @start_x = (@layouter.width/2-@shape.width/2).to_i
  @start_y = (@layouter.height/2-@shape.height/2).to_i

  # when shift of position is more than max delta (diagonal of cloud)
  # there is no hope it will eventually found its place
  @max_delta = Math.sqrt(@layouter.width**2 + @layouter.height**2)

  # algo of next position calc
  @spiral = make_spiral(@shape.size)

  # direction of spiral
  @dt = rand < 0.5 ? 1 : -1 

  # initial point of time before we start to look for place
  @t = -@dt
end

Public Instance Methods

next!() click to toggle source
# File lib/magic_cloud/layouter/place.rb, line 33
def next!
  @t += @dt
  dx, dy = @spiral.call(@t)

  fail PlaceNotFound if [dx, dy].map(&:abs).min > @max_delta

  @shape.x = @start_x + dx
  @shape.y = @start_y + dy
end
ready?() click to toggle source
# File lib/magic_cloud/layouter/place.rb, line 43
def ready?
  !out_of_board? && !@layouter.board.collides?(@shape)
end

Private Instance Methods

archimedean_spiral(size) click to toggle source

rubocop:disable Metrics/AbcSize

# File lib/magic_cloud/layouter/place.rb, line 64
def archimedean_spiral(size)
  e = width / height
  ->(t){
    t1 = t * size * 0.01

    [
      e * t1 * Math.cos(t1),
      t1 * Math.sin(t1)
    ].map(&:round)
  }
end
make_spiral(step) click to toggle source

FIXME: now we always use “rectangular spiral” d3.layout.cloud had two of them as an option - rectangular and archimedean. I assume, it should be checked if usage of two spirals produce significantly different results, and then either one spiral should left, or it should became an option.

# File lib/magic_cloud/layouter/place.rb, line 59
def make_spiral(step)
  rectangular_spiral(step)
end
out_of_board?() click to toggle source
# File lib/magic_cloud/layouter/place.rb, line 49
def out_of_board?
  @shape.left < 0 || @shape.top < 0 ||
    @shape.right > @layouter.width || @shape.bottom > @layouter.height
end
rectangular_spiral(size) click to toggle source
# File lib/magic_cloud/layouter/place.rb, line 76
def rectangular_spiral(size)
  dy = 4 * size * 0.1
  dx = dy * @layouter.width / @layouter.height
  x = 0
  y = 0
  ->(t){
    sign = t < 0 ? -1 : 1

    # zverok: this is original comment & code from d3.layout.cloud.js
    # Looks too witty for me.
    #
    # See triangular numbers: T_n = n * (n + 1) / 2.
    case (Math.sqrt(1 + 4 * sign * t) - sign).to_i & 3
    when 0 then x += dx
    when 1 then y += dy
    when 2 then x -= dx
    else        y -= dy
    end

    [x, y].map(&:round)
  }
end