Index: trunk/ippMonitor/raw/functions.php
===================================================================
--- trunk/ippMonitor/raw/functions.php	(revision 42972)
+++ trunk/ippMonitor/raw/functions.php	(revision 43071)
@@ -283,7 +283,11 @@
         cam_id         : \${row.cam_id}<br>
         moon_angle     : \${row.moon_angle}<br>
-        raw_chips      : \${row.n_raw}<br>
-        good_chips     : \${row.g_chips}<br>
+        n_chips        : \${Number(row.null_bg) + Number(row.good_chip)}<br>
+        good_phot      : \${row.good_phot}<br>
+        good_astro     : \${row.good_astro}<br>
+        null_bg        : \${row.null_bg}<br>
+        poor_chips     : \${row.poor_chip}<br>
         comment        : \${row.comment}<br>
+        <img src="http://ippmonitor.ipp.ifa.hawaii.edu/ippMonitor/getimage.php?name=\${row.image_path}&rule=PPIMAGE.JPEG2&camera=${proj}&class_id=NONE">
     `;
 }
@@ -302,6 +306,6 @@
 
     // testing day
-    //$mjdDay = 60945;
     $mjdDay  = getMJD();
+    //$mjdDay = 61109;
     $mjdDay_yearback = $mjdDay - 366;
     $mjdDay_monthback = $mjdDay - 32;
@@ -335,18 +339,10 @@
     $stmt->execute([':expname' => $expname]);
     $row = $stmt->fetch(PDO::FETCH_ASSOC);
-//    if (!empty($row['min_exp_id'])) {
-//        $min_exp_id = $row['min_exp_id'];
-//    } else {
-//        echo "<center><b>No results found for min_exp_id</b></center>";
-//    }
-
     if (!empty($row['min_exp_id'])) {
         $min_exp_id = $row['min_exp_id'];
-        echo "<center><b>min_exp_id:$min_exp_id $mjdDay_monthback $mjdDay</b></center>";
+        echo "<center><b>min_exp_id:$min_exp_id month:$mjdDay_monthback today:$mjdDay</b></center>";
     } else {
-        echo "<center><b>No results found for min_exp_id:$min_exp_id $mjdDay_monthback $mjdDay</b></center>";
-    }
-
-    //echo "<br><br>";
+        echo "<center><b>No results found for min_exp_id:$min_exp_id month:$mjdDay_monthback today:$mjdDay</b></center>";
+    }
 
     // Determine the date constraint based on the range
@@ -380,495 +376,4 @@
  
     // Updated SQL query
-    $sql = "select * from (SELECT SUBTIME(dateobs, '10:00:00') AS time, AST_R0, AST_D0, AST_RS, AST_DS, AST_T0, posang,
-                   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, round(ra*180/pi(), 5) as RA, round(decl*180/pi(), 5) as DECL, exp_name, alt, 
-                   az, fwhm_major, fwhm_minor, zpt_obs, zpt_stdev, airmass, exp_time, 
-                   rawExp.exp_id, camRun.label, moon_angle, cam_id, BIT_COUNT(astrom_chips) AS working_chips
-            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 10000) as sub order by time;";
-
-    $stmt = $db->query($sql);
-    $DQarray = $stmt->fetchAll(PDO::FETCH_ASSOC);
-    
-    // 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">
-    // Use the encoded JSON directly in JavaScript
-    const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
-
-    const nRows = dataArray.length;
-
-    // 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({type: 'number', role: 'interval'});
-            data.addColumn({type: 'number', role: 'interval'});
-            data.addColumn('number', 'Dec_offset (AST_D0)');
-            data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
-            data.addColumn({type: 'number', role: 'interval'});
-            data.addColumn({type: 'number', role: 'interval'});
-            data.addColumn('number', '20 TP unit ~5.1"');
-            data.addColumn('number', '-20 TP unit ~5.1"');
-
-	const mjdValues = dataArray.map(r => parseFloat(r.MJD));
-        let mjdMin0 = Math.min(...mjdValues);
-        const mjdMin = Math.floor(mjdMin0 * 10) / 10; // 1 decimal place or floor/ceil depending on your needs
-        let mjdMax0 = Math.max(...mjdValues);
-        const mjdMax = Math.ceil(mjdMax0 * 10) / 10; // 1 decimal place or floor/ceil depending on your needs
-	// Extract the first MJD value from dataArray
-        const firstMJD = dataArray.length > 0 ? Math.floor(parseFloat(dataArray[0].MJD)) : "N/A";
-        console.log("First MJD Value:", firstMJD);
-        const mjdoff = mjdMin;
-
-	const mjdVals = [];
-	const rows = dataArray.map(row => {
-            const mjd     = parseFloat(row.MJD) - mjdoff; // MJD shift to first one
-            mjdVals.push(mjd);  // collect MJD offset values
-            const ra_off  = parseFloat(row.AST_R0);  // RA_offset (AST_R0)
-            const dec_off = parseFloat(row.AST_D0);  // DEC_offset (AST_D0)
-            const ra_sct  = parseFloat(row.AST_RS);  // RA_scatter (AST_RS)
-            const dec_sct = parseFloat(row.AST_DS);  // DEC_scatter (AST_DS)
-            const tooltip  = buildTooltip(row);      // Tooltip
-
-	    return [
-              mjd, ra_off, tooltip, ra_off-ra_sct, ra_off+ra_sct, dec_off, tooltip, dec_off-dec_sct, dec_off+dec_sct, 
-              20,   // Dummy value for 20 TP unit
-              -20  // Dummy value for -20 TP unit
-	    ];
-        });
-
-        data.addRows(rows);
-
-        // After building rows:
-        const allVerticalVals = rows.flatMap(r => [
-          r[3], // ra_off - ra_sct
-          r[4], // ra_off + ra_sct
-          r[7], // dec_off - dec_sct
-          r[8]  // dec_off + dec_sct
-        ]).filter(v => !isNaN(v)); // â
- remove NaN values
-        
-        // If array is empty after filtering, use safe defaults
-        const minY = allVerticalVals.length ? Math.min(...allVerticalVals) : 0;
-        const maxY = allVerticalVals.length ? Math.max(...allVerticalVals) : 0;
-        
-        // Clamp values: use Â±100 if beyond 100
-        const yMin = Math.abs(minY) > 150 ? -150 : minY;
-        const yMax = Math.abs(maxY) > 150 ?  150 : maxY;
-        
-        console.log("minY:", minY, yMin, "maxY:", maxY, yMax);
-
-        // Now compute min and max
-        const mjdMin1 = Math.min(...mjdVals);
-        const mjdMax1 = Math.max(...mjdVals);
-
-        const defaultSeries = { pointSize: 5, lineWidth: 0 };
-
-        const titleText = `Pointing Offset vs. MJD ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
-        const options = {
-            //title: 'Pointing Offset vs. MJD ({$proj})',
-            title: titleText,
-            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: { lineWidth: .5, style: 'sticks' },
-            interval: {
-              0: {color: 'blue'},    // interval columns attached to first series
-              1: {color: 'green'}    // interval columns attached to second series
-            },
-	    hAxis: {
-                title: 'MJD - ' + mjdoff + ' (day)',
-                viewWindow: {
-                   min: 0.,  // small padding
-                   max: mjdMax1 + 0.005
-                },
-               gridlines: { count: -1 }  // auto gridlines
-            },
-            vAxis: {
-              title: 'Pointing Offset (TP units)',
-              viewWindow: {
-                min: yMin,
-                max: yMax
-              }
-            },
-            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);
-    }
-
-    // position angle
-    function drawPOSChart() {
-        var data = new google.visualization.DataTable();
-            data.addColumn('number', 'Posang');
-            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 posang  = parseFloat(row.posang);  // Position Angle
-            const ra_off  = parseFloat(row.AST_R0);  // RA_offset (AST_R0)
-            const dec_off = parseFloat(row.AST_D0);  // DEC_offset (AST_D0)
-            const tooltip  = buildTooltip(row);      // Tooltip
-	    return [
-              posang, ra_off, tooltip, dec_off, tooltip
-	    ];
-        });
-
-        data.addRows(rows);
-
-        const titleText = `Pointing Offset vs. Position Angle ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
-        const options = {
-            title: titleText,
-            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 (deg)',
-                gridlines: {count: -1}
-            },
-             vAxis: {
-                 title: 'Pointing 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 alt     = parseFloat(row.alt);     // Altitude
-            const ra_off  = parseFloat(row.AST_R0);  // RA_offset (AST_R0)
-            const dec_off = parseFloat(row.AST_D0);  // DEC_offset (AST_D0)
-	    const tooltip  = buildTooltip(row);      // Tooltip
-	    return [
-              alt, ra_off, tooltip, dec_off, tooltip
-	    ];
-        });
-
-        data.addRows(rows);
-
-        const titleText = `Pointing Offset vs. Altitude ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
-        const options = {
-            title: titleText,
-            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 (deg)',
-                gridlines: {count: -1}
-            },
-            vAxis: {
-                title: 'Pointing 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 az      = parseFloat(row.az);      // Azumith
-            const ra_off  = parseFloat(row.AST_R0);  // RA_offset (AST_R0)
-            const dec_off = parseFloat(row.AST_D0);  // DEC_offset (AST_D0)
-	    const tooltip  = buildTooltip(row);      // Tooltip
-	    return [
-              az, ra_off, tooltip, dec_off, tooltip
-	    ];
-        });
-
-        data.addRows(rows);
-
-        const titleText = `Pointing Offset vs. Azumith ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
-        const options = {
-            title: titleText,
-            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 (deg)',
-                gridlines: {count: -1}
-            },
-            vAxis: {
-                title: 'Pointing 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);
-    }
-
-
-   function drawScatChart() {
-        var data = new google.visualization.DataTable();
-            data.addColumn('number', 'tmp');
-            data.addColumn('number', 'RA_scat (AST_RS)');
-            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
-            data.addColumn('number', 'Dec_scat (AST_DS)');
-            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
-
-	const rows = dataArray.map(row => {
-            const par  = parseFloat(row.MJD); 
-            const ra_scat  = parseFloat(row.AST_RS);  // RA_scat (AST_RS)
-            const dec_scat = parseFloat(row.AST_DS);  // DEC_scat (AST_DS)
-	    const tooltip  = buildTooltip(row);       // Tooltip
-	    return [
-              par, ra_scat, tooltip, dec_scat, tooltip
-	    ];
-        });
-
-        data.addRows(rows);
-
-        const titleText = `Pointing Offset vs. MJD ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
-        const options = {
-            title: titleText,
-            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: 'MJD',
-                gridlines: {count: -1}
-            },
-            vAxis: {
-                title: 'Pointing Scatter (TP units)',
-              viewWindow: {
-                min: 0,
-               // max: 20
-              }
-            },
-            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_scat'));
-        chart.draw(data, options);
-    }
-
-    google.charts.setOnLoadCallback(drawMJDChart);
-    google.charts.setOnLoadCallback(drawPOSChart);
-    google.charts.setOnLoadCallback(drawALTChart);
-    google.charts.setOnLoadCallback(drawAZChart);
-    google.charts.setOnLoadCallback(drawScatChart);
-</script>
-
-<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
-    <div id="dq_div_mjd" style="width:1240px;"></div>
-</div>
-
-<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
-    <div id="dq_div_scat" style="width:1240px;"></div>
-</div>
-
-<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
-    <div id="dq_div_pos" style="width:1240px;"></div>
-</div>
-
-<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
-    <div id="dq_div_alt" style="width:1240px;"></div>
-</div>
-
-<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
-    <div id="dq_div_az" style="width:1240px;"></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', $proj) {
-    // Start timer
-    $start = microtime(true);
-
-    // testing day
-    $mjdDay  = getMJD();
-    //$mjdDay = 60950;
-    $mjdDay_yearback = $mjdDay - 366;
-    $mjdDay_monthback = $mjdDay - 32;
-    $min_exp_id = 0;
-
-
-    // Determine the reference exp_name based on the selected range
-    switch ($range) {
-        case 'year':
-            $expname = "o" . $mjdDay_yearback . "%";
-            break;
-        case 'month':
-            $expname = "o" . $mjdDay_monthback . "%";
-            break;
-        case 'week':
-            $expname = "o" . ($mjdDay - 7) . "%";
-            break;
-        case 'yesterday':
-            $expname = "o" . ($mjdDay - 1) . "%";
-            break;
-        case 'today':
-            $expname = "o" . $mjdDay . "%";
-            break;
-        default:
-            $expname = "o" . ($mjdDay - 7) . "%"; // Default: week
-            break;
-    }
-
-    // --- PDO query for min_exp_id ---
-    $stmt = $db->prepare("SELECT MIN(exp_id) AS min_exp_id FROM rawExp WHERE exp_name LIKE :expname");
-    $stmt->execute([':expname' => $expname]);
-    $row = $stmt->fetch(PDO::FETCH_ASSOC);
-    if (!empty($row['min_exp_id'])) {
-        $min_exp_id = $row['min_exp_id'];
-        echo "<center><b>min_exp_id:$min_exp_id $mjdDay_monthback $mjdDay</b></center>";
-    } else {
-        echo "<center><b>No results found for min_exp_id:$min_exp_id $mjdDay_monthback $mjdDay</b></center>";
-    }
-
-    // Determine the date constraint based on the range
-    switch ($range) {
-        case 'year':
-            $dateConstraint = "rawExp.dateobs >= NOW() - INTERVAL 365 DAY AND rawExp.exp_id >= $min_exp_id";
-            break;
-        case 'month':
-            $dateConstraint = "rawExp.dateobs >= NOW() - INTERVAL 30 DAY AND rawExp.exp_id >= $min_exp_id";
-            break;
-        case 'week':
-            $expnames = array();
-            for ($i = 0; $i < 7; $i++) {
-                $expnames[] = "rawExp.exp_name LIKE 'o" . ($mjdDay - $i) . "%'";
-            }
-            $dateConstraint = "(" . implode(" OR ", $expnames) . ") AND rawExp.exp_id >= $min_exp_id";
-            $qday = $mjdDay - 6; // or something meaningful for week
-            break;
-        case 'yesterday':
-            $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
-	    $qday = $mjdDay - 1;
-            break;
-        case 'today':
-            $dateConstraint = "rawExp.exp_name LIKE 'o$mjdDay%' AND rawExp.exp_id >= $min_exp_id";
-	    $qday = $mjdDay;
-            break;
-        case 'all':
-            $dateConstraint = "rawExp.exp_id >= 0";
-            break;
-        default: // Default to last night
-            $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
-	    $qday = $mjdDay - 1;
-            break;
-    }
-
-    // Updated SQL query
-    $sql1 = "select * from (SELECT SUBTIME(dateobs, '10:00:00') AS time, AST_R0, AST_D0, AST_RS, AST_DS, AST_T0, posang,
-                   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, round(ra*180/pi(), 5) as RA, round(decl*180/pi(), 5) as DECL, exp_name, alt, 
-                   az, fwhm_major, fwhm_minor, zpt_obs, zpt_stdev, airmass, exp_time, 
-                   rawExp.exp_id, camRun.label, moon_angle, cam_id, BIT_COUNT(astrom_chips) AS working_chips
-            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;";
     $sql = " SELECT * FROM (SELECT
     SUBTIME(rawExp.dateobs, '10:00:00') AS time, 
