Update new unified dose management, style/other improvements
This commit is contained in:
@@ -34,9 +34,8 @@ const CHART_COLORS = {
|
||||
} as const;
|
||||
|
||||
const SimulationChart = ({
|
||||
idealProfile,
|
||||
deviatedProfile,
|
||||
correctedProfile,
|
||||
combinedProfile,
|
||||
templateProfile,
|
||||
chartView,
|
||||
showDayTimeOnXAxis,
|
||||
therapeuticRange,
|
||||
@@ -57,8 +56,6 @@ const SimulationChart = ({
|
||||
return ticks;
|
||||
}, [totalHours]);
|
||||
|
||||
const chartWidthPercentage = Math.max(100, (totalHours / ( (parseInt(displayedDays, 10) || 2) * 25)) * 100);
|
||||
|
||||
const chartDomain = React.useMemo(() => {
|
||||
const numMin = parseFloat(yAxisMin);
|
||||
const numMax = parseFloat(yAxisMax);
|
||||
@@ -71,49 +68,134 @@ const SimulationChart = ({
|
||||
const mergedData = React.useMemo(() => {
|
||||
const dataMap = new Map();
|
||||
|
||||
// Add ideal profile data
|
||||
idealProfile?.forEach((point: any) => {
|
||||
// Add combined profile data (actual plan with all days)
|
||||
combinedProfile?.forEach((point: any) => {
|
||||
dataMap.set(point.timeHours, {
|
||||
timeHours: point.timeHours,
|
||||
idealDamph: point.damph,
|
||||
idealLdx: point.ldx
|
||||
combinedDamph: point.damph,
|
||||
combinedLdx: point.ldx
|
||||
});
|
||||
});
|
||||
|
||||
// Add deviated profile data
|
||||
deviatedProfile?.forEach((point: any) => {
|
||||
// Add template profile data (regular plan only) if provided
|
||||
templateProfile?.forEach((point: any) => {
|
||||
const existing = dataMap.get(point.timeHours) || { timeHours: point.timeHours };
|
||||
dataMap.set(point.timeHours, {
|
||||
...existing,
|
||||
deviatedDamph: point.damph,
|
||||
deviatedLdx: point.ldx
|
||||
});
|
||||
});
|
||||
|
||||
// Add corrected profile data
|
||||
correctedProfile?.forEach((point: any) => {
|
||||
const existing = dataMap.get(point.timeHours) || { timeHours: point.timeHours };
|
||||
dataMap.set(point.timeHours, {
|
||||
...existing,
|
||||
correctedDamph: point.damph,
|
||||
correctedLdx: point.ldx
|
||||
templateDamph: point.damph,
|
||||
templateLdx: point.ldx
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(dataMap.values()).sort((a, b) => a.timeHours - b.timeHours);
|
||||
}, [idealProfile, deviatedProfile, correctedProfile]);
|
||||
}, [combinedProfile, templateProfile]);
|
||||
|
||||
// Calculate chart dimensions
|
||||
const [containerWidth, setContainerWidth] = React.useState(1000);
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const updateWidth = () => {
|
||||
if (containerRef.current) {
|
||||
setContainerWidth(containerRef.current.clientWidth);
|
||||
}
|
||||
};
|
||||
|
||||
updateWidth();
|
||||
window.addEventListener('resize', updateWidth);
|
||||
return () => window.removeEventListener('resize', updateWidth);
|
||||
}, []);
|
||||
|
||||
const simDays = parseInt(simulationDays, 10) || 3;
|
||||
const dispDays = parseInt(displayedDays, 10) || 2;
|
||||
|
||||
// Y-axis takes ~80px, scrollable area gets the rest
|
||||
const yAxisWidth = 80;
|
||||
const scrollableWidth = containerWidth - yAxisWidth;
|
||||
|
||||
// Calculate chart width for scrollable area
|
||||
const chartWidth = simDays <= dispDays
|
||||
? scrollableWidth
|
||||
: Math.ceil((scrollableWidth / dispDays) * simDays);
|
||||
|
||||
return (
|
||||
<div className="flex-grow w-full overflow-x-auto overflow-y-hidden">
|
||||
<div style={{ width: `${chartWidthPercentage}%`, height: '100%', minWidth: '100%' }}>
|
||||
<div ref={containerRef} className="flex-grow w-full flex flex-col overflow-y-hidden">
|
||||
{/* Fixed Legend at top */}
|
||||
<div style={{ height: 40, marginBottom: 8, paddingLeft: yAxisWidth + 10 }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={mergedData} margin={{ top: 20, right: 20, left: 0, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<LineChart data={mergedData} margin={{ top: 0, right: 20, left: 0, bottom: 0 }}>
|
||||
<Legend
|
||||
verticalAlign="top"
|
||||
align="left"
|
||||
height={36}
|
||||
wrapperStyle={{ paddingLeft: 0 }}
|
||||
/>
|
||||
{/* Invisible lines just to show in legend */}
|
||||
{(chartView === 'damph' || chartView === 'both') && (
|
||||
<Line
|
||||
dataKey="combinedDamph"
|
||||
name={`${t.dAmphetamine}`}
|
||||
stroke={CHART_COLORS.idealDamph}
|
||||
strokeWidth={2.5}
|
||||
dot={false}
|
||||
strokeOpacity={0}
|
||||
/>
|
||||
)}
|
||||
{(chartView === 'ldx' || chartView === 'both') && (
|
||||
<Line
|
||||
dataKey="combinedLdx"
|
||||
name={`${t.lisdexamfetamine}`}
|
||||
stroke={CHART_COLORS.idealLdx}
|
||||
strokeWidth={2}
|
||||
strokeDasharray="3 3"
|
||||
dot={false}
|
||||
strokeOpacity={0}
|
||||
/>
|
||||
)}
|
||||
{templateProfile && (chartView === 'damph' || chartView === 'both') && (
|
||||
<Line
|
||||
dataKey="templateDamph"
|
||||
name={`${t.dAmphetamine} (${t.regularPlan} ${t.continuation || 'continuation'})`}
|
||||
stroke={CHART_COLORS.idealDamph}
|
||||
strokeWidth={2}
|
||||
strokeDasharray="3 3"
|
||||
dot={false}
|
||||
strokeOpacity={0}
|
||||
/>
|
||||
)}
|
||||
{templateProfile && (chartView === 'ldx' || chartView === 'both') && (
|
||||
<Line
|
||||
dataKey="templateLdx"
|
||||
name={`${t.lisdexamfetamine} (${t.regularPlan} ${t.continuation || 'continuation'})`}
|
||||
stroke={CHART_COLORS.idealLdx}
|
||||
strokeWidth={1.5}
|
||||
strokeDasharray="3 3"
|
||||
dot={false}
|
||||
strokeOpacity={0}
|
||||
/>
|
||||
)}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
{/* Chart */}
|
||||
<div className="flex-grow flex overflow-y-hidden">
|
||||
{/* Scrollable chart area */}
|
||||
<div className="flex-grow overflow-x-auto overflow-y-hidden">
|
||||
<div style={{ width: chartWidth, height: '100%' }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart
|
||||
data={mergedData}
|
||||
margin={{ top: 0, right: 20, left: 0, bottom: 5 }}
|
||||
syncId="medPlanChart"
|
||||
>
|
||||
<XAxis
|
||||
dataKey="timeHours"
|
||||
type="number"
|
||||
domain={[0, totalHours]}
|
||||
ticks={chartTicks}
|
||||
tickCount={chartTicks.length}
|
||||
interval={0}
|
||||
tickFormatter={(h) => {
|
||||
if (showDayTimeOnXAxis) {
|
||||
// Show 24h repeating format (0-23h)
|
||||
@@ -126,19 +208,25 @@ const SimulationChart = ({
|
||||
xAxisId="hours"
|
||||
/>
|
||||
<YAxis
|
||||
label={{ value: t.concentration, angle: -90, position: 'insideLeft', offset: -10 }}
|
||||
domain={chartDomain as any}
|
||||
allowDecimals={false}
|
||||
/>
|
||||
yAxisId="concentration"
|
||||
//label={{ value: t.concentration, angle: -90, position: 'insideLeft', offset: -10 }}
|
||||
domain={chartDomain as any}
|
||||
allowDecimals={false}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: any, name) => [`${typeof value === 'number' ? value.toFixed(1) : value} ${t.ngml}`, name]}
|
||||
labelFormatter={(label) => `${t.hour.replace('h', 'Hour')}: ${label}${t.hour}`}
|
||||
labelFormatter={(label, payload) => {
|
||||
// Extract timeHours from the payload data point
|
||||
const timeHours = payload?.[0]?.payload?.timeHours ?? label;
|
||||
return `${t.hour.replace('h', 'Hour')}: ${timeHours}${t.hour}`;
|
||||
}}
|
||||
wrapperStyle={{ pointerEvents: 'none', zIndex: 200 }}
|
||||
allowEscapeViewBox={{ x: false, y: false }}
|
||||
cursor={{ stroke: CHART_COLORS.cursor, strokeWidth: 1, strokeDasharray: '1 1' }}
|
||||
position={{ y: 0 }}
|
||||
/>
|
||||
<Legend verticalAlign="top" align="left" height={36} wrapperStyle={{ zIndex: 100, marginLeft: 60 }} />
|
||||
/>
|
||||
<CartesianGrid strokeDasharray="1 1" xAxisId="hours" yAxisId="concentration" />
|
||||
|
||||
|
||||
{(chartView === 'damph' || chartView === 'both') && (
|
||||
<ReferenceLine
|
||||
@@ -147,6 +235,7 @@ const SimulationChart = ({
|
||||
stroke={CHART_COLORS.therapeuticMin}
|
||||
strokeDasharray="3 3"
|
||||
xAxisId="hours"
|
||||
yAxisId="concentration"
|
||||
/>
|
||||
)}
|
||||
{(chartView === 'damph' || chartView === 'both') && (
|
||||
@@ -156,10 +245,11 @@ const SimulationChart = ({
|
||||
stroke={CHART_COLORS.therapeuticMax}
|
||||
strokeDasharray="3 3"
|
||||
xAxisId="hours"
|
||||
yAxisId="concentration"
|
||||
/>
|
||||
)}
|
||||
|
||||
{[...Array(parseInt(simulationDays, 10) || 0).keys()].map(day => (
|
||||
{[...Array(parseInt(simulationDays, 10) || 3).keys()].map(day => (
|
||||
day > 0 && (
|
||||
<ReferenceLine
|
||||
key={day}
|
||||
@@ -174,85 +264,68 @@ const SimulationChart = ({
|
||||
{(chartView === 'damph' || chartView === 'both') && (
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="idealDamph"
|
||||
name={`${t.dAmphetamine} (Ideal)`}
|
||||
dataKey="combinedDamph"
|
||||
name={`${t.dAmphetamine}`}
|
||||
stroke={CHART_COLORS.idealDamph}
|
||||
strokeWidth={2.5}
|
||||
dot={false}
|
||||
xAxisId="hours"
|
||||
yAxisId="concentration"
|
||||
connectNulls
|
||||
/>
|
||||
)}
|
||||
{(chartView === 'ldx' || chartView === 'both') && (
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="idealLdx"
|
||||
name={`${t.lisdexamfetamine} (Ideal)`}
|
||||
dataKey="combinedLdx"
|
||||
name={`${t.lisdexamfetamine}`}
|
||||
stroke={CHART_COLORS.idealLdx}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
strokeDasharray="3 3"
|
||||
xAxisId="hours"
|
||||
yAxisId="concentration"
|
||||
connectNulls
|
||||
/>
|
||||
)}
|
||||
|
||||
{deviatedProfile && (chartView === 'damph' || chartView === 'both') && (
|
||||
{templateProfile && (chartView === 'damph' || chartView === 'both') && (
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="deviatedDamph"
|
||||
name={`${t.dAmphetamine} (Deviation)`}
|
||||
stroke={CHART_COLORS.deviatedDamph}
|
||||
dataKey="templateDamph"
|
||||
name={`${t.dAmphetamine} (${t.regularPlan} ${t.continuation || 'continuation'})`}
|
||||
stroke={CHART_COLORS.idealDamph}
|
||||
strokeWidth={2}
|
||||
strokeDasharray="5 5"
|
||||
strokeDasharray="3 3"
|
||||
dot={false}
|
||||
xAxisId="hours"
|
||||
yAxisId="concentration"
|
||||
connectNulls
|
||||
strokeOpacity={0.5}
|
||||
/>
|
||||
)}
|
||||
{deviatedProfile && (chartView === 'ldx' || chartView === 'both') && (
|
||||
{templateProfile && (chartView === 'ldx' || chartView === 'both') && (
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="deviatedLdx"
|
||||
name={`${t.lisdexamfetamine} (Deviation)`}
|
||||
stroke={CHART_COLORS.deviatedLdx}
|
||||
dataKey="templateLdx"
|
||||
name={`${t.lisdexamfetamine} (${t.regularPlan} ${t.continuation || 'continuation'})`}
|
||||
stroke={CHART_COLORS.idealLdx}
|
||||
strokeWidth={1.5}
|
||||
strokeDasharray="5 5"
|
||||
strokeDasharray="3 3"
|
||||
dot={false}
|
||||
xAxisId="hours"
|
||||
yAxisId="concentration"
|
||||
connectNulls
|
||||
strokeOpacity={0.5}
|
||||
/>
|
||||
)}
|
||||
|
||||
{correctedProfile && (chartView === 'damph' || chartView === 'both') && (
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="correctedDamph"
|
||||
name={`${t.dAmphetamine} (Correction)`}
|
||||
stroke={CHART_COLORS.correctedDamph}
|
||||
strokeWidth={2.5}
|
||||
strokeDasharray="3 7"
|
||||
dot={false}
|
||||
xAxisId="hours"
|
||||
connectNulls
|
||||
/>
|
||||
)}
|
||||
{correctedProfile && (chartView === 'ldx' || chartView === 'both') && (
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="correctedLdx"
|
||||
name={`${t.lisdexamfetamine} (Correction)`}
|
||||
stroke={CHART_COLORS.correctedLdx}
|
||||
strokeWidth={2}
|
||||
strokeDasharray="3 7"
|
||||
dot={false}
|
||||
xAxisId="hours"
|
||||
connectNulls
|
||||
/>
|
||||
)}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};export default SimulationChart;
|
||||
};
|
||||
|
||||
export default SimulationChart;
|
||||
|
||||
Reference in New Issue
Block a user