/** @see prefuse.action.Action#run(double) */
  public void run(double frac) {

    Graph g = (Graph) m_vis.getGroup(m_group);
    initSchema(g.getNodes());

    m_origin = getLayoutAnchor();
    NodeItem n = getLayoutRoot();
    Params np = (Params) n.get(PARAMS);

    // calc relative widths and maximum tree depth
    // performs one pass over the tree
    m_maxDepth = 0;
    calcAngularWidth(n, 0);

    if (m_autoScale) setScale(getLayoutBounds());
    if (!m_setTheta) calcAngularBounds(n);

    // perform the layout
    if (m_maxDepth > 0) layout(n, m_radiusInc, m_theta1, m_theta2);

    // update properties of the root node
    setX(n, null, m_origin.getX());
    setY(n, null, m_origin.getY());
    np.angle = m_theta2 - m_theta1;
  }
  /**
   * Compute the layout.
   *
   * @param n the root of the current subtree under consideration
   * @param r the radius, current distance from the center
   * @param theta1 the start (in radians) of this subtree's angular region
   * @param theta2 the end (in radians) of this subtree's angular region
   */
  protected void layout(NodeItem n, double r, double theta1, double theta2) {
    double dtheta = (theta2 - theta1);
    double dtheta2 = dtheta / 2.0;
    double width = ((Params) n.get(PARAMS)).width;
    double cfrac, nfrac = 0.0;

    Iterator childIter = sortedChildren(n);
    while (childIter != null && childIter.hasNext()) {
      NodeItem c = (NodeItem) childIter.next();
      Params cp = (Params) c.get(PARAMS);
      cfrac = cp.width / width;
      if (c.isExpanded() && c.getChildCount() > 0) {
        layout(c, r + m_radiusInc, theta1 + nfrac * dtheta, theta1 + (nfrac + cfrac) * dtheta);
      }
      setPolarLocation(c, n, r, theta1 + nfrac * dtheta + cfrac * dtheta2);

      cp.angle = cfrac * dtheta;
      nfrac += cfrac;
    }
  }
  /**
   * Calculates the angular bounds of the layout, attempting to preserve the angular orientation of
   * the display across transitions.
   */
  private void calcAngularBounds(NodeItem r) {
    if (m_prevRoot == null || !m_prevRoot.isValid() || r == m_prevRoot) {
      m_prevRoot = r;
      return;
    }

    // try to find previous parent of root
    NodeItem p = m_prevRoot;
    while (true) {
      NodeItem pp = (NodeItem) p.getParent();
      if (pp == r) {
        break;
      } else if (pp == null) {
        m_prevRoot = r;
        return;
      }
      p = pp;
    }

    // compute offset due to children's angular width
    double dt = 0;
    Iterator iter = sortedChildren(r);
    while (iter.hasNext()) {
      Node n = (Node) iter.next();
      if (n == p) break;
      dt += ((Params) n.get(PARAMS)).width;
    }
    double rw = ((Params) r.get(PARAMS)).width;
    double pw = ((Params) p.get(PARAMS)).width;
    dt = -MathLib.TWO_PI * (dt + pw / 2) / rw;

    // set angular bounds
    m_theta1 = dt + Math.atan2(p.getY() - r.getY(), p.getX() - r.getX());
    m_theta2 = m_theta1 + MathLib.TWO_PI;
    m_prevRoot = r;
  }
  /**
   * Computes relative measures of the angular widths of each expanded subtree. Node diameters are
   * taken into account to improve space allocation for variable-sized nodes.
   *
   * <p>This method also updates the base angle value for nodes to ensure proper ordering of nodes.
   */
  private double calcAngularWidth(NodeItem n, int d) {
    if (d > m_maxDepth) m_maxDepth = d;
    double aw = 0;

    Rectangle2D bounds = n.getBounds();
    double w = bounds.getWidth(), h = bounds.getHeight();
    double diameter = d == 0 ? 0 : Math.sqrt(w * w + h * h) / d;

    if (n.isExpanded() && n.getChildCount() > 0) {
      Iterator childIter = n.children();
      while (childIter.hasNext()) {
        NodeItem c = (NodeItem) childIter.next();
        aw += calcAngularWidth(c, d + 1);
      }
      aw = Math.max(diameter, aw);
    } else {
      aw = diameter;
    }
    ((Params) n.get(PARAMS)).width = aw;
    return aw;
  }