@@ -901,4 +406,5 @@
     cme.zpt_obs, 
     cme.zpt_stdev, 
+    cme.path_base AS image_path,
     rawExp.airmass, 
     rawExp.exp_time,
@@ -907,42 +413,558 @@
     camRun.label,
     camRun.cam_id,
-    BIT_COUNT(astrom_chips) AS g_chips,
-    SUM(CASE WHEN rim.bg > 0 THEN 1 ELSE 0 END) AS n_raw,
-    SUM(CASE WHEN chi.bg IS NOT NULL THEN 1 ELSE 0 END) AS n_read,
-    SUM(CASE WHEN chi.quality > 0 THEN 1 ELSE 0 END) AS n_poor
-
-FROM camProcessedExp cme
-JOIN camRun USING (cam_id)
-JOIN chipRun USING (chip_id)
-JOIN chipProcessedImfile chi USING (chip_id)
-JOIN rawExp ON (rawExp.exp_id = chipRun.exp_id)
-JOIN rawImfile rim ON (rim.exp_id = rawExp.exp_id)
-
-WHERE
-    $dateConstraint
-    AND rawExp.exp_type = 'OBJECT'
-    AND camRun.state = 'full'
-    AND cme.fault = 0
-    AND cme.zpt_obs != 0
-    AND rim.class_id = chi.class_id
-
-GROUP BY
+    BIT_COUNT(astrom_chips) AS good_astro,
+    SUM(chi.bg IS NOT NULL) AS good_chip,
+    SUM(chi.bg IS NULL)     AS null_bg,
+    SUM(chi.n_stars > 5)    AS good_phot,
+    SUM(chi.quality > 0)    AS poor_chip 
+
+    FROM camProcessedExp cme
+    JOIN camRun USING (cam_id)
+    JOIN chipRun USING (chip_id)
+    JOIN chipProcessedImfile chi USING (chip_id)
+    JOIN rawExp ON (rawExp.exp_id = chipRun.exp_id)
+    
+    WHERE
+        $dateConstraint
+        AND rawExp.exp_type = 'OBJECT'
+        AND camRun.state = 'full'
+        AND cme.fault = 0
+        AND cme.zpt_obs != 0
+    
+    GROUP BY
+        rawExp.exp_name,
+        camRun.label,
+        cam_id,
+        rawExp.dateobs,
+        rawExp.ra,
+        rawExp.decl,
+        rawExp.alt,
+        rawExp.moon_angle,
+        astrom_chips,
+        cme.bg,
+        cme.n_stars
+    
+    ORDER BY RAND() limit 5000) as sub order by time;
+    ";
+
+    $stmt = $db->query($sql);
+    $DQarray = $stmt->fetchAll(PDO::FETCH_ASSOC);
+    
+    // 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">
+    // Use the encoded JSON directly in JavaScript
+    const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
+
+    const nRows = dataArray.length;
+
+    // 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({type: 'number', role: 'interval'});
+            data.addColumn({type: 'number', role: 'interval'});
+            data.addColumn('number', 'Dec_offset (AST_D0)');
+            data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
+            data.addColumn({type: 'number', role: 'interval'});
+            data.addColumn({type: 'number', role: 'interval'});
+            data.addColumn('number', '20 TP unit ~5.1"');
+            data.addColumn('number', '-20 TP unit ~5.1"');
+
+        const mjdoff = $mjdDay;
+	const rows = dataArray.map(row => {
+            const mjd     = parseFloat(row.MJD) - mjdoff; // MJD shift to first one
+            const ra_off  = parseFloat(row.AST_R0);  // RA_offset (AST_R0)
+            const dec_off = parseFloat(row.AST_D0);  // DEC_offset (AST_D0)
+            const ra_sct  = parseFloat(row.AST_RS);  // RA_scatter (AST_RS)
+            const dec_sct = parseFloat(row.AST_DS);  // DEC_scatter (AST_DS)
+            const tooltip  = buildTooltip(row);      // Tooltip
+
+	    return [
+              mjd, ra_off, tooltip, ra_off-ra_sct, ra_off+ra_sct, dec_off, tooltip, dec_off-dec_sct, dec_off+dec_sct, 
+              20,   // Dummy value for 20 TP unit
+              -20  // Dummy value for -20 TP unit
+	    ];
+        });
+
+        data.addRows(rows);
+
+        // After building rows:
+        const allVerticalVals = rows.flatMap(r => [
+          r[3], // ra_off - ra_sct
+          r[4], // ra_off + ra_sct
+          r[7], // dec_off - dec_sct
+          r[8]  // dec_off + dec_sct
+        ]).filter(v => !isNaN(v)); // â
+ remove NaN values
+        
+        // If array is empty after filtering, use safe defaults
+        const minY = allVerticalVals.length ? Math.min(...allVerticalVals) : 0;
+        const maxY = allVerticalVals.length ? Math.max(...allVerticalVals) : 0;
+        
+        // Clamp values: use Â±100 if beyond 100
+        const yMin = Math.abs(minY) > 150 ? -150 : minY;
+        const yMax = Math.abs(maxY) > 150 ?  150 : maxY;
+        
+        console.log("minY:", minY, yMin, "maxY:", maxY, yMax);
+
+        const defaultSeries = { pointSize: 5, lineWidth: 0 };
+
+        const titleText = `Pointing Offset vs. MJD ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
+        const options = {
+            //title: 'Pointing Offset vs. MJD ({$proj})',
+            title: titleText,
+            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: { lineWidth: .5, style: 'sticks' },
+            interval: {
+              0: {color: 'blue'},    // interval columns attached to first series
+              1: {color: 'green'}    // interval columns attached to second series
+            },
+	    hAxis: {
+                title: 'MJD - ' + mjdoff + ' (today)',
+               gridlines: { count: -1 }  // auto gridlines
+            },
+            vAxis: {
+              title: 'Pointing Offset (TP units)',
+              viewWindow: {
+                min: yMin,
+                max: yMax
+              }
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+              explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
+            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);
+    }
+
+    // position angle
+    function drawPOSChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'Posang');
+            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 posang  = parseFloat(row.posang);  // Position Angle
+            const ra_off  = parseFloat(row.AST_R0);  // RA_offset (AST_R0)
+            const dec_off = parseFloat(row.AST_D0);  // DEC_offset (AST_D0)
+            const tooltip  = buildTooltip(row);      // Tooltip
+	    return [
+              posang, ra_off, tooltip, dec_off, tooltip
+	    ];
+        });
+
+        data.addRows(rows);
+
+        const titleText = `Pointing Offset vs. Position Angle ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
+        const options = {
+            title: titleText,
+            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 (deg)',
+                gridlines: {count: -1}
+            },
+             vAxis: {
+                 title: 'Pointing Offset (TP units)',
+             },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+              explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
+            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 alt     = parseFloat(row.alt);     // Altitude
+            const ra_off  = parseFloat(row.AST_R0);  // RA_offset (AST_R0)
+            const dec_off = parseFloat(row.AST_D0);  // DEC_offset (AST_D0)
+	    const tooltip  = buildTooltip(row);      // Tooltip
+	    return [
+              alt, ra_off, tooltip, dec_off, tooltip
+	    ];
+        });
+
+        data.addRows(rows);
+
+        const titleText = `Pointing Offset vs. Altitude ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
+        const options = {
+            title: titleText,
+            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 (deg)',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Pointing Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+              explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
+            //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 az      = parseFloat(row.az);      // Azumith
+            const ra_off  = parseFloat(row.AST_R0);  // RA_offset (AST_R0)
+            const dec_off = parseFloat(row.AST_D0);  // DEC_offset (AST_D0)
+	    const tooltip  = buildTooltip(row);      // Tooltip
+	    return [
+              az, ra_off, tooltip, dec_off, tooltip
+	    ];
+        });
+
+        data.addRows(rows);
+
+        const titleText = `Pointing Offset vs. Azumith ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
+        const options = {
+            title: titleText,
+            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 (deg)',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Pointing Offset (TP units)',
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+              explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
+            //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);
+    }
+
+
+   function drawScatChart() {
+        var data = new google.visualization.DataTable();
+            data.addColumn('number', 'MJD');
+            data.addColumn('number', 'RA_scat (AST_RS)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+            data.addColumn('number', 'Dec_scat (AST_DS)');
+            data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const mjdoff = $mjdDay;
+	const rows = dataArray.map(row => {
+            const mjd      = parseFloat(row.MJD) - mjdoff; // MJD shift to first one
+            const ra_scat  = parseFloat(row.AST_RS);  // RA_scat (AST_RS)
+            const dec_scat = parseFloat(row.AST_DS);  // DEC_scat (AST_DS)
+	    const tooltip  = buildTooltip(row);       // Tooltip
+	    return [
+              mjd, ra_scat, tooltip, dec_scat, tooltip
+	    ];
+        });
+
+        data.addRows(rows);
+
+        const titleText = `Pointing Offset vs. MJD ({$proj}) â \${nRows} rows\${dataArray.length >= 10000 ? " (sampled)" : ""}`;
+        const options = {
+            title: titleText,
+            titleTextStyle: {color: 'black', fontSize: 15},
+            width: '100%',
+            height: 280,
+            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: 'MJD - ' + mjdoff + ' (today)',
+                gridlines: {count: -1}
+            },
+            vAxis: {
+                title: 'Scatter (TP units)',
+              viewWindow: {
+                min: 0,
+               // max: 20
+              }
+            },
+            tooltip: { isHtml: true }, // Render tooltips as HTML
+              explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
+            //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_scat'));
+        chart.draw(data, options);
+    }
+
+    google.charts.setOnLoadCallback(drawMJDChart);
+    google.charts.setOnLoadCallback(drawPOSChart);
+    google.charts.setOnLoadCallback(drawALTChart);
+    google.charts.setOnLoadCallback(drawAZChart);
+    google.charts.setOnLoadCallback(drawScatChart);
+</script>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_mjd" style="width:1240px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:300px;">
+    <div id="dq_div_scat" style="width:1240px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_pos" style="width:1240px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_alt" style="width:1240px;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
+    <div id="dq_div_az" style="width:1240px;"></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', $proj) {
+    // Start timer
+    $start = microtime(true);
+    // mjd today
+    $mjdDay  = getMJD();
+    // testing day
+    //$mjdDay = 61104;
+    $mjdDay_yearback = $mjdDay - 366;
+    $mjdDay_monthback = $mjdDay - 30;
+    $min_exp_id = 0;
+
+
+    // Determine the reference exp_name based on the selected range
+    switch ($range) {
+        case 'year':
+            $expname = "o" . $mjdDay_yearback . "%";
+            break;
+        case 'month':
+            $expname = "o" . $mjdDay_monthback . "%";
+            break;
+        case 'week':
+            $expname = "o" . ($mjdDay - 7) . "%";
+            break;
+        case 'yesterday':
+            $expname = "o" . ($mjdDay - 1) . "%";
+            break;
+        case 'today':
+            $expname = "o" . $mjdDay . "%";
+            break;
+        default:
+            $expname = "o" . ($mjdDay - 7) . "%"; // Default: week
+            break;
+    }
+
+    // --- PDO query for min_exp_id ---
+    $stmt = $db->prepare("SELECT MIN(exp_id) AS min_exp_id FROM rawExp WHERE exp_name LIKE :expname");
+    $stmt->execute([':expname' => $expname]);
+    $row = $stmt->fetch(PDO::FETCH_ASSOC);
+    if (!empty($row['min_exp_id'])) {
+        $min_exp_id = $row['min_exp_id'];
+        echo "<center><b>min_exp_id $min_exp_id in $range $expname - $mjdDay (today)</b></center>";
+    } else {
+        echo "<center><b>No min_exp_id $min_exp_id in $range $expname - $mjdDay (today)</b></center>";
+    }
+
+    // Determine the date constraint based on the range
+    switch ($range) {
+        case 'year':
+            $dateConstraint = "rawExp.dateobs >= NOW() - INTERVAL 365 DAY AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'month':
+            $dateConstraint = "rawExp.dateobs >= NOW() - INTERVAL 30 DAY AND rawExp.exp_id >= $min_exp_id";
+            break;
+        case 'week':
+            $expnames = array();
+            for ($i = 0; $i < 7; $i++) {
+                $expnames[] = "rawExp.exp_name LIKE 'o" . ($mjdDay - $i) . "%'";
+            }
+            $dateConstraint = "(" . implode(" OR ", $expnames) . ") AND rawExp.exp_id >= $min_exp_id";
+            $qday = $mjdDay - 6; // or something meaningful for week
+            break;
+        case 'yesterday':
+            $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
+	    $qday = $mjdDay - 1;
+            break;
+        case 'today':
+            $dateConstraint = "rawExp.exp_name LIKE 'o$mjdDay%' AND rawExp.exp_id >= $min_exp_id";
+	    $qday = $mjdDay;
+            break;
+        case 'all':
+            $dateConstraint = "rawExp.exp_id >= 0";
+            break;
+        default: // Default to last night
+            $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
+	    $qday = $mjdDay - 1;
+            break;
+    }
+
+    // Updated SQL query
+    $sql = " SELECT * FROM (SELECT
+    SUBTIME(rawExp.dateobs, '10:00:00') AS time, 
+    cme.AST_R0, 
+    cme.AST_D0, 
+    cme.AST_RS, 
+    cme.AST_DS,  
+    cme.AST_T0, 
+    rawExp.posang,
+    ROUND(
+        (367*YEAR(rawExp.dateobs)
+        - FLOOR((7*(YEAR(rawExp.dateobs)+FLOOR((MONTH(rawExp.dateobs)+9)/12)))/4)
+        + FLOOR(275*MONTH(rawExp.dateobs)/9)
+        + DAY(rawExp.dateobs)
+        + 1721013.5
+        + (HOUR(rawExp.dateobs)/24.0)
+        + (MINUTE(rawExp.dateobs)/1440.0)
+        + (SECOND(rawExp.dateobs)/86400.0)
+        - 2400000.5),5
+    ) AS MJD,
+    rawExp.filter,
+    rawExp.comment,
+    ROUND(rawExp.ra*180/PI(),5) AS RA,
+    ROUND(rawExp.decl*180/PI(),5) AS DECL,
     rawExp.exp_name,
+    rawExp.alt,
+    rawExp.az,
+    cme.fwhm_major, 
+    cme.fwhm_minor, 
+    cme.zpt_obs, 
+    cme.zpt_stdev, 
+    cme.path_base AS image_path,
+    rawExp.airmass, 
+    rawExp.exp_time,
+    rawExp.exp_id,
+    rawExp.moon_angle,
     camRun.label,
-    cam_id,
-    rawExp.dateobs,
-    rawExp.ra,
-    rawExp.decl,
-    rawExp.alt,
-    rawExp.moon_angle,
-    astrom_chips,
-    cme.bg,
-    cme.n_stars
-
-ORDER BY RAND() limit 5000) as sub order by time;
-";
+    camRun.cam_id,
+    BIT_COUNT(astrom_chips) AS good_astro,
+    SUM(chi.bg IS NOT NULL) AS good_chip,
+    SUM(chi.bg IS NULL)     AS null_bg,
+    SUM(chi.n_stars > 5)    AS good_phot,
+    SUM(chi.quality > 0)    AS poor_chip 
+
+    FROM camProcessedExp cme
+    JOIN camRun USING (cam_id)
+    JOIN chipRun USING (chip_id)
+    JOIN chipProcessedImfile chi USING (chip_id)
+    JOIN rawExp ON (rawExp.exp_id = chipRun.exp_id)
+    
+    WHERE
+        $dateConstraint
+        AND rawExp.exp_type = 'OBJECT'
+        AND camRun.state = 'full'
+        AND cme.fault = 0
+    
+    GROUP BY
+        rawExp.exp_name,
+        camRun.label,
+        cam_id,
+        rawExp.dateobs,
+        rawExp.ra,
+        rawExp.decl,
+        rawExp.alt,
+        rawExp.moon_angle,
+        astrom_chips,
+        cme.bg,
+        cme.n_stars
+    
+    ORDER BY RAND() limit 5000) as sub order by time;
+    ";
 
 //echo "<center>$sql</center><br>";
