package nl.tudelft.lifetiles.graph.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import nl.tudelft.lifetiles.core.util.Timer;
import nl.tudelft.lifetiles.sequence.model.SequenceSegment;
/**
* A bucket cache which adds the sequence segments to buckets for improved
* graph drawing performance.
*
* @author Jos
*
*/
public class BucketCache {
/**
* Graph that has been inserted into the bucket cache.
*/
private final Graph<SequenceSegment> graph;
/**
* Number of buckets the graph is divided in.
*/
private final int numberBuckets;
/**
* Buckets used to store the sequenceSegments.
*/
private List<Bucket> buckets;
/**
* Width of a bucket based on the width of the graph and the number of
* buckets being cached.
*/
private final long bucketWidth;
/**
* Max unified end coordinate position in the graph, should be stored
* because it is called on every view update.
*/
private final long maxUnifiedEnd;
/**
* Constructs a bucket cache and divides the graph into n buckets.
*
* @param numberBuckets
* Number of buckets the graph is divided in.
* @param graph
* Graph that needs to be divided.
*/
public BucketCache(final int numberBuckets,
final Graph<SequenceSegment> graph) {
// Number of buckets is ceiled to a power of 2. Needed for diagram view.
this.numberBuckets = (int) Math.round(Math.pow(2,
Math.ceil(Math.log(numberBuckets) / Math.log(2))));
this.graph = graph;
maxUnifiedEnd = getMaxUnifiedEnd();
bucketWidth = maxUnifiedEnd / this.numberBuckets;
cacheGraph();
}
/**
* Caches the graph that has been inserted into the bucket cache into the
* number of buckets the graph should be divided in.
*/
private void cacheGraph() {
Timer timer = Timer.getAndStart();
buckets = new ArrayList<Bucket>();
for (int index = 0; index < numberBuckets; index++) {
// We do actually need to instantiate here.
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
Bucket bucket = new Bucket();
buckets.add(index, bucket);
}
for (SequenceSegment vertex : graph.getAllVertices()) {
cacheVertex(vertex);
}
timer.stopAndLog("Graph caching");
}
/**
* Caches the vertex to the bucket cache, a vertex can be in multiple
* buckets in the bucket cache.
*
* @param vertex
* The vertex to cache in the buckets in the cache.
*/
private void cacheVertex(final SequenceSegment vertex) {
int startBucket = bucketStartPosition(vertex.getUnifiedStart());
int endBucket = bucketEndPosition(vertex.getUnifiedEnd());
for (SortedSet<SequenceSegment> bucket : buckets.subList(startBucket,
endBucket)) {
bucket.add(vertex);
}
if (startBucket == endBucket) {
buckets.get(startBucket - 1).add(vertex);
}
}
/**
* Get the maximal unified end position based on the sinks of the graph.
*
* @return the maximal unified end position.
*/
private long getMaxUnifiedEnd() {
long max = 0;
for (SequenceSegment vertex : graph.getSinks()) {
if (max < vertex.getUnifiedEnd()) {
max = vertex.getUnifiedEnd();
}
}
return max;
}
/**
* Returns number of buckets the graph is divided in.
*
* @return number of buckets the graph is divided in.
*/
public int getNumberBuckets() {
return numberBuckets;
}
/**
* Returns the list of the sortedSet of sequence segments.
*
* @return graph that has been inserted into the bucket cache.
*/
public List<Bucket> getBuckets() {
return buckets;
}
/**
* Returns the set of sequence segments on a certain domain.
* This will all sequence segment from the starting bucket to the ending
* bucket.
*
* @param start
* the minimal Bucket to search on the domain
* @param end
* the maximal Bucket to search on the domain
* @return set of sequence segments on the domain.
*/
public Set<SequenceSegment> getSegments(final int start, final int end) {
Set<SequenceSegment> set = new TreeSet<SequenceSegment>();
int startBucket = Math.max(0, start);
int endBucket = Math.min(numberBuckets, end);
for (Set<SequenceSegment> bucket : buckets.subList(startBucket,
endBucket)) {
set.addAll(bucket);
}
return set;
}
/**
* Returns the position in the bucketCache given a location on the scrollbar.
*
* @param position
* Percentage position in the GraphController
* @return position in the bucketCache.
*/
public int getBucketPosition(final double position) {
return (int) ((position * maxUnifiedEnd) / bucketWidth);
}
/**
* Returns the minimal bucket it can find in the bucketCache, given a
* position relative to the screen.
*
* @param position
* relative position on the screen
* @return position in the bucketCache.
*/
public int bucketStartPosition(final double position) {
return (int) Math.min(numberBuckets,
Math.max(0, position / bucketWidth));
}
/**
* Returns the maximal bucket it can find in the bucketCache, given a
* position relative to the screen.
*
* @param position
* relative position on the screen
* @return position in the bucketCache.
*/
public int bucketEndPosition(final double position) {
return (int) Math.min(numberBuckets,
Math.ceil(Math.max(0, position / bucketWidth)));
}
}