package nl.tudelft.lifetiles.tree.view; import javafx.geometry.Point2D; import javafx.scene.control.Tooltip; import javafx.scene.paint.Color; import javafx.scene.shape.ArcTo; import javafx.scene.shape.FillRule; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; import javafx.scene.shape.Shape; import nl.tudelft.lifetiles.sequence.SequenceColor; import nl.tudelft.lifetiles.sequence.model.Sequence; import nl.tudelft.lifetiles.tree.model.PhylogeneticTreeItem; /** * A {@link SunburstRingSegment} represents a segment of the sunburst diagrams * rings. * * @author Albert Smit * */ public class SunburstRingSegment extends AbstractSunburstNode { /** * the max brightness for the color of a Node. */ private static final double MAX_BRIGHTNESS = 0.8; /** * The default saturation of the color of a Node. */ private static final double SATURATION = 0.9; /** * The default name for distance. */ private static final String DISTANCE_NAME = "Branch Length: "; /** * Creates a SunburstRingSegment. * * * @param value * the {@link PhylogeneticTreeItem} this part of the ring will * represent * @param layer * the layer at which it is located in the tree, layer 0 is the * first layer * @param angle * the start and end positions of this ringSegment, non null * @param center * the coordinates of the center of the circle, non null * @param scale * the scaling factor */ public SunburstRingSegment(final PhylogeneticTreeItem value, final int layer, final DegreeRange angle, final Point2D center, final double scale) { // check input assert angle != null; assert center != null; // set the value, and create the text and semi-circle setValue(value); String name = getValue().getName(); setDisplay(createRing(layer, angle, center, scale)); double distance = getValue().getDistance(); StringBuffer tooltip = new StringBuffer(); if (name != null) { tooltip.append(name).append(System.lineSeparator()); } tooltip.append(DISTANCE_NAME).append(distance); setName(new Tooltip(tooltip.toString())); setDisplay(createRing(layer, angle, center, scale)); // add the text and semicircle to the group getChildren().add(getDisplay()); } /** * Creates a semi-circle with in the specified location. * * layer starts at 0 * * @param layer * The layer to place this element at, the first child of root is * layer 0. * @param angle * The start and end points of this ring * @param center * the coordinates of the center of the circle * @param scale * the scaling factor * @return a semi-circle with the specified dimensions */ private Shape createRing(final int layer, final DegreeRange angle, final Point2D center, final double scale) { Path result = new Path(); result.setFill(createColor(angle.getStartAngle(), layer)); result.setFillRule(FillRule.EVEN_ODD); // check if this is a large arc boolean largeArc = angle.angle() > AbstractSunburstNode.CIRCLEDEGREES / 2; // calculate the radii of the two arcs double innerRadius = scale * (CENTER_RADIUS + (layer * RING_WIDTH)); double outerRadius = innerRadius + scale * RING_WIDTH; // convert degrees to radians for Math.sin and Math.cos double angleAlpha = Math.toRadians(angle.getStartAngle()); double angleAlphaNext = Math.toRadians(angle.getEndAngle()); // draw the semi-circle // first go to the start point double startX = center.getX() + innerRadius * Math.sin(angleAlpha); double startY = center.getY() - innerRadius * Math.cos(angleAlpha); MoveTo move1 = new MoveTo(startX, startY); // draw a line from point 1 to point 2 LineTo line1To2 = createLine(outerRadius, center, angleAlpha); // draw an arc from point 2 to point 3 ArcTo arc2To3 = createArc(outerRadius, center, angleAlphaNext, true, largeArc); // draw a line from point 3 to point 4 LineTo line3To4 = createLine(innerRadius, center, angleAlphaNext); // draw an arc from point 4 back to point 1 ArcTo arc4To1 = createArc(innerRadius, center, angleAlpha, false, largeArc); // add all elements to the path result.getElements() .addAll(move1, line1To2, arc2To3, line3To4, arc4To1); return result; } /** * Creates an {@link ArcTo} with the specified parameters. * * Coordinates of the end point of the arc are given in polar form relative * to the center of the arcs. * * @param radius * The radius of the arc. * @param center * The center coordinates of the arc. * @param angle * The angle of the end point. * @param sweep * The draw direction of the arc. * @param largeArc * if true draw an arc larger than 180 degrees. * @return an ArcTo with the specified parameters. */ private ArcTo createArc(final double radius, final Point2D center, final double angle, final boolean sweep, final boolean largeArc) { // calculate the end point of the arc double endX = center.getX() + radius * Math.sin(angle); double endY = center.getY() - radius * Math.cos(angle); // create the arc ArcTo result = new ArcTo(); result.setRadiusX(radius); result.setRadiusY(radius); result.setX(endX); result.setY(endY); result.setSweepFlag(sweep); result.setLargeArcFlag(largeArc); return result; } /** * Creates a {@link LineTo} with the specified parameters. * * Coordinates of the end point of the arc are given in polar form relative * to the center of the arcs. * * @param radius * The radius of the arc. * @param center * The center coordinates of the arc. * @param angle * The angle of the end point. * @return the LineTo with the specified parameters. */ private LineTo createLine(final double radius, final Point2D center, final double angle) { // calculate the end point coordinates double endX = center.getX() + radius * Math.sin(angle); double endY = center.getY() - radius * Math.cos(angle); return new LineTo(endX, endY); } /** * Creates a {@link Color} for this node. the color will be red by default, * and the color associated with the sequence when the node has a sequence. * * @param degrees * the location where the ringSeqment is drawn, will become the * hue of the color. * @param layer * the layer where the ringSegment is drawn, is used for the * brightness * @return a Color object that specifies what color this node will be. */ private Color createColor(final double degrees, final int layer) { Sequence sequence = getValue().getSequence(); if (sequence == null) { double brightness = Math.min(MAX_BRIGHTNESS, 1d / layer); brightness = Math.abs(brightness - 1); return Color.hsb(degrees, SATURATION, brightness); } else { return SequenceColor.getColor(sequence); } } }