-
Notifications
You must be signed in to change notification settings - Fork 21
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
[DC-710]Add mocked visualizations and total cohort count #5114
base: dev
Are you sure you want to change the base?
Changes from 8 commits
01d0bdd
e2d6084
2abd209
0e7647e
4282ced
22960c1
f2ae525
2f7f032
fc82127
01df407
9193dce
6cd5b82
cd2e344
c1cfdb4
d392616
17466c5
9709e28
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 |
---|---|---|
|
@@ -2,6 +2,7 @@ import { Spinner, useLoadedData } from '@terra-ui-packages/components'; | |
import _ from 'lodash/fp'; | ||
import React, { Fragment, useEffect, useRef, useState } from 'react'; | ||
import { div, h, h2, h3, strong } from 'react-hyperscript-helpers'; | ||
import Chart from 'src/components/Chart'; | ||
import { ButtonOutline, ButtonPrimary, GroupedSelect, Link, Select } from 'src/components/common'; | ||
import Slider from 'src/components/common/Slider'; | ||
import { icon } from 'src/components/icons'; | ||
|
@@ -17,6 +18,13 @@ import { | |
ProgramDataListCriteria, | ||
ProgramDataRangeCriteria, | ||
} from 'src/dataset-builder/DatasetBuilderUtils'; | ||
import { | ||
cohortAgeData, | ||
cohortDemographicData, | ||
CohortDemographics, | ||
generateAgeSeries, | ||
generateDemographicSeries, | ||
} from 'src/dataset-builder/TestConstants'; | ||
import { | ||
DataRepo, | ||
SnapshotBuilderCountResponse, | ||
|
@@ -592,48 +600,141 @@ export const CohortEditor: React.FC<CohortEditorProps> = (props) => { | |
getNextCriteriaIndex, | ||
} = props; | ||
const [cohort, setCohort] = useState<Cohort>(originalCohort); | ||
const [snapshotRequestParticipantCount, setSnapshotRequestParticipantCount] = | ||
useLoadedData<SnapshotBuilderCountResponse>(); | ||
const [cohortAges, setCohortAges] = useState<CohortDemographics>(cohortAgeData); | ||
const [cohortDemographics, setCohortDemographics] = useState<CohortDemographics>(cohortDemographicData); | ||
|
||
function chartOptions(cohortDemographics: CohortDemographics) { | ||
return { | ||
chart: { | ||
spacingLeft: 20, | ||
spacingRight: 30, | ||
height: cohortDemographics.height, | ||
style: { fontFamily: 'inherit' }, | ||
type: 'bar', | ||
}, | ||
legend: { enabled: cohortDemographics.legendEnabled }, | ||
plotOptions: { series: { stacking: 'normal' } }, | ||
series: cohortDemographics.series, | ||
title: { | ||
align: 'left', | ||
style: { fontSize: '16px', fontWeight: 'bold', color: '#333f52' }, | ||
text: cohortDemographics.title, | ||
}, | ||
tooltip: { | ||
followPointer: true, | ||
formatter() { | ||
// @ts-ignore | ||
// eslint-disable-next-line react/no-this-in-sfc | ||
const currCategory = _.find((category) => category.short === this.x, cohortDemographics.categories); | ||
const categoryDescription = currCategory.long || currCategory.short; | ||
if (cohortDemographics.showSeriesName) { | ||
// @ts-ignore | ||
// eslint-disable-next-line react/no-this-in-sfc | ||
return `${categoryDescription} <br/><span style="color:${this.color}">\u25CF</span> ${this.series.name}<br/> ${this.y}`; | ||
} | ||
// @ts-ignore | ||
// eslint-disable-next-line react/no-this-in-sfc | ||
return `${categoryDescription} <br/> ${this.y}`; | ||
}, | ||
}, | ||
xAxis: { | ||
categories: _.map('short', cohortDemographics.categories), | ||
crosshair: true, | ||
}, | ||
yAxis: { | ||
crosshair: true, | ||
title: { text: cohortDemographics.yTitle }, | ||
}, | ||
accessibility: { | ||
point: { | ||
descriptionFormatter: (point) => { | ||
return `${point.index + 1}. Category ${point.category}, ${point.series.name}: ${point.y}.`; | ||
}, | ||
}, | ||
}, | ||
exporting: { buttons: { contextButton: { x: -15 } } }, | ||
}; | ||
} | ||
|
||
const updateCohort = (updateCohort: (Cohort) => Cohort) => setCohort(updateCohort); | ||
|
||
return h(Fragment, [ | ||
h(CohortEditorContents, { | ||
updateCohort, | ||
cohort, | ||
snapshotId, | ||
snapshotBuilderSettings, | ||
onStateChange, | ||
getNextCriteriaIndex, | ||
}), | ||
// add div to cover page to footer | ||
div( | ||
{ | ||
style: { | ||
display: 'flex', | ||
backgroundColor: editorBackgroundColor, | ||
alignItems: 'end', | ||
flexDirection: 'row-reverse', | ||
padding: wideMargin, | ||
useEffect(() => { | ||
setSnapshotRequestParticipantCount( | ||
withErrorReporting(`Error fetching snapshot builder count for snapshot ${snapshotId}`)(async () => | ||
DataRepo() | ||
.snapshot(snapshotId) | ||
.getSnapshotBuilderCount(createSnapshotBuilderCountRequest([cohort])) | ||
) | ||
); | ||
}, [snapshotId, setSnapshotRequestParticipantCount, cohort]); | ||
|
||
useEffect(() => { | ||
setTimeout(() => { | ||
cohortAgeData.series = generateAgeSeries(3, 90); | ||
setCohortAges(cohortAgeData); | ||
}, 2000); | ||
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'm not sure this timeout is doing everything I want it to do. It does stop a double re-render (like without it, the graphs re-render when the async count method is called and again when it is returned), but I wanted to add a timeout so that it would return at a similar time as the async count call and it isn't doing that even if I increase the time. What am I missing here? 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. So, I wouldn't expect you to need both the timeout and the useEffect subscribed to snapshotRequestParticipantCount. This effect should be called whenever |
||
}, [snapshotRequestParticipantCount]); | ||
|
||
useEffect(() => { | ||
setTimeout(() => { | ||
cohortDemographicData.series = generateDemographicSeries(); | ||
setCohortDemographics(cohortDemographicData); | ||
}, 2000); | ||
}, [snapshotRequestParticipantCount]); | ||
|
||
return div({ style: { display: 'flex' } }, [ | ||
div([ | ||
h(CohortEditorContents, { | ||
updateCohort, | ||
cohort, | ||
snapshotId, | ||
snapshotBuilderSettings, | ||
onStateChange, | ||
getNextCriteriaIndex, | ||
}), | ||
// add div to cover page to footer | ||
div( | ||
{ | ||
style: { | ||
display: 'flex', | ||
backgroundColor: editorBackgroundColor, | ||
alignItems: 'end', | ||
flexDirection: 'row-reverse', | ||
padding: '0 3rem', | ||
}, | ||
}, | ||
}, | ||
[ | ||
h( | ||
ButtonPrimary, | ||
{ | ||
onClick: () => { | ||
updateCohorts((cohorts) => { | ||
const index = _.findIndex((c) => _.equals(c.name, cohort.name), cohorts); | ||
if (index === -1) { | ||
// Only add to selectedCohorts on creation of new cohort | ||
addSelectedCohort(cohort); | ||
} | ||
return _.set(`[${index === -1 ? cohorts.length : index}]`, cohort, cohorts); | ||
}); | ||
onStateChange(homepageState.new()); | ||
[ | ||
h( | ||
ButtonPrimary, | ||
{ | ||
onClick: () => { | ||
updateCohorts((cohorts) => { | ||
const index = _.findIndex((c) => _.equals(c.name, cohort.name), cohorts); | ||
if (index === -1) { | ||
// Only add to selectedCohorts on creation of new cohort | ||
addSelectedCohort(cohort); | ||
} | ||
return _.set(`[${index === -1 ? cohorts.length : index}]`, cohort, cohorts); | ||
}); | ||
onStateChange(homepageState.new()); | ||
}, | ||
}, | ||
}, | ||
['Save cohort'] | ||
), | ||
] | ||
), | ||
['Save cohort'] | ||
), | ||
] | ||
), | ||
]), | ||
div({ style: { backgroundColor: 'white', width: '42rem' } }, [ | ||
h2({ style: { marginLeft: '1rem' } }, [ | ||
'Total participant count: ', | ||
snapshotRequestParticipantCount.status === 'Ready' | ||
s-rubenstein marked this conversation as resolved.
Show resolved
Hide resolved
|
||
? formatCount(snapshotRequestParticipantCount.state.result.total) | ||
: h(Spinner), | ||
]), | ||
h(Chart, { options: chartOptions(cohortAges) }), | ||
h(Chart, { options: chartOptions(cohortDemographics) }), | ||
]), | ||
]); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -175,3 +175,113 @@ const dummyConcepts = [ | |
export const dummyGetConceptForId = (id: number): Concept => { | ||
return _.find({ id }, dummyConcepts)!; | ||
}; | ||
|
||
interface ChartLabel { | ||
short: string; | ||
long?: string; | ||
} | ||
interface ChartSeries { | ||
name?: string; | ||
data: number[]; | ||
} | ||
|
||
export interface CohortDemographics { | ||
categories: ChartLabel[]; | ||
series: ChartSeries[]; | ||
title: string; | ||
yTitle: string; | ||
height: string; | ||
legendEnabled: boolean; | ||
showSeriesName: boolean; | ||
} | ||
|
||
export const cohortAgeData = { | ||
categories: [ | ||
{ short: 'Female' }, | ||
{ short: 'Male' }, | ||
{ short: 'Other', long: 'Nonbinary, 2 Spirit, Genderqueer, etc.' }, | ||
], | ||
series: generateAgeSeries(3, 90), | ||
title: 'Gender identity', | ||
yTitle: 'AVERAGE AGE', | ||
height: '250rem', | ||
legendEnabled: false, | ||
showSeriesName: false, | ||
}; | ||
|
||
export const cohortDemographicData = { | ||
categories: [ | ||
{ short: 'Female' }, | ||
{ short: 'Female 18-44' }, | ||
{ short: 'Female 45-64' }, | ||
{ short: 'Female 65+' }, | ||
{ short: 'Male' }, | ||
{ short: 'Male 18-44' }, | ||
{ short: 'Male 45-64' }, | ||
{ short: 'Male 65+' }, | ||
{ short: 'Other', long: 'Nonbinary, 2 Spirit, Genderqueer, etc.' }, | ||
{ short: 'Other 18-44', long: 'Nonbinary, 2 Spirit, Genderqueer, etc. 18-44' }, | ||
{ short: 'Other 45-64', long: 'Nonbinary, 2 Spirit, Genderqueer, etc. 45-64' }, | ||
{ short: 'Other 65+', long: 'Nonbinary, 2 Spirit, Genderqueer, etc. 65+' }, | ||
], | ||
series: generateDemographicSeries(), | ||
title: 'Gender identity, current age, race', | ||
yTitle: 'OVERALL PERCENTAGE', | ||
height: '500rem', | ||
legendEnabled: true, | ||
showSeriesName: true, | ||
}; | ||
|
||
export function generateAgeSeries(numNumbers: number, max: number) { | ||
const randomNumbers: number[] = []; | ||
for (let i = 0; i < numNumbers; i++) { | ||
const randomNumber = Math.floor(Math.random() * max) + 1; | ||
randomNumbers.push(randomNumber); | ||
} | ||
return [{ name: 'Overall', data: randomNumbers }]; | ||
} | ||
|
||
function generateRandomNumbersThatAddUpTo(total: number, numNumbers: number): number[] { | ||
const randomNumbers: number[] = []; | ||
let remaining = total; | ||
for (let i = 0; i < numNumbers - 1; i++) { | ||
const randomNumber = Math.floor(Math.random() * remaining); | ||
remaining -= randomNumber; | ||
randomNumbers.push(randomNumber); | ||
} | ||
randomNumbers.push(remaining); | ||
return _.shuffle(randomNumbers); | ||
} | ||
|
||
// series and data will always be the same length | ||
function addToSeriesData(series: ChartSeries[], data: number[]) { | ||
for (let i = 0; i < series.length; i++) { | ||
series[i].data.push(data[i]); | ||
} | ||
} | ||
|
||
export function generateDemographicSeries(): ChartSeries[] { | ||
// order of data points is Female, Male, Other, Female 18-44, Female 45-64, Female 65+, Male 18-44, Male | ||
const series: ChartSeries[] = [ | ||
{ name: 'Asian', data: [] }, | ||
{ name: 'Black', data: [] }, | ||
{ name: 'White', data: [] }, | ||
{ name: 'Native American', data: [] }, | ||
{ name: 'Pacific Islander', data: [] }, | ||
]; | ||
// for each of the three gender identity totals, | ||
const genderTotals = generateRandomNumbersThatAddUpTo(100, 3); | ||
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. Just as a note - and its dummy data so not a big deal, but I think we would want it to add up to the number of participants, not to 100? That said, I think trying to get the dummy data perfect is not worth the effort, and just letting it render charts seems fine to me. 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. the graph is based on the overall percentage of participants not the number of participants. that's how it was in the mock |
||
for (const genderTotal of genderTotals) { | ||
// generate the race breakdown for the gender totals | ||
const genderTotalRaceBreakDown = generateRandomNumbersThatAddUpTo(genderTotal, 5); | ||
addToSeriesData(series, genderTotalRaceBreakDown); | ||
// get the three age group breakdowns | ||
const ageGroupGenderTotal = generateRandomNumbersThatAddUpTo(genderTotal, 3); | ||
// get race breakdowns for each of the gender age groups | ||
for (const genderAgeGroupTotal of ageGroupGenderTotal) { | ||
const genderAgeGroupRaceBreakdown = generateRandomNumbersThatAddUpTo(genderAgeGroupTotal, 5); | ||
addToSeriesData(series, genderAgeGroupRaceBreakdown); | ||
} | ||
} | ||
return series; | ||
} |
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.
The charts are meaty enough they may be worth an intermediate component, so that we can isolate the chart configuration. What are your thoughts on that?
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.
yeah I think that's a good idea