1 package prefuse.action.layout.graph; 2 3 import java.awt.geom.Point2D ; 4 import java.awt.geom.Rectangle2D ; 5 import java.util.Iterator ; 6 7 import prefuse.data.Graph; 8 import prefuse.data.Node; 9 import prefuse.data.Schema; 10 import prefuse.data.tuple.TupleSet; 11 import prefuse.util.ArrayLib; 12 import prefuse.util.MathLib; 13 import prefuse.visual.NodeItem; 14 15 16 32 public class RadialTreeLayout extends TreeLayout { 33 34 public static final int DEFAULT_RADIUS = 50; 35 private static final int MARGIN = 30; 36 37 protected int m_maxDepth = 0; 38 protected double m_radiusInc; 39 protected double m_theta1, m_theta2; 40 protected boolean m_setTheta = false; 41 protected boolean m_autoScale = true; 42 43 protected Point2D m_origin; 44 protected NodeItem m_prevRoot; 45 46 52 public RadialTreeLayout(String group) { 53 super(group); 54 m_radiusInc = DEFAULT_RADIUS; 55 m_prevRoot = null; 56 m_theta1 = 0; 57 m_theta2 = m_theta1 + MathLib.TWO_PI; 58 } 59 60 69 public RadialTreeLayout(String group, int radius) { 70 this(group); 71 m_radiusInc = radius; 72 m_autoScale = false; 73 } 74 75 81 public double getRadiusIncrement() { 82 return m_radiusInc; 83 } 84 85 91 public void setRadiusIncrement(double inc) { 92 m_radiusInc = inc; 93 } 94 95 99 public boolean getAutoScale() { 100 return m_autoScale; 101 } 102 103 108 public void setAutoScale(boolean s) { 109 m_autoScale = s; 110 } 111 112 117 public void setAngularBounds(double theta, double width) { 118 m_theta1 = theta; 119 m_theta2 = theta+width; 120 m_setTheta = true; 121 } 122 123 126 public void run(double frac) { 127 Graph g = (Graph)m_vis.getGroup(m_group); 128 initSchema(g.getNodes()); 129 130 m_origin = getLayoutAnchor(); 131 NodeItem n = getLayoutRoot(); 132 Params np = (Params)n.get(PARAMS); 133 134 m_maxDepth = 0; 137 calcAngularWidth(n, 0); 138 139 if ( m_autoScale ) setScale(getLayoutBounds()); 140 if ( !m_setTheta ) calcAngularBounds(n); 141 142 if ( m_maxDepth > 0 ) 144 layout(n, m_radiusInc, m_theta1, m_theta2); 145 146 setX(n, null, m_origin.getX()); 148 setY(n, null, m_origin.getY()); 149 np.angle = m_theta2-m_theta1; 150 } 151 152 protected void setScale(Rectangle2D bounds) { 153 double r = Math.min(bounds.getWidth(),bounds.getHeight())/2.0; 154 if ( m_maxDepth > 0 ) 155 m_radiusInc = (r-MARGIN)/m_maxDepth; 156 } 157 158 162 private void calcAngularBounds(NodeItem r) { 163 if ( m_prevRoot == null || !m_prevRoot.isValid() || r == m_prevRoot ) 164 { 165 m_prevRoot = r; 166 return; 167 } 168 169 NodeItem p = m_prevRoot; 171 while ( true ) { 172 NodeItem pp = (NodeItem)p.getParent(); 173 if ( pp == r ) { 174 break; 175 } else if ( pp == null ) { 176 m_prevRoot = r; 177 return; 178 } 179 p = pp; 180 } 181 182 double dt = 0; 184 Iterator iter = sortedChildren(r); 185 while ( iter.hasNext() ) { 186 Node n = (Node)iter.next(); 187 if ( n == p ) break; 188 dt += ((Params)n.get(PARAMS)).width; 189 } 190 double rw = ((Params)r.get(PARAMS)).width; 191 double pw = ((Params)p.get(PARAMS)).width; 192 dt = -MathLib.TWO_PI * (dt+pw/2)/rw; 193 194 m_theta1 = dt + Math.atan2(p.getY()-r.getY(), p.getX()-r.getX()); 196 m_theta2 = m_theta1 + MathLib.TWO_PI; 197 m_prevRoot = r; 198 } 199 200 208 private double calcAngularWidth(NodeItem n, int d) { 209 if ( d > m_maxDepth ) m_maxDepth = d; 210 double aw = 0; 211 212 Rectangle2D bounds = n.getBounds(); 213 double w = bounds.getWidth(), h = bounds.getHeight(); 214 double diameter = d==0 ? 0 : Math.sqrt(w*w+h*h) / d; 215 216 if ( n.isExpanded() && n.getChildCount() > 0 ) { 217 Iterator childIter = n.children(); 218 while ( childIter.hasNext() ) { 219 NodeItem c = (NodeItem)childIter.next(); 220 aw += calcAngularWidth(c,d+1); 221 } 222 aw = Math.max(diameter, aw); 223 } else { 224 aw = diameter; 225 } 226 ((Params)n.get(PARAMS)).width = aw; 227 return aw; 228 } 229 230 private static final double normalize(double angle) { 231 while ( angle > MathLib.TWO_PI ) { 232 angle -= MathLib.TWO_PI; 233 } 234 while ( angle < 0 ) { 235 angle += MathLib.TWO_PI; 236 } 237 return angle; 238 } 239 240 private Iterator sortedChildren(final NodeItem n) { 241 double base = 0; 242 NodeItem p = (NodeItem)n.getParent(); 244 if ( p != null ) { 245 base = normalize(Math.atan2(p.getY()-n.getY(), p.getX()-n.getX())); 246 } 247 int cc = n.getChildCount(); 248 if ( cc == 0 ) return null; 249 250 NodeItem c = (NodeItem)n.getFirstChild(); 251 252 if ( !c.isStartVisible() ) { 256 return n.children(); 258 } 259 260 double angle[] = new double[cc]; 261 final int idx[] = new int[cc]; 262 for ( int i=0; i<cc; ++i, c=(NodeItem)c.getNextSibling() ) { 263 idx[i] = i; 264 angle[i] = normalize(-base + 265 Math.atan2(c.getY()-n.getY(), c.getX()-n.getX())); 266 } 267 ArrayLib.sort(angle, idx); 268 269 return new Iterator () { 271 int cur = 0; 272 public Object next() { 273 return n.getChild(idx[cur++]); 274 } 275 public boolean hasNext() { 276 return cur < idx.length; 277 } 278 public void remove() { 279 throw new UnsupportedOperationException (); 280 } 281 }; 282 } 283 284 291 protected void layout(NodeItem n, double r, double theta1, double theta2) { 292 double dtheta = (theta2-theta1); 293 double dtheta2 = dtheta / 2.0; 294 double width = ((Params)n.get(PARAMS)).width; 295 double cfrac, nfrac = 0.0; 296 297 Iterator childIter = sortedChildren(n); 298 while ( childIter != null && childIter.hasNext() ) { 299 NodeItem c = (NodeItem)childIter.next(); 300 Params cp = (Params)c.get(PARAMS); 301 cfrac = cp.width / width; 302 if ( c.isExpanded() && c.getChildCount()>0 ) { 303 layout(c, r+m_radiusInc, theta1 + nfrac*dtheta, 304 theta1 + (nfrac+cfrac)*dtheta); 305 } 306 setPolarLocation(c, n, r, theta1 + nfrac*dtheta + cfrac*dtheta2); 307 cp.angle = cfrac*dtheta; 308 nfrac += cfrac; 309 } 310 311 } 312 313 320 protected void setPolarLocation(NodeItem n, NodeItem p, double r, double t) { 321 setX(n, p, m_origin.getX() + r*Math.cos(t)); 322 setY(n, p, m_origin.getY() + r*Math.sin(t)); 323 } 324 325 328 331 public static final String PARAMS = "_radialTreeLayoutParams"; 332 335 public static final Schema PARAMS_SCHEMA = new Schema(); 336 static { 337 PARAMS_SCHEMA.addColumn(PARAMS, Params.class, new Params()); 338 } 339 340 protected void initSchema(TupleSet ts) { 341 ts.addColumns(PARAMS_SCHEMA); 342 } 343 344 347 public static class Params implements Cloneable { 348 double width; 349 double angle; 350 public Object clone() { 351 Params p = new Params(); 352 p.width = this.width; 353 p.angle = this.angle; 354 return p; 355 } 356 } 357 358 } | Popular Tags |