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 = () => ( {
Team Project / {bannerText} {isEditable && ( - { showModal(); @@ -72,7 +73,7 @@ const TeamProjectHeader = ({ isEditable }) => { }} > - + )}
{isEditable && (