Current File : //usr/share/texlive/texmf-dist/tex/generic/pgf/graphdrawing/lua/pgf/gd/circular/Tantau2012.lua
-- Copyright 2012 by Till Tantau
--
-- This file may be distributed an/or modified
--
-- 1. under the LaTeX Project Public License and/or
-- 2. under the GNU Public License
--
-- See the file doc/generic/pgf/licenses/LICENSE for more information

-- @release $Header$


-- Imports
local declare = require("pgf.gd.interface.InterfaceToAlgorithms").declare

local routing = require("pgf.gd.routing")

-- The algorithm class
local Tantau2012 = {}

---
declare {
  key       = "simple necklace layout",
  algorithm = Tantau2012,

  postconditions = {
    upward_oriented = true
  },

  documentation_in = "pgf.gd.circular.doc"
}



-- Imports

local Coordinate = require "pgf.gd.model.Coordinate"
local Hints      = require "pgf.gd.routing.Hints"

local lib        = require "pgf.gd.lib"




-- The implementation

function Tantau2012:run()
  local g = self.ugraph
  local vertices = g.vertices
  local n = #vertices

  local sib_dists = self:computeNodeDistances ()
  local radii = self:computeNodeRadii()
  local diam, adjusted_radii = self:adjustNodeRadii(sib_dists, radii)

  -- Compute total necessary length. For this, iterate over all
  -- consecutive pairs and keep track of the necessary space for
  -- this node. We imagine the nodes to be aligned from left to
  -- right in a line.
  local carry = 0
  local positions = {}
  local function wrap(i) return (i-1)%n + 1 end
  local ideal_pos = 0
  for i = 1,n do
    positions[i] = ideal_pos + carry
    ideal_pos = ideal_pos + sib_dists[i]
    local node_sep =
      lib.lookup_option('node post sep', vertices[i], g) +
      lib.lookup_option('node pre sep', vertices[wrap(i+1)], g)
    local arc = node_sep + adjusted_radii[i] + adjusted_radii[wrap(i+1)]
    local needed = carry + arc
    local dist = math.sin( arc/diam ) * diam
    needed = needed + math.max ((radii[i] + radii[wrap(i+1)]+node_sep)-dist, 0)
    carry = math.max(needed-sib_dists[i],0)
  end
  local length = ideal_pos + carry

  local radius = length / (2 * math.pi)
  for i,vertex in ipairs(vertices) do
    vertex.pos.x = radius * math.cos(2 * math.pi * (positions[i] / length + 1/4))
    vertex.pos.y = -radius * math.sin(2 * math.pi * (positions[i] / length + 1/4))
  end

  -- Add routing infos
  local necklace = lib.icopy({g.vertices[1]}, lib.icopy(g.vertices))
  Hints.addNecklaceCircleHint(g, necklace, nil, true)
end


function Tantau2012:computeNodeDistances()
  local sib_dists = {}
  local sum_length = 0
  local vertices = self.digraph.vertices
  for i=1,#vertices do
    sib_dists[i] = lib.lookup_option('node distance', vertices[i], self.digraph)
    sum_length = sum_length + sib_dists[i]
  end

  local missing_length = self.digraph.options['radius'] * 2 * math.pi - sum_length
  if missing_length > 0 then
    -- Ok, the sib_dists to not add up to the desired minimum value.
    -- What should we do? Hmm... We increase all by the missing amount:
    for i=1,#vertices do
      sib_dists[i] = sib_dists[i] + missing_length/#vertices
    end
  end

  sib_dists.total = math.max(self.digraph.options['radius'] * 2 * math.pi, sum_length)

  return sib_dists
end


function Tantau2012:computeNodeRadii()
  local radii = {}
  for i,v in ipairs(self.digraph.vertices) do
    local min_x, min_y, max_x, max_y = v:boundingBox()
    local w, h = max_x-min_x, max_y-min_y
    if v.shape == "circle" or v.shape == "ellipse" then
      radii[i] = math.max(w,h)/2
    else
      radii[i] = math.sqrt(w*w + h*h)/2
    end
  end
  return radii
end


function Tantau2012:adjustNodeRadii(sib_dists,radii)
  local total = 0
  local max_rad = 0
  for i=1,#radii do
    total = total + 2*radii[i]
            + lib.lookup_option('node post sep', self.digraph.vertices[i], self.digraph)
            + lib.lookup_option('node pre sep', self.digraph.vertices[i], self.digraph)
    max_rad = math.max(max_rad, radii[i])
  end
  total = math.max(total, sib_dists.total, max_rad*math.pi)
  local diam = total/(math.pi)

  -- Now, adjust the radii:
  local adjusted_radii = {}
  for i=1,#radii do
    adjusted_radii[i] = (math.pi - 2*math.acos(radii[i]/diam))*diam/2
  end

  return diam, adjusted_radii
end


-- done

return Tantau2012