Skip to content

Commit 32f4bca

Browse files
author
Amy Chen
committed
add age, modify server side model schema
1 parent 45bfba5 commit 32f4bca

File tree

9 files changed

+197
-16
lines changed

9 files changed

+197
-16
lines changed

src/server/Model/Cohort/DatasetRecord.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ public PatientDemographic ToIdentifiedPatientDemographic()
277277
AddressState = AddressState.ValueElseUnknown(),
278278
Ethnicity = Ethnicity.ValueElseUnknown(),
279279
Gender = Gender.ValueElseUnknown(),
280+
Sex = Sex.ValueElseUnknown(),
280281
Age = Age,
281282
Language = Language.ValueElseUnknown(),
282283
MaritalStatus = MaritalStatus.ValueElseUnknown(),

src/server/Model/Cohort/PatientDemographic.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ public class PatientDemographic : ShapedDataset
2424
[Field(Name = DemographicColumns.Gender, Type = LeafType.String, Required = true)]
2525
public string Gender { get; set; }
2626

27+
[Field(Name = DemographicColumns.Sex, Type = LeafType.String)]
28+
public string Sex { get; set; }
29+
2730
// NOTE(ndobb) this gets calculated after sql runs, so there is no field for this
31+
[Field(Name = DemographicColumns.Age, Type = LeafType.Numeric)]
2832
public int? Age { get; set; }
2933

3034
[Field(Name = DemographicColumns.Language, Type = LeafType.String, Required = true)]

src/server/Services/Cohort/DemographicsExecutor.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ PatientDemographicRecord GetCohortRecord(ILeafDbDataReader reader)
154154
AddressState = reader.GetNullableString(Plan.AddressState?.Index),
155155
Ethnicity = reader.GetNullableString(Plan.Ethnicity?.Index),
156156
Gender = reader.GetNullableString(Plan.Gender?.Index),
157+
Sex = reader.GetNullableString(Plan.Sex?.Index),
157158
Language = reader.GetNullableString(Plan.Language?.Index),
158159
MaritalStatus = reader.GetNullableString(Plan.MaritalStatus?.Index),
159160
Race = reader.GetNullableString(Plan.Race?.Index),
@@ -164,7 +165,8 @@ PatientDemographicRecord GetCohortRecord(ILeafDbDataReader reader)
164165
BirthDate = reader.GetNullableDateTime(Plan.BirthDate?.Index),
165166
DeceasedDateTime = reader.GetNullableDateTime(Plan.DeathDate?.Index),
166167
Name = reader.GetNullableString(Plan.Name?.Index),
167-
Mrn = reader.GetNullableString(Plan.Mrn?.Index)
168+
Mrn = reader.GetNullableString(Plan.Mrn?.Index),
169+
Age = reader.GetNullableString(Plan.Age?.Index)
168170
};
169171

170172
rec.Age = rec.CalculateAge();

