1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package org.utgenome.gwt.utgb.client.canvas;
26
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30
31 import org.utgenome.gwt.utgb.client.UTGBEntryPointBase;
32 import org.utgenome.gwt.utgb.client.bio.CompactWIGData;
33 import org.utgenome.gwt.utgb.client.canvas.GWTGenomeCanvas.DragPoint;
34 import org.utgenome.gwt.utgb.client.track.TrackConfig;
35 import org.utgenome.gwt.utgb.client.track.TrackWindow;
36 import org.utgenome.gwt.utgb.client.util.Optional;
37 import org.utgenome.gwt.widget.client.Style;
38
39 import com.google.gwt.core.client.GWT;
40 import com.google.gwt.core.client.JavaScriptException;
41 import com.google.gwt.user.client.DOM;
42 import com.google.gwt.user.client.Event;
43 import com.google.gwt.user.client.Window;
44 import com.google.gwt.user.client.ui.AbsolutePanel;
45 import com.google.gwt.user.client.ui.Composite;
46 import com.google.gwt.user.client.ui.Label;
47 import com.google.gwt.widgetideas.graphics.client.Color;
48 import com.google.gwt.widgetideas.graphics.client.GWTCanvas;
49
50
51
52
53
54
55
56
57 public class GWTGraphCanvas extends Composite {
58
59
60 private GWTCanvas frameCanvas = new GWTCanvas();
61 private AbsolutePanel panel = new AbsolutePanel();
62 private TrackWindow viewWindow;
63
64 private final HashMap<TrackWindow, GraphCanvas> canvasMap = new HashMap<TrackWindow, GraphCanvas>();
65
66
67
68
69
70
71
72 private static class GraphCanvas {
73 public TrackWindow window;
74 public final List<CompactWIGData> graphData;
75 public final GWTCanvas canvas = new GWTCanvas();
76 public final int span;
77 private int height;
78 private boolean toDelete = false;
79
80 public GraphCanvas(TrackWindow window, List<CompactWIGData> graphData, int height) {
81 this.window = window;
82 this.graphData = graphData;
83
84 int maxSpan = 1;
85 for (CompactWIGData each : graphData) {
86 int span = each.getSpan();
87 if (span > maxSpan)
88 maxSpan = span;
89 }
90 this.span = maxSpan;
91 this.height = height;
92
93 setPixelHeight(height);
94 }
95
96 public void setToDelete() {
97 this.toDelete = true;
98 }
99
100 public boolean isToDelete() {
101 return toDelete;
102 }
103
104 public void updatePixelWidth(int newPixelWidth) {
105 window = window.newPixelWidthWindow(newPixelWidth);
106 setPixelHeight(height);
107 }
108
109 public void clearCanvas() {
110 canvas.clear();
111 }
112
113 public void setPixelHeight(int height) {
114 this.height = height;
115
116 int pixelWidthWithSpan = window.convertToPixelLength(window.getSequenceLength() + this.span - 1);
117
118 canvas.setCoordSize(pixelWidthWithSpan, height);
119 canvas.setPixelSize(pixelWidthWithSpan, height);
120 }
121
122 @Override
123 public String toString() {
124 return window.toString();
125 }
126
127 }
128
129
130
131
132
133
134
135 public static class GraphStyle {
136 public int windowHeight = 100;
137 public float maxValue = 20.0f;
138 public float minValue = 0.0f;
139 public float autoScaledMax = 20.0f;
140 public float autoScaledMin = 0.0f;
141
142 public boolean autoScale = false;
143 public boolean logScale = false;
144 public boolean drawZeroValue = false;
145 public boolean drawScale = true;
146 public boolean showScaleLabel = true;
147 public Optional<String> color = new Optional<String>();
148 public float logBase = 2.0f;
149
150 public final static String CONFIG_TRACK_HEIGHT = "trackHeight";
151 private final static String CONFIG_MAX_VALUE = "maxValue";
152 private final static String CONFIG_MIN_VALUE = "minValue";
153 private final static String CONFIG_AUTO_SCALE = "autoScale";
154 private final static String CONFIG_LOG_SCALE = "logScale";
155 private final static String CONFIG_LOG_BASE = "log base";
156 private final static String CONFIG_SHOW_ZERO_VALUE = "showZero";
157 private final static String CONFIG_DRAW_SCALE = "drawScale";
158 private final static String CONFIG_SHOW_SCALE_LABEL = "showScaleLabel";
159 private final static String CONFIG_COLOR = "color";
160
161 public float getDefaultMinValue() {
162 return minValue;
163 }
164
165 public float getDefaultMaxValue() {
166 return maxValue;
167 }
168
169 public boolean isReverseYAxis() {
170 return minValue > maxValue;
171 }
172
173
174
175
176
177
178 public void load(TrackConfig config) {
179 maxValue = config.getFloat(CONFIG_MAX_VALUE, maxValue);
180 minValue = config.getFloat(CONFIG_MIN_VALUE, minValue);
181 autoScale = config.getBoolean(CONFIG_AUTO_SCALE, autoScale);
182 logScale = config.getBoolean(CONFIG_LOG_SCALE, logScale);
183 logBase = config.getFloat(CONFIG_LOG_BASE, logBase);
184 drawZeroValue = config.getBoolean(CONFIG_SHOW_ZERO_VALUE, drawZeroValue);
185 drawScale = config.getBoolean(CONFIG_DRAW_SCALE, drawScale);
186 showScaleLabel = config.getBoolean(CONFIG_SHOW_SCALE_LABEL, showScaleLabel);
187 windowHeight = config.getInt(CONFIG_TRACK_HEIGHT, windowHeight);
188 if (windowHeight <= 0)
189 windowHeight = 100;
190 String colorStr = config.getString(CONFIG_COLOR, "");
191 if (colorStr != null && colorStr.length() > 0)
192 color.set(colorStr);
193 else
194 color.reset();
195 }
196
197
198
199
200
201
202 public void setup(TrackConfig config) {
203 config.addConfigDouble("Y Max", CONFIG_MAX_VALUE, maxValue);
204 config.addConfigDouble("Y Min", CONFIG_MIN_VALUE, minValue);
205 config.addConfigBoolean("Auto Scale", CONFIG_AUTO_SCALE, autoScale);
206 config.addConfigBoolean("Log Scale", CONFIG_LOG_SCALE, logScale);
207 config.addConfigDouble("Log Base", CONFIG_LOG_BASE, logBase);
208 config.addConfigBoolean("Show Zero Value", CONFIG_SHOW_ZERO_VALUE, drawZeroValue);
209 config.addConfigBoolean("Draw Scale", CONFIG_DRAW_SCALE, drawScale);
210 config.addConfigBoolean("Show Scale Label", CONFIG_SHOW_SCALE_LABEL, showScaleLabel);
211 config.addConfigString("Graph Color", CONFIG_COLOR, "");
212 }
213
214 }
215
216 private GraphStyle style = new GraphStyle();
217
218 public GWTGraphCanvas() {
219
220 init();
221 }
222
223 private void init() {
224
225 Style.padding(panel, 0);
226 Style.margin(panel, 0);
227
228 panel.add(frameCanvas, 0, 0);
229
230 initWidget(panel);
231
232 sinkEvents(Event.ONMOUSEMOVE | Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONMOUSEDOWN | Event.ONMOUSEUP);
233 }
234
235 private Optional<DragPoint> dragStartPoint = new Optional<DragPoint>();
236
237 @Override
238 public void onBrowserEvent(Event event) {
239 super.onBrowserEvent(event);
240
241 int type = DOM.eventGetType(event);
242 switch (type) {
243 case Event.ONMOUSEOVER:
244
245 break;
246 case Event.ONMOUSEMOVE: {
247
248
249 if (dragStartPoint.isDefined()) {
250
251 int clientX = DOM.eventGetClientX(event) + Window.getScrollLeft();
252
253
254 DragPoint p = dragStartPoint.get();
255 int xDiff = clientX - p.x;
256
257
258 }
259 else {
260
261 }
262
263 break;
264 }
265 case Event.ONMOUSEOUT: {
266 resetDrag(event);
267 break;
268 }
269 case Event.ONMOUSEDOWN: {
270
271 int clientX = DOM.eventGetClientX(event) + Window.getScrollLeft();
272 int clientY = DOM.eventGetClientY(event) + Window.getScrollTop();
273
274 if (dragStartPoint.isUndefined()) {
275 dragStartPoint.set(new DragPoint(clientX, clientY));
276
277 event.preventDefault();
278 }
279
280 break;
281 }
282 case Event.ONMOUSEUP: {
283
284 resetDrag(event);
285 break;
286 }
287 }
288 }
289
290 private void resetDrag(Event event) {
291
292 int clientX = DOM.eventGetClientX(event) + Window.getScrollLeft();
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309 dragStartPoint.reset();
310
311
312 }
313
314 public void clear() {
315 clearCanvas();
316 clearScale();
317 }
318
319 public void clearCanvas() {
320 for (GraphCanvas each : canvasMap.values()) {
321 each.canvas.removeFromParent();
322 }
323 canvasMap.clear();
324 }
325
326 public void clearScale() {
327 frameCanvas.clear();
328
329 for (Label each : graphLabels) {
330 each.removeFromParent();
331 }
332 graphLabels.clear();
333 }
334
335
336
337
338
339
340
341 private GraphCanvas getCanvas(TrackWindow w, List<CompactWIGData> data) {
342 GraphCanvas graphCanvas = canvasMap.get(w);
343 if (graphCanvas == null) {
344
345 graphCanvas = new GraphCanvas(w, data, style.windowHeight);
346 canvasMap.put(w, graphCanvas);
347 int x = viewWindow.convertToPixelX(w.getStartOnGenome());
348 panel.add(graphCanvas.canvas, 0, 0);
349 panel.setWidgetPosition(graphCanvas.canvas, x, 0);
350 }
351
352 return graphCanvas;
353 }
354
355 private final String DEFAULT_COLOR = "rgba(12,106,193,0.7)";
356
357 public void redrawWigGraph() {
358 for (GraphCanvas each : canvasMap.values()) {
359 each.clearCanvas();
360 each.setPixelHeight(style.windowHeight);
361 drawWigGraph(each);
362 }
363 }
364
365 public void drawWigGraph(List<CompactWIGData> data, TrackWindow w) {
366 if (data == null)
367 return;
368
369 GraphCanvas canvas = getCanvas(w, data);
370 drawWigGraph(canvas);
371 }
372
373 protected void drawWigGraph(GraphCanvas graphCanvas) {
374
375 for (CompactWIGData data : graphCanvas.graphData) {
376
377
378 Color graphColor = new Color(DEFAULT_COLOR);
379 if (style.color.isDefined()) {
380 graphColor = new Color(style.color.get());
381 }
382 else if (data.getTrack().containsKey("color")) {
383 String colorStr = data.getTrack().get("color");
384 String c[] = colorStr.split(",");
385 if (c.length == 3)
386 graphColor = new Color(Integer.valueOf(c[0]), Integer.valueOf(c[1]), Integer.valueOf(c[2]));
387 }
388
389
390 GWTCanvas canvas = graphCanvas.canvas;
391
392 canvas.saveContext();
393 canvas.setLineWidth(1.0f);
394 canvas.setStrokeStyle(graphColor);
395
396
397
398 float y2 = getYPosition(0.0f);
399
400
401 final boolean isReverse = graphCanvas.window.isReverseStrand();
402 final int pixelWidth = data.getData().length;
403
404 float min = style.autoScale ? autoScaledMinValue : style.minValue;
405 float max = style.autoScale ? autoScaledMaxValue : style.maxValue;
406
407 for (int i = 0; i < pixelWidth; ++i) {
408 float value = data.getData()[i];
409 float y1;
410 if (value == 0.0f) {
411 if (!style.drawZeroValue)
412 continue;
413 else {
414 y1 = y2 + ((min < max) ? -0.5f : 0.5f);
415 }
416 }
417 else {
418 y1 = getYPosition(value);
419 }
420
421 int x = i;
422 if (isReverse) {
423 x = pixelWidth - x - 1;
424 }
425
426 canvas.saveContext();
427 canvas.beginPath();
428 canvas.translate(x + 0.5f, 0);
429 canvas.moveTo(0, y1);
430 canvas.lineTo(0, y2);
431 canvas.stroke();
432 canvas.restoreContext();
433 }
434 canvas.restoreContext();
435 }
436
437 }
438
439 public void clearOutSideOf(TrackWindow globalWindow) {
440 ArrayList<GraphCanvas> out = new ArrayList<GraphCanvas>();
441 for (GraphCanvas each : canvasMap.values()) {
442
443 if (!globalWindow.overlapWith(each.window) || each.isToDelete()) {
444 out.add(each);
445 }
446 }
447 for (GraphCanvas each : out) {
448 each.canvas.clear();
449 each.canvas.removeFromParent();
450 canvasMap.remove(each);
451 }
452 }
453
454 private List<Label> graphLabels = new ArrayList<Label>();
455
456 public void drawFrame() {
457
458 if (!style.drawScale)
459 return;
460
461
462 frameCanvas.saveContext();
463 frameCanvas.setStrokeStyle(new Color(0, 0, 0, 0.5f));
464 frameCanvas.setLineWidth(1.0f);
465 frameCanvas.beginPath();
466 frameCanvas.rect(0, 0, viewWindow.getPixelWidth(), style.windowHeight);
467 frameCanvas.stroke();
468 frameCanvas.restoreContext();
469
470
471 Indent indent = createIndent();
472
473 frameCanvas.saveContext();
474 frameCanvas.setStrokeStyle(Color.BLACK);
475 frameCanvas.setGlobalAlpha(0.2f);
476 frameCanvas.setLineWidth(0.5f);
477 for (int i = 0; i <= indent.nSteps; i++) {
478 float value = indent.getIndentValue(i);
479
480 frameCanvas.saveContext();
481 frameCanvas.beginPath();
482 frameCanvas.translate(0, getYPosition(value) + 0.5d);
483 frameCanvas.moveTo(0d, 0d);
484 frameCanvas.lineTo(viewWindow.getPixelWidth(), 0);
485 frameCanvas.stroke();
486 frameCanvas.restoreContext();
487 }
488 {
489
490 frameCanvas.saveContext();
491 frameCanvas.beginPath();
492 frameCanvas.translate(0, getYPosition(0f));
493 frameCanvas.moveTo(0, 0);
494 frameCanvas.lineTo(viewWindow.getPixelWidth(), 0);
495 frameCanvas.stroke();
496 frameCanvas.restoreContext();
497 }
498
499 frameCanvas.restoreContext();
500
501 }
502
503 public void drawScaleLabel() {
504
505 if (!style.showScaleLabel)
506 return;
507
508 Indent indent = createIndent();
509 int fontHeight = 10;
510
511 for (int i = 0; i <= indent.nSteps; i++) {
512 float value = indent.getIndentValue(i);
513 String labelString = indent.getIndentString(i);
514 Label label = new Label(labelString);
515 label.setTitle(labelString);
516 Style.fontSize(label, fontHeight);
517 Style.textAlign(label, "left");
518 Style.fontColor(label, "#003366");
519
520 int labelX = 1;
521 int labelY = (int) (getYPosition(value) - (fontHeight / 2.0f) - 1);
522
523 if (labelY < 0 && labelY > -fontHeight)
524 labelY = -1;
525
526 if (labelY > style.windowHeight - fontHeight) {
527 labelY = style.windowHeight - fontHeight;
528 }
529
530
531
532
533
534 graphLabels.add(label);
535 panel.add(label, labelX, labelY);
536
537 }
538 }
539
540 private Indent createIndent() {
541 if (style.autoScale) {
542 return new Indent(autoScaledMinValue, autoScaledMaxValue, style);
543 }
544 else {
545 return new Indent(style.minValue, style.maxValue, style);
546 }
547 }
548
549 public static class Indent {
550 public int exponent = 0;
551 public long fraction = 0;
552
553 public int nSteps = 0;
554
555 public float min = 0.0f;
556 public float max = 0.0f;
557
558 private GraphStyle style;
559
560 public Indent(float minValue, float maxValue, GraphStyle style) {
561 this.style = style;
562
563 final int indentHeight = 10;
564
565 min = minValue < maxValue ? minValue : maxValue;
566 max = minValue > maxValue ? minValue : maxValue;
567
568 if (style.logScale) {
569 min = getLogValue(min, style.logBase);
570 max = getLogValue(max, style.logBase);
571 }
572
573 try {
574 double tempIndentValue = (max - min) / style.windowHeight * indentHeight;
575
576 if (style.logScale && tempIndentValue < 1.0)
577 tempIndentValue = 1.0;
578
579 fraction = (long) Math.floor(Math.log10(tempIndentValue));
580 exponent = (int) Math.ceil(Math.round(tempIndentValue / Math.pow(10, fraction - 3)) / 1000.0);
581
582 if (exponent <= 5)
583 ;
584
585
586 else {
587 exponent = 1;
588 fraction++;
589 }
590 double stepSize = exponent * Math.pow(10, fraction);
591 max = (float) (Math.floor(max / stepSize) * stepSize);
592 min = (float) (Math.ceil(min / stepSize) * stepSize);
593
594 nSteps = (int) Math.abs((max - min) / stepSize);
595 }
596 catch (JavaScriptException e) {
597 UTGBEntryPointBase.showErrorMessage(e.getMessage());
598 }
599
600 }
601
602 public float getIndentValue(int step) {
603 double indentValue = min + (step * exponent * Math.pow(10, fraction));
604
605 if (!style.logScale)
606 return (float) indentValue;
607 else if (indentValue == 0.0f)
608 return 0.0f;
609 else if (indentValue >= 0.0f)
610 return (float) Math.pow(2, indentValue - 1);
611 else
612 return (float) -Math.pow(2, -indentValue - 1);
613 }
614
615 public String getIndentString(int step) {
616 float indentValue = getIndentValue(step);
617
618 if (indentValue == (int) indentValue)
619 return String.valueOf((int) indentValue);
620 else {
621 int exponent_tmp = (int) Math.ceil(Math.round(indentValue / Math.pow(10, fraction - 3)) / 1000.0);
622 int endIndex = String.valueOf(exponent_tmp).length() + 1;
623 if (fraction < 0)
624 endIndex -= fraction;
625 endIndex = Math.min(String.valueOf(indentValue).length(), endIndex);
626
627 return String.valueOf(indentValue).substring(0, endIndex);
628 }
629 }
630 }
631
632 public float getYPosition(float value) {
633
634 float min = style.minValue;
635 float max = style.maxValue;
636
637 if (style.autoScale) {
638 min = autoScaledMinValue;
639 max = autoScaledMaxValue;
640 }
641
642 if (min == max)
643 return 0.0f;
644
645 float tempMin = max < min ? max : min;
646 float tempMax = max > min ? max : min;
647
648 if (style.logScale) {
649 value = getLogValue(value, style.logBase);
650 tempMax = getLogValue(tempMax, style.logBase);
651 tempMin = getLogValue(tempMin, style.logBase);
652 }
653 float valueHeight = (value - tempMin) / (tempMax - tempMin) * style.windowHeight;
654
655 if (style.isReverseYAxis())
656 return valueHeight;
657 else
658 return style.windowHeight - valueHeight;
659 }
660
661 public static float getLogValue(float value, float logBase) {
662 if (Math.log(logBase) == 0.0)
663 return value;
664
665 float temp = 0.0f;
666 if (value > 0.0f) {
667 temp = (float) (Math.log(value) / Math.log(logBase) + 1.0);
668 if (temp < 0.0f)
669 temp = 0.0f;
670 }
671 else if (value < 0.0f) {
672 temp = (float) (Math.log(-value) / Math.log(logBase) + 1.0);
673 if (temp < 0.0f)
674 temp = 0.0f;
675 temp *= -1.0f;
676 }
677 return temp;
678 }
679
680 private float autoScaledMinValue = 0.0f;
681 private float autoScaledMaxValue = 0.0f;
682
683 public void setViewWindow(final TrackWindow view) {
684
685 if (viewWindow != null) {
686 if (viewWindow.hasSameScaleWith(view)) {
687 float tempMinValue = autoScaledMinValue;
688 float tempMaxValue = autoScaledMaxValue;
689
690 if (style.autoScale) {
691 autoScaledMinValue = 0.0f;
692 autoScaledMaxValue = 0.0f;
693 }
694
695 for (GraphCanvas each : canvasMap.values()) {
696 int start = each.window.getStartOnGenome();
697 int s = view.convertToPixelX(start);
698 panel.add(each.canvas, s, 0);
699
700
701
702 if (style.autoScale) {
703
704 int pw = view.getPixelWidth();
705 int pw_e = each.window.getPixelWidth();
706
707 for (CompactWIGData wigData : each.graphData) {
708 float data[] = wigData.getData();
709
710 int loopStart, loopEnd;
711 if (!view.isReverseStrand()) {
712 loopStart = Math.max(-s, 0);
713 loopEnd = Math.min(pw - s, pw_e);
714 }
715 else {
716 loopStart = Math.max(s - pw, 0);
717 loopEnd = Math.min(s, pw_e);
718 }
719
720 for (int pos = loopStart; pos < loopEnd; pos++) {
721 autoScaledMinValue = Math.min(autoScaledMinValue, data[pos]);
722 autoScaledMaxValue = Math.max(autoScaledMaxValue, data[pos]);
723 }
724 }
725 GWT.log("scale: " + autoScaledMinValue + " - " + autoScaledMaxValue);
726 }
727 }
728
729 if (autoScaledMinValue == autoScaledMaxValue) {
730 autoScaledMinValue = style.minValue;
731 autoScaledMaxValue = style.maxValue;
732 }
733
734 if (style.autoScale && (autoScaledMinValue != tempMinValue || autoScaledMaxValue != tempMaxValue)) {
735 redrawWigGraph();
736 }
737
738 }
739 else {
740
741 for (GraphCanvas each : canvasMap.values()) {
742 int newPixelWidth = view.convertToPixelLength(each.window.getSequenceLength());
743
744 each.setToDelete();
745
746
747
748
749
750 }
751 redrawWigGraph();
752 }
753
754 }
755
756 viewWindow = view;
757 }
758
759 public TrackWindow getViewWindow() {
760 return viewWindow;
761 }
762
763 public void setStyle(GraphStyle style) {
764 this.style = style;
765 setPixelSize(viewWindow.getPixelWidth(), style.windowHeight);
766
767 clearScale();
768 drawFrame();
769 drawScaleLabel();
770
771 redrawWigGraph();
772 }
773
774 public GraphStyle getStyle() {
775 return style;
776 }
777
778 @Override
779 public void setPixelSize(int width, int height) {
780
781 for (GraphCanvas each : canvasMap.values()) {
782 each.setPixelHeight(height);
783 }
784
785 frameCanvas.setCoordSize(width, height);
786 frameCanvas.setPixelWidth(width);
787 frameCanvas.setPixelHeight(height);
788
789 panel.setPixelSize(width, height);
790 }
791
792 }