package org.genedb.web.gui;
import org.apache.log4j.Logger;
import java.util.BitSet;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
/**
* Represents the layout of a tracked diagram (i.e. which tracks are filled where)
* in a form that allows first-fit layout to be implemented reasonably efficiently.
*
* @author rh11
*/
public class DiagramLayout {
protected static final Logger logger = Logger.getLogger(DiagramLayout.class);
/**
* Maps position to a set of tracks that are filled from that location rightwards.
* There should be an entry here for each feature boundary.
*/
private NavigableMap<Integer, BitSet> filledTracksAfter = new TreeMap<Integer, BitSet> ();
public DiagramLayout() {
// empty
}
private static final BitSet EMPTY = new UnmodifiableBitSet();
protected BitSet filledTracksAtPosition(int position) {
Map.Entry<Integer, BitSet> floorEntry = filledTracksAfter.floorEntry(position);
if (floorEntry == null) {
logger.trace(String.format("No filled tracks at position %d", position));
return EMPTY;
}
logger.trace(String.format("Filled tracks at position %d are %s, from boundary at %d",
position, floorEntry.getValue(), floorEntry.getKey()));
return new UnmodifiableBitSet(floorEntry.getValue());
}
protected BitSet filledTracksInRange(int start, int end) {
BitSet filledTracks = new BitSet();
filledTracks.or(filledTracksAtPosition(start));
for (BitSet bitSet: filledTracksAfter.subMap(start, true, end, true).values()) {
filledTracks.or(bitSet);
}
logger.trace(String.format("Filled tracks in range %d-%d are %s",
start, end, filledTracks.toString()));
return filledTracks;
}
private int numberOfTracks = 0;
protected void addPositionedBlock(int startPosition, int endPosition, int startTrack, int height) {
assert height >= 1;
if (startTrack + height - 1 > numberOfTracks) {
numberOfTracks = startTrack + height - 1;
}
BitSet filledTracksAtStart = new BitSet();
filledTracksAtStart.or(filledTracksAtPosition(startPosition));
filledTracksAtStart.set(startTrack, startTrack + height);
BitSet filledTracksAtEnd = filledTracksAtPosition(endPosition + 1);
filledTracksAfter.put(startPosition, filledTracksAtStart);
filledTracksAfter.put(endPosition + 1, filledTracksAtEnd);
for (Map.Entry<Integer, BitSet> entry: filledTracksAfter.subMap(startPosition, false, endPosition, true).entrySet()) {
BitSet set = new BitSet();
set.or(entry.getValue());
set.set(startTrack, startTrack + height);
entry.setValue(set);
}
}
protected int chooseTrack(int startPosition, int endPosition, int height) {
BitSet filledTracks = filledTracksInRange(startPosition, endPosition);
int i = 1;
while (true) {
i = filledTracks.nextClearBit(i);
for (int j = i; j < i + height; j++) {
if (filledTracks.get(j)) {
i = j;
continue;
}
}
return i;
}
}
/**
* Add a block in the specified position.
* @param startPosition
* @param endPosition
* @param height
* @return the track number of the bottom of the block.
*/
public int addBlock(int startPosition, int endPosition, int height) {
int firstFit = chooseTrack(startPosition, endPosition, height);
addPositionedBlock(startPosition, endPosition, firstFit, height);
logger.trace(String.format("%s: Block %d-%d (height %d) allocated to track %d",
this, startPosition, endPosition, height, firstFit));
return firstFit;
}
public int numberOfTracks() {
return numberOfTracks;
}
}