src/ui-client/src/actions/cohort/count.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,20 +169,34 @@ const getDemographics = () => {
169169
fetchDemographics(getState(), nr, queryId, cancelSource)
170170
.then(
171171
async demResponse => {
172-
173-
const cloneResponse = JSON.parse(JSON.stringify(demResponse));
174-
172+
175173
// Make sure query hasn't been reset
176174
if (getState().cohort.count.state !== CohortStateType.LOADED) { return; }
177175
atLeastOneSucceeded = true;
178-
console.log(" original ", cloneResponse.data);
179176
const demographics = demResponse.data as DemographicDTO;
177+
const getCategoryData = (category:string, patientData:[] = []) => {
178+
if (!category || !patientData || !patientData.length) return null;
179+
const matchedData = [...new Set(patientData.map(o => o[category]))];
180+
console.log("matched data ", matchedData.filter(o => !!o));
181+
if (!matchedData.length) return null;
182+
const getEntries = (category:string, data:[]) => Object.fromEntries(matchedData.filter(c => !!c).map(c => [c, data.filter(o => {
183+
console.log(o[category], c);
184+
return o[category] === c}).length]))
185+
return getEntries(category, patientData);
186+
};
187+
180188
console.log(" demo data ", demographics);
181-
const genders = [...new Set(demographics.patients.map(o => o.gender))];
182-
const genderData = Object.fromEntries(genders.map(gender => [gender, demographics.patients.filter(o => o.gender === gender).length]));
183-
console.log("genderData ", genderData)
189+
// const genders = [...new Set(demographics.patients.map(o => o.gender))];
190+
// const genderData = Object.fromEntries(genders.map(gender => [gender, demographics.patients.filter(o => o.gender === gender).length]));
191+
// console.log("genderData ", genderData)
192+
let categoryData = {};
193+
["gender", "sex", "race"].forEach(category => {
194+
categoryData[`${category}Data`] = getCategoryData(category, demographics.patients);
195+
});
196+
197+
console.log("category data ", categoryData)
184198

185-
dispatch(setNetworkVisualizationData(nr.id, {...demographics.statistics, patients: demographics.patients, genderData: genderData}));
199+
dispatch(setNetworkVisualizationData(nr.id, {...demographics.statistics, patients: demographics.patients, ...categoryData}));
186200
getPatientListFromNewBaseDataset(nr.id, demographics.patients, dispatch, getState);
187201

188202
if (demographics.columnNames) {
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/* Copyright (c) 2022, UW Medicine Research IT, University of Washington
2+
* Developed by Nic Dobbins and Cliff Spital, CRIO Sean Mooney
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import React from "react";
9+
import {
10+
Bar,
11+
BarChart,
12+
ResponsiveContainer,
13+
XAxis,
14+
YAxis,
15+
LabelList,
16+
Cell,
17+
} from "recharts";
18+
import { visualizationConfig } from "../../config/visualization";
19+
import { PatientCountMap } from "../../models/cohort/DemographicDTO";
20+
21+
interface Props {
22+
counts: PatientCountMap;
23+
delay: number;
24+
height: number;
25+
width: number;
26+
}
27+
28+
interface State {
29+
showAll: boolean;
30+
useDelay: boolean;
31+
}
32+
33+
export class Age extends React.PureComponent<Props, State> {
34+
private className = "visualization-age";
35+
private maxWidth = 800;
36+
private defaultDataLength = 20;
37+
38+
public constructor(props: Props) {
39+
super(props);
40+
this.state = {
41+
showAll: false,
42+
useDelay: true,
43+
};
44+
}
45+
46+
public render() {
47+
const config = visualizationConfig.demographics.age;
48+
const { height, width, patientData, delay } = this.props;
49+
const { showAll, useDelay } = this.state;
50+
const c = this.className;
51+
const del = useDelay ? delay : 0;
52+
const w = width > this.maxWidth ? this.maxWidth : width;
53+
54+
if (!patientData) return <div style={{margin: "24px"}}>No data available</div>;
55+
let data = Object.entries(this.formatData(patientData))
56+
.map(([key, value]) => ({ key, value }))
57+
.sort((a, b) => (a.value > b.value ? 0 : 1));
58+
const len = data.length;
59+
60+
if (!showAll) {
61+
data = data.slice(0, this.defaultDataLength);
62+
}
63+
64+
console.log(" age data ", data);
65+
return (
66+
<div className={`${c}-column`} style={{ height, width: w }}>
67+
{/* Show all toggle */}
68+
{len > this.defaultDataLength && (
69+
<div className="visualization-showall-toggle">
70+
<span
71+
className={`visualization-showall false ${
72+
showAll ? "" : "selected"
73+
}`}
74+
onClick={this.handleShowAllToggleClick.bind(null, false)}
75+
>{`Show top ${this.defaultDataLength} only`}</span>
76+
<span
77+
className={`visualization-showall true ${
78+
showAll ? "selected" : ""
79+
}`}
80+
onClick={this.handleShowAllToggleClick.bind(null, true)}
81+
>{`Show all ${len}`}</span>
82+
</div>
83+
)}
84+
85+
{/* Chart */}
86+
<div style={{ height }}>
87+
<ResponsiveContainer>
88+
<BarChart
89+
data={data}
90+
margin={{ top: 30, right: 30, left: 10, bottom: 5 }}
91+
>
92+
<XAxis dataKey="key" />
93+
<YAxis />
94+
<Bar
95+
animationBegin={del}
96+
barSize={config.barSize}
97+
dataKey="value"
98+
isAnimationActive={true}
99+
>
100+
{data.map((d, i) => (
101+
<Cell key={d.key} fill={this.color(i, config.colors)} />
102+
))}
103+
<LabelList
104+
dataKey="value"
105+
formatter={this.formatNumber}
106+
position="top"
107+
/>
108+
</Bar>
109+
</BarChart>
110+
</ResponsiveContainer>
111+
</div>
112+
</div>
113+
);
114+
}
115+
116+
private formatNumber = (val: any) => val.toLocaleString();
117+
private formatData = (data: []) => {
118+
if (!data) return null;
119+
const ageData = data.filter(o => !!o.age).map(o => o.age);
120+
let bracketData = {};
121+
bracketData["< 20"] = ageData.filter(n => parseInt(n) < 20).length;
122+
bracketData["20 - 29"] = ageData.filter(n => parseInt(n) >= 20 && parseInt(n) < 30).length;
123+
bracketData["30 - 39"] = ageData.filter(n => parseInt(n) >= 30 && parseInt(n) < 40).length;
124+
bracketData["40 - 49"] = ageData.filter(n => parseInt(n) >= 40 && parseInt(n) < 49).length;
125+
bracketData["50 - 59"] = ageData.filter(n => parseInt(n) >= 50 && parseInt(n) < 60).length;
126+
bracketData[">= 60"] = ageData.filter(n => parseInt(n) >= 60).length;
127+
return bracketData;
128+
};
129+
130+
private color = (i: number, colors: string[]): string => {
131+
const last = colors.length - 1;
132+
if (i <= last) {
133+
return colors[i];
134+
}
135+
return colors[i - Math.floor(i / last) * last - 1];
136+
};
137+
138+
private handleShowAllToggleClick = (showAll: boolean) => {
139+
this.setState({ showAll, useDelay: false });
140+
};
141+
}

