-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Volcano #19
Volcano #19
Changes from 30 commits
02ef746
84fb7e6
c0e8326
1aac185
543514f
a71b8a1
46da6d9
40974db
4345f4e
ea8dacb
777ad32
ab5101f
439c0e5
19d851b
18dfc78
0b8c448
3bc1a4e
9538298
06320f7
28bc6dc
f921d45
1797a64
7063c13
27c96a7
3645576
8c4b458
ff6b5d8
0a31095
fe81146
f52efb5
9b62891
aa0ae52
9469695
b71d809
4b033dd
07b2575
94d7d41
c2c79b1
26fba4d
6d62e39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
import { PlotProps } from './PlotlyPlot'; | ||
|
||
import { significanceColors } from '../types/plots'; | ||
import { VolcanoPlotData } from '../types/plots/volcanoplot'; | ||
import { NumberRange } from '../types/general'; | ||
import { | ||
XYChart, | ||
Tooltip, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
Axis, | ||
Grid, | ||
GlyphSeries, | ||
Annotation, | ||
AnnotationLineSubject, | ||
} from '@visx/xychart'; | ||
import { Group } from '@visx/group'; | ||
import { max, min } from 'lodash'; | ||
|
||
export interface VolcanoPlotProps extends PlotProps<VolcanoPlotData> { | ||
/** | ||
* Used to set the fold change thresholds. Will | ||
* set two thresholds at +/- this number. | ||
*/ | ||
log2FoldChangeThreshold: number; | ||
/** Set the threshold for significance. */ | ||
significanceThreshold: number; | ||
/** x-axis range: */ | ||
independentAxisRange?: NumberRange; | ||
/** y-axis range: */ | ||
dependentAxisRange?: NumberRange; | ||
/** | ||
* Array of size 2 that contains a label for the left and right side | ||
* of the x axis. (Not yet implemented). Expect this to be passed by the viz based | ||
* on the type of data we're using (genes vs taxa vs etc.) | ||
*/ | ||
comparisonLabels?: Array<string>; | ||
/** What is this plot's name? */ | ||
plotTitle?: string; | ||
/** marker color opacity: range from 0 to 1 */ | ||
markerBodyOpacity?: number; | ||
} | ||
|
||
const EmptyVolcanoPlotData: VolcanoPlotData = { | ||
foldChange: [], | ||
pValue: [], | ||
adjustedPValue: [], | ||
pointId: [], | ||
}; | ||
|
||
interface DataPoint { | ||
foldChange: string; | ||
pValue: string; | ||
adjustedPValue: string; | ||
pointId: string; | ||
color: string; | ||
} | ||
|
||
/** | ||
* The Volcano Plot displays points on a (magnitude change) by (significance) xy axis. | ||
* It also colors the points based on their significance and magnitude change. | ||
*/ | ||
function VolcanoPlot(props: VolcanoPlotProps) { | ||
const { | ||
data = EmptyVolcanoPlotData, | ||
independentAxisRange, | ||
dependentAxisRange, | ||
significanceThreshold, | ||
log2FoldChangeThreshold, | ||
markerBodyOpacity, | ||
...restProps | ||
} = props; | ||
|
||
/** | ||
* Find mins and maxes of the data and for the plot | ||
*/ | ||
|
||
const dataXMin = min(data.foldChange.map(Number)); | ||
const dataXMax = max(data.foldChange.map(Number)); | ||
const dataYMin = min(data.pValue.map(Number)); | ||
const dataYMax = max(data.pValue.map(Number)); | ||
|
||
// Determine mins, maxes of axes in the plot. | ||
// These are different than the data mins/maxes because | ||
// of the log transform and the little bit of padding. | ||
// | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Dangling |
||
|
||
let xMin: number; | ||
let xMax: number; | ||
let yMin: number; | ||
let yMax: number; | ||
|
||
// Log transform for plotting, and add a little margin for axes | ||
if (dataXMin && dataXMax) { | ||
xMin = Math.log2(dataXMin); | ||
xMax = Math.log2(dataXMax); | ||
xMin = xMin - (xMax - xMin) * 0.05; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit/suggestion: 0.05 is the measure of statistical significance, right? Maybe we can save that to a constant called |
||
xMax = xMax + (xMax - xMin) * 0.05; | ||
} else { | ||
xMin = 0; | ||
xMax = 0; | ||
} | ||
if (dataYMin && dataYMax) { | ||
yMin = -Math.log10(dataYMax); | ||
yMax = -Math.log10(dataYMin); | ||
yMin = yMin - (yMax - yMin) * 0.05; | ||
yMax = yMax + (yMax - yMin) * 0.05; | ||
} else { | ||
yMin = 0; | ||
yMax = 0; | ||
} | ||
console.log(yMin); | ||
asizemore marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Turn the data (array of arrays) into data points (array of points) | ||
*/ | ||
|
||
let dataPoints: DataPoint[] = []; | ||
|
||
// Loop through the data and return points. Doesn't really matter | ||
// which var of the data we map over. | ||
data.foldChange.forEach((fc, ind: number) => { | ||
dataPoints.push({ | ||
foldChange: fc, | ||
pValue: data.pValue[ind], | ||
adjustedPValue: data.adjustedPValue[ind], | ||
pointId: data.pointId[ind], | ||
color: assignSignificanceColor( | ||
Math.log2(Number(fc)), | ||
Number(data.pValue[ind]), | ||
significanceThreshold, | ||
log2FoldChangeThreshold, | ||
significanceColors | ||
), | ||
}); | ||
}); | ||
|
||
/** | ||
* Accessors - tell visx which value of each points we should use and where. | ||
*/ | ||
|
||
const dataAccessors = { | ||
xAccessor: (d: any) => { | ||
return Math.log2(d?.foldChange); | ||
}, | ||
yAccessor: (d: any) => { | ||
return -Math.log10(d?.pValue); | ||
}, | ||
}; | ||
|
||
const thresholdLineAccessors = { | ||
xAccessor: (d: any) => { | ||
return d?.x; | ||
}, | ||
yAccessor: (d: any) => { | ||
return d?.y; | ||
}, | ||
}; | ||
|
||
/** | ||
* Plot styles | ||
* (can eventually be moved to a new file and applied as a visx theme) | ||
*/ | ||
const thresholdLineStyles = { | ||
stroke: '#aaaaaa', | ||
strokeWidth: 1, | ||
strokeDasharray: 3, | ||
}; | ||
const axisStyles = { | ||
stroke: '#bbbbbb', | ||
strokeWidth: 1, | ||
}; | ||
const gridStyles = { | ||
stroke: '#dddddd', | ||
strokeWidth: 0.5, | ||
}; | ||
console.log(yMin); | ||
|
||
return ( | ||
// From docs " For correct tooltip positioning, it is important to wrap your | ||
// component in an element (e.g., div) with relative positioning." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. random: If you or anyone would like to understand absolute/relative positioning, this Codepen might help. The docs say this because it sounds like the tooltips are positioned absolutely. Absolutely positioned elements position themselves in relation to their nearest parent that's positioned relatively. 🤯 You likely know this, but I just wanted to put it in there in case! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i did not know this! Thank you! That was a really clear example |
||
<div style={{ position: 'relative' }}> | ||
<XYChart | ||
height={300} | ||
xScale={{ type: 'linear', domain: [xMin, xMax] }} | ||
yScale={{ type: 'linear', domain: [yMin, yMax], zero: false }} | ||
width={300} | ||
> | ||
<Grid numTicks={6} lineStyle={gridStyles} /> | ||
<Axis orientation="left" label="-log10 Raw P Value" {...axisStyles} /> | ||
<Axis orientation="bottom" label="log2 Fold Change" {...axisStyles} /> | ||
|
||
{/* Draw threshold lines as annotations below the data points */} | ||
{significanceThreshold && ( | ||
<Annotation | ||
datum={{ | ||
x: 0, // horizontal line so x could be anything | ||
y: -Math.log10(Number(significanceThreshold)), | ||
}} | ||
{...thresholdLineAccessors} | ||
> | ||
<AnnotationLineSubject | ||
orientation="horizontal" | ||
{...thresholdLineStyles} | ||
/> | ||
</Annotation> | ||
)} | ||
{log2FoldChangeThreshold && ( | ||
<> | ||
<Annotation | ||
datum={{ | ||
x: -log2FoldChangeThreshold, | ||
y: 0, // vertical line so y could be anything | ||
}} | ||
{...thresholdLineAccessors} | ||
> | ||
<AnnotationLineSubject {...thresholdLineStyles} /> | ||
</Annotation> | ||
<Annotation | ||
datum={{ | ||
x: log2FoldChangeThreshold, | ||
y: 0, // vertical line so y could be anything | ||
}} | ||
{...thresholdLineAccessors} | ||
> | ||
<AnnotationLineSubject {...thresholdLineStyles} /> | ||
</Annotation> | ||
</> | ||
)} | ||
|
||
{/* The data itself */} | ||
<Group opacity={markerBodyOpacity ?? 1}> | ||
<GlyphSeries | ||
dataKey={'data'} | ||
data={dataPoints} | ||
{...dataAccessors} | ||
colorAccessor={(d) => { | ||
return d.color; | ||
}} | ||
/> | ||
</Group> | ||
</XYChart> | ||
</div> | ||
); | ||
} | ||
|
||
/** | ||
* Assign color to point based on significance and magnitude change thresholds | ||
*/ | ||
function assignSignificanceColor( | ||
xValue: number, // has already been log2 transformed | ||
yValue: number, // the raw pvalue | ||
significanceThreshold: number, | ||
log2FoldChangeThreshold: number, | ||
significanceColors: string[] // Assuming the order is [high, low, not significant] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: This isn't at all unclear code or anything complicated, so this is really just a suggestion. I don't know if it makes the code better. Given this assumption, you could name the indicies: const HIGH = 0;
const LOW = 1;
const INSIGNIFICANT = 2;
if (yValue >= significanceThreshold) {
return significanceColors[INSIGNIFICANT];
}
if (xValue >= log2FoldChangeThreshold) {
return significanceColors[HIGH];
}
... |
||
) { | ||
// Test 1. If the y value is higher than the significance threshold, just return not significant | ||
if (yValue >= significanceThreshold) { | ||
return significanceColors[2]; | ||
} | ||
|
||
// Test 2. So the y is significant. Is the x larger than the positive foldChange threshold? | ||
if (xValue >= log2FoldChangeThreshold) { | ||
return significanceColors[0]; | ||
} | ||
|
||
// Test 3. Is the x value lower than the negative foldChange threshold? | ||
if (xValue <= -log2FoldChangeThreshold) { | ||
return significanceColors[1]; | ||
} | ||
|
||
// If we're still here, it must be a non significant point. | ||
return significanceColors[2]; | ||
} | ||
|
||
export default VolcanoPlot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this added? I don't see class properties used anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!! I forgot to ask about this in our PR review! I was curious since it's scoped to storybook.
Ann's probably hacking us 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh yes good catch! This was a byproduct of my adventures trying to install some of the visx packages a while back. I just removed it and all seems to work 👍