From 4cf91ec17ae81e5f4be97aacfb171e70add999c3 Mon Sep 17 00:00:00 2001 From: Jim Procter Date: Wed, 19 Oct 2022 13:10:58 +0100 Subject: [PATCH] JAL-2349 JAL-4033 refactored contact matrix geom calc to allow reporting of mapped positions in tooltip --- src/jalview/gui/AnnotationPanel.java | 57 +++++++++++--- src/jalview/gui/SeqPanel.java | 5 +- src/jalview/renderer/ContactGeometry.java | 105 ++++++++++++++++++++++++++ src/jalview/renderer/ContactMapRenderer.java | 50 +++++------- 4 files changed, 171 insertions(+), 46 deletions(-) create mode 100644 src/jalview/renderer/ContactGeometry.java diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index 7c28bd0..d80c749 100755 --- a/src/jalview/gui/AnnotationPanel.java +++ b/src/jalview/gui/AnnotationPanel.java @@ -50,15 +50,18 @@ import javax.swing.JPopupMenu; import javax.swing.Scrollable; import javax.swing.ToolTipManager; +import jalview.api.AlignViewportI; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.Annotation; import jalview.datamodel.ColumnSelection; +import jalview.datamodel.ContactListI; import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceI; import jalview.gui.JalviewColourChooser.ColourChooserListener; import jalview.renderer.AnnotationRenderer; import jalview.renderer.AwtRenderPanelI; +import jalview.renderer.ContactGeometry; import jalview.schemes.ResidueProperties; import jalview.util.Comparison; import jalview.util.MessageManager; @@ -778,8 +781,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, { int yPos = evt.getY(); AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation(); - - int row = getRowIndex(yPos, aa); + int rowAndOffset[] = getRowIndexAndOffset(yPos, aa); + int row = rowAndOffset[0]; if (row == -1) { @@ -801,10 +804,11 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, if (row > -1 && ann.annotations != null && column < ann.annotations.length) { - String toolTip = buildToolTip(ann, column, aa); + String toolTip = buildToolTip(ann, column, aa, rowAndOffset[1], av); setToolTipText(toolTip == null ? null : JvSwingUtils.wrapTooltip(true, toolTip)); - String msg = getStatusMessage(av.getAlignment(), column, ann); + String msg = getStatusMessage(av.getAlignment(), column, ann, + rowAndOffset[1], av); ap.alignFrame.setStatus(msg); } else @@ -830,23 +834,38 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, { return -1; } + return getRowIndexAndOffset(yPos, aa)[0]; + } + + static int[] getRowIndexAndOffset(int yPos, AlignmentAnnotation[] aa) + { + int[] res = new int[2]; + if (aa == null) + { + res[0] = -1; + res[1] = 0; + return res; + } int row = -1; - int height = 0; + int height = 0, lheight = 0; for (int i = 0; i < aa.length; i++) { if (aa[i].visible) { + lheight = height; height += aa[i].height; } if (height > yPos) { row = i; + res[0] = row; + res[1] = yPos - lheight; break; } } - return row; + return res; } /** @@ -857,9 +876,10 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, * @param ann * @param column * @param anns + * @param rowAndOffset */ static String buildToolTip(AlignmentAnnotation ann, int column, - AlignmentAnnotation[] anns) + AlignmentAnnotation[] anns, int rowAndOffset, AlignViewportI av) { String tooltip = null; if (ann.graphGroup > -1) @@ -891,7 +911,18 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, { tooltip = ann.annotations[column].description; } - + // TODO abstract tooltip generator so different implementations can be built + if (ann.graph == AlignmentAnnotation.CUSTOMRENDERER) + { + ContactListI clist = av.getContactList(ann, column); + if (clist != null) + { + ContactGeometry cgeom = new ContactGeometry(clist, ann.graphHeight); + ContactGeometry.contactInterval ci = cgeom.mapFor(rowAndOffset, + rowAndOffset); + tooltip += "Contact from " + ci.cStart + " to " + ci.cEnd; + } + } return tooltip; } @@ -901,9 +932,10 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, * @param al * @param column * @param ann + * @param rowAndOffset */ static String getStatusMessage(AlignmentI al, int column, - AlignmentAnnotation ann) + AlignmentAnnotation ann, int rowAndOffset, AlignViewportI av) { /* * show alignment column and annotation description if any @@ -1124,11 +1156,12 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, Graphics2D gg = (Graphics2D) image.getGraphics(); - if (imgWidth>Math.abs(horizontal*av.getCharWidth())) { - //scroll is less than imgWidth away so can re-use buffered graphics + if (imgWidth > Math.abs(horizontal * av.getCharWidth())) + { + // scroll is less than imgWidth away so can re-use buffered graphics gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.getCharWidth(), 0); - + if (horizontal > 0) // scrollbar pulled right, image to the left { transX = (er - sr - horizontal) * av.getCharWidth(); diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 111b4c0..6918811 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -1175,6 +1175,7 @@ public class SeqPanel extends JPanel final int column = pos.column; final int rowIndex = pos.annotationIndex; + // TODO - get yOffset for annotation, too if (column < 0 || !av.getWrapAlignment() || !av.isShowAnnotation() || rowIndex < 0) { @@ -1183,7 +1184,7 @@ public class SeqPanel extends JPanel AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation(); String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column, - anns); + anns, 0, av); if (tooltip == null ? tooltip != lastTooltip : !tooltip.equals(lastTooltip)) { @@ -1194,7 +1195,7 @@ public class SeqPanel extends JPanel } String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column, - anns[rowIndex]); + anns[rowIndex], 0, av); ap.alignFrame.setStatus(msg); } diff --git a/src/jalview/renderer/ContactGeometry.java b/src/jalview/renderer/ContactGeometry.java new file mode 100644 index 0000000..0e5107a --- /dev/null +++ b/src/jalview/renderer/ContactGeometry.java @@ -0,0 +1,105 @@ +package jalview.renderer; + +import java.util.Iterator; + +import jalview.datamodel.ContactListI; + +public class ContactGeometry +{ + final int pixels_step; + + final double contacts_per_pixel; + + final int contact_height; + + public ContactGeometry(ContactListI contacts, int graphHeight) + { + contact_height = contacts.getContactHeight(); + // fractional number of contacts covering each pixel + contacts_per_pixel = ((double) contact_height) / ((double) graphHeight); + + if (contacts_per_pixel >= 1) + { + // many contacts rendered per pixel + pixels_step = 1; + } + else + { + // pixel height for each contact + pixels_step = (int) Math + .ceil(((double) graphHeight) / (double) contact_height); + } + } + + public class contactInterval + { + public contactInterval(int cStart, int cEnd, int pStart, int pEnd) + { + this.cStart = cStart; + this.cEnd = cEnd; + this.pStart = pStart; + this.pEnd = pEnd; + } + + // range on contact list + public final int cStart; + + public final int cEnd; + + // range in pixels + public final int pStart; + + public final int pEnd; + } + + /** + * + * @param pStart + * @param pEnd + * @return range for + */ + public contactInterval mapFor(int pStart, int pEnd) + { + int cStart = (int) Math.floor(pStart * contacts_per_pixel); + contactInterval ci = new contactInterval(cStart, + (int) Math.min(contact_height, + Math.ceil( + cStart + (pEnd - pStart) * contacts_per_pixel)), + pStart, pEnd); + + return ci; + } + + public Iterator iterateOverContactIntervals( + int graphHeight) + { + // NOT YET IMPLEMENTED + return null; + // int cstart = 0, cend; + // + // for (int ht = y2, + // eht = y2 - graphHeight; ht >= eht; ht -= pixels_step) + // { + // cstart = (int) Math.floor(((double) y2 - ht) * contacts_per_pixel); + // cend = (int) Math.min(contact_height, + // Math.ceil(cstart + contacts_per_pixel * pixels_step)); + // + // return new Iterator() { + // + // @Override + // public boolean hasNext() + // { + // // TODO Auto-generated method stub + // return false; + // } + // + // @Override + // public contactIntervals next() + // { + // // TODO Auto-generated method stub + // return null; + // } + // + // } + } +} \ No newline at end of file diff --git a/src/jalview/renderer/ContactMapRenderer.java b/src/jalview/renderer/ContactMapRenderer.java index f5c4c31..7a435c3 100644 --- a/src/jalview/renderer/ContactMapRenderer.java +++ b/src/jalview/renderer/ContactMapRenderer.java @@ -77,33 +77,18 @@ public class ContactMapRenderer implements AnnotationRowRendererI { return; } - int contact_height = contacts.getContactHeight(); - // fractional number of contacts covering each pixel - double contacts_per_pixel = ((double) contact_height) - / ((double) _aa.graphHeight); + // Bean holding mapping from contact list to pixels + final ContactGeometry cgeom = new ContactGeometry(contacts, + _aa.graphHeight); - int pixels_step; - - if (contacts_per_pixel >= 1) - { - // many contacts rendered per pixel - pixels_step = 1; - } - else - { - // pixel height for each contact - pixels_step = (int) Math - .ceil(((double) _aa.graphHeight) / (double) contact_height); - } - - int cstart = 0, cend; - - for (int ht = y2, - eht = y2 - _aa.graphHeight; ht >= eht; ht -= pixels_step) + for (int ht = y2, eht = y2 + - _aa.graphHeight; ht >= eht; ht -= cgeom.pixels_step) { - cstart = (int) Math.floor(((double) y2 - ht) * contacts_per_pixel); - cend = (int) Math.min(contact_height, - Math.ceil(cstart + contacts_per_pixel * pixels_step)); + ContactGeometry.contactInterval ci = cgeom.mapFor(y2 - ht, + y2 - ht + cgeom.pixels_step); + // cstart = (int) Math.floor(((double) y2 - ht) * contacts_per_pixel); + // cend = (int) Math.min(contact_height, + // Math.ceil(cstart + contacts_per_pixel * pixels_step)); // TODO show maximum colour for range - sort of done // also need a 'getMaxPosForRange(start,end)' to accurately render @@ -113,14 +98,14 @@ public class ContactMapRenderer implements AnnotationRowRendererI { if (_aa.sequenceRef == null) { - rowsel = columnSelection.intersects(cstart, cend); + rowsel = columnSelection.intersects(ci.cStart, ci.cEnd); } else { // TODO check we have correctly mapped cstart to local sequence // numbering - int s = _aa.sequenceRef.findIndex(cstart); - int e = _aa.sequenceRef.findIndex(cend); + int s = _aa.sequenceRef.findIndex(ci.cStart); + int e = _aa.sequenceRef.findIndex(ci.cEnd); if (s > 0 && s < _aa.sequenceRef.getLength()) { rowsel = columnSelection.intersects(s, e); @@ -131,17 +116,18 @@ public class ContactMapRenderer implements AnnotationRowRendererI if (colsel || rowsel) { - col = getSelectedColorForRange(min, max, contacts, cstart, cend); + col = getSelectedColorForRange(min, max, contacts, ci.cStart, + ci.cEnd); g.setColor(col); } else { - col = getColorForRange(min, max, contacts, cstart, cend); + col = getColorForRange(min, max, contacts, ci.cStart, ci.cEnd); g.setColor(col); } - if (pixels_step > 1) + if (cgeom.pixels_step > 1) { - g.fillRect(x * charWidth, ht, charWidth, 1 + pixels_step); + g.fillRect(x * charWidth, ht, charWidth, 1 + cgeom.pixels_step); } else { -- 1.7.10.2