-//echo "<center>$sql1</center>";
     $stmt = $db->query($sql);
     $DQarray = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -985,33 +1007,25 @@
         var data = new google.visualization.DataTable();
         data.addColumn('number', 'MJD');
-        data.addColumn('number', 'Number of raw Chips');
+        data.addColumn('number', 'good phot (nstar>5)');
         data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
-//        data.addColumn('number', 'Number of Good Chips');
-//        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
-
-        const mjdValues = dataArray.map(r => parseFloat(r.MJD));
-        let mjdMin0 = Math.min(...mjdValues);
-	const mjdMin = Math.floor(mjdMin0 * 10) / 10; // 1 decimal place or floor/ceil depending on your needs
-        let mjdMax0 = Math.max(...mjdValues);
-	const mjdMax = Math.ceil(mjdMax0 * 10) / 10; // 1 decimal place or floor/ceil depending on your needs
-        // Extract the first MJD value from dataArray
-        const firstMJD = dataArray.length > 0 ? Math.floor(parseFloat(dataArray[0].MJD)) : "N/A";
-	const mjdoff = mjdMin;
-	// output at console
-        // console.log("First MJD Value:", firstMJD);
-
+        data.addColumn('number', 'good astro');
+        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+        data.addColumn('number', 'null bg');
+        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+        data.addColumn('number', 'poor (q>0)');
+        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+
+        const mjdoff = $mjdDay;
 	// build data rows
