Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/web/StaticLinkExamples.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
<body>

<h2>IGV desktop links</h2>
<p>
<a href = "http://localhost:60151/load?file=https://ftp.1000genomes.ebi.ac.uk/vol1/ftp/data_collections/illumina_platinum_pedigree/data/CEU/NA12878/alignment/NA12878.alt_bwamem_GRCh38DH.20150706.CEU.illumina_platinum_ped.cram">Load Cram (hg38)</a>
</p>
<p>
<a href = "http://localhost:60151/goto?locus=MYC">Goto MYC</a>
</p>
<p>
<a href="http://localhost:60151/load?genome=hg38&locus=chr8:127733434-127744951&file=https://hgdownload.soe.ucsc.edu/gbdb/hg38/knownGene.bb,https://hgdownload.soe.ucsc.edu/gbdb/hg38/ncbiRefSeq/ncbiRefSeqOther.bb&name=Known genes, Refseq other">
Load multiple files, new session, specify genome and locaus</a>
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/igv/batch/CommandListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class CommandListener implements Runnable {
private static final String NO_CACHE = "Cache-Control: no-cache, no-store";
private static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin: *";
private static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers: access-control-allow-origin";
private static final String ACCESS_CONTROL_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network: true";


private int port = -1;
Expand Down Expand Up @@ -299,6 +300,8 @@ private void sendHTTPResponse(PrintWriter out, String result, String contentType
out.print(CRLF);
out.print(ACCESS_CONTROL_ALLOW_ORIGIN);
out.print(CRLF);
out.print(ACCESS_CONTROL_PRIVATE_NETWORK);
out.print(CRLF);
if (result != null) {
out.print("Content-Type: " + contentType);
out.print(CRLF);
Expand Down Expand Up @@ -328,7 +331,10 @@ private void sendHTTPOptionsResponse(PrintWriter out) {
out.print(CRLF);
out.print(ACCESS_CONTROL_ALLOW_HEADERS);
out.print(CRLF);
out.println("Access-Control-Allow-Methods: HEAD, GET, OPTIONS");
out.print(ACCESS_CONTROL_PRIVATE_NETWORK);
out.print(CRLF);
out.print("Access-Control-Allow-Methods: HEAD, GET, OPTIONS");
out.print(CRLF);
out.print(CRLF);

out.close();
Expand Down
29 changes: 13 additions & 16 deletions src/main/java/org/igv/bedpe/InteractionTrack.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import org.igv.Globals;
import org.igv.event.IGVEvent;
import org.igv.event.IGVEventObserver;
import org.igv.jbrowse.CircularViewUtilities;
import org.igv.circview.CircularViewUtilities;
import org.igv.logging.LogManager;
import org.igv.logging.Logger;
import org.igv.prefs.Constants;
Expand Down Expand Up @@ -414,21 +414,18 @@ public List<Component> getPopupMenuItems(TrackClickEvent te) {
items.add(TrackMenuUtils.getChangeFeatureWindow(Collections.singletonList(this)));


// Experimental JBrowse.
if (PreferencesManager.getPreferences().getAsBoolean(Constants.CIRC_VIEW_ENABLED) &&
CircularViewUtilities.ping()) {
items.add(new JPopupMenu.Separator());
JMenuItem circViewItem = new JMenuItem("Add Features to Circular View");
circViewItem.addActionListener(e -> {
List<ReferenceFrame> frames = te.getFrame() != null ?
Collections.singletonList(te.getFrame()) :
FrameManager.getFrames();
List<? extends BedPE> visibleFeatures = getVisibleFeatures(frames);
CircularViewUtilities.sendBedpeToJBrowse(visibleFeatures, InteractionTrack.this.getName(), InteractionTrack.this.getColor());
});
items.add(circViewItem);
items.add(new JPopupMenu.Separator());
}
// Circular view
items.add(new JPopupMenu.Separator());
JMenuItem circViewItem = new JMenuItem("Add Features to Circular View");
circViewItem.addActionListener(e -> {
List<ReferenceFrame> frames = te.getFrame() != null ?
Collections.singletonList(te.getFrame()) :
FrameManager.getFrames();
List<? extends BedPE> visibleFeatures = getVisibleFeatures(frames);
CircularViewUtilities.addBedPE(visibleFeatures, InteractionTrack.this.getName(), InteractionTrack.this.getColor());
});
items.add(circViewItem);
items.add(new JPopupMenu.Separator());
}

return items;
Expand Down
246 changes: 246 additions & 0 deletions src/main/java/org/igv/circview/CircularViewUtilities.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package org.igv.circview;

import htsjdk.samtools.SAMTag;
import htsjdk.tribble.Feature;
import org.igv.bedpe.BedPE;
import org.igv.circview.model.Assembly;
import org.igv.circview.model.Chord;
import org.igv.circview.model.Chromosome;
import org.igv.circview.model.Mate;
import org.igv.circview.ui.CircularView;
import org.igv.circview.ui.CircularViewConfig;
import org.igv.circview.ui.CircularViewPanel;
import org.igv.circview.util.ChrColors;
import org.igv.circview.util.ColorUtils;
import org.igv.feature.genome.Genome;
import org.igv.feature.genome.GenomeManager;
import org.igv.sam.Alignment;
import org.igv.ui.IGV;
import org.igv.util.Downsampler;
import org.igv.variant.Variant;
import org.igv.variant.vcf.MateVariant;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* Bridge between IGV and the in-process {@link CircularView} widget.
*
* <p>This replaces the JBrowse/Electron based {@code org.igv.jbrowse.CircularViewUtilities},
* which spoke to an external app over a socket. The view is now a singleton Swing
* {@link JFrame} owned by this class; tracks add chords through the static
* {@code add*} methods, and the window is created and shown lazily on the first add.
*
* <p>Chord features are built from IGV model objects via the {@code Chord.from*}
* factory methods (the same conversions the old code used), then downsampled to
* {@link #MAX_CHORDS} to keep rendering responsive.
*/
public class CircularViewUtilities {

/**
* Maximum number of chords to render in a single set.
*/
static int MAX_CHORDS = 10000;

/**
* Flanking bases added on each side when navigating to a clicked chord's regions.
*/
private static final int CLICK_FLANKING = 2000;

// Singleton view + window, lazily created on the EDT.
private static CircularView view;
private static CircularViewPanel panel;
private static JFrame frame;

/**
* Id of the genome currently set as the assembly, to avoid needless resets.
*/
private static String currentGenomeId;

private CircularViewUtilities() {
}

// ---- Public API ---------------------------------------------------------

/**
* True if the circular view window currently exists and is showing.
*/
public static boolean isOpen() {
return frame != null && frame.isVisible();
}

public static void addBedPE(List<? extends BedPE> features, String trackName, Color color) {
List<Chord> chords = new ArrayList<>(features.size());
for (BedPE f : features) {
chords.add(Chord.fromBedPE(f));
}
addChords(chords, trackName, color, 0.5f);
}

public static void addAlignments(List<Alignment> alignments, String trackName, Color color) {
List<Chord> chords = new ArrayList<>();
for (Alignment a : alignments) {
if (a.isPaired() && a.getMate().isMapped()) {
chords.add(Chord.fromPEAlignment(a));
}
if (a.getAttribute(SAMTag.SA.name()) != null) {
chords.addAll(Chord.fromSAString(a));
}
}
addChords(chords, trackName, color, 0.1f);
}

public static void addVariants(List<Feature> variants, String trackName, Color color) {
List<Chord> chords = new ArrayList<>(variants.size());
for (Feature f : variants) {
if (f instanceof Variant) {
Variant v = f instanceof MateVariant ? ((MateVariant) f).mate : (Variant) f;
chords.add(Chord.fromVariant(v));
}
}
addChords(chords, trackName, color, 0.5f);
}

/**
* Add a set of chords to the view, opening (and creating) the window if needed.
*
* @param chords the chord features
* @param trackName name for this chord set / track row
* @param color base color; {@code alpha} is applied to it
* @param alpha opacity fraction in [0, 1]
*/
public static void addChords(List<Chord> chords, String trackName, Color color, float alpha) {
Chord[] arr = chords.toArray(new Chord[0]);
if (arr.length > MAX_CHORDS) {
arr = new Downsampler<Chord>().sample(arr, MAX_CHORDS);
}
final List<Chord> sampled = new ArrayList<>(Arrays.asList(arr));
final Color c = ColorUtils.setAlpha(color, alpha);
runOnEdt(() -> {
CircularView v = getInstance();
ensureAssembly(v);
v.addChords(sampled, trackName, c);
open();
});
}

/**
* Update the assembly to the given genome. No-op unless the view already
* exists; on a genome switch the previously added chords no longer apply, so
* this resets the view (matching {@link CircularView#setAssembly}).
*/
public static void changeGenome(Genome genome) {
if (view == null || genome == null) {
return;
}
runOnEdt(() -> {
view.setAssembly(toAssembly(genome));
currentGenomeId = genome.getId();
});
}

public static void clearChords() {
if (view != null) {
runOnEdt(view::clearChords);
}
}

// ---- Internals ----------------------------------------------------------

/**
* Lazily create the singleton view and its window. Must run on the EDT.
*/
private static CircularView getInstance() {
if (view == null) {
CircularViewConfig config = new CircularViewConfig();
config.onChordClick = CircularViewUtilities::onChordClick;

view = new CircularView(config);
panel = new CircularViewPanel(view);

frame = new JFrame("Circular View");
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(IGV.hasInstance() ? IGV.getInstance().getMainFrame() : null);
}
return view;
}

/**
* Must run on the event thread
*/
public static void open() {
if (frame == null) {
getInstance();
}
if (!frame.isVisible()) {
frame.setVisible(true);
}
frame.toFront();

}

/**
* Set the assembly from the current genome if it differs from what's shown.
*/
private static void ensureAssembly(CircularView v) {
Genome genome = GenomeManager.getInstance().getCurrentGenome();
if (genome != null && !genome.getId().equals(currentGenomeId)) {
v.setAssembly(toAssembly(genome));
currentGenomeId = genome.getId();
}
}

/**
* Convert an IGV genome to a circular-view {@link Assembly}. Only the "long"
* (whole-genome) chromosomes are drawn; names are shortened so they match the
* shortened {@code refName}s on the chords.
*/
private static Assembly toAssembly(Genome genome) {
List<Chromosome> chromosomes = new ArrayList<>();
for (String chr : genome.getLongChromosomeNames()) {
org.igv.feature.Chromosome c = genome.getChromosome(chr);
String shortName = Chromosome.shortChrName(chr);
chromosomes.add(new Chromosome(shortName, c.getLength(), ChrColors.getChrColor(shortName)));
}
return new Assembly(genome.getDisplayName(), genome.getId(), chromosomes);
}

/**
* Navigate IGV to a clicked chord's two regions (with flanking), shown
* side-by-side. Port of the onChordClick callback in circularView.js.
*/
private static void onChordClick(Chord feature) {
if (!IGV.hasInstance()) {
return;
}
Mate mate = feature.getMate();
if (mate == null) {
return;
}
Genome genome = GenomeManager.getInstance().getCurrentGenome();
String locus1 = locusString(genome, feature.getRefName(), feature.getStart(), feature.getEnd());
String locus2 = locusString(genome, mate.getRefName(), mate.getStart(), mate.getEnd());
IGV.getInstance().goToLocus(locus1 + " " + locus2);
}

private static String locusString(Genome genome, String refName, long start, long end) {
String chr = (genome != null) ? genome.getCanonicalChrName(refName) : refName;
long s = Math.max(0, start - CLICK_FLANKING);
long e = end + CLICK_FLANKING;
return chr + ":" + s + "-" + e;
}

private static void runOnEdt(Runnable r) {
if (SwingUtilities.isEventDispatchThread()) {
r.run();
} else {
SwingUtilities.invokeLater(r);
}
}
}
Loading
Loading