Index: trunk/ippMonitor/INSTALL
===================================================================
--- trunk/ippMonitor/INSTALL	(revision 42736)
+++ trunk/ippMonitor/INSTALL	(revision 42790)
@@ -39,4 +39,5 @@
 # $CZARPLOTDIR and $METRICSPLOTDIR will be generated as links in the same folder as ippMonitor  
 # the structure of the names for these two directories are defined in Makefile.in a
+# pltdir in ipp117 is /var/www/localhost/htdocs/ippMonitor
 psconfigure --htdocs /var/www/localhost/htdocs --wwwbin /var/www/localhost/wwwbin --pltdir /some/path --site ~/ippconfig/ippmonitor.config 
 
Index: trunk/ippMonitor/raw/czartool_labels.php
===================================================================
--- trunk/ippMonitor/raw/czartool_labels.php	(revision 42736)
+++ trunk/ippMonitor/raw/czartool_labels.php	(revision 42790)
@@ -1668,4 +1668,9 @@
     $qry = $db->query($sql);
     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
+
+    // Set environment variables
+    putenv("PATH=/usr/local/bin:/usr/bin:/bin:/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/bin");
+    putenv("PERL5LIB=/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/lib");
+
     # not sure why neb-host doesn't work on ipp113 CCL.20210216
     exec("neb-host |cut -b 17-30,86-", $hostnoteall);
