Refactor previous name into dedicated service (#7571)
[openemr.git] / interface / forms / vitals / growthchart / chart.php
blobd18e14c0edb92a925b8e04ba9c4f1a0ccfccfd9d
1 <?php
3 /**
4 * vitals growthchart chart.php
6 * Description
7 * Script can create growth charts for following formats:
8 * 1)png image
9 * 2)pdf
10 * 3)html (css for printing)
12 * The png image and pdf require following files in current directory:
13 * 2-20yo_boys_BMI.png
14 * 2-20yo_girls_BMI.png
15 * birth-24mos_boys_HC.png
16 * birth-24mos_girls_HC.png
18 * The html (css for printing) require the following files in current directory
19 * 2-20yo_boys_BMI-1.png
20 * 2-20yo_boys_BMI-2.png
21 * 2-20yo_girls_BMI-1.png
22 * 2-20yo_girls_BMI-2.png
23 * birth-24mos_boys_HC-1.png
24 * birth-24mos_boys_HC-2.png
25 * birth-24mos_girls_HC-1.png
26 * birth-24mos_girls_HC-2.png
27 * bluedot.gif
28 * redbox.gif
29 * reddot.gif
30 * chart.php
31 * page1.css
32 * page2.css
34 * @package OpenEMR
35 * @link http://www.open-emr.org
36 * @author Brady Miller <brady.g.miller@gmail.com>
37 * @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com>
38 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
41 require_once("../../../../interface/globals.php");
42 require_once($GLOBALS['fileroot'] . "/library/patient.inc.php");
44 use OpenEMR\Common\Csrf\CsrfUtils;
45 use OpenEMR\Services\VitalsService;
48 if (!CsrfUtils::verifyCsrfToken($_GET["csrf_token_form"])) {
49 CsrfUtils::csrfNotVerified();
52 $chartpath = $GLOBALS['fileroot'] . "/interface/forms/vitals/growthchart/";
53 $name = "";
54 $pid = $_GET['pid'];
56 if ($pid == "") {
57 // no pid? no graph for you.
58 echo "<p>" . xlt('Missing PID.') . ' ' . xlt('Please close this window.') . "</p>";
59 exit;
62 $vitalsService = new VitalsService();
64 $isMetric = ((($GLOBALS['units_of_measurement'] == 2) || ($GLOBALS['units_of_measurement'] == 4)) ? true : false);
66 $patient_data = "";
67 if (isset($pid) && is_numeric($pid)) {
68 $patient_data = getPatientData($pid, "fname, lname, sex, DATE_FORMAT(DOB,'%Y%m%d') as DOB");
69 $nowAge = getPatientAge($patient_data['DOB']);
70 $dob = $patient_data['DOB'];
71 $name = $patient_data['fname'] . " " . $patient_data['lname'];
74 // The first data point in the DATA set is significant. It tells the date
75 // of the currently viewed vitals by the user. We will use this
76 // date to define which chart is displayed on the screen
77 $charttype = "2-20"; // default the chart-type to ages 2-20
78 $datapoints = $vitalsService->getVitalsHistoryForPatient($pid, true);
79 $first_datapoint = $datapoints[0];
80 if (!empty($first_datapoint)) {
81 $date = str_replace('-', '', substr($first_datapoint['date'], 0, 10));
82 $height = (($isMetric) ? convertHeightToUs($first_datapoint['height']) : $first_datapoint['height']);
83 $weight = (($isMetric) ? convertWeightToUS($first_datapoint['weight']) : $first_datapoint['weight']);
84 $head_circ = (($isMetric) ? convertHeightToUs($first_datapoint['head_circ']) : $first_datapoint['head_circ']);
86 if ($date != "") {
87 $charttype_date = $date;
90 $tmpAge = getPatientAgeInDays($patient_data['DOB'], $date);
91 // use the birth-24 chart if the age-on-date-of-vitals is 24months or younger
92 if ($tmpAge < (365 * 2)) {
93 $charttype = "birth";
97 if (isset($_GET['chart_type'])) {
98 $charttype = $_GET['chart_type'];
101 //sort the datapoints
102 rsort($datapoints);
105 // convert to applicable weight units from Config Locale
106 function unitsWt($wt)
108 global $isMetric;
109 if ($isMetric) {
110 //convert to metric
111 return (number_format(($wt * 0.45359237), 2, '.', '') . xl('kg', '', ' '));
112 } else {
113 //keep US
114 return number_format($wt, 2) . xl('lb', '', ' ');
118 // convert to applicable length units from Config Locale
119 function unitsDist($dist)
121 global $isMetric;
122 if ($isMetric) {
123 //convert to metric
124 return (number_format(($dist * 2.54), 2, '.', '') . xl('cm', '', ' '));
125 } else {
126 //keep US
127 return number_format($dist, 2) . xl('in', '', ' ');
131 // convert vitals service data to US values for graphing
132 function convertHeightToUs($height)
134 return $height * 0.393701;
137 function convertWeightToUs($weight)
139 return $weight * 2.20462262185;
141 /******************************/
142 /******************************/
143 /******************************/
146 $name_x = 650;
147 $name_y = 50;
148 $name_x1 = 1650;
149 $name_y1 = 60;
151 $ageOffset = 0;
152 $heightOffset = 0;
153 $weightOffset = 0;
155 if ($charttype == 'birth') {
156 // Use birth to 24 months chart
158 $dot_x = 190; //months starts here (pixel)
159 $delta_x = 26.13; //pixels per month - length
160 $dot_y1 = 768; //height starts here - at 15 inches
161 $delta_y1 = 24.92; //pixels per inch - height
162 $dot_y2 = 1170; //weight starts here - at 3 lbs
163 $delta_y2 = 22.16; //pixels per pound - weight
165 $HC_dot_x = 1180; //months starts here for Head circumference chart
166 $HC_delta_x = 26.04; //pixels per month for Head circumference chart
167 $HC_dot_y = 764; //Head circumference starts here - at 11 inches
168 $HC_delta_y = 60.00; //calculated pixels per inch for head circumference
170 $WT_y = 1127; //start here to draw wt and height graph at bottom of Head circumference chart
171 $WT_delta_y = 12.96;
172 $HT_x = 1187; //start here to draw wt and height graph at bottom of Head circumference chart
173 $HT_delta_x = 24.32;
175 if (preg_match('/^male/i', $patient_data['sex'])) {
176 $chart = "birth-24mos_boys_HC.png";
178 // added by BM for CSS html output
179 $chartCss1 = "birth-24mos_boys_HC-1.png";
180 $chartCss2 = "birth-24mos_boys_HC-2.png";
181 } elseif (preg_match('/^female/i', $patient_data['sex'])) {
182 $chart = "birth-24mos_girls_HC.png";
184 // added by BM for CSS html output
185 $chartCss1 = "birth-24mos_girls_HC-1.png";
186 $chartCss2 = "birth-24mos_girls_HC-2.png";
189 $ageOffset = 0;
190 $heightOffset = 15; // Substract 15 because the graph starts at 15 inches
191 $weightOffset = 3; // graph starts at 3 lbs
192 $WToffset = 0; //for wt and ht table at bottom half of HC graph
193 $HToffset = 18; // starting inch for wt and ht table at bottom half of HC graph
195 // pixel positions and offsets for data table
196 $datatable_x = 370;
197 $datatable_age_offset = 75;
198 $datatable_weight_offset = 145;
199 $datatable_height_offset = 220;
200 $datatable_hc_offset = 300;
201 $datatable_y = 1052;
202 $datatable_y_increment = 17;
204 // pixel positions and offsets for head-circ data table
205 $datatable2_x = 1360;
206 $datatable2_age_offset = 75;
207 $datatable2_weight_offset = 145;
208 $datatable2_height_offset = 210;
209 $datatable2_hc_offset = 290;
210 $datatable2_y = 1098;
211 $datatable2_y_increment = 18;
212 } elseif ($charttype == "2-20") {
213 // current patient age between 2 and 20 years
215 $dot_x = 177;
216 $delta_x = 35.17;
217 $dot_y1 = 945;
218 $delta_y1 = 16.74;
219 $dot_y2 = 1176; //at 14 lbs, 1157 at 20 lbs
220 $delta_y2 = 3.01;
222 $bmi_dot_x = 1135;
223 $bmi_delta_x = 39.89;
224 $bmi_dot_y = 1130;
225 $bmi_delta_y = 37.15;
227 if (preg_match('/^male/i', $patient_data['sex'])) {
228 $chart = "2-20yo_boys_BMI.png";
230 // added by BM for CSS html output
231 $chartCss1 = "2-20yo_boys_BMI-1.png";
232 $chartCss2 = "2-20yo_boys_BMI-2.png";
233 } elseif (preg_match('/^female/i', $patient_data['sex'])) {
234 $chart = "2-20yo_girls_BMI.png";
236 // added by BM for CSS html output
237 $chartCss1 = "2-20yo_girls_BMI-1.png";
238 $chartCss2 = "2-20yo_girls_BMI-2.png";
241 $ageOffset = 2;
242 $heightOffset = 30;
243 $weightOffset = 14;
245 // pixel positions and offsets data table
246 $datatable_x = 96;
247 $datatable_age_offset = 84;
248 $datatable_weight_offset = 180;
249 $datatable_height_offset = 270;
250 $datatable_bmi_offset = 360;
251 $datatable_y = 188;
252 $datatable_y_increment = 18;
254 // pixel positions and offsets for BMI data table
255 $datatable2_x = 1071;
256 $datatable2_age_offset = 73;
257 $datatable2_weight_offset = 145;
258 $datatable2_height_offset = 215;
259 $datatable2_bmi_offset = 310;
260 $datatable2_y = 152;
261 $datatable2_y_increment = 17;
262 } else {
263 // bad age data? no graph for you.
264 echo "<p>" . xlt('Age data is out of range.') . "</p>";
265 exit;
268 /******************************/
269 // Section for the CSS HTML table
270 // this will bypass gd and pdf requirements
271 // to allow internationalization and flexibility
272 // with fonts
274 $cssWidth = 524;
275 $cssHeight = 668;
277 function cssHeader()
279 global $cssWidth, $cssHeight;
282 <html>
283 <head>
284 <link rel="stylesheet" title="page1" href="page1.css">
285 <link rel="stylesheet" title="page2" href="page2.css">
286 <style>
287 html {
288 padding: 0;
289 margin: 0;
291 body {
292 font-family: sans-serif;
293 font-weight: normal;
294 font-size: 10pt;
295 background: white;
296 color: red;
297 margin: 0;
298 padding: 0;
300 div {
301 padding: 0;
302 margin: 0;
304 div.paddingdiv {
305 width: <?php echo attr($cssWidth); ?>pt;
306 height: <?php echo attr($cssHeight); ?>pt;
307 page-break-after: always;
309 div.label_custom {
310 font-size: 7pt;
312 div.DoNotPrint {
313 position: absolute;
314 top: 2pt;
315 left: 200pt;
317 img {
318 margin: 0;
319 padding: 0;
321 img.background {
322 width: <?php echo attr($cssWidth); ?>pt;
323 height: <?php echo attr($cssHeight); ?>pt;
325 @media print {
326 div.DoNotPrint {
327 display: none;
330 </style>
331 <script>
332 function FormSetup() {
333 changeStyle('page1')
335 function changeStyle(css_title) {
336 var i, link_tag ;
337 for (i = 0, link_tag = document.getElementsByTagName("link") ; i < link_tag.length ; i++ ) {
338 if ((link_tag[i].rel.indexOf( "stylesheet" ) != -1) && link_tag[i].title) {
339 link_tag[i].disabled = true ;
340 if (link_tag[i].title == css_title) {
341 link_tag[i].disabled = false ;
346 function pagePrint(title) {
347 changeStyle(title);
348 var win = top.printLogPrint ? top : opener.top;
349 win.printLogPrint(window);
351 </script>
352 <title><?php echo xlt('Growth Chart'); ?></title>
353 </head>
354 <body Onload="FormSetup()">
355 <div class="DoNotPrint">
356 <table>
357 <tr>
358 <td>
359 <input type="button" value="<?php echo xla('View Page 1'); ?>" onclick="javascript:changeStyle('page1')" class="button">
360 <input type="button" value="<?php echo xla('View Page 2'); ?>" onclick="javascript:changeStyle('page2')" class="button">
361 <input type="button" value="<?php echo xla('Print Page 1'); ?>" onclick="javascript:pagePrint('page1')" class="button">
362 <input type="button" value="<?php echo xla('Print Page 2'); ?>" onclick="javascript:pagePrint('page2')" class="button">
363 </td>
364 </tr>
365 </table>
366 </div>
367 <?php
370 function cssFooter()
373 </body>
374 </html>
375 <?php
378 function cssPage($image1, $image2)
381 <div class='paddingdiv' id='page1'>
382 <img class='background' src='<?php echo $image1; ?>' />
383 </div>
384 <div class='paddingdiv' id='page2'>
385 <img class='background' src='<?php echo $image2; ?>' />
386 </div>
387 <?php
390 // Convert a point from above settings for gd into a
391 // a format (pt) to use for css html document
392 // return - Array(Xcoord,Ycoord,page)
393 function convertpoint($coord)
395 global $cssWidth, $cssHeight;
397 //manual offsets to help line up output
398 $Xoffset = -3;
399 $Yoffset = -2;
401 //collect coordinates from input array
402 $Xcoord = $coord[0];
403 $Ycoord = $coord[1];
405 //adjust with offsets
406 $Xcoord = $Xcoord + $Xoffset;
407 $Ycoord = $Ycoord + $Yoffset;
410 if ($Xcoord > 1000) {
411 //on second page so subtract 1000 from x
412 $Xcoord = $Xcoord - 1000;
413 $page = "page2";
414 } else {
415 $page = "page1";
418 //calculate conversion to pt (decrease number of decimals)
419 $Xcoord = ($cssWidth * $Xcoord) / 1000;
420 $Xcoord = number_format($Xcoord, 1, '.', '');
421 $Ycoord = ($cssHeight * $Ycoord) / 1294;
422 $Ycoord = number_format($Ycoord, 1, '.', '');
424 //return point
425 return(array($Xcoord,$Ycoord,$page));
429 if (($_GET['html'] ?? null) == 1) {
430 //build the html css page if selected
431 cssHeader();
432 cssPage($chartCss1, $chartCss2);
434 //output name
435 $point = convertpoint(array($name_x, $name_y));
436 echo("<div id='" . attr($point[2]) . "' class='name' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($name) . "</div>\n");
437 $point = convertpoint(array($name_x1,$name_y1));
438 echo("<div id='" . attr($point[2]) . "' class='name' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($name) . "</div>\n");
440 // counter to limit the number of data points plotted
441 $count = 0;
443 // plot the data points
444 foreach ($datapoints as $data) {
445 if (!empty($data)) {
446 $date = str_replace('-', '', substr($data['date'], 0, 10));
447 // convert to US if metric locale
448 $height = (($isMetric) ? convertHeightToUs($data['height']) : $data['height']);
449 $weight = (($isMetric) ? convertWeightToUs($data['weight']) : $data['weight']);
450 $head_circ = (($isMetric) ? convertHeightToUs($data['head_circ']) : $data['head_circ']);
452 if ($date == "") {
453 continue;
456 // only plot if we have both weight and heights. Skip if either is 0.
457 // Rational is only well visit will need both, sick visit only needs weight
458 // for some clinic.
459 if (empty($weight) || empty($height)) {
460 continue;
463 // get age of patient at this data-point
464 // to get data from function getPatientAgeYMD including $age,$age_in_months, $ageinYMD
465 extract(getPatientAgeYMD($dob, $date));
466 if ($charttype == 'birth') {
467 // for birth style chart, we use the age in months
468 $age = $age_in_months;
471 // exclude data points that do not belong on this chart
472 // for example, a data point for a 18 month old can be excluded
473 // from that patient's 2-20 yr chart
474 $daysold = getPatientAgeInDays($dob, $date);
475 if ($daysold >= (365 * 2) && $charttype == "birth") {
476 continue;
479 if ($daysold <= (365 * 2) && $charttype == "2-20") {
480 continue;
483 // calculate the x-axis (Age) value
484 $x = $dot_x + $delta_x * ($age - $ageOffset);
486 // Draw Height dot
487 $y1 = $dot_y1 - $delta_y1 * ($height - $heightOffset);
488 $point = convertpoint(array((int) $x,$y1));
489 echo("<div id='" . attr($point[2]) . "' class='graphic' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'><img src='reddot.gif' /></div>\n");
491 // Draw Weight bullseye
492 $y2 = $dot_y2 - $delta_y2 * ($weight - $weightOffset);
493 $point = convertpoint(array((int) $x,$y2));
494 echo("<div id='" . attr($point[2]) . "' class='graphic' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'><img src='redbox.gif' /></div>\n");
496 if ($charttype == "birth") {
497 // Draw Head circumference
498 $HC_x = $HC_dot_x + $HC_delta_x * $age;
499 $HC_y = $HC_dot_y - $HC_delta_y * ($head_circ - 11);
500 $point = convertpoint(array($HC_x,$HC_y));
501 echo("<div id='" . attr($point[2]) . "' class='graphic' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'><img src='bluedot.gif' /></div>\n");
502 // Draw Wt and Ht graph at the bottom half
503 $WT = $WT_y - $WT_delta_y * ($weight - $WToffset);
504 $HT = $HT_x + $HT_delta_x * ($height - $HToffset);
505 $point = convertpoint(array($HT,$WT));
506 echo("<div id='" . attr($point[2]) . "' class='graphic' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'><img src='reddot.gif' /></div>\n");
507 } elseif ($charttype == "2-20") {
508 // Draw BMI
509 $bmi = $weight / $height / $height * 703;
510 $bmi_x = $bmi_dot_x + $bmi_delta_x * ($age - 2);
511 $bmi_y = $bmi_dot_y - $bmi_delta_y * ($bmi - 10);
512 $point = convertpoint(array($bmi_x,$bmi_y));
513 echo("<div id='" . attr($point[2]) . "' class='graphic' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'><img src='bluedot.gif' /></div>\n");
516 // fill in data tables
518 $datestr = substr($date, 0, 4) . "/" . substr($date, 4, 2) . "/" . substr($date, 6, 2);
520 //birth to 24 mos chart has 8 rows to fill.
521 if ($count < 8 && $charttype == "birth") {
522 $point = convertpoint(array($datatable_x,$datatable_y));
523 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($datestr) . "</div>\n");
524 $point = convertpoint(array($datatable_x + $datatable_age_offset,$datatable_y));
525 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($ageinYMD) . "</div>\n");
526 $point = convertpoint(array($datatable_x + $datatable_weight_offset,$datatable_y));
527 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsWt($weight)) . "</div>\n");
528 $point = convertpoint(array($datatable_x + $datatable_height_offset,$datatable_y));
529 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsDist($height)) . "</div>\n");
530 $point = convertpoint(array($datatable_x + $datatable_hc_offset,$datatable_y));
531 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsDist($head_circ)) . "</div>\n");
532 $datatable_y = $datatable_y + $datatable_y_increment; // increment the datatable "row pointer"
535 // 2 to 20 year-old chart has 7 rows to fill.
536 if ($count < 7 && $charttype == "2-20") {
537 $point = convertpoint(array($datatable_x,$datatable_y));
538 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($datestr) . "</div>\n");
539 $point = convertpoint(array($datatable_x + $datatable_age_offset,$datatable_y));
540 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($ageinYMD) . "</div>\n");
541 $point = convertpoint(array($datatable_x + $datatable_weight_offset,$datatable_y));
542 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsWt($weight)) . "</div>\n");
543 $point = convertpoint(array($datatable_x + $datatable_height_offset,$datatable_y));
544 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsDist($height)) . "</div>\n");
545 $point = convertpoint(array($datatable_x + $datatable_bmi_offset,$datatable_y));
546 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(substr($bmi, 0, 5)) . "</div>\n");
547 $datatable_y = $datatable_y + $datatable_y_increment; // increment the datatable "row pointer"
550 // Head Circumference chart has 5 rows to fill in
551 if ($count < 5 && $charttype == "birth") {
552 $point = convertpoint(array($datatable2_x,$datatable2_y));
553 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($datestr) . "</div>\n");
554 $point = convertpoint(array($datatable2_x + $datatable2_age_offset,$datatable2_y));
555 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($ageinYMD) . "</div>\n");
556 $point = convertpoint(array($datatable2_x + $datatable2_weight_offset,$datatable2_y));
557 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsWt($weight)) . "</div>\n");
558 $point = convertpoint(array($datatable2_x + $datatable2_height_offset,$datatable2_y));
559 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsDist($height)) . "</div>\n");
560 $point = convertpoint(array($datatable2_x + $datatable2_hc_offset,$datatable2_y));
561 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsDist($head_circ)) . "</div>\n");
562 $datatable2_y = $datatable2_y + $datatable2_y_increment; // increment the datatable2 "row pointer"
565 // BMI chart has 14 rows to fill in.
566 if ($count < 14 && $charttype == "2-20") {
567 $point = convertpoint(array($datatable2_x,$datatable2_y));
568 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($datestr) . "</div>\n");
569 $point = convertpoint(array($datatable2_x + $datatable2_age_offset,$datatable2_y));
570 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text($ageinYMD) . "</div>\n");
571 $point = convertpoint(array($datatable2_x + $datatable2_weight_offset,$datatable2_y));
572 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsWt($weight)) . "</div>\n");
573 $point = convertpoint(array($datatable2_x + $datatable2_height_offset,$datatable2_y));
574 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(unitsDist($height)) . "</div>\n");
575 $point = convertpoint(array($datatable2_x + $datatable2_bmi_offset,$datatable2_y));
576 echo("<div id='" . attr($point[2]) . "' class='label_custom' style='position: absolute; top: " . attr($point[1]) . "pt; left: " . attr($point[0]) . "pt;'>" . text(substr($bmi, 0, 5)) . "</div>\n");
577 $datatable2_y = $datatable2_y + $datatable2_y_increment; // increment the datatable2 "row pointer"
579 $count++;
584 // Done creating CSS HTML output
585 /******************************/
588 // create the graph
589 $im = imagecreatefrompng($chartpath . $chart);
590 $color1 = imagecolorallocate($im, 0, 0, 255); //blue - color scheme imagecolorallocate($im, Red, Green, Blue)
591 $color = imagecolorallocate($im, 255, 51, 51); //red
593 // draw the patient's name
594 imagestring($im, 12, $name_x, $name_y, $name, $color);
595 imagestring($im, 12, $name_x1, $name_y1, $name, $color);
597 // counter to limit the number of data points plotted
598 $count = 0;
600 // plot the data points
601 foreach ($datapoints as $data) {
602 if (!empty($data)) {
603 $date = str_replace('-', '', substr($data['date'], 0, 10));
604 // values can be US or metric thus convert to US for graphing
605 $height = (($isMetric) ? convertHeightToUs($data['height']) : $data['height']);
606 $weight = (($isMetric) ? convertWeightToUs($data['weight']) : $data['weight']);
607 $head_circ = (($isMetric) ? convertHeightToUs($data['head_circ']) : $data['head_circ']);
609 if ($date == "") {
610 continue;
613 // only plot if we have both weight and heights. Skip if either is 0.
614 // Rational is only well visit will need both, sick visit only needs weight
615 // for some clinic.
616 if (empty($weight) || empty($height)) {
617 continue;
620 // get age of patient at this data-point
621 // to get data from function getPatientAgeYMD including $age, $ageinYMD, $age_in_months
622 extract(getPatientAgeYMD($dob, $date));
623 if ($charttype == 'birth') {
624 // for birth style chart, we use the age in months
625 $age = $age_in_months;
628 // exclude data points that do not belong on this chart
629 // for example, a data point for a 18 month old can be excluded
630 // from that patient's 2-20 yr chart
631 $daysold = getPatientAgeInDays($dob, $date);
632 if ($daysold > (365 * 2) && $charttype == "birth") {
633 continue;
636 if ($daysold < (365 * 2) && $charttype == "2-20") {
637 continue;
640 // calculate the x-axis (Age) value
641 $x = $dot_x + $delta_x * ($age - $ageOffset);
643 // Draw Height dot
644 $y1 = $dot_y1 - $delta_y1 * ($height - $heightOffset);
645 imagefilledellipse($im, (int) $x, (int) $y1, 10, 10, $color);
647 // Draw Weight bullseye
648 $y2 = $dot_y2 - $delta_y2 * ($weight - $weightOffset);
649 imageellipse($im, (int) $x, (int) $y2, 12, 12, $color); // outter ring
650 imagefilledellipse($im, (int) $x, (int) $y2, 5, 5, $color); //center dot
652 if ($charttype == "birth") {
653 // Draw Head circumference
654 $HC_x = $HC_dot_x + $HC_delta_x * $age;
655 $HC_y = $HC_dot_y - $HC_delta_y * (((int) $head_circ ?? null) - 11);
656 imagefilledellipse($im, (int) $HC_x, (int) $HC_y, 10, 10, $color1);
657 // Draw Wt and Ht graph at the bottom half
658 $WT = $WT_y - $WT_delta_y * ($weight - $WToffset);
659 $HT = $HT_x + $HT_delta_x * ($height - $HToffset);
660 imagefilledellipse($im, (int) $HT, (int) $WT, 10, 10, $color);
661 } elseif ($charttype == "2-20") {
662 // Draw BMI
663 $bmi = $weight / $height / $height * 703;
664 $bmi_x = $bmi_dot_x + $bmi_delta_x * ($age - 2);
665 $bmi_y = $bmi_dot_y - $bmi_delta_y * ($bmi - 10);
666 imagefilledellipse($im, (int) $bmi_x, (int) $bmi_y, 10, 10, $color1);
669 // fill in data tables
671 $datestr = substr($date, 0, 4) . "/" . substr($date, 4, 2) . "/" . substr($date, 6, 2);
673 //birth to 24 mos chart has 8 rows to fill.
674 if ($count < 8 && $charttype == "birth") {
675 imagestring($im, 2, $datatable_x, $datatable_y, $datestr, $color);
676 imagestring($im, 2, ($datatable_x + $datatable_age_offset), $datatable_y, $ageinYMD, $color);
677 imagestring($im, 2, ($datatable_x + $datatable_weight_offset), $datatable_y, unitsWt($weight), $color);
678 imagestring($im, 2, ($datatable_x + $datatable_height_offset), $datatable_y, unitsDist($height), $color);
679 imagestring($im, 2, ($datatable_x + $datatable_hc_offset), $datatable_y, unitsDist($head_circ), $color);
680 $datatable_y = $datatable_y + $datatable_y_increment; // increment the datatable "row pointer"
683 // 2 to 20 year-old chart has 7 rows to fill.
684 if ($count < 7 && $charttype == "2-20") {
685 imagestring($im, 2, $datatable_x, $datatable_y, $datestr, $color);
686 imagestring($im, 2, ($datatable_x + $datatable_age_offset), $datatable_y, $ageinYMD, $color);
687 imagestring($im, 2, ($datatable_x + $datatable_weight_offset), $datatable_y, unitsWt($weight), $color);
688 imagestring($im, 2, ($datatable_x + $datatable_height_offset), $datatable_y, unitsDist($height), $color);
689 imagestring($im, 2, ($datatable_x + $datatable_bmi_offset), $datatable_y, substr($bmi, 0, 5), $color);
690 $datatable_y = $datatable_y + $datatable_y_increment; // increment the datatable "row pointer"
693 // Head Circumference chart has 5 rows to fill in
694 if ($count < 5 && $charttype == "birth") {
695 imagestring($im, 2, $datatable2_x, $datatable2_y, $datestr, $color);
696 imagestring($im, 2, ($datatable2_x + $datatable2_age_offset), $datatable2_y, $ageinYMD, $color);
697 imagestring($im, 2, ($datatable2_x + $datatable2_weight_offset), $datatable2_y, unitsWt($weight), $color);
698 imagestring($im, 2, ($datatable2_x + $datatable2_height_offset), $datatable2_y, unitsDist($height), $color);
699 imagestring($im, 2, ($datatable2_x + $datatable2_hc_offset), $datatable2_y, unitsDist($head_circ), $color);
700 $datatable2_y = $datatable2_y + $datatable2_y_increment; // increment the datatable2 "row pointer"
703 // BMI chart has 14 rows to fill in.
704 if ($count < 14 && $charttype == "2-20") {
705 imagestring($im, 2, $datatable2_x, $datatable2_y, $datestr, $color);
706 imagestring($im, 2, ($datatable2_x + $datatable2_age_offset), $datatable2_y, $ageinYMD, $color);
707 imagestring($im, 2, ($datatable2_x + $datatable2_weight_offset), $datatable2_y, unitsWt($weight), $color);
708 imagestring($im, 2, ($datatable2_x + $datatable2_height_offset), $datatable2_y, unitsDist($height), $color);
709 imagestring($im, 2, ($datatable2_x + $datatable2_bmi_offset), $datatable2_y, substr($bmi, 0, 5), $color);
710 $datatable2_y = $datatable2_y + $datatable2_y_increment; // increment the datatable2 "row pointer"
713 $count++;
717 if (($_GET['pdf'] ?? null) == 1) {
718 $pdf = new Cezpdf("LETTER");
719 $pdf->ezSetMargins(0, 0, 0, 0);
721 // we start with one large image, break it into two pages
722 $page1 = imagecreate((imagesx($im) / 2), imagesy($im));
723 $page2 = imagecreate((imagesx($im) / 2), imagesy($im));
724 imagecopy($page1, $im, 0, 0, 0, 0, (imagesx($im) / 2), imagesy($im));
725 imagecopy($page2, $im, 0, 0, (imagesx($im) / 2), 0, imagesx($im), imagesy($im));
726 imagedestroy($im);
728 // each page is built
729 $tmpfilename = tempnam("/tmp", "oemr");
730 imagepng($page1, $tmpfilename);
731 imagedestroy($page1);
732 $pdf->ezImage($tmpfilename);
733 $pdf->ezNewPage();
734 imagepng($page2, $tmpfilename);
735 imagedestroy($page2);
736 $pdf->ezImage($tmpfilename);
738 // temporary file is removed
739 unlink($tmpfilename);
741 // output the PDF
742 $pdf->ezStream();
743 } else {
744 cssFooter();