diff --git a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.jsx b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.jsx
index d985e0e1e4..a2188a11f5 100644
--- a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.jsx
+++ b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.jsx
@@ -7,7 +7,7 @@ import './AttritionTable.css';
const { Panel } = Collapse;
const AttritionTable = ({
- selectedCohort, outcome, covariates, tableType,
+ selectedCohort, outcome, covariates, tableType, modalInfo, setModalInfo,
}) => {
const [covariatesProcessed, setCovariatesProcessed] = useState([]);
// Creates an array of arrays such that given input arr [A,B,C]
@@ -109,6 +109,8 @@ const AttritionTable = ({
currentCovariateAndCovariatesFromPrecedingRows={[
applyAutoGenFilters(),
]}
+ modalInfo={modalInfo}
+ setModalInfo={setModalInfo}
/>
)}
@@ -129,6 +131,8 @@ const AttritionTable = ({
...item,
applyAutoGenFilters(),
]}
+ modalInfo={modalInfo}
+ setModalInfo={setModalInfo}
/>
))
@@ -146,6 +150,8 @@ AttritionTable.propTypes = {
outcome: PropTypes.object,
covariates: PropTypes.array,
tableType: PropTypes.string.isRequired,
+ modalInfo: PropTypes.object.isRequired,
+ setModalInfo: PropTypes.func.isRequired,
};
AttritionTable.defaultProps = {
diff --git a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.stories.jsx b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.stories.jsx
index 3efb25a2b9..1a1873f292 100644
--- a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.stories.jsx
+++ b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTable.stories.jsx
@@ -3,6 +3,10 @@ import { QueryClient, QueryClientProvider } from 'react-query';
import { rest } from 'msw';
import AttritionTable from './AttritionTable';
import { SourceContextProvider } from '../../../Utils/Source';
+import {
+ generateEulerTestData,
+ generateHistogramTestData,
+} from '../../../TestData/generateDiagramTestData';
import '../../../GWASApp.css';
export default {
@@ -15,18 +19,19 @@ const mockedQueryClient = new QueryClient();
const MockTemplate = () => {
const [covariateArrSizeTable1, setCovariateArrSizeTable1] = useState(10);
const [covariateArrSizeTable2, setCovariateArrSizeTable2] = useState(2);
-
const selectedCohort = {
+ size: 123,
cohort_definition_id: 123,
cohort_name: 'cohort name abc',
};
-
const outcome = {
+ size: 123,
variable_type: 'custom_dichotomous',
+ cohort_sizes: [10000, 20000],
+ cohort_names: ['name1', 'name2'],
cohort_ids: [1, 2],
provided_name: 'dichotomous test1',
};
-
const covariatesArrFirstTable = Array.from(
{ length: covariateArrSizeTable1 },
(_, i) => ({
@@ -41,6 +46,8 @@ const MockTemplate = () => {
(_, i) => ({
variable_type: 'custom_dichotomous',
provided_name: 'providednamebyuser' + i,
+ cohort_sizes: [10000, 20000],
+ cohort_names: ['name1', 'name2'],
cohort_ids: [i, i * i],
})
);
@@ -151,6 +158,26 @@ MockedSuccess.parameters = {
);
}
),
+ rest.post(
+ 'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId/by-histogram-concept-id/:conceptId',
+ (req, res, ctx) => {
+ return res(
+ ctx.delay(2000),
+ ctx.json({
+ bins: generateHistogramTestData(),
+ })
+ );
+ }
+ ),
+ rest.post(
+ 'http://:cohortmiddlewarepath/cohort-middleware/cohort-stats/check-overlap/by-source-id/:sourceid/by-cohort-definition-ids/:cohortdefinitionA/:cohortdefinitionB',
+ (req, res, ctx) => {
+ const { cohortmiddlewarepath } = req.params;
+ const { cohortdefinitionA } = req.params;
+ const { cohortdefinitionB } = req.params;
+ return res(ctx.delay(1100), ctx.json(generateEulerTestData()));
+ }
+ ),
],
},
};
diff --git a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTableRow.jsx b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTableRow.jsx
index e33cf873ce..8edbab6f3b 100644
--- a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTableRow.jsx
+++ b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTable/AttritionTableRow.jsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useQuery } from 'react-query';
-import { Spin } from 'antd';
+import { Spin, Button } from 'antd';
import { fetchConceptStatsByHareSubset } from '../../../Utils/cohortMiddlewareApi';
import queryConfig from '../../../../SharedUtils/QueryConfig';
import BarChart from '../ChartIcons/BarChart';
@@ -14,6 +14,8 @@ const AttritionTableRow = ({
rowObject,
outcome,
currentCovariateAndCovariatesFromPrecedingRows,
+ modalInfo,
+ setModalInfo,
}) => {
const sourceId = useSourceContext().source;
const [breakdownSize, setBreakdownSize] = useState(null);
@@ -130,13 +132,32 @@ const AttritionTableRow = ({
return value;
};
+ const determineModalTitle = () => {
+ let title = rowObject.variable_type === 'concept' ? 'Continuous ' : 'Dichotomous ';
+ title += rowType;
+ title += rowType === 'Outcome' ? ' Phenotype' : '';
+ return title;
+ };
+ const handleChartIconClick = () => {
+ setModalInfo({
+ ...modalInfo,
+ title: determineModalTitle(),
+ isModalOpen: true,
+ currentCovariateAndCovariatesFromPrecedingRows,
+ rowObject,
+ rowType,
+ });
+ };
+
return (
{rowType === 'Outcome' ? 'Outcome Phenotype' : rowType}
|
- {determineChartIcon(rowType)}
+
|
{rowName()} |
@@ -158,11 +179,15 @@ AttritionTableRow.propTypes = {
outcome: PropTypes.object,
rowObject: PropTypes.object,
selectedCohort: PropTypes.object.isRequired,
+ modalInfo: PropTypes.object,
+ setModalInfo: PropTypes.func,
};
AttritionTableRow.defaultProps = {
outcome: null,
rowObject: null,
+ modalInfo: null,
+ setModalInfo: () => null,
};
export default AttritionTableRow;
diff --git a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.css b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.css
new file mode 100644
index 0000000000..31293c15e7
--- /dev/null
+++ b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.css
@@ -0,0 +1,19 @@
+.attrition-table-modal .ant-modal-content {
+ width: 650px;
+ min-height: 480px;
+}
+
+.attrition-table-modal .ant-modal-body {
+ margin: 0 auto;
+}
+
+.attrition-table-modal h3 {
+ color: #2e77b8;
+ font-size: 14px;
+}
+
+.attrition-table-modal h4,
+.attrition-table-modal .histrogram-loading,
+.attrition-table-modal .euler-loading {
+ text-align: center;
+}
diff --git a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.jsx b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.jsx
new file mode 100644
index 0000000000..b7d949928a
--- /dev/null
+++ b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Modal } from 'antd';
+import './AttritionTableModal.css';
+import PhenotypeHistogram from '../../Diagrams/PhenotypeHistogram/PhenotypeHistogram';
+import CohortsOverlapDiagram from '../../Diagrams/CohortsOverlapDiagram/CohortsOverlapDiagram';
+
+const AttritionTableModal = ({ modalInfo, setModalInfo }) => {
+ const modalWidth = 650;
+ const rowIsOutcome = modalInfo.rowType === 'Outcome';
+
+ return (
+ {modalInfo.title}}
+ open={modalInfo.isModalOpen}
+ onOk={() => setModalInfo({ ...modalInfo, isModalOpen: false })}
+ onCancel={() => setModalInfo({ ...modalInfo, isModalOpen: false })}
+ footer={null}
+ width={modalWidth}
+ className='attrition-table-modal'
+ >
+ {modalInfo?.rowObject
+ && modalInfo.rowObject.variable_type === 'concept' && (
+
+ )}
+ {modalInfo?.rowObject
+ && modalInfo.rowObject.variable_type === 'custom_dichotomous' && (
+
+
+
+ )}
+
+ );
+};
+
+AttritionTableModal.propTypes = {
+ modalInfo: PropTypes.object.isRequired,
+ setModalInfo: PropTypes.func.isRequired,
+};
+
+export default AttritionTableModal;
diff --git a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.test.jsx b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.test.jsx
new file mode 100644
index 0000000000..adf4dc7aae
--- /dev/null
+++ b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableModal/AttritionTableModal.test.jsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import { QueryClient, QueryClientProvider } from 'react-query';
+import AttritionTableModal from './AttritionTableModal';
+import { SourceContextProvider } from '../../../Utils/Source';
+import {
+ fetchSimpleOverlapInfo,
+ useSourceFetch,
+} from '../../../Utils/cohortMiddlewareApi';
+
+// Mock the PhenotypeHistogram component
+jest.mock(
+ '../../Diagrams/PhenotypeHistogram/PhenotypeHistogram',
+ () => () => null,
+);
+jest.mock('../../../Utils/cohortMiddlewareApi');
+fetchSimpleOverlapInfo.mockResolvedValue({
+ cohort_overlap: {
+ case_control_overlap: 123,
+ },
+});
+useSourceFetch.mockResolvedValue({
+ sourceId: 2,
+ loading: false,
+});
+console.error = jest.fn();
+
+describe('AttritionTableModal', () => {
+ const mockSetModalInfo = jest.fn();
+ const defaultProps = {
+ modalInfo: {
+ isModalOpen: true,
+ title: 'Test Modal',
+ rowObject: {
+ variable_type: 'concept',
+ },
+ selectedCohort: {},
+ currentCovariateAndCovariatesFromPrecedingRows: [],
+ outcome: {},
+ },
+ setModalInfo: mockSetModalInfo,
+ };
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders modal with title and histogram content', () => {
+ render();
+ expect(screen.getByText('Test Modal')).toBeInTheDocument();
+ expect(
+ screen.queryByTestId('phenotype-histogram-diagram'),
+ ).toBeInTheDocument();
+ expect(screen.queryByTestId('euler-diagram')).not.toBeInTheDocument();
+ });
+
+ it('displays Euler Diagram when variable_type is custom_dichotomous', () => {
+ const mockedQueryClient = new QueryClient({
+ defaultOptions: {
+ queries: { retry: false },
+ },
+ });
+ const customProps = {
+ ...defaultProps,
+ modalInfo: {
+ ...defaultProps.modalInfo,
+ rowObject: {
+ variable_type: 'custom_dichotomous',
+ cohort_names: [1],
+ cohort_ids: [2],
+ cohort_sizes: [3],
+ },
+ },
+ };
+ render(
+
+
+
+
+ ,
+ );
+ expect(screen.queryByTestId('euler-diagram')).toBeInTheDocument();
+ expect(
+ screen.queryByTestId('phenotype-histogram-diagram'),
+ ).not.toBeInTheDocument();
+ });
+
+ it('does not render content if rowObject is not provided', () => {
+ const noRowObjectProps = {
+ ...defaultProps,
+ modalInfo: {
+ ...defaultProps.modalInfo,
+ rowObject: null,
+ },
+ };
+ render();
+ expect(screen.getByText('Test Modal')).toBeInTheDocument();
+ expect(
+ screen.queryByTestId('phenotype-histogram-diagram'),
+ ).not.toBeInTheDocument();
+ expect(screen.queryByTestId('euler-diagram')).not.toBeInTheDocument();
+ });
+});
diff --git a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableWrapper.jsx b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableWrapper.jsx
index 59b6e44401..85f1192606 100644
--- a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableWrapper.jsx
+++ b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableWrapper.jsx
@@ -1,16 +1,38 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import AttritionTable from './AttritionTable/AttritionTable';
+import AttritionTableModal from './AttritionTableModal/AttritionTableModal';
const AttritionTableWrapper = ({ covariates, selectedCohort, outcome }) => {
const useSecondTable = outcome?.variable_type === 'custom_dichotomous';
+
+ const [modalInfo, setModalInfo] = useState({
+ title: '',
+ isModalOpen: false,
+ selectedCohort: null,
+ currentCovariateAndCovariatesFromPrecedingRows: null,
+ outcome: null,
+ rowObject: null,
+ });
+ // Keep modal info up-to-date with changes in the data needed for data viz
+ useEffect(() => {
+ setModalInfo({
+ ...modalInfo,
+ selectedCohort,
+ outcome,
+ });
+ }, [selectedCohort, covariates, outcome]);
+
return (
+
{useSecondTable && (
{
selectedCohort={selectedCohort}
outcome={outcome}
tableType={'Control Cohort'}
+ modalInfo={modalInfo}
+ setModalInfo={setModalInfo}
/>
)}
diff --git a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableWrapper.stories.jsx b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableWrapper.stories.jsx
index 1007320e6a..a98ee7f8f1 100644
--- a/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableWrapper.stories.jsx
+++ b/src/Analysis/GWASApp/Components/AttritionTableWrapper/AttritionTableWrapper.stories.jsx
@@ -3,6 +3,10 @@ import { QueryClient, QueryClientProvider } from 'react-query';
import { rest } from 'msw';
import AttritionTableWrapper from './AttritionTableWrapper';
import { SourceContextProvider } from '../../Utils/Source';
+import {
+ generateEulerTestData,
+ generateHistogramTestData,
+} from '../../TestData/generateDiagramTestData';
import '../../GWASApp.css';
let rowCount = 0;
@@ -24,6 +28,7 @@ export const WithConceptOutcome = Template.bind({});
WithConceptOutcome.args = {
sourceId: 1,
outcome: {
+ size: 123,
variable_type: 'concept',
concept_id: 'id',
concept_name: 'concept name',
@@ -32,10 +37,14 @@ WithConceptOutcome.args = {
{
variable_type: 'custom_dichotomous',
provided_name: 'providednamebyuser',
+ cohort_sizes: [9999912, 9999932],
+ cohort_names: ['name1', 'name2'],
cohort_ids: [12, 32],
},
{
variable_type: 'custom_dichotomous',
+ cohort_sizes: [9999912, 9999932],
+ cohort_names: ['name1', 'name2'],
cohort_ids: [1, 2],
provided_name: 'dichotomous test1',
},
@@ -51,6 +60,7 @@ WithConceptOutcome.args = {
},
],
selectedCohort: {
+ size: 213,
cohort_definition_id: 123,
cohort_name: 'cohort name abc',
},
@@ -106,6 +116,26 @@ WithConceptOutcome.parameters = {
);
}
),
+ rest.post(
+ 'http://:cohortmiddlewarepath/cohort-middleware/histogram/by-source-id/:sourceid/by-cohort-definition-id/:cohortdefinitionId/by-histogram-concept-id/:conceptId',
+ (req, res, ctx) => {
+ return res(
+ ctx.delay(2000),
+ ctx.json({
+ bins: generateHistogramTestData(),
+ })
+ );
+ }
+ ),
+ rest.post(
+ 'http://:cohortmiddlewarepath/cohort-middleware/cohort-stats/check-overlap/by-source-id/:sourceid/by-cohort-definition-ids/:cohortdefinitionA/:cohortdefinitionB',
+ (req, res, ctx) => {
+ const { cohortmiddlewarepath } = req.params;
+ const { cohortdefinitionA } = req.params;
+ const { cohortdefinitionB } = req.params;
+ return res(ctx.delay(1100), ctx.json(generateEulerTestData()));
+ }
+ ),
],
},
};
@@ -115,7 +145,9 @@ WithDichotomousOutcome.args = {
...WithConceptOutcome.args,
outcome: {
variable_type: 'custom_dichotomous',
- cohort_ids: [1, 2],
+ cohort_sizes: [123, 293],
+ cohort_names: ['VZ 293 Participants', 'test1234 - team1 - test june'],
+ cohort_ids: [468, 453],
provided_name: 'dichotomous test1',
},
};
diff --git a/src/Analysis/GWASApp/Components/Covariates/CustomDichotomousCovariates.jsx b/src/Analysis/GWASApp/Components/Covariates/CustomDichotomousCovariates.jsx
index 256125a90d..781678b587 100644
--- a/src/Analysis/GWASApp/Components/Covariates/CustomDichotomousCovariates.jsx
+++ b/src/Analysis/GWASApp/Components/Covariates/CustomDichotomousCovariates.jsx
@@ -21,6 +21,14 @@ const CustomDichotomousCovariates = ({
const handleDichotomousSubmit = () => {
const dichotomous = {
variable_type: 'custom_dichotomous',
+ cohort_names: [
+ firstPopulation.cohort_name,
+ secondPopulation.cohort_name,
+ ],
+ cohort_sizes: [
+ firstPopulation.size,
+ secondPopulation.size,
+ ],
cohort_ids: [
firstPopulation.cohort_definition_id,
secondPopulation.cohort_definition_id,
diff --git a/src/Analysis/GWASApp/Components/Diagrams/CohortsOverlapDiagram/CohortsOverlapDiagram.jsx b/src/Analysis/GWASApp/Components/Diagrams/CohortsOverlapDiagram/CohortsOverlapDiagram.jsx
index 67345fdd1a..0fec31329e 100644
--- a/src/Analysis/GWASApp/Components/Diagrams/CohortsOverlapDiagram/CohortsOverlapDiagram.jsx
+++ b/src/Analysis/GWASApp/Components/Diagrams/CohortsOverlapDiagram/CohortsOverlapDiagram.jsx
@@ -2,7 +2,10 @@ import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useQueries } from 'react-query';
import { Spin, Button } from 'antd';
-import { fetchSimpleOverlapInfo, addCDFilter } from '../../../Utils/cohortMiddlewareApi';
+import {
+ fetchSimpleOverlapInfo,
+ addCDFilter,
+} from '../../../Utils/cohortMiddlewareApi';
import queryConfig from '../../../../SharedUtils/QueryConfig';
import Simple3SetsEulerDiagram from '../../../../SharedUtils/DataViz/Simple3Sets/Simple3SetsEulerDiagram';
import Simple3SetsLegend from '../../../../SharedUtils/DataViz/Simple3Sets/Simple3SetsLegend';
@@ -18,10 +21,13 @@ const CohortsOverlapDiagram = ({
selectedControlCohort,
selectedCovariates,
outcome,
+ useInlineErrorMessages,
+ diagramId,
}) => {
+ const [inlineErrorMessage, setInlineErrorMessage] = useState(null);
const { source } = useSourceContext();
const [showTextVersion, setShowTextVersion] = useState(false);
- const sourceId = source; // TODO - change name of source to sourceId for clarity
+ const sourceId = source;
const results = useQueries([
{
queryKey: [
@@ -58,6 +64,7 @@ const CohortsOverlapDiagram = ({
outcome,
),
...queryConfig,
+ refetchOnMount: true,
},
{
queryKey: [
@@ -134,15 +141,21 @@ const CohortsOverlapDiagram = ({
dataStudyPopulationAndCase.cohort_overlap.case_control_overlap === 0
|| dataStudyPopulationAndControl.cohort_overlap.case_control_overlap === 0
) {
- dispatch({
- type: ACTIONS.ADD_MESSAGE,
- payload: MESSAGES.OVERLAP_ERROR,
- });
+ if (useInlineErrorMessages) setInlineErrorMessage(❌ {MESSAGES.OVERLAP_ERROR.title});
+ if (dispatch !== null) {
+ dispatch({
+ type: ACTIONS.ADD_MESSAGE,
+ payload: MESSAGES.OVERLAP_ERROR,
+ });
+ }
} else {
- dispatch({
- type: ACTIONS.DELETE_MESSAGE,
- payload: MESSAGES.OVERLAP_ERROR,
- });
+ setInlineErrorMessage(null);
+ if (dispatch !== null) {
+ dispatch({
+ type: ACTIONS.DELETE_MESSAGE,
+ payload: MESSAGES.OVERLAP_ERROR,
+ });
+ }
}
}
}, [dataStudyPopulationAndCase, dataStudyPopulationAndControl]);
@@ -165,7 +178,11 @@ const CohortsOverlapDiagram = ({
statusStudyPopulationAndCaseAndControl,
].some((status) => status === 'loading')
) {
- return ;
+ return (
+
+ Fetching euler diagram data...
+
+ );
}
const eulerArgs = {
set1Size: selectedStudyPopulationCohort.size,
@@ -182,12 +199,14 @@ const CohortsOverlapDiagram = ({
set1Label: selectedStudyPopulationCohort.cohort_name,
set2Label: selectedCaseCohort.cohort_name,
set3Label: selectedControlCohort.cohort_name,
+ diagramId,
};
return (
{!showTextVersion && (
+ {inlineErrorMessage}
{
const { source } = useSourceContext();
+ const [inlineErrorMessage, setInlineErrorMessage] = useState(null);
const sourceId = source; // TODO - change name of source to sourceId for clarity
const { data, status } = useQuery(
[
@@ -39,17 +42,25 @@ const PhenotypeHistogram = ({
useEffect(() => {
// Validate and give error message if there is no data:
- if (data?.bins === null
- || (status === 'success' && data?.bins === undefined)) {
- dispatch({
- type: ACTIONS.ADD_MESSAGE,
- payload: MESSAGES.NO_BINS_ERROR,
- });
+ if (
+ data?.bins === null
+ || (status === 'success' && data?.bins === undefined)
+ ) {
+ setInlineErrorMessage(❌ {MESSAGES.NO_BINS_ERROR.title});
+ if (dispatch) {
+ dispatch({
+ type: ACTIONS.ADD_MESSAGE,
+ payload: MESSAGES.NO_BINS_ERROR,
+ });
+ }
} else {
- dispatch({
- type: ACTIONS.DELETE_MESSAGE,
- payload: MESSAGES.NO_BINS_ERROR,
- });
+ setInlineErrorMessage(null);
+ if (dispatch) {
+ dispatch({
+ type: ACTIONS.DELETE_MESSAGE,
+ payload: MESSAGES.NO_BINS_ERROR,
+ });
+ }
}
}, [data]);
@@ -70,21 +81,32 @@ const PhenotypeHistogram = ({
barColor: 'darkblue',
xAxisLegend: selectedContinuousItem.concept_name,
yAxisLegend: 'Persons',
+ useAnimation,
};
- return ;
+ return (
+
+ {useInlineErrorMessages && inlineErrorMessage}
+
+
+ );
};
PhenotypeHistogram.propTypes = {
- dispatch: PropTypes.func.isRequired,
+ useInlineErrorMessages: PropTypes.bool,
+ dispatch: PropTypes.func,
selectedStudyPopulationCohort: PropTypes.object.isRequired,
selectedCovariates: PropTypes.array,
outcome: PropTypes.object,
selectedContinuousItem: PropTypes.object.isRequired,
+ useAnimation: PropTypes.bool,
};
PhenotypeHistogram.defaultProps = {
+ useInlineErrorMessages: false,
+ dispatch: null,
selectedCovariates: [],
outcome: null,
+ useAnimation: true,
};
export default PhenotypeHistogram;
diff --git a/src/Analysis/GWASApp/TestData/generateDiagramTestData.js b/src/Analysis/GWASApp/TestData/generateDiagramTestData.js
new file mode 100644
index 0000000000..e5d2a0a8ee
--- /dev/null
+++ b/src/Analysis/GWASApp/TestData/generateDiagramTestData.js
@@ -0,0 +1,26 @@
+export const generateHistogramTestData = () => {
+ const minNumberOfBars = 5;
+ const maxNumberOfBars = 15;
+ const minPersonCount = 100;
+ const maxPersonCount = 2000;
+ const binSizeOffSet = 10;
+ const numberOfBars = Math.floor(Math.random() * maxNumberOfBars) + minNumberOfBars;
+ // Create an array of numberOfBars objects
+ const objectsArray = Array.from({ length: numberOfBars }, (v, i) => {
+ const start = i * binSizeOffSet;
+ return {
+ start,
+ end: start + binSizeOffSet,
+ personCount:
+ Math.floor(Math.random() * (maxPersonCount - minPersonCount))
+ + minPersonCount,
+ };
+ });
+ return objectsArray;
+};
+
+export const generateEulerTestData = () => ({
+ cohort_overlap: {
+ case_control_overlap: Math.floor(Math.random() * 50),
+ },
+});
diff --git a/src/Analysis/SharedUtils/DataViz/Histogram/Histogram.jsx b/src/Analysis/SharedUtils/DataViz/Histogram/Histogram.jsx
index 521c2099e0..17e58cba31 100644
--- a/src/Analysis/SharedUtils/DataViz/Histogram/Histogram.jsx
+++ b/src/Analysis/SharedUtils/DataViz/Histogram/Histogram.jsx
@@ -51,34 +51,38 @@ const Histogram = ({
barColor,
xAxisLegend,
yAxisLegend,
-}) => (
-
-
- formatNumber(tick)}
+ useAnimation,
+}) => {
+ const defaultAnimationTime = 400;
+ return (
+
+
-
-
-
-
-
- } />
-
-
-
-
-);
+ formatNumber(tick)}
+ >
+
+
+
+
+
+ } />
+
+
+
+
+ );
+};
Histogram.propTypes = {
data: PropTypes.array.isRequired,
@@ -89,6 +93,7 @@ Histogram.propTypes = {
barColor: PropTypes.string,
xAxisLegend: PropTypes.string,
yAxisLegend: PropTypes.string,
+ useAnimation: PropTypes.bool,
};
Histogram.defaultProps = {
@@ -97,6 +102,7 @@ Histogram.defaultProps = {
barColor: '#8884d8',
xAxisLegend: null,
yAxisLegend: null,
+ useAnimation: true,
};
export default Histogram;
diff --git a/src/Analysis/SharedUtils/DataViz/Simple3Sets/Simple3SetsEulerDiagram.jsx b/src/Analysis/SharedUtils/DataViz/Simple3Sets/Simple3SetsEulerDiagram.jsx
index 084727f038..806063cb58 100644
--- a/src/Analysis/SharedUtils/DataViz/Simple3Sets/Simple3SetsEulerDiagram.jsx
+++ b/src/Analysis/SharedUtils/DataViz/Simple3Sets/Simple3SetsEulerDiagram.jsx
@@ -20,6 +20,7 @@ const Simple3SetsEulerDiagram = ({
set13Label,
set23Label,
set123Label,
+ diagramId,
}) => {
const sets = [
{
@@ -78,13 +79,13 @@ const Simple3SetsEulerDiagram = ({
);
}
const chart = venn.VennDiagram().height(maxDiagramSize);
- d3.select('#euler')
+ d3.select(`#${diagramId}`)
.datum(sets)
.call(chart);
}, [sets]);
return (
-
+
);
};
@@ -103,6 +104,7 @@ Simple3SetsEulerDiagram.propTypes = {
set23Label: PropTypes.string,
set123Size: PropTypes.number.isRequired,
set123Label: PropTypes.string,
+ diagramId: PropTypes.string,
};
Simple3SetsEulerDiagram.defaultProps = {
@@ -113,6 +115,7 @@ Simple3SetsEulerDiagram.defaultProps = {
set13Label: null,
set23Label: null,
set123Label: null,
+ diagramId: 'euler',
};
export default Simple3SetsEulerDiagram;
diff --git a/src/Analysis/SharedUtils/TeamProject/TeamProjectHeader/Icons/EditIcon.jsx b/src/Analysis/SharedUtils/TeamProject/TeamProjectHeader/Icons/EditIcon.jsx
index 71cbdd5ade..ce10706064 100644
--- a/src/Analysis/SharedUtils/TeamProject/TeamProjectHeader/Icons/EditIcon.jsx
+++ b/src/Analysis/SharedUtils/TeamProject/TeamProjectHeader/Icons/EditIcon.jsx
@@ -2,7 +2,6 @@ import React from 'react';
const EditIcon = () => (
|