array(), 1 => array()); /** * The names of the axes * @var array */ private $axis = array(0 => 'X Axis', 1 => 'Y Axis'); /** * Number of ticks to display on the axes * @var integer */ private $ticks = 10; // ################################################################### /** * Does the actual graphing and returns a byte stream of a PNG image * * @return string Byte stream */ public function graph() { $colors = $this->_primeColors(); $this->_paintCanvas(); // draw the axes $originx = self::PADDING + imagefontwidth(1) + self::SPACING + imagefontwidth(3) + self::PADDING; $originy = $this->dimensions['height'] - (self::PADDING + imagefontheight(1) + self::SPACING + imagefontheight(3) + self::SPACING); $endx = $this->dimensions['width'] - self::PADDING - 150 - self::PADDING; $endy = 40; $length = $endx - $originx; $height = $originy - $endy; imageline($this->image, $originx, $originy, $endx, $originy, $colors['grey']); imageline($this->image, $originx, $originy, $originx, $endy, $colors['grey']); // just to give us some padding $this->ticks++; // calculates the standard deviation of the two piles to determine the x and y intervals $xmin = min($this->piles[0]); $xmax = max($this->piles[0]); $xint = round(($xmax - $xmin) / $this->ticks); $xmin = ($xmin - $xint < 0 ? 0 : $xmin - $xint); $xmax = $xmax + $xint; $ymin = min($this->piles[1]); $ymax = max($this->piles[1]); $yint = round(($ymax - $ymin) / $this->ticks); $ymin = ($ymin - $yint < 0 ? 0 : $ymin - $yint); $ymax = $ymax + $yint; // label the axes imagestring($this->image, 3, $length / 2, $this->dimensions['height'] - self::SPACING - imagefontheight(3), $this->axis[0], $colors['black']); imagestringup($this->image, 3, self::SPACING, $height / 2 + $endy, $this->axis[1], $colors['black']); // score the axes $count = 0; for ($i = $originx; $i <= $endx; $i += ($length / $this->ticks)) { imageline($this->image, $i - self::SPACING, $originy + self::SPACING, $i + self::SPACING, $originy - self::SPACING, $colors['grey']); imagestring($this->image, 1, $i, $originy + self::PADDING, round($count), $colors['black']); $count += $xint; } $count = 0; for ($i = $originy; $i >= $endy; $i -= ($height / $this->ticks)) { imageline($this->image, $originx, $i, $endx, $i, $colors['grey']); imagestring($this->image, 1, self::SPACING + self::SPACING + self::PADDING + self::SPACING, $i - self::SPACING, round($count), $colors['black']); $count += $yint; } // draw the legend $legy = $endy + self::SPACING; // "cursor" y-coord for the legend $legx = $endx + self::PADDING; // x-coord for the legend BORDER $legex = $this->dimensions['width'] - self::PADDING; // end x-coord for the legend BORDER imageline($this->image, $legx, $endy, $legex, $endy, $colors['black']); // top legend border // go through and plot each dataset foreach ($this->dataset AS $data) { // plot each point and connect the dots $oldpoint = null; foreach ($data[1] AS $points) { $xcord = $originx + ($points[0] * ($length / $xmax)); $ycord = $originy - ($points[1] * ($height / $ymax)); imagefilledellipse($this->image, $xcord, $ycord, 5, 5, $data[2]); if ($oldpoint) { imageline($this->image, $xcord, $ycord, $oldpoint[0], $oldpoint[1], $data[2]); } $oldpoint = array($xcord, $ycord); } // draw the legend bit $box = array( $legx + 1 + self::SPACING, $legy, // top left $legx + 1 + self::SPACING, $legy + self::PADDING, // bottom left $legx + 11 + self::SPACING, $legy + self::PADDING, // bottom right $legx + 11 + self::SPACING, $legy // top right ); imagefilledpolygon($this->image, $box, 4, $data[2]); imagestring($this->image, 2, $legx + 11 + self::SPACING + self::SPACING, $legy - 1, $data[0], $colors['black']); $legy += self::PADDING + self::SPACING; } // finish the legend border imageline($this->image, $legx, $legy, $legex, $legy, $colors['black']); // bottom imageline($this->image, $legx, $endy, $legx, $legy, $colors['black']); // left imageline($this->image, $legex, $endy, $legex, $legy, $colors['black']); // right return $this->_imageFlush(); } // ################################################################### /** * Adds a "line" with a given name and a set of datapoints in the form * array(x, y) * * @param string The line's name * @param array Array of array(x,y) as data points */ public function addDataSet($name, $points) { $this->_addPoints($points); $this->_sortPoints($points); $this->dataset[] = array($name, $points, $this->_fetchColor()); } // ################################################################### /** * This does the same thing as addDataSet(), except the client code * can specify the color in the form of array(R, G, B) * * @param string The line's name * @param array Array of array(x,y) as data points * @param array A color in the form of 3 RGB points */ public function addDataSetColor($name, $points, $color) { $this->_addPoints($points); $this->_sortPoints($points); $this->dataset[] = array($name, $points, imagecolorallocate($this->image, $color[0], $color[1], $color[2])); } // ################################################################### /** * Adds a set of points to the piles and ensures that they are all valid * * @param array Points to add */ private function _addPoints($points) { $xpairs = array(); foreach ($points AS $point) { if (isset($xpairs["$point[0]"])) { trigger_error('You cannot have more than one of the same x-values for a given data set'); } $xpairs["$point[0]"] = $point[0]; $this->piles[0][] = $point[0]; $this->piles[1][] = $point[1]; } } // ################################################################### /** * Sorts an array of points using quick sort so they're in x-increasing * order * * @param array Array of points */ private function _sortPoints(&$points) { $this->_quickSortPoints($points, 0, sizeof($points) - 1); } // ################################################################### /** * Quicksort function for sorting function * * @param array Array of points * @param integer Lower bound * @param integer Upper bound */ private function _quickSortPoints(&$points, $low, $high) { if (($high - $low) > 1) { $partition = $this->_partitionPoints($points, $low, $high); $this->_quickSortPoints($points, $low, $partition); $this->_quickSortPoints($points, $partition + 1, $high); } } // ################################################################### /** * Quicksort partitioner: returns the index of the pivot element where * all x-coords on the left side of pivot are less than or equal to * pivot, and all x-coords are higher to the right * * @param array Array of points * @param integer Lower bound * @param integer Upper bound * * @return integer Pivot index */ private function _partitionPoints(&$points, $low, $high) { $pivot = $low; for ($unsorted = $low + 1; $unsorted <= $high; $unsorted++) { if ($points[$unsorted][0] < $points[$pivot][0]) { $temp = $points[$pivot]; $points[$pivot] = $points[$unsorted]; $points[$unsorted] = $points[$pivot + 1]; $points[$pivot + 1] = $temp; $pivot++; } } return $pivot; } // ################################################################### /** * Sets the titles of the two axes * * @param string X-axis name * @param string Y-axis name */ public function setAxes($xaxis, $yaxis) { $this->axis[0] = $xaxis; $this->axis[1] = $yaxis; } } /*=====================================================================* || ################################################################### || # $HeadURL$ || # $Id$ || ################################################################### \*=====================================================================*/ ?>