Skip to content
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

Feat: add transformation dropdown to histogram #1666

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ MockedSuccess.parameters = {
}
),
rest.post(
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId/by-histogram-concept-id/:conceptId',
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId',
(req, res, ctx) => {
return res(
ctx.delay(2000),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const AttritionTableModal = ({ modalInfo, setModalInfo }) => {
&& modalInfo.rowObject.variable_type === 'concept' && (
<div data-testid='phenotype-histogram-diagram'>
<PhenotypeHistogram
useInlineErrorMessages
selectedStudyPopulationCohort={modalInfo.selectedCohort}
selectedCovariates={
modalInfo.currentCovariateAndCovariatesFromPrecedingRows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ WithConceptOutcome.parameters = {
}
),
rest.post(
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId/by-histogram-concept-id/:conceptId',
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId',
(req, res, ctx) => {
return res(
ctx.delay(2000),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const BarChart = () => (
<svg
className='bar-chart-icon'
role='img'
aria-labelledby='barChartDesc'
aria-labelledby='barChartTitle'
xmlns='http://www.w3.org/2000/svg'
width='16px'
viewBox='0 0 64 64'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Covariates from './Covariates';
import PhenotypeHistogram from '../Diagrams/PhenotypeHistogram/PhenotypeHistogram';
import FILTERS from '../../../SharedUtils/FiltersEnumeration';
import BarChart from '../AttritionTableWrapper/ChartIcons/BarChart';
import './Covariates.css';

const ContinuousCovariates = ({
Expand All @@ -19,8 +21,57 @@ const ContinuousCovariates = ({
variable_type: 'concept',
concept_id: selected.concept_id,
concept_name: selected.concept_name,
transformation: selected.transformation,
filters: selected.filters,
});

const onChangeTransformation = (selectedTransformationType) => {
setSelected((prevSelected) => {
const transformation = selectedTransformationType.key;
return { ...prevSelected, transformation };
});
};

const onChangeMinOutlierCutoff = (minOutlierCutoff) => {
setSelected((prevSelected) => {
// Ensure filters array exists
const filters = prevSelected.filters ? [...prevSelected.filters] : [];

// Find the index of the ">=" filter
const minFilterIndex = filters.findIndex((filter) => filter.type === FILTERS.greaterThanOrEqualTo);

if (minFilterIndex !== -1) {
// Update the existing ">=" filter
filters[minFilterIndex] = { type: FILTERS.greaterThanOrEqualTo, value: minOutlierCutoff };
} else {
// Add a new ">=" filter
filters.push({ type: FILTERS.greaterThanOrEqualTo, value: minOutlierCutoff });
}

return { ...prevSelected, filters };
});
};

const onChangeMaxOutlierCutoff = (maxOutlierCutoff) => {
setSelected((prevSelected) => {
// Ensure filters array exists
const filters = prevSelected.filters ? [...prevSelected.filters] : [];

// Find the index of the "<=" filter
const maxFilterIndex = filters.findIndex((filter) => filter.type === FILTERS.lessThanOrEqualTo);

if (maxFilterIndex !== -1) {
// Update the existing "<=" filter
filters[maxFilterIndex] = { type: FILTERS.lessThanOrEqualTo, value: maxOutlierCutoff };
} else {
// Add a new "<=" filter
filters.push({ type: FILTERS.lessThanOrEqualTo, value: maxOutlierCutoff });
}

return { ...prevSelected, filters };
});
};

// when a user has selected a outcome phenotype that is a continuous covariate with a concept ID,
// that should not appear as a selectable option, and be included in the submitted covariates.
// If they selected a outcome phenotype that is dichotomous
Expand Down Expand Up @@ -63,18 +114,21 @@ const ContinuousCovariates = ({
</button>
</div>
{selected ? (
<div data-tour='phenotype-histogram'>
<div data-tour='phenotype-histogram' className='phenotype-histogram-component'>
<PhenotypeHistogram
dispatch={dispatch}
selectedStudyPopulationCohort={selectedStudyPopulationCohort}
selectedCovariates={selectedCovariates}
outcome={outcome}
selectedContinuousItem={selected}
handleChangeTransformation={onChangeTransformation}
handleChangeMinOutlierCutoff={onChangeMinOutlierCutoff}
handleChangeMaxOutlierCutoff={onChangeMaxOutlierCutoff}
/>
</div>
) : (
<div data-tour='phenotype-histogram' className='phenotype-histogram-directions'>
Select a concept to render its corresponding histogram
<BarChart />Select a concept to render its corresponding histogram
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ SuccessCase.parameters = {
}
),
rest.post(
//histogram/by-source-id/${sourceId}/by-cohort-definition-id/${cohortId}/by-histogram-concept-id/${currentSelection.concept_id}`;
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId/by-histogram-concept-id/:conceptId',
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId',
(req, res, ctx) => {
const { cohortmiddlewarepath } = req.params;
const { cohortdefinitionId } = req.params;
Expand Down Expand Up @@ -152,8 +151,7 @@ EmptyDataCase.parameters = {
}
),
rest.post(
//histogram/by-source-id/${sourceId}/by-cohort-definition-id/${cohortId}/by-histogram-concept-id/${currentSelection.concept_id}`;
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId/by-histogram-concept-id/:conceptId',
'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId',
(req, res, ctx) => {
const { cohortmiddlewarepath } = req.params;
const { cohortdefinitionId } = req.params;
Expand Down
14 changes: 13 additions & 1 deletion src/Analysis/GWASApp/Components/Covariates/Covariates.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@
display: block;
float: right;
height: 36px;
margin-right: 5%;
}

.GWASApp .phenotype-histogram .recharts-wrapper {
margin-top: 60px;
margin-top: 10px;
}

.GWASApp .phenotype-histogram .histrogram-loading {
Expand All @@ -111,6 +112,17 @@
width: 600px;
}

.GWASApp .phenotype-histogram-directions svg {
margin-right: 4px;
}

.GWASApp .phenotype-histogram-component {
float: left;
margin-top: 25px;
text-align: left;
width: 600px;
}

/* Dichotomous Covariate Selection */
.GWASApp .custom-dichotomous-covariates .GWASUI-flexRow label {
font-weight: bold;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.GWASUI-column.transformation-dropdown-label {
max-width: 25%;
}

.GWASUI-column.transformation-select {
max-width: 65%;
}

label[for='input-minOutlierCutoff'],
label[for='input-maxOutlierCutoff'] {
margin-top: 3px;
}

.GWASUI-row.outlier-inputs {
margin-top: -8px;
}

.recharts-text.xAxisLabel tspan:nth-child(1) {
font-weight: bold;
fill: black;
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useQuery } from 'react-query';
import { Spin } from 'antd';
import { Select, InputNumber, Spin } from 'antd';
import { fetchHistogramInfo } from '../../../Utils/cohortMiddlewareApi';
import queryConfig from '../../../../SharedUtils/QueryConfig';
import Histogram from '../../../../SharedUtils/DataViz/Histogram/Histogram';
import { useSourceContext } from '../../../Utils/Source';
import ACTIONS from '../../../Utils/StateManagement/Actions';
import { MESSAGES } from '../../../Utils/constants';
import FILTERS from '../../../../SharedUtils/FiltersEnumeration';
import './PhenotypeHistogram.css';

const PhenotypeHistogram = ({
dispatch,
selectedStudyPopulationCohort,
selectedCovariates,
outcome,
selectedContinuousItem,
useInlineErrorMessages,
useAnimation,
handleChangeTransformation,
handleChangeMinOutlierCutoff,
handleChangeMaxOutlierCutoff,
}) => {
const { source } = useSourceContext();
const [inlineErrorMessage, setInlineErrorMessage] = useState(null);
const [minOutlierCutoff, setMinOutlierCutoff] = useState(null);
const [maxOutlierCutoff, setMaxOutlierCutoff] = useState(null);
const [selectedTransformation, setSelectedTransformation] = useState(null);
const sourceId = source; // TODO - change name of source to sourceId for clarity
const { data, status } = useQuery(
[
Expand All @@ -29,13 +36,15 @@ const PhenotypeHistogram = ({
selectedCovariates,
outcome,
selectedContinuousItem.concept_id,
selectedContinuousItem.transformation,
],
() => fetchHistogramInfo(
sourceId,
selectedStudyPopulationCohort.cohort_definition_id,
selectedCovariates,
outcome,
selectedContinuousItem.concept_id,
selectedContinuousItem.transformation,
),
queryConfig,
);
Expand Down Expand Up @@ -82,31 +91,145 @@ const PhenotypeHistogram = ({
xAxisLegend: selectedContinuousItem.concept_name,
yAxisLegend: 'Persons',
useAnimation,
minCutoff:
selectedContinuousItem.filters?.find(
(filter) => filter.type === FILTERS.greaterThanOrEqualTo,
)?.value ?? undefined,
maxCutoff:
selectedContinuousItem.filters?.find(
(filter) => filter.type === FILTERS.lessThanOrEqualTo,
)?.value ?? undefined,
};
return (
<React.Fragment>
{useInlineErrorMessages && inlineErrorMessage}
<Histogram {...histogramArgs} />
{inlineErrorMessage}
{data.bins !== null && (
<div>
<div className='GWASUI-row'>
<div className='GWASUI-column transformation-dropdown-label'>
<label
id='transformation-dropdown-label'
htmlFor='transformation-select'
>
Select Transformation
</label>
</div>
<div className='GWASUI-column transformation-select'>
<Select
id='transformation-select'
showSearch={false}
labelInValue
value={selectedTransformation}
onChange={(value) => {
setSelectedTransformation(value);
handleChangeTransformation(value);
}}
placeholder='-optional transformation-'
fieldNames={{ label: 'description', value: 'type' }}
options={[
{ type: 'log', description: 'log transformation' },
{ type: 'z_score', description: 'z-score transformation' },
]}
aria-label='Transformation Selection'
aria-labelledby='transformation-dropdown-label'
/>
</div>
</div>
<Histogram {...histogramArgs} />
<div className='GWASUI-row outlier-inputs'>
<div className='GWASUI-column'>
<label htmlFor='input-minOutlierCutoff'>
Minimum outlier cutoff
</label>
</div>
<div className='GWASUI-column'>
<InputNumber
id='input-minOutlierCutoff'
value={minOutlierCutoff}
onChange={(value) => {
setMinOutlierCutoff(value);
handleChangeMinOutlierCutoff(value);
}}
min={data.bins[0]?.start || 0}
max={
maxOutlierCutoff
|| data.bins[data.bins.length - 1]?.end
|| 100
}
onKeyDown={(e) => {
const { key } = e;
// Allow only numeric keys, backspace, and delete, and one decimal point
if (
!/[\d]/.test(key)
&& key !== 'Backspace'
&& key !== 'Delete'
&& key !== 'ArrowLeft'
&& key !== 'ArrowRight'
&& (key !== '.' || e.target.value.includes('.'))
) {
e.preventDefault();
}
}}
/>
</div>
<div className='GWASUI-column'>
<label htmlFor='input-maxOutlierCutoff'>
Maximum outlier cutoff
</label>
</div>
<div className='GWASUI-column'>
<InputNumber
id='input-maxOutlierCutoff'
value={maxOutlierCutoff}
onChange={(value) => {
setMaxOutlierCutoff(value);
handleChangeMaxOutlierCutoff(value);
}}
min={minOutlierCutoff || data.bins[0]?.start || 0}
max={data.bins[data.bins.length - 1]?.end || 100}
onKeyDown={(e) => {
const { key } = e;
// Allow only numeric keys, backspace, and delete, and one decimal point
if (
!/[\d]/.test(key)
&& key !== 'Backspace'
&& key !== 'Delete'
&& key !== 'ArrowLeft'
&& key !== 'ArrowRight'
&& (key !== '.' || e.target.value.includes('.'))
) {
e.preventDefault();
}
}}
/>
</div>
</div>
</div>
)}
</React.Fragment>
);
};

PhenotypeHistogram.propTypes = {
useInlineErrorMessages: PropTypes.bool,
dispatch: PropTypes.func,
selectedStudyPopulationCohort: PropTypes.object.isRequired,
selectedCovariates: PropTypes.array,
outcome: PropTypes.object,
selectedContinuousItem: PropTypes.object.isRequired,
useAnimation: PropTypes.bool,
handleChangeTransformation: PropTypes.func,
handleChangeMinOutlierCutoff: PropTypes.func,
handleChangeMaxOutlierCutoff: PropTypes.func,
};

PhenotypeHistogram.defaultProps = {
useInlineErrorMessages: false,
dispatch: null,
selectedCovariates: [],
outcome: null,
useAnimation: true,
handleChangeTransformation: null,
handleChangeMinOutlierCutoff: null,
handleChangeMaxOutlierCutoff: null,
};

export default PhenotypeHistogram;
Loading
Loading