package de.uni_luebeck.inb.krabbenhoeft.eQTL.client.scroller;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import de.uni_luebeck.inb.krabbenhoeft.eQTL.api.gwt.DataRetrieval;
import de.uni_luebeck.inb.krabbenhoeft.eQTL.api.gwt.DataRetrievalAsync;
import de.uni_luebeck.inb.krabbenhoeft.eQTL.api.gwt.DataSetLayerOverview;
import de.uni_luebeck.inb.krabbenhoeft.eQTL.api.gwt.ExpressionQtlTrackEntry2D;
import de.uni_luebeck.inb.krabbenhoeft.eQTL.api.gwt.GenomeRange;
import de.uni_luebeck.inb.krabbenhoeft.eQTL.client.AutoRetry;
import de.uni_luebeck.inb.krabbenhoeft.eQTL.client.ClientMainWindow;
import de.uni_luebeck.inb.krabbenhoeft.eQTL.client.scroller.RegisterForAutomation.HasAutomationHandlers;
public class GenomeDisplayScroller2D extends Widget implements HasValueChangeHandlers<Integer[]> {
private final DataRetrievalAsync dataRetrievalService = GWT.create(DataRetrieval.class);
private final DataSetLayerOverview dataSetLayerOverview;
private final DivElement div;
private final String autoId;
public GenomeDisplayScroller2D(DataSetLayerOverview dataSetLayerOverview, String baseId) {
super();
this.dataSetLayerOverview = dataSetLayerOverview;
this.div = Document.get().createDivElement();
setElement(div);
setStyleName("GenomeDisplayScroller2D");
sinkEvents(Event.MOUSEEVENTS | Event.ONLOSECAPTURE);
RegisterForAutomation.clearBaseId(baseId);
autoId = RegisterForAutomation.register(baseId, new AutomationHandlers());
}
public class ScrollAxis {
public int pixelLength;
GenomeRange range;
long rangeSize;
long bpPerPixel;
long bpPerPixelShift;
public void update(GenomeRange newRange) {
range = newRange;
if (pixelLength == 0)
return;
final boolean scaleChanged;
long len = range.toBP - range.fromBP;
if (len != rangeSize) {
rangeSize = len;
scaleChanged = true;
} else
scaleChanged = false;
if (scaleChanged) {
double estimatedBpPerPixel = (double) len / (double) pixelLength;
// to ensure < works also for =
estimatedBpPerPixel *= 0.9;
bpPerPixel = 1;
bpPerPixelShift = 0;
while (bpPerPixel < estimatedBpPerPixel) {
bpPerPixel <<= 1;
bpPerPixelShift++;
}
blockId2block.clear();
div.setInnerHTML("");
}
updatePosition();
}
}
public ScrollAxis rangeX = new ScrollAxis();
public ScrollAxis rangeY = new ScrollAxis();
public String selectedPositionColumnX;
public String selectedPositionColumnY;
String genAutomation(long id, int i) {
final String click = "onclick=\"" + autoId + ".click('" + id + "'," + i + ");\" ";
final String over = "onmouseover=\"" + autoId + ".over('" + id + "'," + i + ");\" ";
final String out = "onmouseout=\"" + autoId + ".out('" + id + "'," + i + ");\"";
return click + over + out;
}
String fillBlock(ExpressionQtlTrackEntry2D[] data, long fromBpX, long fromBpY, long toBpX, long toBpY, long bpPerPixelX, long bpPerPixelY) {
long id = xy2id(fromBpX, fromBpY);
String html = "";
for (int i = 0; i < data.length; i++) {
ExpressionQtlTrackEntry2D eqtl = data[i];
long posSX = (eqtl.positionXStart - fromBpX) / bpPerPixelX;
if (posSX < 0)
posSX = 0;
else
posSX -= 2;
long posXE = (eqtl.positionXEnd - fromBpX) / bpPerPixelX;
if (posXE > 512)
posXE = 512;
else
posXE += 2;
long posSY = (eqtl.positionYStart - fromBpY) / bpPerPixelY;
if (posSY < 0)
posSY = 0;
else
posSY -= 2;
long posYE = (eqtl.positionYEnd - fromBpY) / bpPerPixelY;
if (posYE > 512)
posYE = 512;
else
posYE += 2;
int red = (int) ((1 - eqtl.lodScoreInMinMaxRange) * 255.0f);
int green = (int) (eqtl.lodScoreInMinMaxRange * 255.0f);
final String style = "background-color: rgb(" + red + "," + green + ",0); left:" + posSX + "px;top:" + posSY + "px;width:" + (posXE - posSX) + "px;height:" + (posYE - posSY) + "px; ";
html += "<div class=\"map-eqtl-marker\" style=\"" + style + "\" " + genAutomation(id, i) + " ></div>";
}
return html;
}
class ScrollBlock {
long fromBpX, fromBpY;
DivElement contentsDiv;
ExpressionQtlTrackEntry2D[] data;
public ScrollBlock(long fromBpX2, long fromBpY2) {
contentsDiv = Document.get().createDivElement();
contentsDiv.setClassName("GenomeMapScrollBlock");
contentsDiv.setInnerHTML("loading...");
this.fromBpX = fromBpX2;
this.fromBpY = fromBpY2;
}
}
Map<Long, ScrollBlock> blockId2block = new HashMap<Long, ScrollBlock>();
DivElement createBlock(final long fromBpX, final long fromBpY) {
final long bpPerPixelX = rangeX.bpPerPixel;
final long bpPerPixelY = rangeY.bpPerPixel;
final ScrollBlock block = new ScrollBlock(fromBpX, fromBpY);
new AutoRetry<ExpressionQtlTrackEntry2D[]>() {
public void success(ExpressionQtlTrackEntry2D[] result) {
block.data = result;
block.contentsDiv.setInnerHTML(fillBlock(result, fromBpX, fromBpY, fromBpX + 512 * bpPerPixelX, fromBpY + 512 * bpPerPixelY, bpPerPixelX, bpPerPixelY));
updatePosition();
}
public void invoke(AsyncCallback<ExpressionQtlTrackEntry2D[]> callback) {
final GenomeRange blockRangeX = new GenomeRange(rangeX.range.chromosome, fromBpX, fromBpX + bpPerPixelX * 512);
final GenomeRange blockRangeY = new GenomeRange(rangeY.range.chromosome, fromBpY, fromBpY + bpPerPixelY * 512);
dataRetrievalService.getTopEntriesForArea(dataSetLayerOverview.layerKey, selectedPositionColumnX, blockRangeX, selectedPositionColumnY, blockRangeY, callback);
}
}.run();
long id = xy2id(fromBpX, fromBpY);
blockId2block.put(id, block);
return block.contentsDiv;
}
DivElement getBlock(final long fromBpX, final long fromBpY) {
long id = xy2id(fromBpX, fromBpY);
if (!blockId2block.containsKey(id))
return null;
return blockId2block.get(id).contentsDiv;
}
private long xy2id(final long fromBpX, final long fromBpY) {
return fromBpX << 32 | fromBpY;
}
void updatePosition() {
if (rangeX.range == null || rangeY.range == null)
return;
long fromBlockX = rangeX.range.fromBP >> rangeX.bpPerPixelShift;
fromBlockX -= fromBlockX % 512;
long toX = rangeX.range.toBP;
long stepX = 512 << rangeX.bpPerPixelShift;
long fromBlockY = rangeY.range.fromBP >> rangeY.bpPerPixelShift;
fromBlockY -= fromBlockY % 512;
long toY = rangeY.range.toBP;
long stepY = 512 << rangeY.bpPerPixelShift;
final long cacheRangeX = 4 * stepX;
final long cacheRangeY = 4 * stepY;
final Iterator<Entry<Long, ScrollBlock>> cur = blockId2block.entrySet().iterator();
while (cur.hasNext()) {
final ScrollBlock block = cur.next().getValue();
if ((block.fromBpX + stepX < rangeX.range.fromBP - cacheRangeX) || (block.fromBpX > rangeX.range.toBP + cacheRangeX) || (block.fromBpY + stepY < rangeY.range.fromBP - cacheRangeY)
|| (block.fromBpY > rangeY.range.toBP + cacheRangeY)) {
// out of cache range
div.removeChild(block.contentsDiv);
cur.remove();
} else if ((block.fromBpX + stepX < rangeX.range.fromBP) || (block.fromBpX > rangeX.range.toBP) || (block.fromBpY + stepY < rangeY.range.fromBP) || (block.fromBpY > rangeY.range.toBP)) {
// out of sight
block.contentsDiv.getStyle().setProperty("display", "none");
}
}
for (long x = fromBlockX << rangeX.bpPerPixelShift; x < toX; x += stepX) {
final int offX = (int) ((x - rangeX.range.fromBP) >> rangeX.bpPerPixelShift);
for (long y = fromBlockY << rangeY.bpPerPixelShift; y < toY; y += stepY) {
final int offY = (int) ((y - rangeY.range.fromBP) >> rangeY.bpPerPixelShift);
DivElement block = getBlock(x, y);
if (block == null) {
block = createBlock(x, y);
div.appendChild(block);
}
block.getStyle().setProperty("left", offX + "px");
block.getStyle().setProperty("top", offY + "px");
block.getStyle().setProperty("display", "");
}
}
}
boolean moving = false;
int scrollStartX, scrollStartY;
private void doScrollOrDrag(int x, int y) {
int offx = x - scrollStartX;
scrollStartX = x;
int offy = y - scrollStartY;
scrollStartY = y;
ValueChangeEvent.fire(this, new Integer[] { -offx, -offy });
}
@Override
public void onBrowserEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONMOUSEDOWN: {
scrollStartX = DOM.eventGetClientX(event) - getAbsoluteLeft();
scrollStartY = DOM.eventGetClientY(event) - getAbsoluteTop();
moving = true;
DOM.setCapture((Element) div.cast());
DOM.eventPreventDefault(event);
break;
}
case Event.ONMOUSEUP: {
if (moving) {
moving = false;
DOM.releaseCapture((Element) div.cast());
}
break;
}
case Event.ONMOUSEMOVE: {
if (moving) {
assert DOM.getCaptureElement() != null;
doScrollOrDrag(DOM.eventGetClientX(event) - getAbsoluteLeft(), DOM.eventGetClientY(event) - getAbsoluteTop());
DOM.eventPreventDefault(event);
}
break;
}
case Event.ONLOSECAPTURE: {
moving = false;
break;
}
}
super.onBrowserEvent(event);
}
private HandlerManager handlers = new HandlerManager(null);
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Integer[]> handler) {
return handlers.addHandler(ValueChangeEvent.getType(), handler);
}
public void fireEvent(GwtEvent<?> event) {
handlers.fireEvent(event);
}
public class AutomationHandlers implements HasAutomationHandlers {
private ExpressionQtlTrackEntry2D getObject(String id, int itemIndex) {
return blockId2block.get(Long.parseLong(id)).data[itemIndex];
}
public void onMouseClick(String fromBP, int itemIndex) {
final ExpressionQtlTrackEntry2D e = getObject(fromBP, itemIndex);
Window.open("http://www.ensembl.org/Mus_musculus/Location/View?r=" + rangeX.range.chromosome + ":" + (e.positionXStart - 1000) + "-" + (e.positionXEnd + 1000), "_blank", "");
Window.open("http://www.ensembl.org/Mus_musculus/Location/View?r=" + rangeY.range.chromosome + ":" + (e.positionYStart - 1000) + "-" + (e.positionYEnd + 1000), "_blank", "");
}
Label currentMouseOver = null;
public void onMouseOut(String fromBP, int itemIndex) {
if (currentMouseOver == null)
return;
ClientMainWindow.notifyUserRem(currentMouseOver);
currentMouseOver = null;
}
public void onMouseOver(String fromBP, int itemIndex) {
if (currentMouseOver != null)
ClientMainWindow.notifyUserRem(currentMouseOver);
currentMouseOver = ClientMainWindow.notifyUserAdd("Mouse over: " + getObject(fromBP, itemIndex).toString());
}
}
}