public function scatterplotAction($scope = 'concept', $groupingId = '', $debug = false)
 {
     $this->view->disable();
     // Get our context (this takes care of starting the session, too)
     $context = $this->getDI()->getShared('ltiContext');
     if (!$context->valid) {
         echo '[{"error":"Invalid lti context"}]';
         return;
     }
     // Get the list of questions associated with concepts for the given scope and grouping ID
     $questions = [];
     switch ($scope) {
         case "concept":
             // Filter based on concept
             $questions = MappingHelper::questionsInConcept($groupingId);
             break;
         case "unit":
             // Filter based on unit
             $questions = MappingHelper::questionsInConcepts(MappingHelper::conceptsInUnit($groupingId));
             break;
         default:
             echo '[{"error":"Invalid scope option"}]';
             return;
             break;
     }
     if ($debug) {
         echo "questions for scope {$scope} and grouping {$groupingId}: \n";
         print_r($questions);
     }
     $classHelper = new ClassHelper();
     // Array of questions with more details about each
     $questionDetails = array();
     // Get some info about each question
     foreach ($questions as $question) {
         // Check that it's a valid question
         if ($question != false) {
             // Get number of attempts
             $question["attempts"] = MasteryHelper::countAttemptsForQuestion($context->getUserName(), $question["OA Quiz ID"], $question["Question Number"], $debug);
             $question["scaledAttemptScore"] = $classHelper->calculateScaledAttemptScoreForQuestion($question["attempts"], $question["OA Quiz ID"], $question["Question Number"], $debug);
             // Get amount of associated videos watched
             // Note that question ID is being used instead of assessment ID and question number, since we're searching the csv mapping and not dealing with assessment statements here
             $question["videoPercentage"] = MasteryHelper::calculateUniqueVideoPercentageForQuestion($context->getUserName(), $questionId);
             $questionDetails[] = $question;
         }
     }
     /*
     function randomPoint($group, $q) {
     	// Randomly return outliers
     	if (rand(0,30) == 5) {
     		return [$group, $q["quizNumber"], $q["questionNumber"], rand(-10000, 1000), rand(-10000, 1000)];
     	}
     	return [$group, $q["quizNumber"], $q["questionNumber"], rand(-100, 100), rand(-100, 100)];
     }
     $result = [];
     // For now, return random points based on number of questions
     $numPoints = count($questionDetails);
     //foreach ($questionDetails as $q) {
     	//$result [] = 
     //}
     //
     for ($i=0; $i<$numPoints; $i++) {
     	$result []= randomPoint("student", $questionDetails[$i]);
     	for ($j=0; $j<10; $j++) {
     		$result []= randomPoint("class", $questionDetails[$i]);
     	}
     }
     
     $xValues = array_map(function($point) { return $point[3]; }, $result);
     $yValues = array_map(function($point) { return $point[4]; }, $result);
     
     // TODO check that xValues and yValues have a length, otherwise statshelper will spit out errors
     // Perform some statistics grossness
     	// Remove any outliers for both axes, based on 1.5*IQR
     	// Cap and floor x outliers
     	$xStats = StatsHelper::boxPlotValues($xValues);
     	$result = array_map(function($point) use ($xStats) {
     		$x = $point[3];
     		// Floor upper outliers
     		if ($x > $xStats['q3'] + (1.5 * $xStats['iqr'])) {
     			$x = $xStats['q3'] + (.5 * $xStats['iqr']);
     		}
     		// Cap lower outliers
     		if ($x < $xStats['q1'] - (1.5 * $xStats['iqr'])) {
     			$x = $xStats['q1'] - (.5 * $xStats['iqr']);
     		}
     		$point[3] = $x;
     		return $point;
     	}, $result);
     	// Scale all the scores from 0 to 10
     	// TODO
     
     	// Cap and floor y outliers
     	$yStats = StatsHelper::boxPlotValues($yValues);
     	$result = array_map(function($point) use ($yStats) {
     		$y = $point[4];
     		// Floor upper outliers
     		if ($y > $yStats['q3'] + (1.5 * $yStats['iqr'])) {
     			$y = $yStats['q3'] + (.5 * $yStats['iqr']);
     		}
     		// Cap lower outliers
     		if ($y < $yStats['q1'] - (1.5 * $yStats['iqr'])) {
     			$y = $yStats['q1'] - (.5 * $yStats['iqr']);
     		}
     		$point[4] = $y;
     		return $point;
     	}, $result);
     	//print_r($result);
     	//print_r($yStats);
     
     //die();
     */
     // X = video percentage, Y = question attempts
     $headerRow = ["group", "quiz_number", "question_number", "x", "y"];
     $result = array_map(function ($q) {
         return ["student", $q["OA Quiz ID"], $q["Question Number"], $q["videoPercentage"], $q["scaledAttemptScore"]];
     }, $questionDetails);
     if ($debug) {
         echo "question details for scope {$scope} and grouping {$groupingId}: \n";
         print_r($questionDetails);
         print_r($result);
     }
     // Output data as csv so that we only have to send header information once for so many points
     if (!$debug) {
         header("Content-Type: text/csv");
     }
     $output = fopen("php://output", "w");
     fputcsv($output, $headerRow);
     foreach ($result as $row) {
         fputcsv($output, $row);
         // here you can change delimiter/enclosure
     }
     fclose($output);
 }