src/ui-client/src/components/Visualize/AggregateDemographics.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import { Col, Container, Row } from 'reactstrap'
1010
import { CohortState } from '../../models/state/CohortState';
1111
import { SectionHeader } from '../Other/SectionHeader/SectionHeader';
1212
import { AgeByGender } from './AgeByGender';
13+
import { Age} from './Age';
1314
import { Binary } from './Binary';
15+
import { Gender} from './Gender';
1416
import { LanguageByHeritage } from './LanguageByHeritage';
1517
import { Religion } from './Religion';
16-
import { Gender} from './Gender';
1718
import { NihRaceEthnicityGenderTable } from './NihRaceEthnicityGenderTable';
1819

1920
export interface Props {
@@ -26,10 +27,10 @@ export default class AggregateDemographics extends React.PureComponent<Props> {
2627
private delayIncrementMs = 600;
2728

2829
public render() {
29-
const { ageByGenderData, binarySplitData, languageByHeritageData, religionData, nihRaceEthnicityData, genderData } = this.props.cohort.visualization.demographics;
30+
const { ageByGenderData, binarySplitData, languageByHeritageData, religionData, nihRaceEthnicityData, genderData, patients } = this.props.cohort.visualization.demographics;
3031
const colWidth = this.props.width / 2;
3132
const getDelay = (i: number): number => i * this.delayIncrementMs;
32-
33+
console.log("patients ", patients);
3334
return (
3435
<Container className="visualize-demographic-container aggregate" fluid={true}>
3536
<Row>
@@ -41,6 +42,7 @@ export default class AggregateDemographics extends React.PureComponent<Props> {
4142
height={this.props.height}
4243
width={colWidth}
4344
/> */}
45+
4446
<SectionHeader headerText="Gender" />
4547
<Gender
4648
counts={genderData}
@@ -59,6 +61,17 @@ export default class AggregateDemographics extends React.PureComponent<Props> {
5961
/>
6062
</Col>
6163
</Row>
64+
<Row>
65+
<Col lg={6} md={12} className="visualization-age-container">
66+
<SectionHeader headerText="Age" />
67+
<Age
68+
patientData={patients}
69+
delay={getDelay(0)}
70+
height={this.props.height}
71+
width={colWidth}
72+
/>
73+
</Col>
74+
</Row>
6275
<Row>
6376
<Col lg={6} md={12} className="visualization-languagebyheritage-container">
6477
<SectionHeader headerText="Ethnic Heritage by Language" />

src/ui-client/src/components/Visualize/Gender.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export class Gender extends React.PureComponent<Props, State> {
5050
const c = this.className;
5151
const del = useDelay ? delay : 0;
5252
const w = width > this.maxWidth ? this.maxWidth : width;
53+
54+
if (!counts) return <div style={{margin: "24px"}}>No data available</div>;
5355
let data = Object.entries(counts)
5456
.map(([key, value]) => ({ key, value }))
5557
.sort((a, b) => (a.value > b.value ? 0 : 1));
@@ -88,7 +90,7 @@ export class Gender extends React.PureComponent<Props, State> {
8890
margin={{ top: 30, right: 30, left: 10, bottom: 5 }}
8991
layout="vertical"
9092
>
91-
<XAxis type="number" allowDecimals={false} />
93+
<XAxis type="number" allowDecimals={false} hide={true}/>
9294
<YAxis dataKey="key" type="category" interval={0} width={150} />
9395
<Bar
9496
animationBegin={del}
@@ -126,7 +128,7 @@ export class Gender extends React.PureComponent<Props, State> {
126128
transman: "trans-man",
127129
fab: "nonbinary/other FAB",
128130
mab: "nonbinary/other MAB",
129-
}[val];
131+
}[String(val).toLowerCase().replace(/[-_]/g, "")];
130132
if (displayValue) return displayValue;
131133
return val??"other";
132134
};

src/ui-client/src/config/visualization.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,10 @@ export const visualizationConfig = {
4343
"rgb(148, 103, 189)", "rgb(197, 176, 213)", "rgb(140, 86, 75)", "rgb(196, 156, 148)", "rgb(227, 119, 194)", "rgb(247, 182, 210)", "rgb(127, 127, 127)", "rgb(199, 199, 199)",
4444
"rgb(188, 189, 34)", "rgb(219, 219, 141)", "rgb(23, 190, 207)", "rgb(158, 218, 229)"]
4545
},
46+
age: {
47+
barSize: 28,
48+
colors: [ "rgb(255, 152, 150)", "rgb(148, 103, 189)", "rgb(197, 176, 213)", "rgb(140, 86, 75)", "rgb(196, 156, 148)", "rgb(227, 119, 194)", "rgb(247, 182, 210)", "rgb(127, 127, 127)", "rgb(199, 199, 199)",
49+
"rgb(188, 189, 34)", "rgb(219, 219, 141)", "rgb(23, 190, 207)", "rgb(158, 218, 229)"]
50+
},
4651
}
47-
}
52+
}

src/ui-client/src/containers/Visualize/Visualize.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ class Visualize extends React.Component<Props, State> {
9090
let completedResponders = 0;
9191
const c = 'visualize';
9292
const { cohort, responders, auth } = this.props;
93-
console.log("cohort ", cohort)
9493
const demogHeight = 400;
9594
const respPadding = 200;
9695
const data: any = [];

0 commit comments

Comments
 (0)