-	const mjdVals = [];
         const rows = dataArray.map(row => {
 	    const mjd    = parseFloat(row.MJD) - mjdoff;
-            mjdVals.push(mjd);  // collect MJD offset values
-
-  	    const n_raw   = parseFloat(row.n_raw)+1;
-  	    const n_chips = parseFloat(row.g_chips);
-  	    const f_good  = n_chips / n_raw;
+  	    const null_bg    = parseFloat(row.null_bg);
+  	    const good_phot  = parseFloat(row.good_phot);
+  	    const good_astro = parseFloat(row.good_astro);
+  	    const poor_chip  = parseFloat(row.poor_chip);
             const tooltip = buildTooltip(row);
 
 	    return[
-                mjd, f_good, tooltip, 
+                mjd, good_phot, tooltip, good_astro, tooltip, null_bg, tooltip, poor_chip, tooltip, 
             ];
 	});
@@ -1019,9 +1033,65 @@
         data.addRows(rows);
 
-	// Now compute min and max
-        const mjdMin1 = Math.min(...mjdVals);
-        const mjdMax1 = Math.max(...mjdVals);
-
-        const titleText = `N Chips vs. MJD ({$proj}) â \${nRows} rows\${dataArray.length >= 5000 ? " (sampled)" : ""}`;
+        const titleText = `Chips ratio vs. MJD ({$proj}) â \${nRows} rows\${dataArray.length >= 5000 ? " (sampled)" : ""}`;
+
+        const options = {
+               title: titleText, 
+               titleTextStyle: {color: 'black', fontSize: 14, bold: true},
+               width:  '100%',
+               height: Math.min(window.innerHeight * 0.6, 430), // This gives a nice result on smaller screens.
+               legend: { position: 'top', alignment: 'center' },
+               series: {
+                   //0: { color: '#ffe000', pointShape: 'star',  pointSize:  10, lineWidth: 0},
+                   0: { color: '#0000ff', pointShape: 'square',  pointSize:  8, lineWidth: 0},
+                   1: { color: '#33a532', pointShape: 'triangle',pointSize:  5, lineWidth: 0},
+                   2: { color: '#ff0000', pointShape: 'star',  pointSize:  8, lineWidth: 0},
+                   3: { color: '#808080', pointShape: 'triangle',pointSize:  5, lineWidth: 0},
+               },
+              intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
+              hAxis: {
+  		title: 'MJD - ' + mjdoff + ' (today)',
+  	        gridlines: { count: -1 }  // auto gridlines
+              },
+              vAxis: {
+                  title: 'number of chips',
+              },
+              tooltip: { isHtml: true }, // Render tooltips as HTML
+              explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
+              chartArea: {left: '9%', top: '10%', right: '3%', bottom: 50}, // Adjust left and right
+              fontSize: 14,
+         };
+
+      const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_chips'));
+        chart.draw(data, options);
+    }
+
+    // draw FWHM vs MJD
+    function drawFWHMChart() {
+
+	// data header
+        var data = new google.visualization.DataTable();
+        data.addColumn('number', 'MJD');
+        data.addColumn('number', 'FWHM_maj');
+        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+        data.addColumn('number', 'FWHM_min');
+        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
+        data.addColumn('number', '1"');
+        data.addColumn('number', '3"');
+
+	// build data rows
+        const mjdoff = $mjdDay;
+        const rows = dataArray.map(row => {
+	    const mjd    = parseFloat(row.MJD) - mjdoff;
+	    const fwhm_maj = parseFloat(row.fwhm_major);
+	    const fwhm_min = parseFloat(row.fwhm_minor);
+            const tooltip  = buildTooltip(row);
+	    return[
+                mjd, fwhm_maj, tooltip, fwhm_min, tooltip, 4, 12,
+            ];
+	});
+
+        data.addRows(rows);
+
+        const titleText = `FWHM vs. MJD ({$proj}) â \${nRows} rows\${dataArray.length >= 5000 ? " (sampled)" : ""}`;
 
         const options = {
@@ -1039,94 +1109,6 @@
             intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
             hAxis: {
-		title: 'MJD - ' + mjdoff + ' (day)',
-	        viewWindow: {
-	           min: 0.,  // small padding
-                   max: mjdMax1 + 0.005
-                },
-	       gridlines: { count: -1 }  // auto gridlines
-            },
-            vAxis: {
-                title: 'Good chips (number)',
-	    //    viewWindow: {
-            //       max: 65,
-            //       min: 0
-            //    },
-            },
-            tooltip: { isHtml: true }, // Render tooltips as HTML
-            chartArea: {left: '9%', top: '10%', right: '3%', bottom: 50}, // Adjust left and right
-            fontSize: 14,
-        };
-
-        const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_chips'));
-        chart.draw(data, options);
-    }
-
-    // draw FWHM vs MJD
-    function drawFWHMChart() {
-
-	// data header
-        var data = new google.visualization.DataTable();
-        data.addColumn('number', 'MJD');
-        data.addColumn('number', 'FWHM_maj');
-        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
-        data.addColumn('number', 'FWHM_min');
-        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
-        data.addColumn('number', '1"');
-        data.addColumn('number', '3"');
-
-        const mjdValues = dataArray.map(r => parseFloat(r.MJD));
-        let mjdMin0 = Math.min(...mjdValues);
-	const mjdMin = Math.floor(mjdMin0 * 10) / 10; // 1 decimal place or floor/ceil depending on your needs
-        let mjdMax0 = Math.max(...mjdValues);
-	const mjdMax = Math.ceil(mjdMax0 * 10) / 10; // 1 decimal place or floor/ceil depending on your needs
-        // Extract the first MJD value from dataArray
-        const firstMJD = dataArray.length > 0 ? Math.floor(parseFloat(dataArray[0].MJD)) : "N/A";
-	const mjdoff = mjdMin;
-	// output at console
-        // console.log("First MJD Value:", firstMJD);
-
-	// build data rows
-	const mjdVals = [];
-        const rows = dataArray.map(row => {
-	    const mjd    = parseFloat(row.MJD) - mjdoff;
-            mjdVals.push(mjd);  // collect MJD offset values
-
-	    const fwhm_maj = parseFloat(row.fwhm_major);
-	    const fwhm_min = parseFloat(row.fwhm_minor);
-            const tooltip  = buildTooltip(row);
-
-	    return[
-                mjd, fwhm_maj, tooltip, fwhm_min, tooltip, 4, 12,
-            ];
-	});
-
-        data.addRows(rows);
-
-	// Now compute min and max
-        const mjdMin1 = Math.min(...mjdVals);
-        const mjdMax1 = Math.max(...mjdVals);
-
-        const titleText = `FWHM vs. MJD ({$proj}) â \${nRows} rows\${dataArray.length >= 5000 ? " (sampled)" : ""}`;
-
-        const options = {
-            title: titleText, 
-            titleTextStyle: {color: 'black', fontSize: 14, bold: true},
-            width:  '100%',
-            height: Math.min(window.innerHeight * 0.6, 430), // This gives a nice result on smaller screens.
-            legend: { position: 'top', alignment: 'center' },
-            series: {
-                0: { color: '#0000ff', pointSize: 3, lineWidth: 0. },
-                1: { color: '#33a532', pointSize: 3, lineWidth: 0. },
-                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 - ' + mjdoff + ' (day)',
-	        viewWindow: {
-	           min: 0.,  // small padding
-                   max: mjdMax1 + 0.005
-                },
-	       gridlines: { count: -1 }  // auto gridlines
+  		title: 'MJD - ' + mjdoff + ' (today)',
+	        gridlines: { count: -1 }  // auto gridlines
             },
             vAxis: {
@@ -1134,4 +1116,5 @@
             },
             tooltip: { isHtml: true }, // Render tooltips as HTML
+            explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
             chartArea: {left: '9%', top: '10%', right: '3%', bottom: 50}, // Adjust left and right
             fontSize: 14,
@@ -1168,18 +1151,8 @@
         const filterCounts = Object.fromEntries(filters.map(f => [f.key, 0]));
     
-        const mjdValues = dataArray.map(r => parseFloat(r.MJD));
-        const mjdMin0 = Math.min(...mjdValues);
-        const mjdMax0 = Math.max(...mjdValues);
-        const mjdMin = Math.floor(mjdMin0 * 10) / 10;
-        const mjdMax = Math.ceil(mjdMax0 * 10) / 10;
-        const mjdoff = mjdMin;
-    
-        const mjdVals = [];
-    
         // build rows
+        const mjdoff = $mjdDay;
         const rows = dataArray.map(row => {
             const mjd = parseFloat(row.MJD) - mjdoff;
-            mjdVals.push(mjd);
-    
             const filter = row.filter[0];
             const zpt_obs = parseFloat(row.zpt_obs);
@@ -1224,6 +1197,4 @@
         data.addRows(rows);
     
-        const mjdMin1 = Math.min(...mjdVals);
-        const mjdMax1 = Math.max(...mjdVals);
         const defaultSeries = { pointSize: 6, lineWidth: 0 };
     
@@ -1244,6 +1215,5 @@
             intervals: { color: 'red', lineWidth: .0, style: 'sticks' },
             hAxis: {
-                title: 'MJD - ' + mjdoff + ' (day)',
-                viewWindow: { min: 0, max: mjdMax1 + 0.005 },
+  		title: 'MJD - ' + mjdoff + ' (today)',
                 gridlines: { count: -1 }
             },
@@ -1253,13 +1223,8 @@
             },
             tooltip: { isHtml: true },
+            explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
             chartArea: { left: '9%', top: '10%', right: '3%', bottom: 50 },
             fontSize: 14,
         };
-	// options.series[1]  = { color: 'black', lineDashStyle: [2, 3], lineWidth: 2, pointSize: 0 };
-	// options.series[3]  = { color: 'black', lineDashStyle: [2, 3], lineWidth: 2, pointSize: 0 };
-	// options.series[5]  = { color: 'black', lineDashStyle: [2, 3], lineWidth: 2, pointSize: 0 };
-	// options.series[7]  = { color: 'black', lineDashStyle: [2, 3], lineWidth: 2, pointSize: 0 };
-	// options.series[9]  = { color: 'black', lineDashStyle: [2, 3], lineWidth: 2, pointSize: 0 };
-	// options.series[11] = { color: 'black', lineDashStyle: [2, 3], lineWidth: 2, pointSize: 0 };
     
         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_zpt'));
@@ -1269,15 +1234,4 @@
     // draw zoomed zerpoint vs mjd
     function drawZOOMZPTChart() {
-        const mjdValues = dataArray.map(r => parseFloat(r.MJD));
-        let mjdMin0 = Math.min(...mjdValues);
-	const mjdMin = Math.floor(mjdMin0 * 10) / 10; // 1 decimal place or floor/ceil depending on your needs
-        let mjdMax0 = Math.max(...mjdValues);
-	const mjdMax = Math.ceil(mjdMax0 * 10) / 10; // 1 decimal place or floor/ceil depending on your needs
-        // Extract the first MJD value from dataArray
-        const firstMJD = dataArray.length > 0 ? Math.floor(parseFloat(dataArray[0].MJD)) : "N/A";
-	const mjdoff = mjdMin;
-	// output at console
-        console.log("First MJD Value:", firstMJD);
-
         // Compute median zpt_obs per filter
         function median(arr) {
@@ -1313,6 +1267,4 @@
               // ref?
               gzp0 = 24.563; rzp0 = 24.750; izp0 = 24.611; zzp0 = 24.240; yzp0 = 23.320; wzp0 = 26.000;
-              // 
-              //gzp0 = 24.563; rzp0 = 24.750; izp0 = 24.611; zzp0 = 24.240; yzp0 = 23.320; wzp0 = 26.000;
               break;
             case 'gpc2':
@@ -1342,6 +1294,6 @@
         };
 
-        const mjdVals = [];
 	let allZps = [];
+        const mjdoff = $mjdDay;
         const rows = dataArray.map(row => {
             const mjd = parseFloat(row.MJD) - mjdoff; 
@@ -1366,7 +1318,4 @@
                 rowData.push( zp, tooltip, zp !== null ? zp - zpt_stdev : null, zp !== null ? zp + zpt_stdev : null);
             });
-
-            // collect MJD offset values
-            mjdVals.push(mjd); 
 
             // collect valid zp values
@@ -1445,6 +1394,4 @@
 
 	// plot options
-        const mjdMin1 = Math.min(...mjdVals);
-        const mjdMax1 = Math.max(...mjdVals);
 	const defaultSeries = { pointSize: 6, lineWidth: 0 };
 
@@ -1456,6 +1403,6 @@
             width: '100%',
             height: Math.min(window.innerHeight * 0.6, 430), // This gives a nice result on smaller screens.
-            //legend: { position: 'top', alignment: 'center', maxLines: 3, textStyle: {fontSize: 12} },
-            legend: { position: 'none', alignment: 'center', maxLines: 3 },
+            legend: { position: 'top', alignment: 'center', maxLines: 3, textStyle: {fontSize: 12} },
+            //legend: { position: 'none', alignment: 'center', maxLines: 3 },
             series: {
                 0: { ...defaultSeries, color: '#00f000', pointShape: 'circle',   },  // 'g' filter
@@ -1468,9 +1415,5 @@
             intervals: { color: 'red', lineWidth: .0, style: 'sticks' },
             hAxis: {
-                title: 'MJD - ' + mjdoff + ' (day)',
-		viewWindow: {
-	           min: 0,  // small padding
-                   max: mjdMax1 + 0.005
-                },
+  		title: 'MJD - ' + mjdoff + ' (today)',
 	        gridlines: { count: -1 },
                 minorGridlines: { count: 1 }
@@ -1484,4 +1427,5 @@
             },
 	    tooltip: { isHtml: true},
+            explorer: { actions: ['dragToZoom', 'rightClickToReset'], axis: 'horizontal', keepInBounds: true, maxZoomIn: .01}, //  Zoom
             chartArea: { left: '9%', top: '12%', right: '3%', bottom: 50 },
             fontSize: 14,
@@ -1501,4 +1445,8 @@
 <div class="chartWithOverlay" style="position: relative; width: 100%; height:440px;">
     <div id="dq_div_chips" style="width:100%; border:1px solid #000;"></div>
+</div>
+
+<div class="chartWithOverlay" style="position: relative; width: 100%; height:440px;">
+    <div id="dq_div_fwhm" style="width:100%; border:1px solid #000;"></div>
 </div>
 
@@ -1514,9 +1462,4 @@
 </div>
 
-<div class="chartWithOverlay" style="position: relative; width: 100%; height:440px;">
-    <div id="dq_div_fwhm" style="width:100%; border:1px solid #000;"></div>
-</div>
-
-
 HTML;
     // End timer
