Skip to content

Commit

Permalink
Merge branch 'main' into 416-default-bubble-numerator-values
Browse files Browse the repository at this point in the history
  • Loading branch information
chowington committed Aug 17, 2023
2 parents 99cf475 + 4b7cd15 commit 443763a
Show file tree
Hide file tree
Showing 13 changed files with 3,089 additions and 3,821 deletions.
2 changes: 1 addition & 1 deletion packages/libs/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@visx/text": "^1.3.0",
"@visx/tooltip": "^1.3.0",
"@visx/visx": "^1.1.0",
"@visx/xychart": "^3.1.0",
"@visx/xychart": "https://github.com/jernestmyers/visx.git#visx-xychart",
"bootstrap": "^4.5.2",
"color-math": "^1.1.3",
"d3": "^7.1.1",
Expand Down
28 changes: 28 additions & 0 deletions packages/libs/components/src/plots/VolcanoPlot.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.visx-tooltip {
z-index: 1;
}

.VolcanoPlotTooltip {
padding: 5px 10px;
font-size: 12px;
border-radius: 2px;
box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.5);
}

.VolcanoPlotTooltip > .pseudo-hr {
margin: 5px auto;
height: 1px;
width: 100%;
}

.VolcanoPlotTooltip > ul {
margin: 0;
padding: 0;
list-style: none;
line-height: 1.5em;
font-weight: normal;
}

