Skip to content

Commit

Permalink
Issue 50925: Refinements to peptide outlier heat map (#943)
Browse files Browse the repository at this point in the history
  • Loading branch information
ankurjuneja authored Aug 23, 2024
1 parent e9bc4a2 commit 796a996
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 122 deletions.
2 changes: 2 additions & 0 deletions api-src/org/labkey/api/targetedms/TargetedMSService.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.labkey.api.view.ViewBackgroundInfo;

import java.nio.file.Path;
import java.util.Date;
import java.util.List;

/**
Expand Down Expand Up @@ -77,6 +78,7 @@ enum FolderType
void registerTargetedMSFolderTypeListener(TargetedMSFolderTypeListener listener);
List<TargetedMSFolderTypeListener> getTargetedMSFolderTypeListeners();
List<SampleFileInfo> getSampleFiles(Container container, User user, Integer sampleFileLimit);
List<SampleFileInfo> getSampleFiles(Container container, User user, Date startDate, Date endDate);
TargetedMSService.FolderType getFolderType(Container container);

ExperimentRunType getExperimentRunType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,26 @@
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.legend {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 10px;
border: 1px lightgray solid;
}

.legend-square {
width: 20px;
height: 20px;
margin-right: 2px;
width: 3em;
height: 1.5em;
}
.legend-label {
width: 150px;
text-align: center;

.legend-label {
width: 10em;
padding-left: 1em;
padding-right: 1em;
}
.arrow-left::before, .arrow-right::after {
content: '';
display: inline-block;
width: 0;
height: 0;
border-style: solid;
}
.arrow-left::before {
border-width: 15px 20px 15px 0;
border-color: transparent black transparent transparent;
}
.arrow-right::after {
border-width: 15px 0 15px 20px;
border-color: transparent black transparent black;

.heatmap-legend-element {
width: 3em;
height: 1.5em;
}

.heatmap-table {
Expand All @@ -52,6 +40,18 @@
padding: 8px;
text-align: center;
}

.peptide-cell {
white-space: nowrap; /* Ensure the peptide sequence stays on one line */
}
.peptide-cell span {
display: block; /* Charge and mz will be on its own line */
}

.table-container {
margin-top: 1em;
}

</style>

<label for="date-range">Date Range:</label>
Expand All @@ -67,21 +67,22 @@
</select>

<div id="date-picker-container" class="date-picker-container">
<label for="start-date">Start Date:</label>
<input type="date" id="start-date">
<label for="end-date">End Date:</label>
<input type="date" id="end-date">
<label for="ps-start-date">Start Date:</label>
<input type="date" id="ps-start-date">
<label for="ps-end-date">End Date:</label>
<input type="date" id="ps-end-date">
</div>

<div class="legend-container">
<div class="legend-label arrow-left">&nbsp; &nbsp;No Outliers</div>
<div class="legend-label">&nbsp; &nbsp;No outliers</div>
<div id="legend" class="legend"></div>
<div class="legend-label arrow-right">Most Outliers &nbsp; &nbsp;</div>
<div class="legend-label" id="max-outliers"></div>
<div>Total Replicates : </div>
<div id="total-replicates"></div>
</div>

<div id="table-container">
<span id="fom-loading">Loading...<i class="fa fa-spinner fa-pulse"></i></span>
<div id="table-container" class="table-container">

</div>
<script type="text/javascript" nonce="<%=scriptNonce%>">
Expand All @@ -92,16 +93,16 @@

