/* * The MIT License (MIT) * * Copyright (c) 2007-2015 Broad Institute * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.broad.igv.track; import org.broad.igv.feature.Chromosome; import org.broad.igv.feature.LocusScore; import org.broad.igv.feature.genome.Genome; import org.broad.igv.feature.genome.GenomeManager; import org.broad.igv.renderer.ContinuousColorScale; import org.broad.igv.renderer.DataRange; import org.broad.igv.session.IGVSessionReader; import org.broad.igv.session.SubtlyImportant; import org.broad.igv.ui.color.ColorUtilities; import org.broad.igv.ui.panel.FrameManager; import org.broad.igv.ui.panel.IGVPopupMenu; import org.broad.igv.ui.panel.ReferenceFrame; import org.broad.igv.util.ResourceLocator; import javax.swing.*; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; /** * Track to serve as a container for several tracks rendered on top of each other * * @author jacob * @date 2013-Nov-05 */ @XmlType(factoryMethod = "getNextTrack") public class MergedTracks extends DataTrack implements ScalableTrack { @XmlAttribute protected Class clazz = MergedTracks.class; private Collection<DataTrack> memberTracks; /** * This is a session tag, changing it will break backwards compatibility * with sessions! */ public static final String MEMBER_TRACK_TAG_NAME = "Track"; public MergedTracks(String id, String name, Collection<DataTrack> inputTracks) { super(null, id, name); initTrackList(inputTracks); this.autoScale = this.getAutoScale(); setTrackAlphas(120); } private void initTrackList(Collection<DataTrack> inputTracks) { this.memberTracks = new ArrayList<DataTrack>(inputTracks.size()); for (DataTrack inputTrack : inputTracks) { if (inputTrack instanceof MergedTracks) { this.memberTracks.addAll(((MergedTracks) inputTrack).getMemberTracks()); } else { this.memberTracks.add(inputTrack); } } // Set the group autoscale attribute only of all tracks are in the same group this.removeAttribute(AttributeManager.GROUP_AUTOSCALE); if (memberTracks.size() > 0) { String group = memberTracks.iterator().next().getAttributeValue(AttributeManager.GROUP_AUTOSCALE); if(group != null) { for (Track t : memberTracks) { if(!group.equals(t.getAttributeValue(AttributeManager.GROUP_AUTOSCALE))) return; } this.setAttributeValue(AttributeManager.GROUP_AUTOSCALE, group); } } } @Override public boolean isReadyToPaint(ReferenceFrame frame) { return this.memberTracks.stream().allMatch((t) -> t.isReadyToPaint(frame)); } @Override public synchronized void load(ReferenceFrame referenceFrame) { for(DataTrack t : memberTracks) { if(!t.isReadyToPaint(referenceFrame)) { t.load(referenceFrame); } } } @Override public Collection<ResourceLocator> getResourceLocators() { Collection<ResourceLocator> locators = new ArrayList<ResourceLocator>(memberTracks.size()); for (DataTrack memTrack : memberTracks) { locators.addAll(memTrack.getResourceLocators()); } return locators; } @XmlElement(name = MEMBER_TRACK_TAG_NAME) public Collection<DataTrack> getMemberTracks() { return this.memberTracks; } public void setTrackAlphas(int alpha) { for (Track track : memberTracks) { track.setColor(ColorUtilities.modifyAlpha(track.getColor(), alpha)); track.setAltColor(ColorUtilities.modifyAlpha(track.getAltColor(), alpha)); } } @Override public void render(RenderContext context, Rectangle rect) { context.setMerged(true); for (Track track : memberTracks) { track.render(context, rect); } } @Override public int getHeight() { int height = super.getHeight(); for (Track track : memberTracks) { height = Math.max(height, track.getHeight()); } return height; } @Override public void setHeight(int height) { super.setHeight(height); for (Track track : memberTracks) { track.setHeight(height); } } @Override public void setDataRange(DataRange axisDefinition) { super.setDataRange(axisDefinition); for (Track track : memberTracks) { track.setDataRange(axisDefinition); } } @Override public DataRange getDataRange() { if (this.dataRange == null) { this.dataRange = DataRange.getFromTracks(memberTracks); } return this.dataRange; } @Override public String getValueStringAt(String chr, double position, int mouseX, int mouseY, ReferenceFrame frame) { StringBuilder builder = new StringBuilder(memberTracks.size() + 2); builder.append(getName()); builder.append("<br/>--------------<br/>"); for (Track track : memberTracks) { String curS = track.getValueStringAt(chr, position, mouseX, mouseY, frame); if (curS != null) { builder.append(curS); builder.append("<br/>--------------<br/>"); } } return builder.toString(); } @Override public LoadedDataInterval<List<LocusScore>> getSummaryScores(String chr, int startLocation, int endLocation, int zoom) { return null; } @Override public void setRendererClass(Class rc) { super.setRendererClass(rc); for (Track track : memberTracks) { track.setRendererClass(rc); } } @Override public boolean getAutoScale() { boolean autoScale = true; for (Track track : memberTracks) { autoScale &= track.getAutoScale(); } return autoScale; } @Override public void setAutoScale(boolean autoScale) { for (Track track : memberTracks) { track.setAutoScale(autoScale); } } @Override public void setAttributeValue(String name, String value) { super.setAttributeValue(name, value); if (name.equals(AttributeManager.GROUP_AUTOSCALE)) { for (Track track : memberTracks) { track.setAttributeValue(name, value); } } } @Override public void removeAttribute(String name) { super.removeAttribute(name); if (name.equals(AttributeManager.GROUP_AUTOSCALE)) { for (Track track : memberTracks) { track.removeAttribute(name); } } } @Override public ContinuousColorScale getColorScale() { return super.getColorScale(); } @Override public void setColorScale(ContinuousColorScale colorScale) { super.setColorScale(colorScale); for (Track track : memberTracks) { track.setColorScale(colorScale); } } @Override public void setWindowFunction(WindowFunction type) { super.setWindowFunction(type); for (Track track : memberTracks) { track.setWindowFunction(type); } } @Override public void setShowDataRange(boolean showDataRange) { super.setShowDataRange(showDataRange); for (DataTrack track : memberTracks) { track.setShowDataRange(showDataRange); } } @Override public IGVPopupMenu getPopupMenu(TrackClickEvent te) { IGVPopupMenu menu = new IGVPopupMenu(); final List<Track> selfAsList = Arrays.asList((Track) this); menu.add(TrackMenuUtils.getTrackRenameItem(selfAsList)); //Give users the ability to set the color of each track individually JMenu setPosColorMenu = new JMenu("Change Track Color (Positive Values)"); JMenu setNegColorMenu = new JMenu("Change Track Color (Negative Values)"); for (DataTrack track : memberTracks) { Icon posColorIcon = new ColorIcon(track.getColor()); JMenuItem posItem = new JMenuItem(track.getName(), posColorIcon); posItem.addActionListener(new ChangeTrackColorActionListener(track, ChangeTrackMethod.POSITIVE)); setPosColorMenu.add(posItem); Icon negColorIcon = new ColorIcon(track.getAltColor()); JMenuItem negItem = new JMenuItem(track.getName(), negColorIcon); negItem.addActionListener(new ChangeTrackColorActionListener(track, ChangeTrackMethod.NEGATIVE)); setNegColorMenu.add(negItem); } menu.add(setPosColorMenu); menu.add(setNegColorMenu); menu.add(TrackMenuUtils.getChangeTrackHeightItem(selfAsList)); menu.add(TrackMenuUtils.getChangeFontSizeItem(selfAsList)); menu.addSeparator(); TrackMenuUtils.addDataItems(menu, selfAsList, true); for (Component c : menu.getComponents()) { if (c instanceof JMenuItem) { String text = ((JMenuItem) c).getText(); text = text != null ? text.toLowerCase() : "null"; if (text.contains("heatmap")) { c.setEnabled(false); } } } return menu; } @Override public Range getInViewRange(ReferenceFrame referenceFrame) { List<LocusScore> scores = new ArrayList<LocusScore>(); for (DataTrack track : memberTracks) { scores.addAll(track.getInViewScores(referenceFrame)); } if (scores.size() > 0) { float min = Float.MAX_VALUE; float max = -Float.MAX_VALUE; for (LocusScore score : scores) { float value = score.getScore(); if (!Float.isNaN(value)) { min = Math.min(value, min); max = Math.max(value, max); } } return new Range(min, max); } else { return null; } } private enum ChangeTrackMethod { POSITIVE, NEGATIVE } private static class ChangeTrackColorActionListener implements ActionListener { private Track mTrack; private ChangeTrackMethod method; private ChangeTrackColorActionListener(Track track, ChangeTrackMethod method) { this.mTrack = track; this.method = method; } @Override public void actionPerformed(ActionEvent e) { switch (this.method) { case POSITIVE: TrackMenuUtils.changeTrackColor(Arrays.asList(this.mTrack)); break; case NEGATIVE: TrackMenuUtils.changeAltTrackColor(Arrays.asList(this.mTrack)); break; default: throw new IllegalStateException("Method not understood: " + this.method); } } } /** * A square, solid color Icon */ private static class ColorIcon implements Icon { private Color color; private int iconSize; ColorIcon(Color color) { this(color, 16); } ColorIcon(Color color, int iconSize) { this.color = color; this.iconSize = iconSize; } @Override public void paintIcon(Component c, Graphics g, int x, int y) { Graphics cg = g.create(); cg.setColor(this.color); if (this.iconSize > c.getHeight()) { this.iconSize = c.getHeight(); } cg.fillRect(x, y, this.iconSize, this.iconSize); } @Override public int getIconWidth() { return this.iconSize; } @Override public int getIconHeight() { return this.iconSize; } } @SubtlyImportant private MergedTracks() { super(null, null, null); } @SubtlyImportant private static MergedTracks getNextTrack() { MergedTracks out = (MergedTracks) IGVSessionReader.getNextTrack(); if (out == null) { out = new MergedTracks(); } return out; } }