.VolcanoPlotTooltip > ul > li > span {
font-weight: bold;
}
70 changes: 66 additions & 4 deletions packages/libs/components/src/plots/VolcanoPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
VolcanoPlotDataPoint,
} from '../types/plots/volcanoplot';
import { NumberRange } from '../types/general';
import { SignificanceColors } from '../types/plots';
import { SignificanceColors, significanceColors } from '../types/plots';
import {
XYChart,
Axis,
Expand All @@ -20,7 +20,9 @@ import {
AnnotationLineSubject,
DataContext,
AnnotationLabel,
Tooltip,
} from '@visx/xychart';
import findNearestDatumXY from '@visx/xychart/lib/utils/findNearestDatumXY';
import { Group } from '@visx/group';
import {
gridStyles,
Expand All @@ -36,6 +38,7 @@ import Spinner from '../components/Spinner';
import { ToImgopts } from 'plotly.js';
import { DEFAULT_CONTAINER_HEIGHT } from './PlotlyPlot';
import domToImage from 'dom-to-image';
import './VolcanoPlot.css';

export interface RawDataMinMaxValues {
x: NumberRange;
Expand Down Expand Up @@ -215,7 +218,6 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref<HTMLDivElement>) {
{/* The XYChart takes care of laying out the chart elements (children) appropriately.
It uses modularized React.context layers for data, events, etc. The following all becomes an svg,
so use caution when ordering the children (ex. draw axes before data). */}

<XYChart
xScale={{
type: 'linear',
Expand All @@ -235,6 +237,7 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref<HTMLDivElement>) {
],
zero: false,
}}
findNearestDatumOverride={findNearestDatumXY}
>
{/* Set up the axes and grid lines. XYChart magically lays them out correctly */}
<Grid numTicks={6} lineStyle={gridStyles} />
Expand Down Expand Up @@ -322,11 +325,70 @@ function VolcanoPlot(props: VolcanoPlotProps, ref: Ref<HTMLDivElement>) {
<Group opacity={markerBodyOpacity}>
<GlyphSeries
dataKey={'data'} // unique key
data={data} // data as an array of obejcts (points). Accessed with dataAccessors
data={data}
{...dataAccessors}
colorAccessor={(d) => d.significanceColor}
colorAccessor={(d: VolcanoPlotDataPoint) => d.significanceColor}
findNearestDatumOverride={findNearestDatumXY}
/>
</Group>
<Tooltip<VolcanoPlotDataPoint>
snapTooltipToDatumX
snapTooltipToDatumY
showVerticalCrosshair
showHorizontalCrosshair
horizontalCrosshairStyle={{ stroke: 'red' }}
verticalCrosshairStyle={{ stroke: 'red' }}
unstyled
applyPositionStyle
renderTooltip={(d) => {
const data = d.tooltipData?.nearestDatum?.datum;
/**
* Notes regarding colors in the tooltips:
* 1. We use the data point's significanceColor property for background color
* 2. For color contrast reasons, color for text and hr's border is set conditionally:
* - if significanceColor matches the 'inconclusive' color (grey), we use black
* - else, we use white
* (white font meets contrast ratio threshold (min 3:1 for UI-y things) w/ #AC3B4E (red) and #0E8FAB (blue))
*/
const color =
data?.significanceColor === significanceColors['inconclusive']
? 'black'
: 'white';
return (
<div
className="VolcanoPlotTooltip"
style={{
color,
background: data?.significanceColor,
}}
>
<ul>
{data?.pointIDs?.map((id) => (
<li key={id}>
<span>{id}</span>
</li>
))}
</ul>
<div
className="pseudo-hr"
style={{ borderBottom: `1px solid ${color}` }}
></div>
<ul>
<li>
<span>log2 Fold Change:</span> {data?.log2foldChange}
</li>
<li>
<span>P Value:</span> {data?.pValue}
</li>
<li>
<span>Adjusted P Value:</span>{' '}
{data?.adjustedPValue ?? 'n/a'}
</li>
</ul>
</div>
);
}}
/>

{/* Truncation indicators */}
{/* Example from https://airbnb.io/visx/docs/pattern */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ const Template: Story<TemplateProps> = (args) => {
})
.map((d) => ({
...d,
pointID: d.pointID ? [d.pointID] : undefined,
significanceColor: assignSignificanceColor(
Number(d.log2foldChange),
Number(d.pValue),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const Template: Story<TemplateProps> = (args) => {
})
.map((d) => ({
...d,
pointID: d.pointID ? [d.pointID] : undefined,
significanceColor: assignSignificanceColor(
Number(d.log2foldChange),
Number(d.pValue),
Expand Down
2 changes: 1 addition & 1 deletion packages/libs/components/src/types/plots/volcanoplot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type VolcanoPlotDataPoint = {
// Used for thresholding and tooltip
adjustedPValue?: string;
// Used for tooltip
pointID?: string;
pointIDs?: string[];
// Used to determine color of data point in the plot
significanceColor?: string;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ import DataClient, {
VolcanoPlotRequestParams,
VolcanoPlotResponse,
} from '../../../api/DataClient';
import {
VolcanoPlotData,
VolcanoPlotDataPoint,
} from '@veupathdb/components/lib/types/plots/volcanoplot';
import VolcanoSVG from './selectorIcons/VolcanoSVG';
import { NumberOrDate } from '@veupathdb/components/lib/types/general';
import { DifferentialAbundanceConfig } from '../../computations/plugins/differentialabundance';
Expand Down Expand Up @@ -239,14 +243,12 @@ function VolcanoPlotViz(props: VisualizationProps<Options>) {
vizConfig.log2FoldChangeThreshold ?? DEFAULT_FC_THRESHOLD;

/**
* Let's filter out data that falls outside of the plot axis ranges and then
* assign a significance color to the visible data
* This version of the data will get passed to the VolcanoPlot component
*/
const finalData = useMemo(() => {
if (data.value && independentAxisRange && dependentAxisRange) {
// Only return data if the points fall within the specified range! Otherwise they'll show up on the plot.
return data.value
const cleanedData = data.value
// Only return data if the points fall within the specified range! Otherwise they'll show up on the plot.
.filter((d) => {
const log2foldChange = Number(d?.log2foldChange);
const transformedPValue = -Math.log10(Number(d?.pValue));
Expand All @@ -257,16 +259,64 @@ function VolcanoPlotViz(props: VisualizationProps<Options>) {
transformedPValue >= dependentAxisRange.min
);
})
.map((d) => ({
...d,
significanceColor: assignSignificanceColor(
Number(d.log2foldChange),
Number(d.pValue),
significanceThreshold,
log2FoldChangeThreshold,
significanceColors
),
}));
/**
* Okay, this map function is doing a number of things.
* 1. We're going to remove the pointID property and replace it with a pointIDs property that is an array of strings.
* Some data share coordinates but correspond to a different pointID. By converting pointID to pointIDs, we can
* later aggregate data that share coordinates and then render one tooltip that lists all pointIDs corresponding
* to the point on the plot
* 2. We also add a significanceColor property that is assigned a value that gets used in VolcanoPlot when rendering
* the data point and the data point's tooltip. The property is also used in the countsData logic.
*/
.map((d) => {
const { pointID, ...remainingProperties } = d;
return {
...remainingProperties,
pointIDs: pointID ? [pointID] : undefined,
significanceColor: assignSignificanceColor(
Number(d.log2foldChange),
Number(d.pValue),
significanceThreshold,
log2FoldChangeThreshold,
significanceColors
),
};
})
// Sort data in ascending order for tooltips to work most effectively
.sort((a, b) => Number(a.log2foldChange) - Number(b.log2foldChange));

// Here we're going to loop through the cleanedData to aggregate any data with shared coordinates.
// For each entry, we'll check if our aggregatedData includes an item with the same coordinates:
// Yes? => update the matched aggregatedData element's pointID array to include the pointID of the matching entry
// No? => just push the entry onto the aggregatedData array since no match was found
const aggregatedData: VolcanoPlotData = [];
for (const entry of cleanedData) {
const foundIndex = aggregatedData.findIndex(
(d: VolcanoPlotDataPoint) =>
d.log2foldChange === entry.log2foldChange &&
d.pValue === entry.pValue
);
if (foundIndex === -1) {
aggregatedData.push(entry);
} else {
const { pointIDs } = aggregatedData[foundIndex];
if (pointIDs) {
aggregatedData[foundIndex] = {
...aggregatedData[foundIndex],
pointIDs: [
...pointIDs,
...(entry.pointIDs ? entry.pointIDs : []),
],
};
} else {
aggregatedData[foundIndex] = {
...aggregatedData[foundIndex],
pointIDs: entry.pointIDs,
};
}
}
}
return aggregatedData;
}
}, [
data.value,
Expand All @@ -276,7 +326,7 @@ function VolcanoPlotViz(props: VisualizationProps<Options>) {
log2FoldChangeThreshold,
]);

// For the legend, we need the counts of each assigned significance value
// For the legend, we need the counts of the data
const countsData = useMemo(() => {
if (!finalData) return;
const counts = {
Expand All @@ -285,7 +335,14 @@ function VolcanoPlotViz(props: VisualizationProps<Options>) {
[significanceColors['low']]: 0,
};
for (const entry of finalData) {
counts[entry.significanceColor]++;
if (entry.significanceColor) {
// Recall that finalData combines data with shared coords into one point in order to display a
// single tooltip that lists all the pointIDs for that shared point. This means we need to use
// the length of the pointID array to accurately reflect the counts of unique data (not unique coords).
const addend = entry.pointIDs?.length ?? 1;
counts[entry.significanceColor] =
addend + counts[entry.significanceColor];
}
}
return counts;
}, [finalData]);
Expand All @@ -294,7 +351,7 @@ function VolcanoPlotViz(props: VisualizationProps<Options>) {
updateThumbnail,
plotContainerStyles,
[
data,
finalData,
// vizConfig.checkedLegendItems, TODO
vizConfig.independentAxisRange,
vizConfig.dependentAxisRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,8 @@
top: -3em;
margin-left: 310px;
}

.wdk-DataTableCountsContainer {
margin: auto 0 auto 1em;
font-size: 0.9em;
}
23 changes: 23 additions & 0 deletions packages/libs/wdk-client/src/Components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ interface State {
childRows: [HTMLElement, ChildRowProps][];
selectedColumnFilters: string[];
showFieldSelector: boolean;
/** Used to display row counts to user */
numberOfRowsVisible: number | null;
}

/**
Expand Down Expand Up @@ -158,6 +160,7 @@ class DataTable extends PureComponent<Props, State> {
childRows: [],
selectedColumnFilters: [],
showFieldSelector: false,
numberOfRowsVisible: null,
};

_childRowContainers: Map<HTMLTableRowElement, HTMLElement> = new Map();
Expand Down Expand Up @@ -430,6 +433,11 @@ class DataTable extends PureComponent<Props, State> {
.search(searchTermRegex, true, false, true)
.draw();
}
/** set row count after .draw() for correct count */
this.setState((state) => ({
...state,
numberOfRowsVisible: dataTable.page.info().recordsDisplay,
}));
}

_updateSorting(dataTable: DataTables.Api) {
Expand Down Expand Up @@ -662,6 +670,21 @@ class DataTable extends PureComponent<Props, State> {
</ul>
</div>
</HelpIcon>
<div className="wdk-DataTableCountsContainer">
<span>
<strong>
{this.state.numberOfRowsVisible ?? this.props.data.length}
</strong>{' '}
rows
</span>{' '}
{(this.state.numberOfRowsVisible === 0 ||
this.state.numberOfRowsVisible) &&
this.state.numberOfRowsVisible < this.props.data.length && (
<span className="MesaComponent faded">
(filtered from a total of {this.props.data.length})
</span>
)}
</div>
</div>
{this.state.showFieldSelector && (
<DataTableFilterSelector
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { keyBy, mapValues, pick, toString } from 'lodash';
import { isEmpty, keyBy, mapValues, pick, toString } from 'lodash';
import { Seq } from '../Utils/IterableUtils';
import {
combineEpics,
Expand Down Expand Up @@ -1101,7 +1101,7 @@ async function fetchInitialParams(
) {
if (step != null) {
return initialParamDataFromStep(step);
} else if (initialParamData != null) {
} else if (!isEmpty(initialParamData)) {
return extracParamValues(initialParamData, question.paramNames);
} else if (prepopulateWithLastParamValues) {
return (
Expand Down
Loading

0 comments on commit 443763a

Please sign in to comment.