Index: trunk/ippMonitor/raw/dataquality.php
===================================================================
--- trunk/ippMonitor/raw/dataquality.php	(revision 42790)
+++ trunk/ippMonitor/raw/dataquality.php	(revision 42790)
@@ -0,0 +1,858 @@
+<?php
+include 'ipp.php';
+include 'site.php';
+
+$ID = checkID();
+
+// require an explicit project
+if (! $ID['proj']) { projectform ($ID); }
+
+// connect to czarDb 
+$czardb = dbconnect($CZARDBNAME); 
+$projectdb = dbconnect($ID['proj']);
+
+if ($ID['menu']) {$myMenu = $ID['menu'];}
+else {$myMenu = "ipp.czar.dat";}
+
+menu($myMenu, 'Czartool on '.$lastUpdateTime, 'ipp.css', $ID['link'], $ID['proj']);
+
+$pass = $ID['pass'];
+$proj = $ID['proj'];
+$menu = $ID['menu'];
+
+// Fetch data based on range selection
+$range = isset($_GET['range']) ? $_GET['range'] : 'week';
+
+echo "<table border=\"0\">";
+echo "<tr>";
+echo "  <td style=width:1240>";
+echo "    <h1 align=\"middle\">Data Quality Report</h1>";
+echo "  <center>";
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=day">Today (MJD: '.getMJD().')</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=week">Past week (default)</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=month">Past month</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=year">Past year </a> | ';
+echo "  </td>";
+echo "</tr>";
+echo "</table>";
+echo getDataQuality2($projectdb, $range);
+
+menu_end();
+
+/////////////////////////////////////////////////////////////////////////////
+//                                                                         //
+//                               Functions                                 //
+//                                                                         //
+/////////////////////////////////////////////////////////////////////////////
+
+###########################################################################
+#
+# Get data quality
+#
+###########################################################################
+function getDataQuality2($db, $range = 'week') {
+    // Start timer
+    $start = microtime(true);
+
+    $mjdDay  = getMJD();
+    $mjdDay_yearback = $mjdDay - 366;
+    $mjdDay_monthback = $mjdDay - 32;
+    $min_exp_id = 0;
+
+    // Determine the reference exp_name based on the selected range
+    if ($range === 'year') {
+        $expname = "o" . $mjdDay_yearback . "%";
+    } elseif ($range === 'month') {
+        $expname = "o" . $mjdDay_monthback . "%";
+    } else {
+        $expname = "o" . ($mjdDay - 7) . "%"; // Default week-based constraint
+    }
+
+    // Query to get the minimum exp_id
+    $expIdQuery = "SELECT MIN(exp_id) AS min_exp_id FROM rawExp WHERE rawExp.exp_name LIKE '$expname';";
+
+    $result = $db->query($expIdQuery);
+
+    // Fetch result into $row as an associative array
+    $result->fetchInto($row, DB_FETCHMODE_ASSOC);
+
+    if (!empty($row['min_exp_id'])) {
+        $min_exp_id = $row['min_exp_id'];
+        echo "Minimum exp_id: " . $min_exp_id;
+    } else {
+        echo "No results found.";
+    }
+
+    echo "<br><br>";
+
+    // Apply different constraints based on the selected range
+    if ($range === 'year') {
+        // Constraint by dateobs within the last 365 days
+        $dateConstraint = "dateobs >= NOW() - INTERVAL 365 DAY AND rawExp.exp_id >= $min_exp_id";
+    } elseif ($range === 'month') {
+        // Constraint by dateobs within the last 30 days
+        $dateConstraint = "dateobs >= NOW() - INTERVAL 30 DAY AND rawExp.exp_id >= $min_exp_id";
+    } else {
+        // Constraint by exp_name for week and day ranges
+        $expnames = array();
+        $days = ($range === 'day') ? 1 : 7; // Set days to 1 for 'day', otherwise default to 7 (week)
+
+        for ($i = 0; $i < $days; $i++) {
+            $expnames[] = "'o" . ($mjdDay - $i) . "%'";
+        }
+        $expnameFilter = " (rawExp.exp_name LIKE " . implode(" OR rawExp.exp_name LIKE ", $expnames) . ") AND rawExp.exp_id >= $min_exp_id";
+        $dateConstraint = $expnameFilter;
+    }
+
+    // Updated SQL query
+    $sql = "select * from (SELECT SUBTIME(dateobs, '10:00:00') AS time,
+                   round(( 367*YEAR(dateobs)- FLOOR((7 * (YEAR(dateobs) + FLOOR((MONTH(dateobs) + 9) / 12))) / 4) + FLOOR(275 * MONTH(dateobs) / 9)
+                   + DAY(dateobs) + 1721013.5 + (HOUR(dateobs) / 24.0) + (MINUTE(dateobs) / 1440.0) + (SECOND(dateobs) / 86400.0) - 2400000.5), 5) AS MJD,
+                   filter, comment, ra*180/pi() as RA, decl*180/pi() as DECL, exp_name, alt, az, fwhm_major, fwhm_minor, zpt_obs, zpt_stdev, rawExp.exp_id
+            FROM rawExp 
+            JOIN chipRun USING (exp_id) 
+            JOIN camRun USING (chip_id) 
+            JOIN camProcessedExp USING (cam_id)
+            WHERE $dateConstraint
+              AND exp_type = 'OBJECT'
+              AND camRun.state LIKE 'full'
+              AND camProcessedExp.fault = 0
+              AND camProcessedExp.zpt_obs != 0 ORDER BY RAND() limit 5000) as sub order by time;";
+
+    $DQarray = array();
+    $qry = $db->query($sql);
+    if (dberror($qry)) {
+        echo "<b>Error with $sql</b><br>\n";
+        return;
+    }
+        echo "<b>Error with $sql</b><br>\n";
+
+    while ($qry->fetchInto($row)) {
+        $DQarray[] = $row;
+    }
+
+    // Ensure $DQarray is not empty
+    if (empty($DQarray)) {
+        echo "<center><b> No data for MJD: $mjdDay </b></center>";
+        return; // Stop execution if no data to encode
+    }
+
+    // Convert data array to JSON for easier handling in JavaScript
+    $dataJson = json_encode($DQarray);
+
+    if ($dataJson === false) {
+        echo "<b>Error encoding JSON:</b> " . json_last_error_msg(); // Display any JSON encoding errors
+        return; // Stop execution if JSON encoding fails
+    }
+
+    echo "<script>console.log(" . json_encode($dataJson) . ");</script>"; // Log JSON output to console for debugging
+echo <<<HTML
+<script type="text/javascript" src="loader.js"></script>
+
+<script type="text/javascript">
+    google.charts.load('current', {packages: ['corechart']});
+
+    // Use the encoded JSON directly in JavaScript
+    const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
+
+    // draw FWHM vs MJD
+    function drawFWHMChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'FWHM_major');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'FWHM_minor');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'seeing ~ 1"');
+            data.addColumn('number', 'seeing ~ 3"');
+
+        const rows = dataArray.map(row => {
+            const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id] = row;
+
+            return [
+                parseFloat(mjd),      // Position Angle
+                parseFloat(iq_fwhm_maj),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(iq_fwhm_min),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                4,
+                12
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'FWHM (MJD) ',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 1 },
+                1: { color: '#33a532', pointSize: 3, lineWidth: 1 },
+                2: { color: 'red', lineWidth: 2, pointSize: 0 },
+                3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'FWHM (pixels)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '8%', top: 60, right: '5%', bottom: 50}, // Adjust left and right
+            fontSize: 12,
+        };
+
+        const chart = new google.visualization.LineChart(document.getElementById('dq_div_fwhm'));
+        chart.draw(data, options);
+    }
+
+    // draw zoomed zerpoint vs mjd
+    function drawZOOMZPTChart() {
+        var data = new google.visualization.DataTable();
+        data.addColumn('number', 'MJD');
+
+        // Add columns for each filter type to handle separate colors
+        data.addColumn('number', 'g (24.563, 25.013)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'r (24.750, 24.840)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'i (24.611, 24.711)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'z (24.240, 24.300)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'y (23.320, 23.570)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'w (26.000, 26.300)');
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id] = row;
+
+            // Initialize all filter values as null
+            let zp_g = null, zp_r = null, zp_i = null, zp_z = null, zp_y = null, zp_w = null;
+
+            // Set the zeropoint offset based on filter and assign it to the correct series
+            switch (filter[0]) {
+                case 'g':
+                    zp_g = zpt_obs - 24.563;
+                    break;
+                case 'r':
+                    zp_r = zpt_obs - 24.750;
+                    break;
+                case 'i':
+                    zp_i = zpt_obs - 24.611;
+                    break;
+                case 'z':
+                    zp_z = zpt_obs - 24.240;
+                    break;
+                case 'y':
+                   zp_y = zpt_obs - 23.320;
+                    break;
+                case 'w':
+                    zp_w = zpt_obs - 26.000;
+                    break;
+            }
+
+            return [
+                parseFloat(mjd), // MJD value
+                zp_g, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_g - zpt_stdev), parseFloat(zp_g - zpt_stdev*-1),
+                zp_r, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_r - zpt_stdev), parseFloat(zp_r - zpt_stdev*-1),
+                zp_i, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_i - zpt_stdev), parseFloat(zp_i - zpt_stdev*-1),
+                zp_z, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_z - zpt_stdev), parseFloat(zp_z - zpt_stdev*-1),
+                zp_y, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_y - zpt_stdev), parseFloat(zp_y - zpt_stdev*-1),
+                zp_w, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_w - zpt_stdev), parseFloat(zp_w - zpt_stdev*-1)
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Zeropoint zoom-in (MJD)',
+            titleTextStyle: { color: 'black', fontSize: 15 },
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'start', maxLines: 3 },
+            series: {
+                0: { color: '#00f000', pointSize: 10, pointShape: 'circle',   linewidth: 1},  // 'g' filter
+                1: { color: '#f00000', pointSize: 10, pointShape: 'triangle', linewidth: 1},  // 'r' filter
+                2: { color: '#f05000', pointSize: 10, pointShape: 'square',   linewidth: 1},  // 'i' filter
+                3: { color: '#0050a0', pointSize: 10, pointShape: 'diamond',  linewidth: 1},  // 'z' filter
+               4: { color: '#ffe000', pointSize: 10, pointShape: 'star',     linewidth: 1},  // 'y' filter
+                5: { color: '#808080', pointSize: 10, pointShape: 'polygon',  linewidth: 1},  // 'w' filter
+            },
+            intervals: { color: 'red', lineWidth: 1, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: { count: -1 }
+            },
+            vAxis: {
+                title: 'Zeropoint (mag)',
+                viewWindow: {   min: -1.00, max: .5    },
+            },
+            tooltip: { isHtml: true },
+            chartArea: { left: '9%', top: 60, right: '5%', bottom: 50 },
+            fontSize: 12,
+        };
+
+        const chart = new google.visualization.LineChart(document.getElementById('dq_div_zoomzpt'));
+        chart.draw(data, options);
+    }
+
+    // draw zeropoint vs mjd
+    function drawZPTChart() {
+        var data = new google.visualization.DataTable();
+        data.addColumn('number', 'MJD');
+
+        // Add columns for each filter type to handle separate colors
+        data.addColumn('number', 'g' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'r' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'i' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'z' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'y' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn('number', 'w' );
+        data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+        data.addColumn({type: 'number', role: 'interval'});
+        data.addColumn({type: 'number', role: 'interval'});
+
+        // Count occurrences of each filter
+        const filterCounts = { g: 0, r: 0, i: 0, z: 0, y: 0, w: 0 };
+       const rows = dataArray.map(row => {
+            const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id] = row;
+
+            // Initialize all filter values as null
+            let zp_g = null, zp_r = null, zp_i = null, zp_z = null, zp_y = null, zp_w = null;
+
+            // Increment the count for the filter
+            if (filterCounts.hasOwnProperty(filter[0])) {
+                filterCounts[filter[0]] += 1;
+            }
+
+            // Set the zeropoint offset based on filter and assign it to the correct series
+            switch (filter[0]) {
+                case 'g':
+                    zp_g = zpt_obs - 0.0;
+                    break;
+                case 'r':
+                    zp_r = zpt_obs - 0.0;
+                    break;
+                case 'i':
+                    zp_i = zpt_obs - 0.0;
+                    break;
+                case 'z':
+                    zp_z = zpt_obs - 0.0;
+                    break;
+                case 'y':
+                    zp_y = zpt_obs - 0.0;
+                    break;
+                case 'w':
+                    zp_w = zpt_obs - 0.0;
+                    break;
+            }
+
+            return [
+                parseFloat(mjd), // MJD value
+                zp_g, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_g - zpt_stdev), parseFloat(zp_g - zpt_stdev*-1),
+                zp_r, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_r - zpt_stdev), parseFloat(zp_r - zpt_stdev*-1),
+                zp_i, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_i - zpt_stdev), parseFloat(zp_i - zpt_stdev*-1),
+                zp_z, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_z - zpt_stdev), parseFloat(zp_z - zpt_stdev*-1),
+                zp_y, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_y - zpt_stdev), parseFloat(zp_y - zpt_stdev*-1),
+                zp_w, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_w - zpt_stdev), parseFloat(zp_w - zpt_stdev*-1),
+            ];
+        });
+      data.addRows(rows);
+
+        // Include counts in the title using string concatenation
+        const countsTitle = 'Zeropoint (MJD) g: ' + filterCounts.g + ', r: ' + filterCounts.r + ', i: ' + filterCounts.i + ', z: ' + filterCounts.z + ', y: ' + filterCounts.y + ', w: ' + filterCounts.w;
+
+
+        console.log(countsTitle);
+
+        const options = {
+            title: countsTitle,
+            titleTextStyle: { color: 'black', fontSize: 15 },
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#00f000', pointSize: 10, pointShape: 'circle',   linewidth: 1},  // 'g' filter
+                1: { color: '#f00000', pointSize: 10, pointShape: 'triangle', linewidth: 1},  // 'r' filter
+                2: { color: '#f05000', pointSize: 10, pointShape: 'square',   linewidth: 1},  // 'i' filter
+                3: { color: '#0050a0', pointSize: 10, pointShape: 'diamond',  linewidth: 1},  // 'z' filter
+                4: { color: '#ffe000', pointSize: 10, pointShape: 'star',     linewidth: 1},  // 'y' filter
+                5: { color: '#808080', pointSize: 10, pointShape: 'polygon',  linewidth: 1},  // 'w' filter
+            },
+            intervals: { color: 'red', lineWidth: 1, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: { count: -1 }
+            },
+            vAxis: {
+                title: 'Zeropoint (mag)'
+            },
+            tooltip: { isHtml: true },
+            chartArea: { left: '9%', top: 60, right: '5%', bottom: 50 },
+            fontSize: 12,
+        };
+
+        const chart = new google.visualization.LineChart(document.getElementById('dq_div_zpt'));
+        chart.draw(data, options);
+    }
+
+    google.charts.setOnLoadCallback(drawFWHMChart);
+    google.charts.setOnLoadCallback(drawZPTChart);
+    google.charts.setOnLoadCallback(drawZOOMZPTChart);
+
+</script>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_fwhm" style="width:100%;"></div>
+    <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_zpt" style="width:100%;"></div>
+    <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_zoomzpt" style="width:100%;"></div>
+    <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
+</div>
+
+HTML;
+    // End timer
+    $total_time = round(microtime(true) - $start, 3);
+    echo "<center>loading plot in $total_time seconds<br></center>";
+
+}
+
+
+###########################################################################
+#
+# Get data quality
+#
+###########################################################################
+function getDataQuality($db, $range = 'week') {
+    // Start timer
+    $start = microtime(true);
+
+    $mjdDay  = getMJD();
+
+    $expnames = array();
+    // Determine the number of days based on the selected range
+    switch ($range) {
+        case 'week':
+            $days = 7;
+            break;
+        case 'month':
+            $days = 30;
+            break;
+        case 'day':
+        default:
+            $days = 1;
+            break;
+    }
+
+    for ($i = 0; $i < $days; $i++) {
+        $expnames[] = "'o" . ($mjdDay - $i) . "%'";
+    }
+    $expnameFilter = implode(" OR rawExp.exp_name LIKE ", $expnames);
+    $sql = "SELECT SUBTIME(dateobs, '10:00:00') AS time, AST_R0, AST_D0, 
+                   round(( 367*YEAR(dateobs)- FLOOR((7 * (YEAR(dateobs) + FLOOR((MONTH(dateobs) + 9) / 12))) / 4) + FLOOR(275 * MONTH(dateobs) / 9)
+                   + DAY(dateobs) + 1721013.5 + (HOUR(dateobs) / 24.0) + (MINUTE(dateobs) / 1440.0) + (SECOND(dateobs) / 86400.0) - 2400000.5), 5) AS MJD,
+                   filter, exp_time, comment, ra*180/pi() as RA, decl*180/pi() as DECL, AST_RS, AST_DS, AST_T0, posang, exp_name, alt, az, rawExp.exp_id
+            FROM rawExp JOIN chipRun using (exp_id) JOIN camRun using (chip_id) JOIN camProcessedExp using (cam_id)
+            WHERE (rawExp.exp_name LIKE " . $expnameFilter . ")
+               AND exp_type = 'OBJECT'
+               AND camRun.state like 'full'
+               AND camProcessedExp.fault = 0
+               AND camProcessedExp.zpt_obs != 0; ";
+
+    $DQarray = array();
+    $qry = $db->query($sql);
+    if (dberror($qry)) {
+        echo "<b>Error with $sql</b><br>\n";
+        return;
+    }
+    
+    while ($qry->fetchInto($row)) {
+        $DQarray[] = $row;
+    }
+    
+    // Ensure $DQarray is not empty
+    if (empty($DQarray)) {
+        echo "<b>Error:</b> Data array is empty.";
+        return; // Stop execution if no data to encode
+    }
+    
+    // Convert data array to JSON for easier handling in JavaScript
+    $dataJson = json_encode($DQarray);
+    
+    if ($dataJson === false) {
+        echo "<b>Error encoding JSON:</b> " . json_last_error_msg(); // Display any JSON encoding errors
+        return; // Stop execution if JSON encoding fails
+    }
+    
+    echo "<script>console.log(" . json_encode($dataJson) . ");</script>"; // Log JSON output to console for debugging
+    
+    echo <<<HTML
+    <!-- Attempt to load Google Charts library from the Google CDN -->
+    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js" onerror="loadLocalGoogleCharts()"></script>
+    
+    <script type="text/javascript">
+        // Check if Google Charts library loaded successfully
+        window.addEventListener("load", function() {
+            if (typeof google === "undefined" || typeof google.charts === "undefined") {
+                loadLocalGoogleCharts();
+            }
+        });
+    
+        // Function to load local version of Google Charts if CDN fails
+        function loadLocalGoogleCharts() {
+            console.warn("Google Charts CDN failed, loading local loader.js instead.");
+            var script = document.createElement("script");
+            script.src = "loader.js"; // Path to your local loader.js file
+            document.head.appendChild(script);
+        }
+</script>
+
+<script type="text/javascript">
+    google.charts.load('current', {packages: ['corechart']});
+
+    // Use the encoded JSON directly in JavaScript
+    const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
+
+    // draw pointing vs mjd
+    function drawMJDChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', '20 TP unit ~5.1"');
+            data.addColumn('number', '-20 TP unit ~5.1"');
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(mjd),               // MJD
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                20,                            // Dummy value for 20 TP unit
+                -20                            // Dummy value for -20 TP unit
+            ];
+        });
+
+        data.addRows(rows);
+       const options = {
+            title: 'Pointing Offset (MJD)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+                2: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }, // Line at y = 20
+                3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }  // Line at y = -20
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_mjd'));
+        chart.draw(data, options);
+    }
+
+
+    // draw pointing scatter
+    function drawSIGChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset_scatter (AST_RS)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset_scatter (AST_DS)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(mjd),               // MJD
+                parseFloat(ra_offset_sig),     // RA_offset_scatter (AST_RS)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset_sig),    // Dec_offset_scatter (AST_DS)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset Scatter (MJD)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+                2: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }, // Line at y = 20
+                3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }  // Line at y = -20
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset Scatter (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_sig'));
+        chart.draw(data, options);
+    }
+
+
+    // position angle
+    function drawPOSChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(position_ang),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Position Angle)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            // pointSize: 5, // Point size for scatter plot
+            // lineWidth: 2,
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Position Angle',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_pos'));
+        chart.draw(data, options);
+    }
+
+    function drawALTChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'Altitute');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(alt),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Altitude)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Altitude',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            //chartArea: {left: 80, top: 50, right: 20, bottom: 50},
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_alt'));
+        chart.draw(data, options);
+    }
+
+   function drawAZChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'Azumith');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(az),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Azumith)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Azumith',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            //chartArea: {left: 80, top: 50, right: 20, bottom: 50},
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_az'));
+        chart.draw(data, options);
+    }
+
+    google.charts.setOnLoadCallback(drawMJDChart);
+    google.charts.setOnLoadCallback(drawSIGChart);
+    google.charts.setOnLoadCallback(drawPOSChart);
+    google.charts.setOnLoadCallback(drawALTChart);
+    google.charts.setOnLoadCallback(drawAZChart);
+</script>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_mjd" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_sig" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_pos" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_alt" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_az" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+HTML;
+
+// End timer
+$total_time = round(microtime(true) - $start, 3);
+echo "<center>Loading plot in $total_time seconds<br></center>";
+
+}
+
+?>
+
Index: trunk/ippMonitor/raw/ipp.czar.dat
===================================================================
--- trunk/ippMonitor/raw/ipp.czar.dat	(revision 42736)
+++ trunk/ippMonitor/raw/ipp.czar.dat	(revision 42790)
@@ -5,4 +5,6 @@
 menulink   | menuselect      | link    | czartool               | czartool_labels.php
 menulink   | menuselect      | link    | storage status         | storage.php
