/* * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * Jalview is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.io; import jalview.api.FeatureColourI; import jalview.datamodel.DBRefEntry; import jalview.datamodel.DBRefSource; import jalview.datamodel.GeneLociI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.util.MessageManager; import jalview.util.StringUtils; import jalview.util.UrlLink; import jalview.viewmodel.seqfeatures.FeatureRendererModel; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * generate HTML reports for a sequence * * @author jimp */ public class SequenceAnnotationReport { private static final String COMMA = ","; private static final String ELLIPSIS = "..."; private static final int MAX_REFS_PER_SOURCE = 4; private static final int MAX_SOURCES = 40; private static final String[][] PRIMARY_SOURCES = new String[][] { DBRefSource.CODINGDBS, DBRefSource.DNACODINGDBS, DBRefSource.PROTEINDBS }; final String linkImageURL; /* * Comparator to order DBRefEntry by Source + accession id (case-insensitive), * with 'Primary' sources placed before others, and 'chromosome' first of all */ private static Comparator comparator = new Comparator() { @Override public int compare(DBRefEntry ref1, DBRefEntry ref2) { if (ref1 instanceof GeneLociI) { return -1; } if (ref2 instanceof GeneLociI) { return 1; } String s1 = ref1.getSource(); String s2 = ref2.getSource(); boolean s1Primary = isPrimarySource(s1); boolean s2Primary = isPrimarySource(s2); if (s1Primary && !s2Primary) { return -1; } if (!s1Primary && s2Primary) { return 1; } int comp = s1 == null ? -1 : (s2 == null ? 1 : s1 .compareToIgnoreCase(s2)); if (comp == 0) { String a1 = ref1.getAccessionId(); String a2 = ref2.getAccessionId(); comp = a1 == null ? -1 : (a2 == null ? 1 : a1 .compareToIgnoreCase(a2)); } return comp; } private boolean isPrimarySource(String source) { for (String[] primary : PRIMARY_SOURCES) { for (String s : primary) { if (source.equals(s)) { return true; } } } return false; } }; public SequenceAnnotationReport(String linkURL) { this.linkImageURL = linkURL; } /** * Append text for the list of features to the tooltip * * @param sb * @param rpos * @param features * @param minmax */ public void appendFeatures(final StringBuilder sb, int rpos, List features, FeatureRendererModel fr) { if (features != null) { for (SequenceFeature feature : features) { appendFeature(sb, rpos, fr, feature); } } } /** * Appends the feature at rpos to the given buffer * * @param sb * @param rpos * @param minmax * @param feature */ void appendFeature(final StringBuilder sb, int rpos, FeatureRendererModel fr, SequenceFeature feature) { if (feature.isContactFeature()) { if (feature.getBegin() == rpos || feature.getEnd() == rpos) { if (sb.length() > 6) { sb.append("
"); } sb.append(feature.getType()).append(" ").append(feature.getBegin()) .append(":").append(feature.getEnd()); } return; } if (sb.length() > 6) { sb.append("
"); } // TODO: remove this hack to display link only features boolean linkOnly = feature.getValue("linkonly") != null; if (!linkOnly) { sb.append(feature.getType()).append(" "); if (rpos != 0) { // we are marking a positional feature sb.append(feature.begin); } if (feature.begin != feature.end) { sb.append(" ").append(feature.end); } String description = feature.getDescription(); if (description != null && !description.equals(feature.getType())) { description = StringUtils.stripHtmlTags(description); sb.append("; ").append(description); } if (showScore(feature, fr)) { sb.append(" Score=").append(String.valueOf(feature.getScore())); } String status = (String) feature.getValue("status"); if (status != null && status.length() > 0) { sb.append("; (").append(status).append(")"); } /* * add attribute value if coloured by attribute */ if (fr != null) { FeatureColourI fc = fr.getFeatureColours().get(feature.getType()); if (fc != null && fc.isColourByAttribute()) { String[] attName = fc.getAttributeName(); String attVal = feature.getValueAsString(attName); if (attVal != null) { sb.append("; ").append(String.join(":", attName)).append("=") .append(attVal); } } } } } /** * Answers true if score should be shown, else false. Score is shown if it is * not NaN, and the feature type has a non-trivial min-max score range */ boolean showScore(SequenceFeature feature, FeatureRendererModel fr) { if (Float.isNaN(feature.getScore())) { return false; } if (fr == null) { return true; } float[][] minMax = fr.getMinMax().get(feature.getType()); /* * minMax[0] is the [min, max] score range for positional features */ if (minMax == null || minMax[0] == null || minMax[0][0] == minMax[0][1]) { return false; } return true; } /** * Format and appends any hyperlinks for the sequence feature to the string * buffer * * @param sb * @param feature */ void appendLinks(final StringBuffer sb, SequenceFeature feature) { if (feature.links != null) { if (linkImageURL != null) { sb.append(" "); } else { for (String urlstring : feature.links) { try { for (List urllink : createLinksFrom(null, urlstring)) { sb.append("
" + (urllink.get(0).toLowerCase() .equals(urllink.get(1).toLowerCase()) ? urllink .get(0) : (urllink.get(0) + ":" + urllink .get(1))) + "
"); } } catch (Exception x) { System.err.println("problem when creating links from " + urlstring); x.printStackTrace(); } } } } } /** * * @param seq * @param link * @return Collection< List > { List { link target, link * label, dynamic component inserted (if any), url }} */ Collection> createLinksFrom(SequenceI seq, String link) { Map> urlSets = new LinkedHashMap<>(); UrlLink urlLink = new UrlLink(link); if (!urlLink.isValid()) { System.err.println(urlLink.getInvalidMessage()); return null; } urlLink.createLinksFromSeq(seq, urlSets); return urlSets.values(); } public void createSequenceAnnotationReport(final StringBuilder tip, SequenceI sequence, boolean showDbRefs, boolean showNpFeats, FeatureRendererModel fr) { createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats, fr, false); } /** * Builds an html formatted report of sequence details and appends it to the * provided buffer. * * @param sb * buffer to append report to * @param sequence * the sequence the report is for * @param showDbRefs * whether to include database references for the sequence * @param showNpFeats * whether to include non-positional sequence features * @param fr * @param summary * @return */ int createSequenceAnnotationReport(final StringBuilder sb, SequenceI sequence, boolean showDbRefs, boolean showNpFeats, FeatureRendererModel fr, boolean summary) { String tmp; sb.append(""); int maxWidth = 0; if (sequence.getDescription() != null) { tmp = sequence.getDescription(); sb.append("
").append(tmp); maxWidth = Math.max(maxWidth, tmp.length()); } SequenceI ds = sequence; while (ds.getDatasetSequence() != null) { ds = ds.getDatasetSequence(); } if (showDbRefs) { maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary)); } /* * add non-positional features if wanted */ if (showNpFeats) { for (SequenceFeature sf : sequence.getFeatures() .getNonPositionalFeatures()) { int sz = -sb.length(); appendFeature(sb, 0, fr, sf); sz += sb.length(); maxWidth = Math.max(maxWidth, sz); } } sb.append("
"); return maxWidth; } /** * A helper method that appends any DBRefs, returning the maximum line length * added * * @param sb * @param ds * @param summary * @return */ protected int appendDbRefs(final StringBuilder sb, SequenceI ds, boolean summary) { DBRefEntry[] dbrefs = ds.getDBRefs(); if (dbrefs == null) { return 0; } // note this sorts the refs held on the sequence! Arrays.sort(dbrefs, comparator); boolean ellipsis = false; String source = null; String lastSource = null; int countForSource = 0; int sourceCount = 0; boolean moreSources = false; int maxLineLength = 0; int lineLength = 0; for (DBRefEntry ref : dbrefs) { source = ref.getSource(); if (source == null) { // shouldn't happen continue; } boolean sourceChanged = !source.equals(lastSource); if (sourceChanged) { lineLength = 0; countForSource = 0; sourceCount++; } if (sourceCount > MAX_SOURCES && summary) { ellipsis = true; moreSources = true; break; } lastSource = source; countForSource++; if (countForSource == 1 || !summary) { sb.append("
"); } if (countForSource <= MAX_REFS_PER_SOURCE || !summary) { String accessionId = ref.getAccessionId(); lineLength += accessionId.length() + 1; if (countForSource > 1 && summary) { sb.append(", ").append(accessionId); lineLength++; } else { sb.append(source).append(" ").append(accessionId); lineLength += source.length(); } maxLineLength = Math.max(maxLineLength, lineLength); } if (countForSource == MAX_REFS_PER_SOURCE && summary) { sb.append(COMMA).append(ELLIPSIS); ellipsis = true; } } if (moreSources) { sb.append("
").append(source).append(COMMA).append(ELLIPSIS); } if (ellipsis) { sb.append("
("); sb.append(MessageManager.getString("label.output_seq_details")); sb.append(")"); } return maxLineLength; } public void createTooltipAnnotationReport(final StringBuilder tip, SequenceI sequence, boolean showDbRefs, boolean showNpFeats, FeatureRendererModel fr) { int maxWidth = createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats, fr, true); if (maxWidth > 60) { // ? not sure this serves any useful purpose // tip.insert(0, "
"); // tip.append("
"); } } }