@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2821 → 5.4.2-pre.2825
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/dist/{791.js → 343.js} +1 -1
- package/dist/343.js.map +1 -0
- package/dist/kenyaemr-esm-patient-clinical-view-app.js +3 -3
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +27 -27
- package/dist/main.js +17 -17
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +60 -9
- package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +21 -17
- package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +70 -6
- package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +30 -7
- package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +63 -6
- package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +100 -46
- package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +2 -1
- package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +231 -133
- package/src/maternal-and-child-health/partography/partograph.component.tsx +141 -30
- package/src/maternal-and-child-health/partography/partography-data-form.scss +31 -12
- package/src/maternal-and-child-health/partography/partography.scss +22 -86
- package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +15 -1
- package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +170 -1
- package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +88 -15
- package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +138 -1
- package/dist/791.js.map +0 -1
|
@@ -214,54 +214,98 @@ const CervixGraph: React.FC<CervixGraphProps> = ({
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
});
|
|
217
|
-
const
|
|
218
|
-
|
|
217
|
+
const svg = chartContainer.querySelector('svg');
|
|
218
|
+
if (svg) {
|
|
219
|
+
svg.querySelectorAll('[data-custom-marker]').forEach((marker) => marker.remove());
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const circles = chartContainer.querySelectorAll('svg circle');
|
|
223
|
+
let processedCount = 0;
|
|
224
|
+
circles.forEach((circle, index) => {
|
|
219
225
|
const circleElement = circle as SVGCircleElement;
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
226
|
+
const cx = parseFloat(circleElement.getAttribute('cx') || '0');
|
|
227
|
+
const cy = parseFloat(circleElement.getAttribute('cy') || '0');
|
|
228
|
+
const fill = (circleElement.getAttribute('fill') || circleElement.style.fill || '').toLowerCase();
|
|
229
|
+
const stroke = (circleElement.getAttribute('stroke') || circleElement.style.stroke || '').toLowerCase();
|
|
230
|
+
const isGreen =
|
|
231
|
+
fill.includes('green') ||
|
|
232
|
+
stroke.includes('green') ||
|
|
233
|
+
fill.includes('#42be65') ||
|
|
234
|
+
stroke.includes('#42be65') ||
|
|
235
|
+
fill.includes('#24a148') ||
|
|
236
|
+
stroke.includes('#24a148') ||
|
|
237
|
+
fill.includes('#198038') ||
|
|
238
|
+
stroke.includes('#198038') ||
|
|
239
|
+
fill.includes('rgb(66, 190, 101)') ||
|
|
240
|
+
stroke.includes('rgb(66, 190, 101)') ||
|
|
241
|
+
fill.includes('rgb(36, 161, 72)') ||
|
|
242
|
+
stroke.includes('rgb(36, 161, 72)') ||
|
|
243
|
+
fill.includes('rgb(25, 128, 56)') ||
|
|
244
|
+
stroke.includes('rgb(25, 128, 56)') ||
|
|
245
|
+
fill.includes('#008000') ||
|
|
246
|
+
stroke.includes('#008000') ||
|
|
247
|
+
fill.includes('#00ff00') ||
|
|
248
|
+
stroke.includes('#00ff00') ||
|
|
249
|
+
fill.includes('#228b22') ||
|
|
250
|
+
stroke.includes('#228b22') ||
|
|
251
|
+
fill.includes('#32cd32') ||
|
|
252
|
+
stroke.includes('#32cd32') ||
|
|
253
|
+
/^#[0-4][0-9a-f][a-f0-9][0-9a-f][a-f0-9][0-9a-f]/.test(fill) ||
|
|
254
|
+
/^#[0-4][0-9a-f][a-f0-9][0-9a-f][a-f0-9][0-9a-f]/.test(stroke);
|
|
255
|
+
|
|
256
|
+
if (isGreen && svg && cx > 0 && cy > 0) {
|
|
257
|
+
processedCount++;
|
|
258
|
+
circleElement.style.opacity = '0';
|
|
247
259
|
circleElement.style.display = 'none';
|
|
260
|
+
const size = 8;
|
|
261
|
+
const xColor = '#24a148';
|
|
262
|
+
|
|
263
|
+
const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
264
|
+
line1.setAttribute('x1', (cx - size).toString());
|
|
265
|
+
line1.setAttribute('y1', (cy - size).toString());
|
|
266
|
+
line1.setAttribute('x2', (cx + size).toString());
|
|
267
|
+
line1.setAttribute('y2', (cy + size).toString());
|
|
268
|
+
line1.setAttribute('stroke', xColor);
|
|
269
|
+
line1.setAttribute('stroke-width', '3');
|
|
270
|
+
line1.setAttribute('stroke-linecap', 'round');
|
|
271
|
+
line1.setAttribute('data-custom-marker', 'cervical-x');
|
|
272
|
+
|
|
273
|
+
const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
274
|
+
line2.setAttribute('x1', (cx + size).toString());
|
|
275
|
+
line2.setAttribute('y1', (cy - size).toString());
|
|
276
|
+
line2.setAttribute('x2', (cx - size).toString());
|
|
277
|
+
line2.setAttribute('y2', (cy + size).toString());
|
|
278
|
+
line2.setAttribute('stroke', xColor);
|
|
279
|
+
line2.setAttribute('stroke-width', '3');
|
|
280
|
+
line2.setAttribute('stroke-linecap', 'round');
|
|
281
|
+
line2.setAttribute('data-custom-marker', 'cervical-x');
|
|
282
|
+
|
|
283
|
+
svg.appendChild(line1);
|
|
284
|
+
svg.appendChild(line2);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
if (processedCount === 0) {
|
|
288
|
+
circles.forEach((circle, index) => {
|
|
289
|
+
const circleElement = circle as SVGCircleElement;
|
|
248
290
|
const cx = parseFloat(circleElement.getAttribute('cx') || '0');
|
|
249
291
|
const cy = parseFloat(circleElement.getAttribute('cy') || '0');
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
292
|
+
if (cy > 200 && cy < 400 && cx > 100 && svg) {
|
|
293
|
+
const isAlreadyHidden = circleElement.style.opacity === '0' || circleElement.style.display === 'none';
|
|
294
|
+
|
|
295
|
+
if (!isAlreadyHidden) {
|
|
296
|
+
circleElement.style.opacity = '0';
|
|
297
|
+
const size = 8;
|
|
298
|
+
const xColor = '#24a148';
|
|
299
|
+
|
|
256
300
|
const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
257
301
|
line1.setAttribute('x1', (cx - size).toString());
|
|
258
302
|
line1.setAttribute('y1', (cy - size).toString());
|
|
259
303
|
line1.setAttribute('x2', (cx + size).toString());
|
|
260
304
|
line1.setAttribute('y2', (cy + size).toString());
|
|
261
305
|
line1.setAttribute('stroke', xColor);
|
|
262
|
-
line1.setAttribute('stroke-width', '
|
|
306
|
+
line1.setAttribute('stroke-width', '3');
|
|
263
307
|
line1.setAttribute('stroke-linecap', 'round');
|
|
264
|
-
line1.setAttribute('data-
|
|
308
|
+
line1.setAttribute('data-custom-marker', 'cervical-x-alt');
|
|
265
309
|
|
|
266
310
|
const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
267
311
|
line2.setAttribute('x1', (cx + size).toString());
|
|
@@ -269,30 +313,40 @@ const CervixGraph: React.FC<CervixGraphProps> = ({
|
|
|
269
313
|
line2.setAttribute('x2', (cx - size).toString());
|
|
270
314
|
line2.setAttribute('y2', (cy + size).toString());
|
|
271
315
|
line2.setAttribute('stroke', xColor);
|
|
272
|
-
line2.setAttribute('stroke-width', '
|
|
316
|
+
line2.setAttribute('stroke-width', '3');
|
|
273
317
|
line2.setAttribute('stroke-linecap', 'round');
|
|
274
|
-
line2.setAttribute('data-
|
|
318
|
+
line2.setAttribute('data-custom-marker', 'cervical-x-alt');
|
|
275
319
|
|
|
276
|
-
|
|
277
|
-
|
|
320
|
+
svg.appendChild(line1);
|
|
321
|
+
svg.appendChild(line2);
|
|
278
322
|
}
|
|
279
323
|
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
282
326
|
}
|
|
283
327
|
};
|
|
284
328
|
|
|
285
|
-
|
|
329
|
+
// Apply styling with multiple aggressive timing attempts
|
|
330
|
+
const timer1 = setTimeout(applyChartStyling, 50);
|
|
331
|
+
const timer2 = setTimeout(applyChartStyling, 200);
|
|
332
|
+
const timer3 = setTimeout(applyChartStyling, 500);
|
|
333
|
+
const timer4 = setTimeout(applyChartStyling, 1000);
|
|
334
|
+
const timer5 = setTimeout(applyChartStyling, 2000);
|
|
335
|
+
|
|
286
336
|
const chartContainer = document.querySelector(`[data-chart-id="cervix"]`);
|
|
287
337
|
if (chartContainer && window.MutationObserver) {
|
|
288
338
|
observer = new MutationObserver(() => {
|
|
289
|
-
applyChartStyling
|
|
339
|
+
setTimeout(applyChartStyling, 100);
|
|
290
340
|
});
|
|
291
341
|
observer.observe(chartContainer, { childList: true, subtree: true });
|
|
292
342
|
}
|
|
293
343
|
|
|
294
344
|
return () => {
|
|
295
|
-
clearTimeout(
|
|
345
|
+
clearTimeout(timer1);
|
|
346
|
+
clearTimeout(timer2);
|
|
347
|
+
clearTimeout(timer3);
|
|
348
|
+
clearTimeout(timer4);
|
|
349
|
+
clearTimeout(timer5);
|
|
296
350
|
if (observer) {
|
|
297
351
|
observer.disconnect();
|
|
298
352
|
}
|
|
@@ -19,7 +19,8 @@ const OxytocinGraph: React.FC<OxytocinGraphProps> = ({ data }) => {
|
|
|
19
19
|
const emptyColumns = Array.from({ length: 20 }, (_, i) => `grid-${i + 1}`);
|
|
20
20
|
|
|
21
21
|
if (data && data.length > 0) {
|
|
22
|
-
|
|
22
|
+
// Use actual time slots from data instead of generic data-1, data-2
|
|
23
|
+
const dataSlots = data.map((item) => item.timeSlot);
|
|
23
24
|
const remainingEmpty = Array.from({ length: 20 - data.length }, (_, i) => `empty-${i + 1}`);
|
|
24
25
|
return [...dataSlots, ...remainingEmpty];
|
|
25
26
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { LineChart } from '@carbon/charts-react';
|
|
4
4
|
import styles from '../partography.scss';
|
|
5
|
-
import { getColorForGraph } from '../types';
|
|
5
|
+
import { getColorForGraph, SVG_NAMESPACE } from '../types';
|
|
6
6
|
|
|
7
7
|
export interface PulseBPData {
|
|
8
8
|
pulse: number;
|
|
@@ -35,9 +35,9 @@ const PULSE_BP_CHART_OPTIONS = {
|
|
|
35
35
|
title: '',
|
|
36
36
|
mapsTo: 'index',
|
|
37
37
|
scaleType: ScaleTypes.LINEAR,
|
|
38
|
-
domain: [0,
|
|
38
|
+
domain: [0, 20],
|
|
39
39
|
ticks: {
|
|
40
|
-
values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
|
40
|
+
values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
|
|
41
41
|
formatter: (index: number) => '',
|
|
42
42
|
},
|
|
43
43
|
},
|
|
@@ -70,7 +70,7 @@ const PULSE_BP_CHART_OPTIONS = {
|
|
|
70
70
|
grid: {
|
|
71
71
|
x: {
|
|
72
72
|
enabled: true,
|
|
73
|
-
numberOfTicks:
|
|
73
|
+
numberOfTicks: 21,
|
|
74
74
|
},
|
|
75
75
|
y: {
|
|
76
76
|
enabled: true,
|
|
@@ -104,7 +104,7 @@ const PulseBPGraph: React.FC<PulseBPGraphProps> = ({ data }) => {
|
|
|
104
104
|
return now.toLocaleDateString() + ' ' + now.toLocaleTimeString();
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
const actualData: PulseBPData[] = data.length > 0 ? data : [];
|
|
107
|
+
const actualData: PulseBPData[] = React.useMemo(() => (data.length > 0 ? data : []), [data]);
|
|
108
108
|
|
|
109
109
|
const pulseChartData: ChartDataPoint[] = [];
|
|
110
110
|
|
|
@@ -113,154 +113,252 @@ const PulseBPGraph: React.FC<PulseBPGraphProps> = ({ data }) => {
|
|
|
113
113
|
pulseChartData.push({
|
|
114
114
|
index: index,
|
|
115
115
|
value: item.pulse,
|
|
116
|
-
group: 'Pulse',
|
|
116
|
+
group: 'Pulse-Line',
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
actualData.forEach((item, index) => {
|
|
120
|
+
pulseChartData.push({
|
|
121
|
+
index: index,
|
|
122
|
+
value: item.systolicBP,
|
|
123
|
+
group: 'Systolic-Points',
|
|
124
|
+
});
|
|
125
|
+
pulseChartData.push({
|
|
126
|
+
index: index,
|
|
127
|
+
value: item.diastolicBP,
|
|
128
|
+
group: 'Diastolic-Points',
|
|
117
129
|
});
|
|
118
130
|
});
|
|
119
131
|
} else {
|
|
120
|
-
pulseChartData.push({ index: 0, value: 120, group: 'Pulse' });
|
|
132
|
+
pulseChartData.push({ index: 0, value: 120, group: 'Pulse-Line' });
|
|
121
133
|
}
|
|
122
134
|
|
|
123
135
|
const finalChartData = [...pulseChartData];
|
|
136
|
+
const colorScale: { [key: string]: string } = {
|
|
137
|
+
'Pulse-Line': actualData.length > 0 ? getColorForGraph('blue') : 'transparent',
|
|
138
|
+
'Systolic-Points': 'transparent',
|
|
139
|
+
'Diastolic-Points': 'transparent',
|
|
140
|
+
};
|
|
124
141
|
|
|
125
142
|
const chartOptions = {
|
|
126
143
|
...PULSE_BP_CHART_OPTIONS,
|
|
127
144
|
title: 'Pulse and Blood Pressure Monitoring',
|
|
128
145
|
color: {
|
|
129
|
-
scale:
|
|
130
|
-
Pulse: actualData.length > 0 ? getColorForGraph('blue') : 'transparent',
|
|
131
|
-
},
|
|
146
|
+
scale: colorScale,
|
|
132
147
|
},
|
|
133
148
|
points: {
|
|
134
149
|
enabled: actualData.length > 0,
|
|
135
|
-
radius:
|
|
150
|
+
radius: 5,
|
|
136
151
|
filled: true,
|
|
152
|
+
strokeWidth: 2,
|
|
153
|
+
},
|
|
154
|
+
legend: {
|
|
155
|
+
position: 'top',
|
|
156
|
+
clickable: false,
|
|
157
|
+
enabled: false,
|
|
158
|
+
},
|
|
159
|
+
tooltip: {
|
|
160
|
+
showTotal: false,
|
|
161
|
+
customHTML: (data: any) => {
|
|
162
|
+
if (data && data.length > 0) {
|
|
163
|
+
const point = data[0];
|
|
164
|
+
// Just return the value - clean and simple
|
|
165
|
+
return `<div style="background: #333; color: white; padding: 8px; border-radius: 4px; font-size: 14px; border: none;">
|
|
166
|
+
${point.value}
|
|
167
|
+
</div>`;
|
|
168
|
+
}
|
|
169
|
+
return '';
|
|
170
|
+
},
|
|
137
171
|
},
|
|
138
172
|
};
|
|
139
173
|
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
let observer: MutationObserver | null = null;
|
|
176
|
+
const addXMarker = (svg: SVGElement, cx: number, cy: number) => {
|
|
177
|
+
const size = 3;
|
|
178
|
+
const systolicColor = '#da1e28';
|
|
179
|
+
const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
180
|
+
line1.setAttribute('x1', (cx - size).toString());
|
|
181
|
+
line1.setAttribute('y1', (cy + size).toString());
|
|
182
|
+
line1.setAttribute('x2', cx.toString());
|
|
183
|
+
line1.setAttribute('y2', (cy - size).toString());
|
|
184
|
+
line1.setAttribute('stroke', systolicColor);
|
|
185
|
+
line1.setAttribute('stroke-width', '3');
|
|
186
|
+
line1.setAttribute('stroke-linecap', 'round');
|
|
187
|
+
line1.setAttribute('data-custom-marker', 'systolic-inverted-v');
|
|
188
|
+
|
|
189
|
+
const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
190
|
+
line2.setAttribute('x1', cx.toString());
|
|
191
|
+
line2.setAttribute('y1', (cy - size).toString());
|
|
192
|
+
line2.setAttribute('x2', (cx + size).toString());
|
|
193
|
+
line2.setAttribute('y2', (cy + size).toString());
|
|
194
|
+
line2.setAttribute('stroke', systolicColor);
|
|
195
|
+
line2.setAttribute('stroke-width', '3');
|
|
196
|
+
line2.setAttribute('stroke-linecap', 'round');
|
|
197
|
+
line2.setAttribute('data-custom-marker', 'systolic-inverted-v');
|
|
198
|
+
|
|
199
|
+
svg.appendChild(line1);
|
|
200
|
+
svg.appendChild(line2);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const addVMarker = (svg: SVGElement, cx: number, cy: number) => {
|
|
204
|
+
const size = 3;
|
|
205
|
+
const vColor = '#24a148';
|
|
206
|
+
|
|
207
|
+
const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
208
|
+
line1.setAttribute('x1', (cx - size).toString());
|
|
209
|
+
line1.setAttribute('y1', (cy - size).toString());
|
|
210
|
+
line1.setAttribute('x2', cx.toString());
|
|
211
|
+
line1.setAttribute('y2', (cy + size).toString());
|
|
212
|
+
line1.setAttribute('stroke', vColor);
|
|
213
|
+
line1.setAttribute('stroke-width', '3');
|
|
214
|
+
line1.setAttribute('stroke-linecap', 'round');
|
|
215
|
+
line1.setAttribute('data-custom-marker', 'diastolic-v');
|
|
216
|
+
|
|
217
|
+
const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
218
|
+
line2.setAttribute('x1', cx.toString());
|
|
219
|
+
line2.setAttribute('y1', (cy + size).toString());
|
|
220
|
+
line2.setAttribute('x2', (cx + size).toString());
|
|
221
|
+
line2.setAttribute('y2', (cy - size).toString());
|
|
222
|
+
line2.setAttribute('stroke', vColor);
|
|
223
|
+
line2.setAttribute('stroke-width', '3');
|
|
224
|
+
line2.setAttribute('stroke-linecap', 'round');
|
|
225
|
+
line2.setAttribute('data-custom-marker', 'diastolic-v');
|
|
226
|
+
|
|
227
|
+
svg.appendChild(line1);
|
|
228
|
+
svg.appendChild(line2);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const addVerticalConnector = (svg: SVGElement, x: number, y1: number, y2: number, color: string) => {
|
|
232
|
+
const line = document.createElementNS(SVG_NAMESPACE, 'line');
|
|
233
|
+
line.setAttribute('x1', x.toString());
|
|
234
|
+
line.setAttribute('y1', y1.toString());
|
|
235
|
+
line.setAttribute('x2', x.toString());
|
|
236
|
+
line.setAttribute('y2', y2.toString());
|
|
237
|
+
line.setAttribute('stroke', color);
|
|
238
|
+
line.setAttribute('stroke-width', '2');
|
|
239
|
+
line.setAttribute('data-custom-marker', 'vertical-connector');
|
|
240
|
+
|
|
241
|
+
svg.appendChild(line);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const addCustomMarkers = () => {
|
|
245
|
+
const chartContainer = document.querySelector(`[data-chart-id="pulse-bp"]`);
|
|
246
|
+
if (chartContainer && actualData.length > 0) {
|
|
247
|
+
const svg = chartContainer.querySelector('svg');
|
|
248
|
+
if (!svg) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
svg.querySelectorAll('[data-custom-marker]').forEach((marker) => marker.remove());
|
|
253
|
+
|
|
254
|
+
const circles = svg.querySelectorAll('circle');
|
|
255
|
+
|
|
256
|
+
circles.forEach((circle, index) => {
|
|
257
|
+
const cx = parseFloat(circle.getAttribute('cx') || '0');
|
|
258
|
+
const cy = parseFloat(circle.getAttribute('cy') || '0');
|
|
259
|
+
const fill = circle.getAttribute('fill') || circle.style.fill || '';
|
|
260
|
+
const stroke = circle.getAttribute('stroke') || circle.style.stroke || '';
|
|
261
|
+
const className = circle.getAttribute('class') || '';
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const allXPositions = Array.from(circles)
|
|
265
|
+
.map((circle) => {
|
|
266
|
+
return parseFloat(circle.getAttribute('cx') || '0');
|
|
267
|
+
})
|
|
268
|
+
.filter((x) => x > 0)
|
|
269
|
+
.sort((a, b) => a - b);
|
|
270
|
+
|
|
271
|
+
const uniqueXPositions: number[] = [];
|
|
272
|
+
allXPositions.forEach((x) => {
|
|
273
|
+
if (uniqueXPositions.length === 0 || Math.abs(x - uniqueXPositions[uniqueXPositions.length - 1]) > 20) {
|
|
274
|
+
uniqueXPositions.push(x);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
uniqueXPositions.forEach((xPos, timeIndex) => {
|
|
279
|
+
const circlesAtThisX = Array.from(circles).filter((circle) => {
|
|
280
|
+
const cx = parseFloat(circle.getAttribute('cx') || '0');
|
|
281
|
+
return Math.abs(cx - xPos) < 15;
|
|
282
|
+
});
|
|
283
|
+
if (circlesAtThisX.length === 0) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
circlesAtThisX.sort((a, b) => {
|
|
287
|
+
const aY = parseFloat(a.getAttribute('cy') || '0');
|
|
288
|
+
const bY = parseFloat(b.getAttribute('cy') || '0');
|
|
289
|
+
return aY - bY;
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
circlesAtThisX.forEach((circle, idx) => {
|
|
293
|
+
const cx = parseFloat(circle.getAttribute('cx') || '0');
|
|
294
|
+
const cy = parseFloat(circle.getAttribute('cy') || '0');
|
|
295
|
+
});
|
|
296
|
+
let pulseY: number | null = null;
|
|
297
|
+
let systolicY: number | null = null;
|
|
298
|
+
let diastolicY: number | null = null;
|
|
299
|
+
|
|
300
|
+
circlesAtThisX.forEach((circle, circleIndex) => {
|
|
301
|
+
const cx = parseFloat(circle.getAttribute('cx') || '0');
|
|
302
|
+
const cy = parseFloat(circle.getAttribute('cy') || '0');
|
|
303
|
+
if (circlesAtThisX.length >= 3) {
|
|
304
|
+
if (circleIndex === 0) {
|
|
305
|
+
systolicY = cy;
|
|
306
|
+
addXMarker(svg, cx, cy);
|
|
307
|
+
circle.style.opacity = '0';
|
|
308
|
+
} else if (circleIndex === circlesAtThisX.length - 1) {
|
|
309
|
+
diastolicY = cy;
|
|
310
|
+
addVMarker(svg, cx, cy);
|
|
311
|
+
circle.style.opacity = '0';
|
|
312
|
+
} else {
|
|
313
|
+
pulseY = cy;
|
|
314
|
+
}
|
|
315
|
+
} else if (circlesAtThisX.length === 2) {
|
|
316
|
+
if (circleIndex === 0) {
|
|
317
|
+
pulseY = cy;
|
|
318
|
+
} else {
|
|
319
|
+
diastolicY = cy;
|
|
320
|
+
addVMarker(svg, cx, cy);
|
|
321
|
+
circle.style.opacity = '0';
|
|
322
|
+
}
|
|
323
|
+
} else if (circlesAtThisX.length === 1) {
|
|
324
|
+
pulseY = cy;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
const currentX = circlesAtThisX[0] ? parseFloat(circlesAtThisX[0].getAttribute('cx') || '0') : 0;
|
|
328
|
+
|
|
329
|
+
if (pulseY !== null && systolicY !== null) {
|
|
330
|
+
addVerticalConnector(svg, currentX, pulseY, systolicY, '#da1e28'); // Red line to systolic
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (pulseY !== null && diastolicY !== null) {
|
|
334
|
+
addVerticalConnector(svg, currentX, pulseY, diastolicY, '#24a148'); // Green line to diastolic
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const timer = setTimeout(addCustomMarkers, 500);
|
|
340
|
+
const timer2 = setTimeout(addCustomMarkers, 1000);
|
|
341
|
+
const chartContainer = document.querySelector(`[data-chart-id="pulse-bp"]`);
|
|
342
|
+
if (chartContainer && window.MutationObserver) {
|
|
343
|
+
observer = new MutationObserver(() => {
|
|
344
|
+
setTimeout(addCustomMarkers, 100);
|
|
345
|
+
});
|
|
346
|
+
observer.observe(chartContainer, { childList: true, subtree: true });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return () => {
|
|
350
|
+
clearTimeout(timer);
|
|
351
|
+
clearTimeout(timer2);
|
|
352
|
+
if (observer) {
|
|
353
|
+
observer.disconnect();
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}, [actualData]);
|
|
357
|
+
|
|
140
358
|
return (
|
|
141
359
|
<div className={styles.pulseBPGraph}>
|
|
142
360
|
<div className={styles.chartContainer} data-chart-id="pulse-bp">
|
|
143
|
-
<
|
|
144
|
-
<LineChart data={finalChartData} options={chartOptions} />
|
|
145
|
-
|
|
146
|
-
{actualData.length > 0 && (
|
|
147
|
-
<svg
|
|
148
|
-
style={{
|
|
149
|
-
position: 'absolute',
|
|
150
|
-
top: 0,
|
|
151
|
-
left: 0,
|
|
152
|
-
width: '100%',
|
|
153
|
-
height: '100%',
|
|
154
|
-
pointerEvents: 'none',
|
|
155
|
-
zIndex: 5,
|
|
156
|
-
}}>
|
|
157
|
-
{actualData.map((item, index) => {
|
|
158
|
-
const chartMarginTop = 5;
|
|
159
|
-
const chartMarginBottom = 8;
|
|
160
|
-
const chartMarginLeft = 3.59;
|
|
161
|
-
const chartMarginRight = 8;
|
|
162
|
-
const chartWidth = 100 - chartMarginLeft - chartMarginRight;
|
|
163
|
-
const chartHeight = 100 - chartMarginTop - chartMarginBottom;
|
|
164
|
-
const dataPointIndex = index;
|
|
165
|
-
const bpXPosition = chartMarginLeft + ((dataPointIndex + 0) / 12) * chartWidth;
|
|
166
|
-
const yOffset = 23;
|
|
167
|
-
const yOffsetDiastolic = 40;
|
|
168
|
-
const pulseYPosition = chartMarginTop + yOffset + ((260 - item.pulse) / 260) * (chartHeight - yOffset);
|
|
169
|
-
const systolicYPosition =
|
|
170
|
-
chartMarginTop + yOffset + ((260 - item.systolicBP) / 260) * (chartHeight - yOffset);
|
|
171
|
-
const diastolicYPosition =
|
|
172
|
-
chartMarginTop +
|
|
173
|
-
yOffsetDiastolic +
|
|
174
|
-
((260 - item.diastolicBP) / 260) * (chartHeight - yOffsetDiastolic);
|
|
175
|
-
|
|
176
|
-
const greenArrowStartY = Math.min(pulseYPosition, diastolicYPosition);
|
|
177
|
-
const greenArrowEndY = Math.max(pulseYPosition, diastolicYPosition);
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<g key={index}>
|
|
181
|
-
<defs>
|
|
182
|
-
<marker
|
|
183
|
-
id={`systolic-arrow-${index}`}
|
|
184
|
-
markerWidth="10"
|
|
185
|
-
markerHeight="10"
|
|
186
|
-
refX="5"
|
|
187
|
-
refY="5"
|
|
188
|
-
orient="auto"
|
|
189
|
-
markerUnits="strokeWidth">
|
|
190
|
-
<polygon points="0,0 10,5 0,10 3,5" fill={getColorForGraph('red')} />
|
|
191
|
-
</marker>
|
|
192
|
-
<marker
|
|
193
|
-
id={`diastolic-arrow-${index}`}
|
|
194
|
-
markerWidth="10"
|
|
195
|
-
markerHeight="10"
|
|
196
|
-
refX="5"
|
|
197
|
-
refY="5"
|
|
198
|
-
orient="auto"
|
|
199
|
-
markerUnits="strokeWidth">
|
|
200
|
-
<polygon points="0,10 10,5 0,0 3,5" fill={getColorForGraph('green')} />
|
|
201
|
-
</marker>
|
|
202
|
-
</defs>
|
|
203
|
-
|
|
204
|
-
<line
|
|
205
|
-
x1={`${bpXPosition}%`}
|
|
206
|
-
y1={`${pulseYPosition}%`}
|
|
207
|
-
x2={`${bpXPosition}%`}
|
|
208
|
-
y2={`${systolicYPosition}%`}
|
|
209
|
-
stroke={getColorForGraph('red')}
|
|
210
|
-
strokeWidth="2"
|
|
211
|
-
markerEnd={`url(#systolic-arrow-${index})`}
|
|
212
|
-
/>
|
|
213
|
-
|
|
214
|
-
<line
|
|
215
|
-
x1={`${bpXPosition}%`}
|
|
216
|
-
y1={`${pulseYPosition}%`}
|
|
217
|
-
x2={`${bpXPosition}%`}
|
|
218
|
-
y2={`${diastolicYPosition}%`}
|
|
219
|
-
stroke={getColorForGraph('green')}
|
|
220
|
-
strokeWidth="2"
|
|
221
|
-
markerEnd={`url(#diastolic-arrow-${index})`}
|
|
222
|
-
/>
|
|
223
|
-
|
|
224
|
-
<circle
|
|
225
|
-
cx={`${bpXPosition}%`}
|
|
226
|
-
cy={`${systolicYPosition}%`}
|
|
227
|
-
r="3"
|
|
228
|
-
fill={getColorForGraph('red')}
|
|
229
|
-
opacity="0.8">
|
|
230
|
-
<title>
|
|
231
|
-
{formatDateTime(item, index)} - Systolic BP: {item.systolicBP} mmHg
|
|
232
|
-
</title>
|
|
233
|
-
</circle>
|
|
234
|
-
|
|
235
|
-
<circle
|
|
236
|
-
cx={`${bpXPosition}%`}
|
|
237
|
-
cy={`${diastolicYPosition}%`}
|
|
238
|
-
r="3"
|
|
239
|
-
fill={getColorForGraph('green')}
|
|
240
|
-
opacity="0.8">
|
|
241
|
-
<title>
|
|
242
|
-
{formatDateTime(item, index)} - Diastolic BP: {item.diastolicBP} mmHg
|
|
243
|
-
</title>
|
|
244
|
-
</circle>
|
|
245
|
-
|
|
246
|
-
<circle
|
|
247
|
-
cx={`${bpXPosition}%`}
|
|
248
|
-
cy={`${pulseYPosition}%`}
|
|
249
|
-
r="4"
|
|
250
|
-
fill={getColorForGraph('blue')}
|
|
251
|
-
stroke="#fff"
|
|
252
|
-
strokeWidth="1"
|
|
253
|
-
opacity="0.9">
|
|
254
|
-
<title>
|
|
255
|
-
{formatDateTime(item, index)} - Pulse: {item.pulse} bpm
|
|
256
|
-
</title>
|
|
257
|
-
</circle>
|
|
258
|
-
</g>
|
|
259
|
-
);
|
|
260
|
-
})}
|
|
261
|
-
</svg>
|
|
262
|
-
)}
|
|
263
|
-
</div>
|
|
361
|
+
<LineChart data={finalChartData} options={chartOptions} />
|
|
264
362
|
</div>
|
|
265
363
|
</div>
|
|
266
364
|
);
|