+menulink   | menuselect      | link    | pointing               | pointing.php
+menulink   | menuselect      | link    | data quality           | dataquality.php
 menulink   | menuselect      | link    | mask stats             | maskStats.php
 menulink   | menuselect      | link    | night summary          | nightSummary.php
Index: trunk/ippMonitor/raw/pointing.php
===================================================================
--- trunk/ippMonitor/raw/pointing.php	(revision 42790)
+++ trunk/ippMonitor/raw/pointing.php	(revision 42790)
@@ -0,0 +1,478 @@
+<?php
+include 'ipp.php';
+include 'site.php';
+
+$ID = checkID();
+
+// require an explicit project
+if (! $ID['proj']) { projectform ($ID); }
+
+// connect to czarDb 
+$czardb = dbconnect($CZARDBNAME); 
+$projectdb = dbconnect($ID['proj']);
+
+if ($ID['menu']) {$myMenu = $ID['menu'];}
+else {$myMenu = "ipp.czar.dat";}
+
+menu($myMenu, 'Czartool on '.$lastUpdateTime, 'ipp.css', $ID['link'], $ID['proj']);
+
+$pass = $ID['pass'];
+$proj = $ID['proj'];
+$menu = $ID['menu'];
+
+// Fetch data based on range selection
+$range = isset($_GET['range']) ? $_GET['range'] : 'week';
+
+echo "<table border=\"0\">";
+echo "<tr>";
+echo "  <td style=width:1240>";
+echo "    <h1 align=\"middle\">Pointing Report</h1>";
+echo "  <center>";
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=day">Today (MJD: '.getMJD().')</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=week">Past week (default)</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=month">Past month</a> | ';
+echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=year">Past year </a> | ';
+echo "  </td>";
+echo "</tr>";
+echo "</table>";
+echo getDataQuality($projectdb, $range);
+
+menu_end();
+
+
+/////////////////////////////////////////////////////////////////////////////
+//                                                                         //
+//                               Functions                                 //
+//                                                                         //
+/////////////////////////////////////////////////////////////////////////////
+###########################################################################
+#
+# Get data quality
+#
+###########################################################################
+function getDataQuality($db, $range = 'week') {
+    // Start timer
+    $start = microtime(true);
+
+    $mjdDay  = getMJD();
+    $mjdDay_yearback = $mjdDay - 366;
+    $mjdDay_monthback = $mjdDay - 32;
+    $min_exp_id = 0;
+
+    // Determine the reference exp_name based on the selected range
+    if ($range === 'year') {
+        $expname = "o" . $mjdDay_yearback . "%";
+    } elseif ($range === 'month') {
+        $expname = "o" . $mjdDay_monthback . "%";
+    } else {
+        $expname = "o" . ($mjdDay - 7) . "%"; // Default week-based constraint
+    }
+
+    // Query to get the minimum exp_id
+    $expIdQuery = "SELECT MIN(exp_id) AS min_exp_id FROM rawExp WHERE rawExp.exp_name LIKE '$expname';";
+
+    $result = $db->query($expIdQuery);
+
+    // Fetch result into $row as an associative array
+    $result->fetchInto($row, DB_FETCHMODE_ASSOC);
+
+    if (!empty($row['min_exp_id'])) {
+        $min_exp_id = $row['min_exp_id'];
+        echo "Minimum exp_id: " . $min_exp_id;
+    } else {
+        echo "No results found.";
+    }
+
+    echo "<br><br>";
+
+    // Apply different constraints based on the selected range
+    if ($range === 'year') {
+        // Constraint by dateobs within the last 365 days
+        $dateConstraint = "dateobs >= NOW() - INTERVAL 365 DAY AND rawExp.exp_id >= $min_exp_id";
+    } elseif ($range === 'month') {
+        // Constraint by dateobs within the last 30 days
+        $dateConstraint = "dateobs >= NOW() - INTERVAL 30 DAY AND rawExp.exp_id >= $min_exp_id";
+    } else {
+        // Constraint by exp_name for week and day ranges
+        $expnames = array();
+        $days = ($range === 'day') ? 1 : 7; // Set days to 1 for 'day', otherwise default to 7 (week)
+
+        for ($i = 0; $i < $days; $i++) {
+            $expnames[] = "'o" . ($mjdDay - $i) . "%'";
+        }
+        $expnameFilter = " (rawExp.exp_name LIKE " . implode(" OR rawExp.exp_name LIKE ", $expnames) . ") AND rawExp.exp_id >= $min_exp_id";
+        $dateConstraint = $expnameFilter;
+    }
+
+   // Updated SQL query
+    $sql = "select * from (SELECT SUBTIME(dateobs, '10:00:00') AS time, AST_R0, AST_D0,
+                   round(( 367*YEAR(dateobs)- FLOOR((7 * (YEAR(dateobs) + FLOOR((MONTH(dateobs) + 9) / 12))) / 4) + FLOOR(275 * MONTH(dateobs) / 9)
+                   + DAY(dateobs) + 1721013.5 + (HOUR(dateobs) / 24.0) + (MINUTE(dateobs) / 1440.0) + (SECOND(dateobs) / 86400.0) - 2400000.5), 5) AS MJD,
+                   filter, exp_time, comment, ra*180/pi() as RA, decl*180/pi() as DECL, AST_RS, AST_DS, AST_T0, posang, exp_name, alt, az, rawExp.exp_id
+            FROM rawExp
+            JOIN chipRun USING (exp_id)
+            JOIN camRun USING (chip_id)
+            JOIN camProcessedExp USING (cam_id)
+            WHERE $dateConstraint
+              AND exp_type = 'OBJECT'
+              AND camRun.state LIKE 'full'
+              AND camProcessedExp.fault = 0
+              AND camProcessedExp.zpt_obs != 0 ORDER BY RAND() limit 5000) as sub order by time;";
+
+    $DQarray = array();
+    $qry = $db->query($sql);
+    if (dberror($qry)) {
+        echo "<b>Error with $sql</b><br>\n";
+        return;
+    }
+        echo "$sql<br><br>\n";
+    
+    while ($qry->fetchInto($row)) {
+        $DQarray[] = $row;
+    }
+    
+    // Ensure $DQarray is not empty
+    if (empty($DQarray)) {
+        echo "<b>Error:</b> Data array is empty.";
+        return; // Stop execution if no data to encode
+    }
+    
+    // Convert data array to JSON for easier handling in JavaScript
+    $dataJson = json_encode($DQarray);
+    
+    if ($dataJson === false) {
+        echo "<b>Error encoding JSON:</b> " . json_last_error_msg(); // Display any JSON encoding errors
+        return; // Stop execution if JSON encoding fails
+    }
+    
+    echo "<script>console.log(" . json_encode($dataJson) . ");</script>"; // Log JSON output to console for debugging
+    
+    echo <<<HTML
+    <!-- Attempt to load Google Charts library from the Google CDN -->
+    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js" onerror="loadLocalGoogleCharts()"></script>
+    
+    <script type="text/javascript">
+        // Check if Google Charts library loaded successfully
+        window.addEventListener("load", function() {
+            if (typeof google === "undefined" || typeof google.charts === "undefined") {
+                loadLocalGoogleCharts();
+            }
+        });
+    
+        // Function to load local version of Google Charts if CDN fails
+        function loadLocalGoogleCharts() {
+            console.warn("Google Charts CDN failed, loading local loader.js instead.");
+            var script = document.createElement("script");
+            script.src = "loader.js"; // Path to your local loader.js file
+            document.head.appendChild(script);
+        }
+</script>
+
+<script type="text/javascript">
+    google.charts.load('current', {packages: ['corechart']});
+
+    // Use the encoded JSON directly in JavaScript
+    const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
+
+    // draw pointing vs mjd
+    function drawMJDChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', '20 TP unit ~5.1"');
+            data.addColumn('number', '-20 TP unit ~5.1"');
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(mjd),               // MJD
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                20,                            // Dummy value for 20 TP unit
+                -20                            // Dummy value for -20 TP unit
+            ];
+        });
+
+        data.addRows(rows);
+       const options = {
+            title: 'Pointing Offset (MJD)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+                2: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }, // Line at y = 20
+                3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }  // Line at y = -20
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_mjd'));
+        chart.draw(data, options);
+    }
+
+
+    // draw pointing scatter
+    function drawSIGChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset_scatter (AST_RS)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset_scatter (AST_DS)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(mjd),               // MJD
+                parseFloat(ra_offset_sig),     // RA_offset_scatter (AST_RS)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset_sig),    // Dec_offset_scatter (AST_DS)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset Scatter (MJD)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+                2: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }, // Line at y = 20
+                3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }  // Line at y = -20
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'MJD',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset Scatter (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_sig'));
+        chart.draw(data, options);
+    }
+
+
+    // position angle
+    function drawPOSChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(position_ang),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Position Angle)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            // pointSize: 5, // Point size for scatter plot
+            // lineWidth: 2,
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Position Angle',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_pos'));
+        chart.draw(data, options);
+    }
+
+    function drawALTChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'Altitute');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(alt),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Altitude)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Altitude',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            //chartArea: {left: 80, top: 50, right: 20, bottom: 50},
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_alt'));
+        chart.draw(data, options);
+    }
+
+   function drawAZChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'Azumith');
+            data.addColumn('number', 'RA_offset (AST_R0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const rows = dataArray.map(row => {
+            const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id] = row;
+
+            return [
+                parseFloat(az),      // Position Angle
+                parseFloat(ra_offset),         // RA_offset (AST_R0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+                parseFloat(dec_offset),        // Dec_offset (AST_D0)
+                "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
+            ];
+        });
+
+        data.addRows(rows);
+
+        const options = {
+            title: 'Pointing Offset (Azumith)',
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 480,
+            legend: { position: 'top', alignment: 'center' },
+            series: {
+                0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
+                1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
+            },
+            intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+            hAxis: {
+                title: 'Azumith',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+            //chartArea: {left: 80, top: 50, right: 20, bottom: 50},
+            chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
+            fontSize: 15,
+        };
+
+        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_az'));
+        chart.draw(data, options);
+    }
+
+    google.charts.setOnLoadCallback(drawMJDChart);
+    google.charts.setOnLoadCallback(drawSIGChart);
+    google.charts.setOnLoadCallback(drawPOSChart);
+    google.charts.setOnLoadCallback(drawALTChart);
+    google.charts.setOnLoadCallback(drawAZChart);
+</script>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_mjd" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_sig" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_pos" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_alt" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_az" style="width:1240px;"></div>
+    <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
+</div>
+HTML;
+
+// End timer
+$total_time = round(microtime(true) - $start, 3);
+echo "<center>Loading plot in $total_time seconds<br></center>";
+
+}
+
+?>
+
