// $HeadURL$ // $Id$ // // Copyright © 2006, 2010, 2011, 2012 by the President and Fellows of Harvard College. // // Screensaver is an open-source project developed by the ICCB-L and NSRB labs // at Harvard Medical School. This software is distributed under the terms of // the GNU General Public License. package edu.harvard.med.screensaver.ui.screenresults.heatmaps; import java.awt.Color; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import javax.faces.model.DataModel; import javax.faces.model.ListDataModel; import edu.harvard.med.screensaver.analysis.ChainedFilter; import edu.harvard.med.screensaver.analysis.Filter; import edu.harvard.med.screensaver.analysis.heatmaps.ColorFunction; import edu.harvard.med.screensaver.analysis.heatmaps.ControlWellsFilter; import edu.harvard.med.screensaver.analysis.heatmaps.DefaultMultiColorGradient; import edu.harvard.med.screensaver.analysis.heatmaps.EdgeWellsFilter; import edu.harvard.med.screensaver.analysis.heatmaps.HeatMap; import edu.harvard.med.screensaver.db.GenericEntityDAO; import edu.harvard.med.screensaver.db.LibrariesDAO; import edu.harvard.med.screensaver.db.ScreenResultsDAO; import edu.harvard.med.screensaver.model.libraries.Plate; import edu.harvard.med.screensaver.model.libraries.PlateSize; import edu.harvard.med.screensaver.model.libraries.Well; import edu.harvard.med.screensaver.model.libraries.WellKey; import edu.harvard.med.screensaver.model.screenresults.AssayPlate; import edu.harvard.med.screensaver.model.screenresults.DataColumn; import edu.harvard.med.screensaver.model.screenresults.ResultValue; import edu.harvard.med.screensaver.model.screenresults.ScreenResult; import edu.harvard.med.screensaver.ui.arch.util.UISelectManyBean; import edu.harvard.med.screensaver.ui.arch.util.UISelectOneBean; import edu.harvard.med.screensaver.ui.arch.view.AbstractBackingBean; import edu.harvard.med.screensaver.ui.libraries.WellViewer; import edu.harvard.med.screensaver.util.Pair; import org.apache.log4j.Logger; import com.google.common.base.Functions; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; @SuppressWarnings("serial") public class HeatMapViewer extends AbstractBackingBean { // static data members private static final HeatMapCell EMPTY_HEAT_MAP_CELL = new HeatMapCell(); private static final List<NumberFormat> NUMBER_FORMATS = new ArrayList<NumberFormat>(); static { DecimalFormat nf1 = new HackedDecimalFormat("0.0##E0;-0.0##E0"); NUMBER_FORMATS.add(nf1); DecimalFormat nf1m = new HackedDecimalFormat("0.0##E0;(0.0##E0)"); NUMBER_FORMATS.add(nf1m); DecimalFormat nf2 = new HackedDecimalFormat("#,##0.0##;-#,##0.0##"); NUMBER_FORMATS.add(nf2); DecimalFormat nf2m = new HackedDecimalFormat("#,##0.0##;(#,##0.0##)"); NUMBER_FORMATS.add(nf2m); DecimalFormat nf3 = new HackedDecimalFormat("0;-0"); NUMBER_FORMATS.add(nf3); DecimalFormat nf3m = new HackedDecimalFormat("0;(0)"); NUMBER_FORMATS.add(nf3m); } private static final Collection<Filter<Pair<WellKey,ResultValue>>> EXCLUDED_WELL_FILTERS = new ArrayList<Filter<Pair<WellKey,ResultValue>>>(); static { EXCLUDED_WELL_FILTERS.add(new ControlWellsFilter()); EXCLUDED_WELL_FILTERS.add(new EdgeWellsFilter()); } private static final Filter<Pair<WellKey,ResultValue>> IMPLICIT_FILTER = new ExcludedOrNonDataProducingWellFilter(); private static final Double SAMPLE_NUMBER = new Double(-1234.567); private static final NumberFormat DECIMAL_FORMAT = new DecimalFormat("0.0##"); private static final int COLOR_LEGEND_GRADIENT_STEPS = 10; private static final DataModel HEAT_MAP_CELL_LEGEND_MODEL; static { List<LegendItem> legendItems = new ArrayList<LegendItem>(); legendItems.add(new LegendItem("Experimental", HeatMapCell.getStyle(true, false, true, false, Color.GREEN))); legendItems.add(new LegendItem("Control", HeatMapCell.getStyle(true, false, false, true, Color.BLUE))); legendItems.add(new LegendItem("Empty/Excluded", HeatMapCell.getStyle(true, true, false, false, Color.WHITE))); HEAT_MAP_CELL_LEGEND_MODEL = new ListDataModel(legendItems); } private static Logger log = Logger.getLogger(HeatMapViewer.class); // instance data members private GenericEntityDAO _dao; private ScreenResultsDAO _screenResultsDao; private LibrariesDAO _librariesDao; private WellViewer _wellViewer; private ScreenResult _screenResult; private UISelectOneBean<Integer> _plateNumber; private ArrayList<HeatMap> _heatMaps; private boolean _showValues; private List<HeatMapConfiguration> _heatMapConfigurations; private DataModel _heatMapConfigurationsDataModel; private List<DataModel> _heatMapDataModels; private List<DataModel> _heatMapStatistics; private List<DataModel> _heatMapColumnDataModels; private List<String> _heatMapRowLabels; private boolean _updateNeeded = true; // constructors /** * @motivation for CGLIB2 */ protected HeatMapViewer() { } public HeatMapViewer(GenericEntityDAO dao, ScreenResultsDAO screenResultsDao, LibrariesDAO librariesDao, WellViewer wellViewer) { _dao = dao; _screenResultsDao = screenResultsDao; _librariesDao = librariesDao; _wellViewer = wellViewer; resetView(); // basically, initialize collections } public void setScreenResult(ScreenResult screenResult) { _screenResult = screenResult; if (_screenResult != null) { resetView(); Set<Integer> distinctPlateNumbers = Sets.newHashSet(Iterables.transform(_screenResult.getScreen().getAssayPlates(), AssayPlate.ToPlateNumber)); _plateNumber.setDomain(distinctPlateNumbers); addHeatMap(); } } private void resetView() { _plateNumber = new UISelectOneBean<Integer>(); _heatMaps = new ArrayList<HeatMap>(); _heatMapConfigurations = new ArrayList<HeatMapConfiguration>(); _heatMapConfigurationsDataModel = null; _heatMapDataModels = null; _heatMapStatistics = null; _heatMapColumnDataModels = null; _heatMapRowLabels = null; _updateNeeded = true; } public ScreenResult getScreenResult() { return _screenResult; } public UISelectOneBean<Integer> getPlateNumber() { return _plateNumber; } public boolean isShowValues() { return _showValues; } public void setShowValues(boolean showValues) { _showValues = showValues; } public String[] getHeatMapRowLabels() { return getHeatMaps().get(_heatMapConfigurationsDataModel.getRowIndex()).getPlateSize().getRowsLabels().toArray(new String[] {}); } public DataModel getCellTypeLegendDataModel() { return HEAT_MAP_CELL_LEGEND_MODEL; } public List<DataModel> getHeatMapDataModels() { doLazyUpdate(); return _heatMapDataModels; } public List<DataModel> getHeatMapColumnDataModels() { doLazyUpdate(); return _heatMapColumnDataModels; } public DataModel getColorLegendDataModel() { doLazyUpdate(); int heatMapIndex = _heatMapConfigurationsDataModel.getRowIndex(); HeatMapConfiguration heatMapConfig = _heatMapConfigurations.get(heatMapIndex); ColorFunction colorFn = _heatMaps.get(heatMapIndex).getColorFunction(); NumberFormat format = heatMapConfig.getNumericFormat().getSelection(); double min = _heatMaps.get(heatMapIndex).getMin(); double max = _heatMaps.get(heatMapIndex).getMax(); double range = max - min; List<Pair<String,String>> steps = new ArrayList<Pair<String,String>>(COLOR_LEGEND_GRADIENT_STEPS); for (int i = 0; i <= COLOR_LEGEND_GRADIENT_STEPS; ++i) { double stepValue = min + range * ((double) i / (double) COLOR_LEGEND_GRADIENT_STEPS); Color color = colorFn.getColor(stepValue); String cssStyle = String.format("background-color: #%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue()); steps.add(new Pair<String,String>(cssStyle, format.format(stepValue))); } return new ListDataModel(steps); } public List<HeatMap> getHeatMaps() { doLazyUpdate(); return _heatMaps; } public List<DataModel> getHeatMapStatisticsDataModels() { doLazyUpdate(); return _heatMapStatistics; } public DataModel getHeatMapConfigurationsDataModel() { doLazyUpdate(); return _heatMapConfigurationsDataModel; } @SuppressWarnings("unchecked") public HeatMapCell getHeatMapCell() { doLazyUpdate(); int heatMapIndex = _heatMapConfigurationsDataModel.getRowIndex(); DataModel heatMapDataModel = (DataModel) _heatMapDataModels.get(heatMapIndex); DataModel heatMapColumnDataModel = _heatMapColumnDataModels.get(heatMapIndex); if (heatMapColumnDataModel.isRowAvailable()) { Integer columnIndex = (Integer) heatMapColumnDataModel.getRowData(); // getRowData() is really getColumnData() List<HeatMapCell> row = (List<HeatMapCell>) heatMapDataModel.getRowData(); return row.get(columnIndex); } return EMPTY_HEAT_MAP_CELL; } public String getHeatMapTitle() { doLazyUpdate(); int heatMapIndex = _heatMapConfigurationsDataModel.getRowIndex(); HeatMapConfiguration heatMapConfiguration = _heatMapConfigurations.get(heatMapIndex); StringBuilder title = new StringBuilder(); title.append(heatMapConfiguration.getDataColumns().getSelection().getName()); title.append(": "); title.append(heatMapConfiguration.getScoringType().getSelection().toString()); List<Filter<Pair<WellKey,ResultValue>>> filterSelections = heatMapConfiguration.getExcludedWellFilters().getSelections(); if (filterSelections != null && filterSelections.size() > 0) { title.append(" (exclude "); boolean first = true; for (Filter filter : filterSelections) { if (first) { first = false; } else { title.append(", "); } title.append(filter.toString().toLowerCase()); } title.append(")"); } return title.toString(); } // JSF application methods @SuppressWarnings("unchecked") public String update() { _updateNeeded = true; return REDISPLAY_PAGE_ACTION_RESULT; } private void doLazyUpdate() { if (_updateNeeded) { _updateNeeded = false; _heatMapDataModels = new ArrayList<DataModel>(); _heatMapColumnDataModels = new ArrayList<DataModel>(); _heatMaps = new ArrayList<HeatMap>(); _heatMapStatistics = new ArrayList<DataModel>(); PlateSize plateSize = _librariesDao.findLibraryWithPlate(_plateNumber.getSelection()).getPlateSize(); for (HeatMapConfiguration heatMapConfig : _heatMapConfigurations) { if (heatMapConfig.getDataColumns().getSelection() != null && _plateNumber.getSelection() != null) { Map<WellKey,ResultValue> resultValues = _screenResultsDao.findResultValuesByPlate(_plateNumber.getSelection(), heatMapConfig.getDataColumns().getSelection()); HeatMap heatMap = new HeatMap(_plateNumber.getSelection(), plateSize, resultValues, new ChainedFilter<Pair<WellKey,ResultValue>>( IMPLICIT_FILTER, new ChainedFilter<Pair<WellKey,ResultValue>>(heatMapConfig.getExcludedWellFilters().getSelections())), heatMapConfig.getScoringType().getSelection().getFunction(), new DefaultMultiColorGradient()); NumberFormat format = heatMapConfig.getNumericFormat().getSelection(); List<List<HeatMapCell>> rows = new ArrayList<List<HeatMapCell>>(); for (int row = 0; row < plateSize.getRows(); row++) { List<HeatMapCell> rowData = new ArrayList<HeatMapCell>(); for (int column = 0; column < plateSize.getColumns(); column++) { rowData.add(new HeatMapCell(heatMap.getResultValue(row, column), heatMap.getWellKey(row, column), heatMap.getScoredValue(row, column), heatMap.getColor(row, column), isShowValues(), format)); } rows.add(rowData); } _heatMapDataModels.add(new ListDataModel(rows)); List<Integer> columnIndexes = new ArrayList<Integer>(); for (int column = 0; column < plateSize.getColumns(); column++) { columnIndexes.add(column); } _heatMapColumnDataModels.add(new ListDataModel(columnIndexes)); _heatMaps.add(heatMap); if (format == null) { format = NUMBER_FORMATS.get(1); } List<FormattedStatistic> heatMapStatistics = new ArrayList<FormattedStatistic>(); heatMapStatistics.add(new FormattedStatistic("N", heatMap.getCount())); heatMapStatistics.add(new FormattedStatistic("Min", heatMap.getMin(), format)); heatMapStatistics.add(new FormattedStatistic("Max", heatMap.getMax(), format)); heatMapStatistics.add(new FormattedStatistic("Mean", heatMap.getMean(), format)); heatMapStatistics.add(new FormattedStatistic("Median", heatMap.getMedian(), format)); heatMapStatistics.add(new FormattedStatistic("Stdev", heatMap.getStandardDeviation(), format)); heatMapStatistics.add(new FormattedStatistic("Var", heatMap.getVariance(), format)); heatMapStatistics.add(new FormattedStatistic("Skewness", heatMap.getSkewness(), DECIMAL_FORMAT)); _heatMapStatistics.add(new ListDataModel(heatMapStatistics)); } } _heatMapConfigurationsDataModel = new ListDataModel(_heatMapConfigurations); } } // TODO: set initial values to previous HeatMapConfig public String addHeatMap() { if (_screenResult.getNumericDataColumns().size() == 0) { return REDISPLAY_PAGE_ACTION_RESULT; } HeatMapConfiguration heatMapConfiguration = new HeatMapConfiguration(); heatMapConfiguration.setDataColumns(new UISelectOneBean<DataColumn>(_screenResult.getNumericDataColumns()) { protected String makeLabel(DataColumn t) { return t.getName(); } }); heatMapConfiguration.setScoringType(new UISelectOneBean<ScoringType>(Arrays.asList(ScoringType.values()))); heatMapConfiguration.setNumericFormat(new UISelectOneBean<NumberFormat>(NUMBER_FORMATS) { protected String makeLabel(NumberFormat t) { return t.format(SAMPLE_NUMBER); } }); heatMapConfiguration.setExcludedWellFilters(new UISelectManyBean<Filter<Pair<WellKey,ResultValue>>>(EXCLUDED_WELL_FILTERS)); _heatMapConfigurations.add(heatMapConfiguration); // set default values heatMapConfiguration.getExcludedWellFilters().setValue(Arrays.asList(new String[] { (String) heatMapConfiguration.getExcludedWellFilters().getSelectItems().get(0).getValue() })); return update(); } public String deleteHeatMap() { int heatMapIndexToDelete = ((Integer) getHttpServletRequest().getAttribute("heatMapIndex")).intValue(); _heatMapConfigurations.remove(heatMapIndexToDelete); _heatMapColumnDataModels.remove(heatMapIndexToDelete); _heatMapDataModels.remove(heatMapIndexToDelete); _heatMaps.remove(heatMapIndexToDelete); _heatMapStatistics.remove(heatMapIndexToDelete); _heatMapConfigurationsDataModel = new ListDataModel(_heatMapConfigurations); return REDISPLAY_PAGE_ACTION_RESULT; } public String viewWell() { HeatMapCell heatMapCell = getHeatMapCell(); Well well = _librariesDao.findWell(heatMapCell.getWellKey()); return _wellViewer.viewEntity(well); } public String nextPlate() { return gotoPlate(_plateNumber.getSelectionIndex() + 1); } public String previousPlate() { return gotoPlate(_plateNumber.getSelectionIndex() - 1); } // private methods private String gotoPlate(int plateIndex) { plateIndex = Math.max(0, Math.min(_plateNumber.getSelectItems().size() - 1, plateIndex)); _plateNumber.setSelectionIndex(plateIndex); return update(); } }