function generateLegend() {
const legendContainer = document.getElementById('legend');
const numSquares = 20;
const minColor = [255, 255, 255];
const maxColor = [255, 0, 0];
const numSquares = 6;
let alpha = 1.0;

for (let i = 0; i < numSquares; i++) {
const ratio = i / (numSquares - 1);
const red = Math.round(minColor[0] + ratio * (maxColor[0] - minColor[0]));
const green = Math.round(minColor[1] + ratio * (maxColor[1] - minColor[1]));
const blue = Math.round(minColor[2] + ratio * (maxColor[2] - minColor[2]));
const color = `rgb(${red}, ${green}, ${blue})`;
const red = 255;
const green = 0;
const blue = 0;
alpha = 1.0 - (0.2 * (numSquares - i - 1));
const color = `rgb(${red}, ${green}, ${blue}, ${alpha})`;

const square = document.createElement('div');
square.id = 'legend-square-' + i;
Expand Down Expand Up @@ -158,6 +159,7 @@
}

function generateTable(data) {
$('#fom-loading').hide();
const tableContainer = document.getElementById('table-container');
let tableHTML = '<table id="heatmap-table" class="heatmap-table">';
tableHTML += '<thead><tr><th></th>';
Expand All @@ -171,7 +173,20 @@

let totalOutliersByMetric = {};
for (let i = 0; i < data.peptideOutliers.length; i++) {
tableHTML += '<tr><td>' + LABKEY.Utils.encodeHtml(data.peptideOutliers[i].peptide) + '</td>';
let parts = data.peptideOutliers[i].peptide.split(' ');
let sequence = parts[0];
let chargeMz = parts.slice(1).join(' ');

tableHTML += '<tr>';
tableHTML += '<td class="peptide-cell">';
if (data.peptideOutliers[i].precursorId > 0) {
tableHTML += '<span>' + LABKEY.Utils.encodeHtml(sequence) + '</span>';
tableHTML += '<span>' + LABKEY.Utils.encodeHtml(chargeMz) + '</span>';
}
else {
tableHTML += LABKEY.Utils.encodeHtml(data.peptideOutliers[i].peptide);
}
tableHTML += '</td>';

for (let j = 0; j < data.metrics.length; j++) {
let metric = data.metrics[j];
Expand Down Expand Up @@ -221,6 +236,7 @@
}

function getData() {
$('#fom-loading').show();
LABKEY.Ajax.request({
url: LABKEY.ActionURL.buildURL('targetedms', 'GetPeptideOutliers.api'),
params: {
Expand Down Expand Up @@ -251,14 +267,14 @@
}

const min = Math.min(...values);
const max = Math.max(...values);

const max = Math.max(...values, 0);
document.getElementById('max-outliers').innerHTML = '&nbsp;' + max + ' outliers';
// Apply colors based on values, excluding the last row (totals row)
for (let i = 1; i < table.rows.length - 1; i++) { // skip header
const row = table.rows[i];
for (let j = 1; j < row.cells.length - 1; j++) { // skip the first and last column
const value = parseFloat(row.cells[j].textContent);
if (!isNaN(value)) {
if (!isNaN(value) && value > 0) {
const bgColor = getColor(value, min, max);
const textColor = getContrastColor(bgColor);
row.cells[j].style.backgroundColor = bgColor;
Expand Down Expand Up @@ -289,13 +305,14 @@
}

function customDateRange() {
if (document.getElementById("start-date").value) {
startDate = formatDate(document.getElementById("start-date").value);
$('#fom-loading').show();
if (document.getElementById("ps-start-date").value) {
startDate = formatDate(document.getElementById("ps-start-date").value);
}
if (document.getElementById("end-date").value) {
endDate = formatDate(document.getElementById("end-date").value);
if (document.getElementById("ps-end-date").value) {
endDate = formatDate(document.getElementById("ps-end-date").value);
}
if (document.getElementById("start-date").value && document.getElementById("end-date").value) {
if (document.getElementById("ps-start-date").value && document.getElementById("ps-end-date").value) {
persistDateRange(-1);
getData();
}
Expand Down Expand Up @@ -343,8 +360,8 @@
const datePickerContainer = document.getElementById('date-picker-container');
document.getElementById('date-range').value = '-1';
datePickerContainer.style.display = 'block';
document.getElementById('start-date').value = formatDate(startDate);
document.getElementById('end-date').value = formatDate(endDate);
document.getElementById('ps-start-date').value = formatDate(startDate);
document.getElementById('ps-end-date').value = formatDate(endDate);
}
else {
document.getElementById('date-range').value = dateRangeOffset;
Expand All @@ -365,8 +382,8 @@
document.addEventListener('DOMContentLoaded', function () {
const dateRangeSelect = document.getElementById('date-range');
const datePickerContainer = document.getElementById('date-picker-container');
const startDateInput = document.getElementById('start-date');
const endDateInput = document.getElementById('end-date');
const startDateInput = document.getElementById('ps-start-date');
const endDateInput = document.getElementById('ps-end-date');

dateRangeSelect.addEventListener('change', function () {
if (dateRangeSelect.value === '-1') { // Custom range selected
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<view xmlns="http://labkey.org/data/xml/view" title="Outlier Heatmap Summary">
<view xmlns="http://labkey.org/data/xml/view" title="Peptide/Molecule Summary">
<dependencies>
<dependency path="internal/jQuery"/>
<dependency path="Ext4"/>
Expand Down
12 changes: 7 additions & 5 deletions src/org/labkey/targetedms/TargetedMSController.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
import org.labkey.api.files.view.FilesWebPart;
import org.labkey.api.module.DefaultFolderType;
import org.labkey.api.module.Module;
import org.labkey.api.module.ModuleHtmlView;
import org.labkey.api.module.ModuleLoader;
import org.labkey.api.module.ModuleProperty;
import org.labkey.api.pipeline.LocalDirectory;
Expand Down Expand Up @@ -252,7 +253,7 @@
import org.labkey.targetedms.view.PeptidePrecursorChromatogramsView;
import org.labkey.targetedms.view.PeptidePrecursorsView;
import org.labkey.targetedms.view.PeptideTransitionsView;
import org.labkey.targetedms.view.QCSummaryWebPart;
import org.labkey.targetedms.view.ReplicateSummaryWebPart;
import org.labkey.targetedms.view.SmallMoleculePrecursorsView;
import org.labkey.targetedms.view.SmallMoleculeTransitionsView;
import org.labkey.targetedms.view.TargetedMsRunListView;
Expand Down Expand Up @@ -1193,14 +1194,15 @@ public ModelAndView getView(Object o, BindException errors)
calendarView.addClientDependency(ClientDependency.fromPath("TargetedMS/js/misc.js"));
calendarView.setTitle("Utilization Calendar");
calendarView.setFrame(WebPartView.FrameType.PORTAL);
QCSummaryWebPart summaryView = new QCSummaryWebPart(getViewContext(), null);
return new VBox(calendarView, summaryView);
ReplicateSummaryWebPart summaryView = new ReplicateSummaryWebPart(getViewContext(), null);
ModuleHtmlView replicateSummary = ModuleHtmlView.get(ModuleLoader.getInstance().getModule(TargetedMSModule.class), "peptideSummary");
return new VBox(calendarView, replicateSummary, summaryView);
}

@Override
public void addNavTrail(NavTree root)
{
root.addChild("QC Summary History");
root.addChild("Replicate Summary History");
}
}

Expand Down Expand Up @@ -1357,7 +1359,7 @@ public Object execute(PeptideOutliersForm form, BindException errors)
Map<GuideSetKey, GuideSetStats> stats = outlierGenerator.getAllProcessedMetricGuideSets(rawMetricDataSets, guideSets.stream().collect(Collectors.toMap(GuideSet::getRowId, Function.identity())));
response.put("peptideOutliers", outlierGenerator.getPeptideOutliers(rawMetricDataSets, stats).stream().map(PeptideOutliers::toJSON).collect(Collectors.toList()));
response.put("metrics", enabledQCMetricConfigurations.stream().map(QCMetricConfiguration::getName).collect(Collectors.toList()));
response.put("replicatesCount", TargetedMSService.get().getSampleFiles(getContainer(), getUser(), null).size());
response.put("replicatesCount", TargetedMSService.get().getSampleFiles(getContainer(), getUser(), form.getStartDate(), form.getEndDate()).size());
return response;
}
}
Expand Down
36 changes: 21 additions & 15 deletions src/org/labkey/targetedms/TargetedMSModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
import org.labkey.targetedms.view.CrossLinkedPeptideInfo;
import org.labkey.targetedms.view.FiguresOfMeritView;
import org.labkey.targetedms.view.LibraryQueryViewWebPart;
import org.labkey.targetedms.view.QCSummaryWebPart;
import org.labkey.targetedms.view.ReplicateSummaryWebPart;
import org.labkey.targetedms.view.TargetedMSRunsWebPartView;
import org.labkey.targetedms.view.TransitionPeptideSearchViewProvider;
import org.labkey.targetedms.view.TransitionProteinSearchViewProvider;
Expand Down Expand Up @@ -116,12 +116,12 @@ public class TargetedMSModule extends SpringModule implements ProteomicsModule
public static final String TARGETED_MS_RUNS_WEBPART_NAME = "Targeted MS Runs";
public static final String TARGETED_MS_PROTEIN_SEARCH = "Targeted MS Protein Search";
public static final String TARGETED_MS_PEPTIDE_SEARCH = "Targeted MS Peptide Search";
public static final String TARGETED_MS_QC_SUMMARY = "Targeted MS QC Summary";
public static final String TARGETED_MS_REPLICATE_SUMMARY = "Targeted MS Replicate Summary";
public static final String TARGETED_MS_QC_PLOTS = "Targeted MS QC Plots";
public static final String TARGETED_MS_PARETO_PLOT = "Targeted MS Pareto Plot";
public static final String TARGETED_MS_CALIBRATION_CURVE = "Targeted MS Calibration Curve";
public static final String TARGETED_MS_FIGURES_OF_MERIT = "Targeted MS Figures of Merit";
public static final String TARGETED_MS_OUTLIER_HEATMAP_SUMMARY_VIEW = "Outlier Heatmap Summary View";
public static final String TARGETED_MS_PEPTIDE_MOLECULE_SUMMARY = "Peptide/Molecule Summary";

public static final String PEPTIDE_TAB_NAME = "Peptides";
public static final String PROTEIN_TAB_NAME = "Proteins";
Expand All @@ -148,7 +148,7 @@ public class TargetedMSModule extends SpringModule implements ProteomicsModule
TARGETED_MS_PEPTIDE_GROUP_VIEW
};

public static final String[] QC_FOLDER_WEB_PARTS = new String[] {TARGETED_MS_QC_SUMMARY, TARGETED_MS_QC_PLOTS};
public static final String[] QC_FOLDER_WEB_PARTS = new String[] {TARGETED_MS_REPLICATE_SUMMARY, TARGETED_MS_QC_PLOTS};

public final ModuleProperty FOLDER_TYPE_PROPERTY;
public final ModuleProperty SKYLINE_AUDIT_LEVEL_PROPERTY;
Expand Down Expand Up @@ -186,7 +186,7 @@ public TargetedMSModule()
addModuleProperty(SKYLINE_AUDIT_LEVEL_PROPERTY);
//------------------------rr

// setup the QC Summary webpart AutoQCPing timeout
// setup the replicate summary webpart AutoQCPing timeout
AUTO_QC_PING_TIMEOUT_PROPERTY = new ModuleProperty(this, "TargetedMS AutoQCPing Timeout");
AUTO_QC_PING_TIMEOUT_PROPERTY.setDescription("The number of minutes before the most recent AutoQCPing indicator is considered stale.");
AUTO_QC_PING_TIMEOUT_PROPERTY.setDefaultValue("15");
Expand Down Expand Up @@ -392,12 +392,18 @@ public WebPartView<?> getWebPartView(@NotNull ViewContext portalCtx, @NotNull Po
}
},

new BaseWebPartFactory(TARGETED_MS_QC_SUMMARY)
new BaseWebPartFactory(TARGETED_MS_REPLICATE_SUMMARY)
{
@Override
public WebPartView<?> getWebPartView(@NotNull ViewContext portalCtx, @NotNull Portal.WebPart webPart)
{
return new QCSummaryWebPart(portalCtx, 3);
return new ReplicateSummaryWebPart(portalCtx, 3);
}

@Override
public List<String> getLegacyNames()
{
return List.of("Targeted MS QC Summary");
}
},

Expand Down Expand Up @@ -476,12 +482,12 @@ public boolean isAvailable(Container c, String scope, String location)
}
},

new BaseWebPartFactory(TARGETED_MS_OUTLIER_HEATMAP_SUMMARY_VIEW)
new BaseWebPartFactory(TARGETED_MS_PEPTIDE_MOLECULE_SUMMARY)
{
@Override
public WebPartView<?> getWebPartView(@NotNull ViewContext portalCtx, @NotNull Portal.WebPart webPart)
{
return ModuleHtmlView.get(getModule(), "outlierHeatmapSummary");
return ModuleHtmlView.get(getModule(), "peptideSummary");
}
}
);
Expand Down Expand Up @@ -646,14 +652,14 @@ protected void startupAfterSpringConfig(ModuleContext moduleContext)
fcs.addZiploaderPattern(extBrukerRaw2);
}

Portal.registerNavTreeCustomizer("Targeted MS QC Summary", new QCSummaryMenuCustomizer("configureQCGroups", "Configure Included and Excluded Precursors"));
Portal.registerNavTreeCustomizer("Targeted MS QC Plots", new QCSummaryMenuCustomizer("configureQCGroups", "Configure Included and Excluded Precursors"));
Portal.registerNavTreeCustomizer(TARGETED_MS_REPLICATE_SUMMARY, new QCSummaryMenuCustomizer("configureQCGroups", "Configure Included and Excluded Precursors"));
Portal.registerNavTreeCustomizer(TARGETED_MS_QC_PLOTS, new QCSummaryMenuCustomizer("configureQCGroups", "Configure Included and Excluded Precursors"));

Portal.registerNavTreeCustomizer("Targeted MS QC Summary", new QCSummaryMenuCustomizer("configureQCMetric", "Configure QC Metrics"));
Portal.registerNavTreeCustomizer("Targeted MS QC Plots", new QCSummaryMenuCustomizer("configureQCMetric", "Configure QC Metrics"));
Portal.registerNavTreeCustomizer(TARGETED_MS_REPLICATE_SUMMARY, new QCSummaryMenuCustomizer("configureQCMetric", "Configure QC Metrics"));
Portal.registerNavTreeCustomizer(TARGETED_MS_QC_PLOTS, new QCSummaryMenuCustomizer("configureQCMetric", "Configure QC Metrics"));

Portal.registerNavTreeCustomizer("Targeted MS QC Summary", new QCSummaryMenuCustomizer("subscribeOutlierNotifications", "Subscribe to Outlier Notification Emails"));
Portal.registerNavTreeCustomizer("Targeted MS QC Plots", new QCSummaryMenuCustomizer("subscribeOutlierNotifications", "Subscribe to Outlier Notification Emails"));
Portal.registerNavTreeCustomizer(TARGETED_MS_REPLICATE_SUMMARY, new QCSummaryMenuCustomizer("subscribeOutlierNotifications", "Subscribe to Outlier Notification Emails"));
Portal.registerNavTreeCustomizer(TARGETED_MS_QC_PLOTS, new QCSummaryMenuCustomizer("subscribeOutlierNotifications", "Subscribe to Outlier Notification Emails"));

TargetedMSService.get().registerSkylineDocumentImportListener(QCNotificationSender.get());
}
Expand Down
Loading

0 comments on commit 796a996

Please sign in to comment.