diff --git a/frontend/src/Components/Charts/ChartAccessories/ExtraPairPlots/ExtraPairViolin.tsx b/frontend/src/Components/Charts/ChartAccessories/ExtraPairPlots/ExtraPairViolin.tsx
index 601691dd..24faf77f 100644
--- a/frontend/src/Components/Charts/ChartAccessories/ExtraPairPlots/ExtraPairViolin.tsx
+++ b/frontend/src/Components/Charts/ChartAccessories/ExtraPairPlots/ExtraPairViolin.tsx
@@ -3,7 +3,7 @@ import {
} from 'react';
import { observer } from 'mobx-react';
import {
- scaleLinear, line, curveCatmullRom, format, scaleBand, select, axisBottom,
+ scaleLinear, line, min, max, curveCatmullRom, format, scaleBand, select, extent, axisBottom,
} from 'd3';
import { Tooltip } from '@mui/material';
import styled from '@emotion/styled';
@@ -28,6 +28,16 @@ type Props = {
secondaryMedianSet?: ExtraPairPoint['medianSet'];
};
+/**
+ * Get the x domain of the KDE for the attribute.
+ * @param dataSet - The data set to get the domain from.
+ * @returns The x domain of the KDE from the data set.
+ */
+function getAttributeXDomain(dataSet: ExtraPairPoint['data']) {
+ const allKdeX = Object.values(dataSet).flatMap((result) => result.kdeArray.map((point: { x: number }) => point.x));
+ return [min(allKdeX) ?? 0, max(allKdeX) ?? 20];
+}
+
function ExtraPairViolin({
kdeMax, dataSet, aggregationScaleDomain, aggregationScaleRange, medianSet, name, secondaryDataSet, secondaryMedianSet,
}: Props) {
@@ -38,10 +48,12 @@ function ExtraPairViolin({
return aggScale;
}, [aggregationScaleDomain, aggregationScaleRange]);
- const valueScale = scaleLinear().domain([0, 18]).range([0, ExtraPairWidth.Violin]);
- if (name === 'RISK') {
- valueScale.domain([0, 30]);
- }
+ // Get the x domain dynamically for the attribute
+ const attributeXDomain = getAttributeXDomain(dataSet);
+ // Set x scale for the entire attribute (Violin plots, etc.)
+ const valueScale = scaleLinear()
+ .domain(attributeXDomain)
+ .range([0, ExtraPairWidth.Violin]);
const lineFunction = useCallback(() => {
const calculatedKdeRange = secondaryDataSet ? [-0.25 * aggregationScale().bandwidth(), 0.25 * aggregationScale().bandwidth()] : [-0.5 * aggregationScale().bandwidth(), 0.5 * aggregationScale().bandwidth()];
@@ -113,9 +125,9 @@ function ExtraPairViolin({
diff --git a/frontend/src/Components/Charts/ChartAccessories/ExtraPairPlots/GeneratorExtraPair.tsx b/frontend/src/Components/Charts/ChartAccessories/ExtraPairPlots/GeneratorExtraPair.tsx
index 9b4efdbe..5d7531ca 100644
--- a/frontend/src/Components/Charts/ChartAccessories/ExtraPairPlots/GeneratorExtraPair.tsx
+++ b/frontend/src/Components/Charts/ChartAccessories/ExtraPairPlots/GeneratorExtraPair.tsx
@@ -130,7 +130,7 @@ function GeneratorExtraPair({
explanation = 'Percentage of Patients';
break;
case 'Violin':
- explanation = nameInput === 'RISK' ? 'Scaled 0-30' : (`Scaled 0-18, line at ${nameInput as string === 'Preop HGB' ? HGB_HIGH_STANDARD : HGB_LOW_STANDARD}`);
+ explanation = nameInput === 'DRG_WEIGHT' ? 'Scaled 0-30' : (`Scaled 0-18, line at ${nameInput as string === 'Preop HGB' ? HGB_HIGH_STANDARD : HGB_LOW_STANDARD}`);
break;
case 'BarChart':
explanation = `Scaled 0-${format('.4r')(max(Object.values(extraPairDataPoint.data)))}`;
diff --git a/frontend/src/HelperFunctions/ExtraPairDataGenerator.ts b/frontend/src/HelperFunctions/ExtraPairDataGenerator.ts
index 6fd839a5..072a5055 100644
--- a/frontend/src/HelperFunctions/ExtraPairDataGenerator.ts
+++ b/frontend/src/HelperFunctions/ExtraPairDataGenerator.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
- median, max, mean, sum,
+ median, min, max, mean, sum,
} from 'd3';
import { create as createpd } from 'pdfast';
import {
@@ -30,6 +30,16 @@ const outcomeDataGenerate = (aggregatedBy: string, name: string, label: string,
}) as ExtraPairPoint;
};
+/**
+ * Compute an attribute-wide min and max.
+ * @param input - The object to compute the min and max over.
+ * @returns The min and max of the object.
+ */
+function getAttributeMinMax(input: Record): { attributeMin: number, attributeMax: number } {
+ const attributeData = Object.values(input).flat();
+ return { attributeMin: min(attributeData)!, attributeMax: max(attributeData)! };
+}
+
export const generateExtrapairPlotData = (aggregatedBy: string, hemoglobinDataSet: SingleCasePoint[], extraPairArray: string[], data: BasicAggregatedDatePoint[]) => {
const newExtraPairData: ExtraPairPoint[] = [];
if (extraPairArray.length > 0) {
@@ -93,82 +103,30 @@ export const generateExtrapairPlotData = (aggregatedBy: string, hemoglobinDataSe
newExtraPairData.push(outcomeDataGenerate(aggregatedBy, 'IRON', 'Iron', data, hemoglobinDataSet));
break;
- case 'RISK':
- // let temporaryDataHolder: any = {}
- data.forEach((dataPoint: BasicAggregatedDatePoint) => {
- temporaryDataHolder[dataPoint.aggregateAttribute] = [];
- caseDictionary[dataPoint.aggregateAttribute] = new Set(dataPoint.caseIDList);
- });
- hemoglobinDataSet.forEach((ob: any) => {
- if (temporaryDataHolder[ob[aggregatedBy]] && caseDictionary[ob[aggregatedBy]].has(ob.CASE_ID)) {
- temporaryDataHolder[ob[aggregatedBy]].push(ob.DRG_WEIGHT);
- }
- });
- for (const [key, value] of Object.entries(temporaryDataHolder)) {
- medianData[key] = median(value as any);
- let pd = createpd(value, { min: 0, max: 30 });
- pd = [{ x: 0, y: 0 }].concat(pd);
-
- if ((value as any).length > 5) {
- kdeMaxTemp = (max(pd, (val: any) => val.y) as any) > kdeMaxTemp ? max(pd, (val: any) => val.y) : kdeMaxTemp;
- }
-
- const reversePd = pd.map((pair: any) => ({ x: pair.x, y: -pair.y })).reverse();
- pd = pd.concat(reversePd);
- newData[key] = { kdeArray: pd, dataPoints: value };
- }
- newExtraPairData.push({
- name: 'RISK', label: 'DRG Weight', data: newData, type: 'Violin', medianSet: medianData, kdeMax: kdeMaxTemp,
- });
- break;
-
+ case 'DRG_WEIGHT':
case 'PREOP_HEMO':
- data.forEach((dataPoint: BasicAggregatedDatePoint) => {
- newData[dataPoint.aggregateAttribute] = [];
- caseDictionary[dataPoint.aggregateAttribute] = new Set(dataPoint.caseIDList);
- });
-
- hemoglobinDataSet.forEach((ob: SingleCasePoint) => {
- const resultValue = ob.PREOP_HEMO;
- if (newData[ob[aggregatedBy]] && resultValue > 0 && caseDictionary[ob[aggregatedBy]].has(ob.CASE_ID)) {
- newData[ob[aggregatedBy]].push(resultValue);
- }
- });
- for (const prop in newData) {
- if (Object.hasOwn(newData, prop)) {
- medianData[prop] = median(newData[prop]);
- let pd = createpd(newData[prop], { width: 2, min: 0, max: 18 });
- pd = [{ x: 0, y: 0 }].concat(pd);
-
- if ((newData[prop] as any).length > 5) {
- kdeMaxTemp = (max(pd, (val: any) => val.y) as any) > kdeMaxTemp ? max(pd, (val: any) => val.y) : kdeMaxTemp;
- }
-
- const reversePd = pd.map((pair: any) => ({ x: pair.x, y: -pair.y })).reverse();
- pd = pd.concat(reversePd);
- newData[prop] = { kdeArray: pd, dataPoints: newData[prop] };
- }
- }
- newExtraPairData.push({
- name: 'PREOP_HEMO', label: 'Preop HGB', data: newData, type: 'Violin', medianSet: medianData, kdeMax: kdeMaxTemp,
- });
- break;
- case 'POSTOP_HEMO':
+ case 'POSTOP_HEMO': {
// let newData = {} as any;
data.forEach((dataPoint: BasicAggregatedDatePoint) => {
newData[dataPoint.aggregateAttribute] = [];
caseDictionary[dataPoint.aggregateAttribute] = new Set(dataPoint.caseIDList);
});
hemoglobinDataSet.forEach((ob: any) => {
- const resultValue = ob.POSTOP_HEMO;
+ const resultValue = ob[variable];
if (newData[ob[aggregatedBy]] && resultValue > 0 && caseDictionary[ob[aggregatedBy]].has(ob.CASE_ID)) {
newData[ob[aggregatedBy]].push(resultValue);
}
});
+
+ // Compute the attribute-wide min and max for 'POSTOP_HEMO'
+ const { attributeMin, attributeMax } = getAttributeMinMax(newData);
+
for (const prop in newData) {
if (Object.hasOwn(newData, prop)) {
medianData[prop] = median(newData[prop]);
- let pd = createpd(newData[prop], { width: 2, min: 0, max: 18 });
+
+ // Create the KDE for the attribute using the computed min and max
+ let pd = createpd(newData[prop], { width: 2, min: attributeMin, max: attributeMax });
pd = [{ x: 0, y: 0 }].concat(pd);
if ((newData[prop] as any).length > 5) {
@@ -181,9 +139,10 @@ export const generateExtrapairPlotData = (aggregatedBy: string, hemoglobinDataSe
}
}
newExtraPairData.push({
- name: 'POSTOP_HEMO', label: 'Postop HGB', data: newData, type: 'Violin', medianSet: medianData, kdeMax: kdeMaxTemp,
+ name: variable, label: variable, data: newData, type: 'Violin', medianSet: medianData, kdeMax: kdeMaxTemp,
});
break;
+ }
default:
break;
}
diff --git a/frontend/src/Presets/DataDict.ts b/frontend/src/Presets/DataDict.ts
index 367a4481..1298c3b5 100644
--- a/frontend/src/Presets/DataDict.ts
+++ b/frontend/src/Presets/DataDict.ts
@@ -46,7 +46,7 @@ export const OutcomeOptions: { key: typeof OUTCOMES[number]; value: string }[] =
{ key: 'AMICAR', value: 'Amicar' },
];
-export const EXTRA_PAIR_OPTIONS = [...OUTCOMES, 'PREOP_HEMO', 'POSTOP_HEMO', 'TOTAL_TRANS', 'PER_CASE', 'ZERO_TRANS', 'RISK'] as const;
+export const EXTRA_PAIR_OPTIONS = [...OUTCOMES, 'PREOP_HEMO', 'POSTOP_HEMO', 'TOTAL_TRANS', 'PER_CASE', 'ZERO_TRANS', 'DRG_WEIGHT'] as const;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const ExtraPairOptions: { key: typeof EXTRA_PAIR_OPTIONS[number]; value: string }[] = (OutcomeOptions as any).concat([
{ key: 'PREOP_HEMO', value: 'Preop HGB' },
@@ -54,7 +54,7 @@ export const ExtraPairOptions: { key: typeof EXTRA_PAIR_OPTIONS[number]; value:
{ key: 'TOTAL_TRANS', value: 'Total Transfused' },
{ key: 'PER_CASE', value: 'Per Case' },
{ key: 'ZERO_TRANS', value: 'Zero Transfused' },
- { key: 'RISK', value: 'APR-DRG Weight' },
+ { key: 'DRG_WEIGHT', value: 'APR-DRG Weight' },
]);
const dumbbellValueOptions = [{ key: 'HGB_VALUE', value: 'Hemoglobin Value' }];
@@ -89,7 +89,6 @@ export const AcronymDictionary = {
TVR: 'Tricuspid Valve Repair',
PVR: 'Proliferative Vitreoretinopathy',
VENT: 'Over 24 Hours Ventilator Usage',
- RISK: 'Diagnosis-related Group Weight (Risk Score)',
'Zero %': 'Zero Transfusion',
DEATH: 'Death in hospital',
STROKE: 'Stroke',