diff --git a/packages/oncoprintjs/src/js/oncoprinttrackinfoview.ts b/packages/oncoprintjs/src/js/oncoprinttrackinfoview.ts
index cee6747b0e6..7b2154ad791 100644
--- a/packages/oncoprintjs/src/js/oncoprinttrackinfoview.ts
+++ b/packages/oncoprintjs/src/js/oncoprinttrackinfoview.ts
@@ -84,8 +84,10 @@ export default class OncoprintTrackInfoView {
let formattedPercent = '';
if (isNaN(float)) {
- formattedPercent = 'N/P';
- suffix = ''; // we don't want any suffix in this case
+ // Non-numeric info (the "N/P" sentinel, or a warning glyph)
+ // is shown verbatim rather than coerced to a percentage.
+ formattedPercent = text;
+ suffix = '';
} else if (isNumber(float)) {
formattedPercent =
float < 1 && float > 0
diff --git a/src/shared/components/oncoprint/DeltaUtils.ts b/src/shared/components/oncoprint/DeltaUtils.ts
index ec7a831a0f4..831cb022d94 100644
--- a/src/shared/components/oncoprint/DeltaUtils.ts
+++ b/src/shared/components/oncoprint/DeltaUtils.ts
@@ -1560,6 +1560,9 @@ export function transitionHeatmapTrack(
nextProps.caseLinkOutInTooltips
),
track_info: nextSpec.info || '',
+ $track_info_tooltip_elt: nextSpec.infoTooltip
+ ? $('
' + nextSpec.infoTooltip + '
')
+ : undefined,
onSortDirectionChange: nextProps.onTrackSortDirectionChange,
expansion_of: expansionParentKey
? trackSpecKeyToTrackId[expansionParentKey]
@@ -1627,6 +1630,14 @@ export function transitionHeatmapTrack(
if (nextSpec.info !== prevSpec.info && nextSpec.info !== undefined) {
oncoprint.setTrackInfo(trackId, nextSpec.info);
}
+ if (nextSpec.infoTooltip !== prevSpec.infoTooltip) {
+ oncoprint.setTrackInfoTooltip(
+ trackId,
+ nextSpec.infoTooltip
+ ? $('' + nextSpec.infoTooltip + '
')
+ : undefined
+ );
+ }
if (
nextSpec.movable !== prevSpec.movable &&
nextSpec.movable !== undefined
@@ -1733,6 +1744,9 @@ export function transitionCategoricalTrack(
nextProps.caseLinkOutInTooltips
),
track_info: nextSpec.info || '',
+ $track_info_tooltip_elt: nextSpec.infoTooltip
+ ? $('' + nextSpec.infoTooltip + '
')
+ : undefined,
onSortDirectionChange: nextProps.onTrackSortDirectionChange,
};
const newTrackId = oncoprint.addTracks([trackParams])[0];
diff --git a/src/shared/components/oncoprint/Oncoprint.tsx b/src/shared/components/oncoprint/Oncoprint.tsx
index 3b486c23bc6..724a180b609 100644
--- a/src/shared/components/oncoprint/Oncoprint.tsx
+++ b/src/shared/components/oncoprint/Oncoprint.tsx
@@ -214,6 +214,7 @@ export interface IHeatmapTrackSpec extends IBaseHeatmapTrackSpec {
data: IBaseHeatmapTrackDatum[]; // can be IGeneHeatmapTrackDatum or IGenericAssayHeatmapTrackDatum
naLegendLabel?: string;
info?: string;
+ infoTooltip?: string;
labelColor?: string;
labelCircleColor?: string;
labelFontWeight?: string;
@@ -256,6 +257,7 @@ export interface ICategoricalTrackSpec {
naLegendLabel?: string;
description?: string;
info?: string;
+ infoTooltip?: string;
}
export const GENETIC_TRACK_GROUP_INDEX = 1;
diff --git a/src/shared/components/oncoprint/OncoprintUtils.ts b/src/shared/components/oncoprint/OncoprintUtils.ts
index ee089131626..33116bd75a8 100644
--- a/src/shared/components/oncoprint/OncoprintUtils.ts
+++ b/src/shared/components/oncoprint/OncoprintUtils.ts
@@ -74,6 +74,10 @@ import {
isGenericAssayCategoricalProfile,
isGenericAssayHeatmapProfile,
} from 'shared/components/oncoprint/ResultsViewOncoprintUtils';
+import { getProfileDescriptionCaveat } from 'shared/lib/GenericAssayUtils/GenericAssayCommonUtils';
+
+// Warning glyph shown in a track's info slot when its profile carries a caveat.
+export const PROFILE_CAVEAT_GLYPH = '⚠';
import { ExtendedClinicalAttribute } from 'pages/resultsView/ResultsViewPageStoreUtils';
import { CaseAggregatedData } from 'shared/model/CaseAggregatedData';
import { AnnotatedExtendedAlteration } from 'shared/model/AnnotatedExtendedAlteration';
@@ -1651,11 +1655,16 @@ export function makeGenericAssayProfileCategoricalTracksMobxPromise(
const entityLinkMap = oncoprint.genericAssayPromises
.genericAssayEntitiesGroupedByGenericAssayTypeLinkMap
.result![profile.genericAssayType];
+ const caveat = getProfileDescriptionCaveat(profile.description);
return {
key: `GENERICASSAYCATEGORICALTRACK_${molecularProfileId},${entityId}`,
label: query.entityName,
description: query.description,
+ info: caveat ? PROFILE_CAVEAT_GLYPH : undefined,
+ infoTooltip: caveat
+ ? `${PROFILE_CAVEAT_GLYPH} Caveat: ${caveat}`
+ : undefined,
molecularProfileId: query.molecularProfileId,
molecularProfileName:
molecularProfileIdToMolecularProfile[molecularProfileId]
@@ -1760,11 +1769,16 @@ export function makeGenericAssayProfileHeatmapTracksMobxPromise(
const entityLinkMap = oncoprint.genericAssayPromises
.genericAssayEntitiesGroupedByGenericAssayTypeLinkMap
.result![profile.genericAssayType];
+ const caveat = getProfileDescriptionCaveat(profile.description);
return {
key: `GENERICASSAYHEATMAPTRACK_${molecularProfileId},${entityId}`,
label: query.entityName,
description: query.description,
+ info: caveat ? PROFILE_CAVEAT_GLYPH : undefined,
+ infoTooltip: caveat
+ ? `${PROFILE_CAVEAT_GLYPH} Caveat: ${caveat}`
+ : undefined,
molecularProfileId: query.molecularProfileId,
molecularProfileName:
molecularProfileIdToMolecularProfile[molecularProfileId]
diff --git a/src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.spec.ts b/src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.spec.ts
index 2ec11f51cb9..f8804352e5d 100644
--- a/src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.spec.ts
+++ b/src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.spec.ts
@@ -9,6 +9,7 @@ import {
filterGenericAssayEntitiesByGenes,
makeGenericAssayPlotsTabOption,
filterGenericAssayOptionsByGenes,
+ getProfileDescriptionCaveat,
} from './GenericAssayCommonUtils';
import { getServerConfig } from 'config/config';
import ServerConfigDefaults from 'config/serverConfigDefaults';
@@ -471,4 +472,39 @@ describe('GenericAssayCommonUtils', () => {
);
});
});
+
+ describe('getProfileDescriptionCaveat()', () => {
+ it('returns undefined when description is undefined or empty', () => {
+ assert.isUndefined(getProfileDescriptionCaveat(undefined));
+ assert.isUndefined(getProfileDescriptionCaveat(''));
+ });
+
+ it('returns undefined when there is no caveat marker', () => {
+ assert.isUndefined(
+ getProfileDescriptionCaveat('Cell Type Relative Fractions.')
+ );
+ });
+
+ it('extracts the caveat text after the marker', () => {
+ assert.equal(
+ getProfileDescriptionCaveat(
+ 'Cell Type Relative Fractions. Caveat: Fractions are derived from CD45 FACS-sorted scRNA-seq.'
+ ),
+ 'Fractions are derived from CD45 FACS-sorted scRNA-seq.'
+ );
+ });
+
+ it('is case-insensitive on the marker', () => {
+ assert.equal(
+ getProfileDescriptionCaveat('caveat: something to note'),
+ 'something to note'
+ );
+ });
+
+ it('returns undefined when the marker has no following text', () => {
+ assert.isUndefined(
+ getProfileDescriptionCaveat('Some description. Caveat: ')
+ );
+ });
+ });
});
diff --git a/src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.ts b/src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.ts
index d85a8b9d5aa..62ff1f68e73 100644
--- a/src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.ts
+++ b/src/shared/lib/GenericAssayUtils/GenericAssayCommonUtils.ts
@@ -118,9 +118,8 @@ export async function fetchGenericAssayMetaByMolecularProfileIdsGroupByMolecular
} = {};
for (const profile of genericAssayProfiles) {
const suffix = getSuffixOfMolecularProfile(profile);
- genericAssayMetaGroupByMolecularProfileId[
- profile.molecularProfileId
- ] = metaBySuffix[suffix] || [];
+ genericAssayMetaGroupByMolecularProfileId[profile.molecularProfileId] =
+ metaBySuffix[suffix] || [];
}
return genericAssayMetaGroupByMolecularProfileId;
@@ -228,12 +227,14 @@ export function fetchGenericAssayDataByStableIdsAndMolecularIds(
stableIds: string[],
molecularProfileIds: string[]
) {
- return getClient().fetchGenericAssayDataInMultipleMolecularProfilesUsingPOST({
- genericAssayDataMultipleStudyFilter: {
- genericAssayStableIds: stableIds,
- molecularProfileIds: molecularProfileIds,
- } as GenericAssayDataMultipleStudyFilter,
- });
+ return getClient().fetchGenericAssayDataInMultipleMolecularProfilesUsingPOST(
+ {
+ genericAssayDataMultipleStudyFilter: {
+ genericAssayStableIds: stableIds,
+ molecularProfileIds: molecularProfileIds,
+ } as GenericAssayDataMultipleStudyFilter,
+ }
+ );
}
export function makeGenericAssayOption(meta: GenericAssayMeta) {
@@ -356,6 +357,21 @@ export function getCategoryOrderByGenericAssayType(genericAssayType: string) {
: undefined;
}
+// A profile description may carry a curated caveat about how its data should be
+// interpreted, flagged with a "Caveat:" marker (e.g. data derived from a sorted
+// cell population). Everything after the marker is the caveat text, surfaced as
+// a per-track warning so viewers see it without reading the profile description.
+export function getProfileDescriptionCaveat(
+ description: string | undefined
+): string | undefined {
+ if (!description) {
+ return undefined;
+ }
+ const match = /caveat:\s*([\s\S]+)/i.exec(description);
+ const caveat = match && match[1].trim();
+ return caveat ? caveat : undefined;
+}
+
export function constructGeneRegex(hugoGeneSymbol: string) {
// Match whole gene symbol text and case-insensitive, non-alphanumeric characters is allowed after gene symbol
// Sometimes, gene symbol might appear in description text