diff --git a/packages/libs/components/src/plots/VolcanoPlot.tsx b/packages/libs/components/src/plots/VolcanoPlot.tsx index c117697b2e..1347097f46 100755 --- a/packages/libs/components/src/plots/VolcanoPlot.tsx +++ b/packages/libs/components/src/plots/VolcanoPlot.tsx @@ -79,12 +79,16 @@ export interface VolcanoPlotProps { showSpinner?: boolean; /** used to determine truncation logic */ rawDataMinMaxValues: RawDataMinMaxValues; + /** The maximum possible y axis value. Points with pValue=0 will get plotted at -log10(minPValueCap). */ + minPValueCap?: number; } const EmptyVolcanoPlotData: VolcanoPlotData = [ { log2foldChange: '0', pValue: '1' }, ]; +const MARGIN_DEFAULT = 50; + interface TruncationRectangleProps { x1: number; x2: number; @@ -134,6 +138,7 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref) { truncationBarFill, showSpinner = false, rawDataMinMaxValues, + minPValueCap = 2e-300, } = props; // Use ref forwarding to enable screenshotting of the plot for thumbnail versions. @@ -155,42 +160,33 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref) { const { min: dataYMin, max: dataYMax } = rawDataMinMaxValues.y; // Set mins, maxes of axes in the plot using axis range props + // The y axis max should not be allowed to exceed -log10(minPValueCap) const xAxisMin = independentAxisRange?.min ?? 0; const xAxisMax = independentAxisRange?.max ?? 0; const yAxisMin = dependentAxisRange?.min ?? 0; - const yAxisMax = dependentAxisRange?.max ?? 0; + const yAxisMax = dependentAxisRange?.max + ? dependentAxisRange.max > -Math.log10(minPValueCap) + ? -Math.log10(minPValueCap) + : dependentAxisRange.max + : 0; - /** - * Accessors - tell visx which value of the data point we should use and where. - */ - - // For the actual volcano plot data - const dataAccessors = { - xAccessor: (d: VolcanoPlotDataPoint) => Number(d?.log2foldChange), - yAccessor: (d: VolcanoPlotDataPoint) => -Math.log10(Number(d?.pValue)), - }; - - // For all other situations where we need to access point values. For example - // threshold lines and annotations. - const xyAccessors = { - xAccessor: (d: VisxPoint) => { - return d?.x; - }, - yAccessor: (d: VisxPoint) => { - return d?.y; - }, - }; + // Do we need to show the special annotation for the case when the y axis is maxxed out? + const showCappedDataAnnotation = yAxisMax === -Math.log10(minPValueCap); // Truncation indicators // If we have truncation indicators, we'll need to expand the plot range just a tad to // ensure the truncation bars appear. The folowing showTruncationBar variables will // be either 0 (do not show bar) or 1 (show bar). + // The y axis has special logic because it gets capped at -log10(minPValueCap) const showXMinTruncationBar = Number(dataXMin < xAxisMin); const showXMaxTruncationBar = Number(dataXMax > xAxisMax); const xTruncationBarWidth = 0.02 * (xAxisMax - xAxisMin); const showYMinTruncationBar = Number(-Math.log10(dataYMax) < yAxisMin); - const showYMaxTruncationBar = Number(-Math.log10(dataYMin) > yAxisMax); + const showYMaxTruncationBar = + dataYMin === 0 + ? Number(-Math.log10(minPValueCap) > yAxisMax) + : Number(-Math.log10(dataYMin) > yAxisMax); const yTruncationBarHeight = 0.02 * (yAxisMax - yAxisMin); /** @@ -205,6 +201,30 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref) { -Math.log10(Number(significanceThreshold)) > yAxisMin && -Math.log10(Number(significanceThreshold)) < yAxisMax; + /** + * Accessors - tell visx which value of the data point we should use and where. + */ + + // For the actual volcano plot data. Y axis points are capped at -Math.log10(minPValueCap) + const dataAccessors = { + xAccessor: (d: VolcanoPlotDataPoint) => Number(d?.log2foldChange), + yAccessor: (d: VolcanoPlotDataPoint) => + d.pValue === '0' + ? -Math.log10(minPValueCap) + : -Math.log10(Number(d?.pValue)), + }; + + // For all other situations where we need to access point values. For example + // threshold lines and annotations. + const xyAccessors = { + xAccessor: (d: VisxPoint) => { + return d?.x; + }, + yAccessor: (d: VisxPoint) => { + return d?.y; + }, + }; + return ( // Relative positioning so that tooltips are positioned correctly (tooltips are positioned absolutely)
) { zero: false, }} findNearestDatumOverride={findNearestDatumXY} + margin={{ + top: MARGIN_DEFAULT, + right: showCappedDataAnnotation ? 150 : MARGIN_DEFAULT, + left: MARGIN_DEFAULT, + bottom: MARGIN_DEFAULT, + }} > {/* Set up the axes and grid lines. XYChart magically lays them out correctly */} @@ -318,6 +344,31 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref) { )} + {/* infinity y data annotation line */} + {showCappedDataAnnotation && ( + + + + + )} + {/* The data itself */} {/* Wrapping in a group in order to change the opacity of points. The GlyphSeries is somehow a bunch of glyphs which are so there should be a way to pass opacity diff --git a/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx b/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx index 4af1ee0693..06d1188a5f 100755 --- a/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx +++ b/packages/libs/components/src/stories/plots/VolcanoPlot.stories.tsx @@ -49,6 +49,8 @@ const dataSetVolcano: VEuPathDBVolcanoPlotData = { '-8', '-4', '-3', + '-8.2', + '7', ], pValue: [ '0.001', @@ -63,9 +65,26 @@ const dataSetVolcano: VEuPathDBVolcanoPlotData = { '0.001', '0.0001', '0.002', + '0', + '0', + ], + adjustedPValue: ['0.01', '0.001', '0.01', '0.001', '0.02', '0', '0'], + pointID: [ + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'buzz', + 'lightyear', ], - adjustedPValue: ['0.01', '0.001', '0.01', '0.001', '0.02'], - pointID: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'], }, }; @@ -112,7 +131,7 @@ const Template: Story = (args) => { }) .map((d) => ({ ...d, - pointID: d.pointID ? [d.pointID] : undefined, + pointIDs: d.pointID ? [d.pointID] : undefined, significanceColor: assignSignificanceColor( Number(d.log2foldChange), Number(d.pValue), @@ -196,6 +215,10 @@ ManyPoints.args = { significanceThreshold: 0.01, independentAxisRange: { min: -9, max: 9 }, dependentAxisRange: { min: 0, max: 9 }, + comparisonLabels: [ + 'up in super long group name', + 'up in other long group name', + ], }; // Test truncation indicators