All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.uci.ics.jung.visualization.util.NodeShapeFactory Maven / Gradle / Ivy

/*
 * Copyright (c) 2003, The JUNG Authors
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see either "license.txt"
 * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
 *
 * Created on Jul 20, 2004
 */
package edu.uci.ics.jung.visualization.util;

import com.google.common.base.Preconditions;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A utility class for generating Shapes for drawing nodes. The available shapes
 * include rectangles, rounded rectangles, ellipses, regular polygons, and regular stars. The
 * dimensions of the requested shapes are defined by the specified node layoutSize function
 * (specified by a {@code Function}) and node aspect ratio function (specified
 * by a {@code Function}) implementations: the width of the bounding box of the
 * shape is given by the node layoutSize, and the height is given by the layoutSize multiplied by
 * the node's aspect ratio.
 *
 * @author Joshua O'Madadhain
 */
public class NodeShapeFactory {

  private static final Logger log = LoggerFactory.getLogger(NodeShapeFactory.class);
  protected Function vsf;
  protected Function varf;

  /**
   * Creates an instance with the specified node layoutSize and aspect ratio functions.
   *
   * @param vsf provides a layoutSize (width) for each node
   * @param varf provides a height/width ratio for each node
   */
  public NodeShapeFactory(Function vsf, Function varf) {
    this.vsf = vsf;
    this.varf = varf;
  }

  /**
   * Creates a NodeShapeFactory with a constant layoutSize of 10 and a constant aspect
   * ratio of 1.
   */
  public NodeShapeFactory() {
    this(n -> 10, n -> 1.0f);
  }

  private static final Rectangle2D theRectangle = new Rectangle2D.Float();

  /**
   * Returns a Rectangle2D whose width and height are defined by this instance's
   * layoutSize and aspect ratio functions for this node.
   *
   * @param v the node for which the shape will be drawn
   * @return a rectangle for this node
   */
  public Rectangle2D getRectangle(N v) {
    float width = vsf.apply(v);
    float height = width * varf.apply(v);
    float h_offset = -(width / 2);
    float v_offset = -(height / 2);
    theRectangle.setFrame(h_offset, v_offset, width, height);
    return theRectangle;
  }

  private static final Ellipse2D theEllipse = new Ellipse2D.Float();

  /**
   * Returns a Ellipse2D whose width and height are defined by this instance's
   * layoutSize and aspect ratio functions for this node.
   *
   * @param v the node for which the shape will be drawn
   * @return an ellipse for this node
   */
  public Ellipse2D getEllipse(N v) {
    theEllipse.setFrame(getRectangle(v));
    return theEllipse;
  }

  private static final RoundRectangle2D theRoundRectangle = new RoundRectangle2D.Float();
  /**
   * Returns a RoundRectangle2D whose width and height are defined by this instance's
   * layoutSize and aspect ratio functions for this node. The arc layoutSize is set to be half the
   * minimum of the height and width of the frame.
   *
   * @param v the node for which the shape will be drawn
   * @return an round rectangle for this node
   */
  public RoundRectangle2D getRoundRectangle(N v) {
    Rectangle2D frame = getRectangle(v);
    float arc_size = (float) Math.min(frame.getHeight(), frame.getWidth()) / 2;
    theRoundRectangle.setRoundRect(
        frame.getX(), frame.getY(), frame.getWidth(), frame.getHeight(), arc_size, arc_size);
    return theRoundRectangle;
  }

  //  private static final GeneralPath thePolygon = new GeneralPath();

  /**
   * Returns a regular num_sides-sided Polygon whose bounding box's width
   * and height are defined by this instance's layoutSize and aspect ratio functions for this node.
   *
   * @param v the node for which the shape will be drawn
   * @param num_sides the number of sides of the polygon; must be ≥ 3.
   * @return a regular polygon for this node
   */
  public Shape getRegularPolygon(N v, int num_sides) {
    GeneralPath thePolygon = new GeneralPath();
    Preconditions.checkArgument(num_sides >= 3, "Number of sides must be >= 3");
    Rectangle2D frame = getRectangle(v);
    float width = (float) frame.getWidth();
    float height = (float) frame.getHeight();

    // generate coordinates
    double angle = 0;
    thePolygon.reset();
    thePolygon.moveTo(0, 0);
    thePolygon.lineTo(width, 0);
    double theta = (2 * Math.PI) / num_sides;
    for (int i = 2; i < num_sides; i++) {
      angle -= theta;
      float delta_x = (float) (width * Math.cos(angle));
      float delta_y = (float) (width * Math.sin(angle));
      Point2D prev = thePolygon.getCurrentPoint();
      thePolygon.lineTo((float) prev.getX() + delta_x, (float) prev.getY() + delta_y);
    }
    thePolygon.closePath();

    // scale polygon to be right layoutSize, translate to center at (0,0)
    Rectangle2D r = thePolygon.getBounds2D();
    double scale_x = width / r.getWidth();
    double scale_y = height / r.getHeight();
    float translationX = (float) (r.getMinX() + r.getWidth() / 2);
    float translationY = (float) (r.getMinY() + r.getHeight() / 2);

    AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y);
    at.translate(-translationX, -translationY);

    return at.createTransformedShape(thePolygon);
  }

  /**
   * Returns a regular Polygon of num_points points whose bounding box's
   * width and height are defined by this instance's layoutSize and aspect ratio functions for this
   * node.
   *
   * @param v the node for which the shape will be drawn
   * @param num_points the number of points of the polygon; must be ≥ 5.
   * @return an star shape for this node
   */
  public Shape getRegularStar(N v, int num_points) {
    GeneralPath thePolygon = new GeneralPath();
    Preconditions.checkArgument(num_points >= 5, "Number of points must be >= 5");
    Rectangle2D frame = getRectangle(v);
    float width = (float) frame.getWidth();
    float height = (float) frame.getHeight();

    // generate coordinates
    double theta = (2 * Math.PI) / num_points;
    double angle = -theta / 2;
    thePolygon.reset();
    thePolygon.moveTo(0, 0);
    float delta_x = width * (float) Math.cos(angle);
    float delta_y = width * (float) Math.sin(angle);
    Point2D prev = thePolygon.getCurrentPoint();
    thePolygon.lineTo((float) prev.getX() + delta_x, (float) prev.getY() + delta_y);
    for (int i = 1; i < num_points; i++) {
      angle += theta;
      delta_x = width * (float) Math.cos(angle);
      delta_y = width * (float) Math.sin(angle);
      prev = thePolygon.getCurrentPoint();
      thePolygon.lineTo((float) prev.getX() + delta_x, (float) prev.getY() + delta_y);
      angle -= theta * 2;
      delta_x = width * (float) Math.cos(angle);
      delta_y = width * (float) Math.sin(angle);
      prev = thePolygon.getCurrentPoint();
      if (prev != null) {
        thePolygon.lineTo((float) prev.getX() + delta_x, (float) prev.getY() + delta_y);
      } else {
        log.error("somehow, prev is null");
      }
    }
    thePolygon.closePath();

    // scale polygon to be right layoutSize, translate to center at (0,0)
    Rectangle2D r = thePolygon.getBounds2D();
    double scale_x = width / r.getWidth();
    double scale_y = height / r.getHeight();

    float translationX = (float) (r.getMinX() + r.getWidth() / 2);
    float translationY = (float) (r.getMinY() + r.getHeight() / 2);

    AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y);
    at.translate(-translationX, -translationY);

    return at.createTransformedShape(thePolygon);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy