package com.baselet.element.sequence_aio.facet; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import com.baselet.element.sequence_aio.facet.LifelineSpanningTickSpanningOccurrence.ContainerPadding; /** * Only supports ContainerPaddings which don't overlap at start, i.e. if the start tick is the same the lifeline * interval must not intersect. * */ public class VerticalDrawingInfoImpl implements VerticalDrawingInfo { private final double startingHeadTopY; private final double startingHeadHeight; private final double tickVerticalPadding; private final double defaultTickHeight; private final double[] accumulativeAddiontalHeightOffsets; private final double[] topPadding; private final double[] bottomPadding; /** contains the bottom padding of the enclosing containers which end at the same tick */ private final Map<Container, Double> containerBottomPadding; private final double startingTickTopY; /** * @param startingHeadTopY * @param startingHeadHeight * @param defaultTickHeight * @param tickVerticalPadding <code>tickVerticalPadding = getVerticalEnd(tick)- getVerticalStart(tick+1)</code> * @param addiontalHeights for every tick how much height should be added to this tick, therefore length at least * lastTick + 1 so that lastTick is a valid index * @param allPaddings */ public VerticalDrawingInfoImpl(double startingHeadTopY, double startingHeadHeight, double defaultTickHeight, double tickVerticalPadding, double[] addiontalHeights, Collection<ContainerPadding> allPaddings) { this.startingHeadTopY = startingHeadTopY; this.startingHeadHeight = startingHeadHeight; this.tickVerticalPadding = tickVerticalPadding; this.defaultTickHeight = defaultTickHeight; topPadding = new double[addiontalHeights.length + 1]; bottomPadding = new double[addiontalHeights.length + 1]; containerBottomPadding = new HashMap<Container, Double>((int) (allPaddings.size() / 0.7)); accumulativeAddiontalHeightOffsets = new double[addiontalHeights.length + 1]; processPaddings(addiontalHeights, allPaddings); startingTickTopY = startingHeadTopY + startingHeadHeight + tickVerticalPadding; } private void processPaddings(double[] addiontalHeights, Collection<ContainerPadding> allPaddings) { Map<Integer, List<ContainerPadding>> endMap = new HashMap<Integer, List<ContainerPadding>>(); Map<Integer, List<ContainerPadding>> startMap = new HashMap<Integer, List<ContainerPadding>>(); for (ContainerPadding cp : allPaddings) { if (!startMap.containsKey(cp.getContainer().getStartTick())) { startMap.put(cp.getContainer().getStartTick(), new LinkedList<ContainerPadding>()); } startMap.get(cp.getContainer().getStartTick()).add(cp); if (!endMap.containsKey(cp.getContainer().getEndTick())) { endMap.put(cp.getContainer().getEndTick(), new LinkedList<ContainerPadding>()); } endMap.get(cp.getContainer().getEndTick()).add(cp); } for (Map.Entry<Integer, List<ContainerPadding>> e : startMap.entrySet()) { for (ContainerPadding cp : e.getValue()) { topPadding[e.getKey()] = Math.max(topPadding[e.getKey()], cp.getTopPadding()); } } calculateBottomPaddings(endMap); double sum = 0; for (int i = 0; i < addiontalHeights.length; i++) { sum += addiontalHeights[i] + topPadding[i] + bottomPadding[i]; accumulativeAddiontalHeightOffsets[i + 1] = sum; } } private void calculateBottomPaddings(Map<Integer, List<ContainerPadding>> endMap) { for (Map.Entry<Integer, List<ContainerPadding>> e : endMap.entrySet()) { while (!e.getValue().isEmpty()) { List<ContainerPadding> cpList = new LinkedList<ContainerPadding>(); cpList.add(e.getValue().remove(0)); int startLl = cpList.get(0).getContainer().getFirstLifeline().getIndex(); int endLl = cpList.get(0).getContainer().getLastLifeline().getIndex(); // add all lifelines to the list which have intersecting lifelines ListIterator<ContainerPadding> cpIter = e.getValue().listIterator(); while (cpIter.hasNext()) { ContainerPadding cp = cpIter.next(); if (isIntersecting(startLl, endLl, cp.getContainer())) { cpList.add(cp); cpIter.remove(); startLl = Math.min(startLl, cp.getContainer().getFirstLifeline().getIndex()); endLl = Math.max(endLl, cp.getContainer().getLastLifeline().getIndex()); } } Collections.sort(cpList, ContainerPadding.getContainerStartTickLifelineAscComparator()); double padding = 0; for (ContainerPadding cp : cpList) { containerBottomPadding.put(cp.getContainer(), padding); padding += cp.getBottomPadding(); } bottomPadding[e.getKey()] = Math.max(bottomPadding[e.getKey()], padding); } } } private boolean contains(int low, int high, int value) { return low <= value && value <= high; } private boolean isIntersecting(int startLl, int endLl, Container container) { boolean isIntersecting = contains(startLl, endLl, container.getFirstLifeline().getIndex()); isIntersecting = isIntersecting || contains(startLl, endLl, container.getLastLifeline().getIndex()); isIntersecting = isIntersecting || contains(container.getLastLifeline().getIndex(), container.getLastLifeline().getIndex(), startLl); return isIntersecting; } @Override public double getVerticalStart(int tick) { return startingTickTopY + tick * (defaultTickHeight + tickVerticalPadding) + accumulativeAddiontalHeightOffsets[tick] + topPadding[tick]; } @Override public double getVerticalEnd(int tick) { return startingTickTopY + tick * (defaultTickHeight + tickVerticalPadding) + defaultTickHeight + accumulativeAddiontalHeightOffsets[tick + 1] - bottomPadding[tick]; } @Override public double getTickHeight(int tick) { return getVerticalEnd(tick) - getVerticalStart(tick); } @Override public double getTickVerticalPadding() { return tickVerticalPadding; } @Override public double getVerticalHeadStart() { return startingHeadTopY; } @Override public double getVerticalHeadEnd() { return getVerticalHeadStart() + startingHeadHeight; } @Override public double getHeadHeight() { return startingHeadHeight; } @Override public double getVerticalCenter(int tick) { return getVerticalStart(tick) / 2 + getVerticalEnd(tick) / 2; } @Override public double getVerticalStart(Container container) { // since the starting points aren't allowed to overlapp we only need to calculate the start without the topPadding int tick = container.getStartTick(); return startingTickTopY + tick * (defaultTickHeight + tickVerticalPadding) + accumulativeAddiontalHeightOffsets[tick]; } @Override public double getVerticalEnd(Container container) { // this is tricky because the end ticks can overlap int tick = container.getEndTick(); return startingTickTopY + tick * (defaultTickHeight + tickVerticalPadding) + defaultTickHeight + accumulativeAddiontalHeightOffsets[tick + 1] - containerBottomPadding.get(container); } }