package nl.tudelft.lifetiles.graph.view; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.paint.Color; import nl.tudelft.lifetiles.annotation.model.GeneAnnotation; import nl.tudelft.lifetiles.annotation.model.KnownMutation; import nl.tudelft.lifetiles.graph.controller.GraphController; import nl.tudelft.lifetiles.graph.model.Graph; import nl.tudelft.lifetiles.sequence.Mutation; import nl.tudelft.lifetiles.sequence.model.SequenceSegment; /** * The TileView is responsible for displaying the graph given from * the TileController. * */ public class TileView { /** * Default color of a tile element. */ private static Color defaultColor = Color.web("a1d3ff"); /** * Color of a collapsed segment. */ private static final Color COLLAPSE_COLOR = Color.DARKGRAY; /** * The edges contains all EdgeLines to be displayed. */ private final Group edges; /** * The bookmarks group contains all bookmarks and annotations to be * displayed. */ private final Group bookmarks; /** * The lanes list which contains the occupation of the lanes inside the * tileview. */ private List<Long> lanes; /** * The factor to apply on the vertices and bookmarks to resize them. */ private double horizontalScale; /** * Controller for the View. */ private final GraphController controller; /** * The maximal screen height. */ private final double screenHeight; /** * Map to hold the calculated lane index of a sequencesegment. */ private Map<SequenceSegment, SegmentInfo> segmentInfoMap; /** * The group that holds the vertices to be drawn. */ private Group nodes; /** * The vertical scale to be applied to all vertices. */ private double verticalScale; /** * The amount of pixels that the height and width at least must have. */ private static final int MINIMALSIZE = 10; /** * Create the TileView by initializing the groups where the to be drawn * vertices and edges are stored. * * @param control * The controller for the TileView * @param height * the maximum allowed height in pixels to draw */ public TileView(final GraphController control, final double height) { controller = control; segmentInfoMap = new HashMap<SequenceSegment, SegmentInfo>(); edges = new Group(); bookmarks = new Group(); screenHeight = height; } /** * Draw the given graph. * * @param segments * Graph to be drawn * @param graph * Graph to base the edges on * @param knownMutations * Map from segment to known mutations. * @param mappedAnnotations * Map from segment to gene annotations. * @param horizontalScale * the horizontal scale to resize all elements of the graph * @return the elements that must be displayed on the screen */ public Group drawGraph(final Set<SequenceSegment> segments, final Graph<SequenceSegment> graph, final Map<SequenceSegment, List<KnownMutation>> knownMutations, final Map<SequenceSegment, List<GeneAnnotation>> mappedAnnotations, final double horizontalScale) { Group root = new Group(); lanes = new ArrayList<Long>(); this.horizontalScale = horizontalScale; nodes = new Group(); for (SequenceSegment segment : segments) { List<KnownMutation> mutations = null; List<GeneAnnotation> annotations = null; if (knownMutations != null && knownMutations.containsKey(segment)) { mutations = knownMutations.get(segment); } if (mappedAnnotations != null && mappedAnnotations.containsKey(segment)) { annotations = mappedAnnotations.get(segment); } int laneIndex = drawVertexLane(segment); segmentInfoMap.put(segment, new SegmentInfo(laneIndex, mutations, annotations)); } verticalScale = screenHeight / lanes.size(); for (Entry<SequenceSegment, SegmentInfo> entry : segmentInfoMap.entrySet()) { SegmentInfo container = entry.getValue(); drawVertex(container.getLocation(), entry.getKey(), container .getMutations(), container.getAnnotations()); } root.getChildren().addAll(nodes, edges, bookmarks); return root; } /** * Draws a given segment to an available position in the graph. * * @param segment * segment to be drawn * @return the index of the lane */ private int drawVertexLane(final SequenceSegment segment) { for (int index = 0; index < lanes.size(); index++) { if (lanes.get(index) <= segment.getUnifiedStart() && segmentFree(index, segment)) { segmentInsert(index, segment); return index; } } segmentInsert(lanes.size(), segment); return lanes.size(); } /** * Returns the mutation color of a given mutation. Default if no mutation. * * @param mutation * mutation to return color from. * @return color of the mutation */ private Color sequenceColor(final Mutation mutation) { if (mutation == null) { return defaultColor; } else { return mutation.getColor(); } } /** * Create a Vertex that can be displayed on the screen. * * @param index * top left y coordinate * @param segment * the segment to be drawn in the vertex * @param knownMutations * the known mutations of the vertex * @param annotations * the annotations of the vertex */ private void drawVertex(final double index, final SequenceSegment segment, final List<KnownMutation> knownMutations, final List<GeneAnnotation> annotations) { String text = segment.getContent().toString(); long start = segment.getUnifiedStart(); long width = segment.getContent().getLength(); long height = segment.getSources().size(); Color color; if (segment.getContent().isCollapsed()) { color = COLLAPSE_COLOR; } else { color = sequenceColor(segment.getMutation()); } Point2D topleft = new Point2D(start, index); Point2D scaling = new Point2D(horizontalScale, verticalScale); VertexView vertex = new VertexView(text, topleft, width, height, scaling, color); vertex.setOnMouseClicked(event -> controller.clicked(segment)); if (vertex.getHeight() > MINIMALSIZE || vertex.getWidth() > MINIMALSIZE) { nodes.getChildren().add(vertex); } if (knownMutations != null) { for (KnownMutation knownMutation : knownMutations) { long segmentPosition = knownMutation.getGenomePosition() - segment.getStart(); // Loop is intended to create these.. @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") Bookmark bookmark = new Bookmark(vertex, knownMutation, segmentPosition, horizontalScale); bookmarks.getChildren().add(bookmark); } } if (annotations != null) { vertex.annotate(annotations.get(0)); } } /** * Check if there is a free spot to draw the segment at this location. * * @param ind * location in the linked list of already drawn segments * @param segment * segment to be drawn * @return Boolean indicating if there is a free spot */ private boolean segmentFree(final int ind, final SequenceSegment segment) { for (int height = 0; height < segment.getSources().size(); height++) { int position = ind + height; if (position < lanes.size() && lanes.get(position) > segment.getUnifiedStart()) { return false; } } return true; } /** * Insert a segment in the linked list. * * @param index * location in the linked list of already drawn segments * @param segment * segment to be inserted */ private void segmentInsert(final int index, final SequenceSegment segment) { for (int height = 0; height < segment.getSources().size(); height++) { int position = index + height; if (position < lanes.size()) { lanes.set(position, segment.getUnifiedEnd()); } else { lanes.add(position, segment.getUnifiedEnd()); } } } } /** * This class holds additional information to be drawn on the screen and also * the lane index for the vertex. */ class SegmentInfo { /** * Lane index. */ private Integer location; /** * Mutations of this vertex. */ private List<KnownMutation> mutations; /** * Annotations of this vertex. */ private List<GeneAnnotation> annotations; /** * Constructs a new segment information class. * * @param location * lane index * @param mutations * List of mutations * @param annotations * List of annotations */ public SegmentInfo(final Integer location, final List<KnownMutation> mutations, final List<GeneAnnotation> annotations) { this.location = location; this.mutations = mutations; this.annotations = annotations; } /** * @return the lane index */ public Integer getLocation() { return location; } /** * @return the mutations */ public List<KnownMutation> getMutations() { return mutations; } /** * @return the annotations */ public List<GeneAnnotation> getAnnotations() { return annotations; } }