- Moving some important things (like painting a blank canvas and allocating common...
[isso.git] / GraphLine.php
1 <?php
2 /*=====================================================================*
3 || ###################################################################
4 || # Blue Static ISSO Framework
5 || # Copyright ©2002-[#]year[#] Blue Static
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version [#]gpl[#] of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 /**
23 * Graphing System: Line Graph (GraphLine.php)
24 *
25 * @package ISSO
26 */
27
28 require_once('ISSO/Graph.php');
29
30 /**
31 * Graphing System: Line Graph
32 *
33 * This creates a line graph from a set of data; each point requires
34 * a line name (because this supports multi-line graphing), an x-value,
35 * and a y-value. It creates PNG images.
36 *
37 * @author Blue Static
38 * @copyright Copyright (c)2002 - [#]year[#], Blue Static
39 * @version $Revision$
40 * @package ISSO
41 *
42 */
43 class BSGraphLine extends BSGraph
44 {
45 /**
46 * Graphing dataset; 4D array
47 * array(array(name, array(array(xval, yval))), color)
48 * @var string
49 */
50 protected $dataset = array();
51
52 /**
53 * Array of data points that are used to calculate the standard deviation
54 * @var array
55 */
56 private $piles = array(0 => array(), 1 => array());
57
58 /**
59 * The names of the axes
60 * @var array
61 */
62 private $axis = array(0 => 'X Axis', 1 => 'Y Axis');
63
64 // ###################################################################
65 /**
66 * Does the actual graphing and returns a byte stream of a PNG image
67 *
68 * @return string Byte stream
69 */
70 public function graph()
71 {
72 // calculates the standard deviation of the two piles to determine the x and y intervals
73 $xint = $this->_standardDeviation($this->piles[0]);
74 $yint = $this->_standardDeviation($this->piles[1]);
75
76 $xmin = min($this->piles[0]);
77 $xmin = ($xmin - $xint < 0 ? 0 : $xmin - $xint);
78 $xmax = max($this->piles[0]) + $xint;
79
80 $ymin = min($this->piles[1]);
81 $ymin = ($ymin - $yint < 0 ? 0 : $ymin - $yint);
82 $ymax = max($this->piles[1]) + $yint;
83
84 $colors = $this->_primeColors();
85 $this->_paintCanvas();
86
87 // draw the axes
88 $originx = self::PADDING + imagefontwidth(1) + self::SPACING + imagefontwidth(3) + self::PADDING;
89 $originy = $this->dimensions['height'] - (self::PADDING + imagefontheight(1) + self::SPACING + imagefontheight(3) + self::SPACING);
90 $endx = $this->dimensions['width'] - self::PADDING - 150 - self::PADDING;
91 $endy = 40;
92 $length = $endx - $originx;
93 $height = $originy - $endy;
94 imageline($this->image, $originx, $originy, $endx, $originy, $colors['grey']);
95 imageline($this->image, $originx, $originy, $originx, $endy, $colors['grey']);
96 imageline($this->image, $endx, $originy, $endx, $endy, $colors['grey']);
97
98 // label the axes
99 imagestring($this->image, 3, $length / 2, $this->dimensions['height'] - self::SPACING - imagefontheight(3), $this->axis[0], $colors['black']);
100 imagestringup($this->image, 3, self::SPACING, $height / 2 + $endy, $this->axis[1], $colors['black']);
101
102 // score the axes
103 $count = 0;
104 for ($i = $originx; $i <= $endx; $i += ($length / $xint))
105 {
106 imageline($this->image, $i - self::SPACING, $originy + self::SPACING, $i + self::SPACING, $originy - self::SPACING, $colors['grey']);
107 imagestring($this->image, 1, $i, $originy + self::PADDING, round($count), $colors['black']);
108 $count += $xint;
109 }
110 $count = 0;
111 for ($i = $originy; $i >= $endy; $i -= ($height / $yint))
112 {
113 imageline($this->image, $originx, $i, $endx, $i, $colors['grey']);
114 imagestring($this->image, 1, self::SPACING + self::PADDING, $i, round($count), $colors['black']);
115 $count += $yint;
116 }
117
118 header("Content-Type: image/png");
119 imagepng($this->image);
120 }
121
122 // ###################################################################
123 /**
124 * Adds a "line" with a given name and a set of datapoints in the form
125 * array(x, y)
126 *
127 * @param string The line's name
128 * @param array Array of array(x,y) as data points
129 */
130 public function addDataSet($name, $points)
131 {
132 $this->_addPoints($points);
133 $this->_sortPoints($points);
134 $this->dataset[] = array($name, $points, $this->_fetchColor());
135 }
136
137 // ###################################################################
138 /**
139 * This does the same thing as addDataSet(), except the client code
140 * can specify the color in the form of array(R, G, B)
141 *
142 * @param string The line's name
143 * @param array Array of array(x,y) as data points
144 * @param array A color in the form of 3 RGB points
145 */
146 public function addDataSetColor($name, $points, $color)
147 {
148 $this->_addPoints($points);
149 $this->_sortPoints($points);
150 $this->dataset[] = array($name, $points, imagecolorallocate($this->image, $color[0], $color[1], $color[2]));
151 }
152
153 // ###################################################################
154 /**
155 * Adds a set of points to the piles and ensures that they are all valid
156 *
157 * @param array Points to add
158 */
159 private function _addPoints($points)
160 {
161 $xpairs = array();
162 foreach ($points AS $point)
163 {
164 if (isset($xpairs["$point[0]"]))
165 {
166 trigger_error('You cannot have more than one of the same x-values for a given data set');
167 }
168 $xpairs["$point[0]"] = $point[0];
169 $this->piles[0][] = $point[0];
170 $this->piles[1][] = $point[1];
171 }
172 }
173
174 // ###################################################################
175 /**
176 * Sorts an array of points using quick sort so they're in x-increasing
177 * order
178 *
179 * @param array Array of points
180 */
181 private function _sortPoints(&$points)
182 {
183 $this->_quickSortPoints($points, 0, sizeof($points) - 1);
184 }
185
186 // ###################################################################
187 /**
188 * Quicksort function for sorting function
189 *
190 * @param array Array of points
191 * @param integer Lower bound
192 * @param integer Upper bound
193 */
194 private function _quickSortPoints(&$points, $low, $high)
195 {
196 if (($high - $low) > 1)
197 {
198 $partition = $this->_partitionPoints($points, $low, $high);
199 $this->_quickSortPoints($points, $low, $partition);
200 $this->_quickSortPoints($points, $partition + 1, $high);
201 }
202 }
203
204 // ###################################################################
205 /**
206 * Quicksort partitioner: returns the index of the pivot element where
207 * all x-coords on the left side of pivot are less than or equal to
208 * pivot, and all x-coords are higher to the right
209 *
210 * @param array Array of points
211 * @param integer Lower bound
212 * @param integer Upper bound
213 *
214 * @return integer Pivot index
215 */
216 private function _partitionPoints(&$points, $low, $high)
217 {
218 $pivot = $low;
219 for ($unsorted = $low + 1; $unsorted <= $high; $unsorted++)
220 {
221 if ($points[$unsorted][0] < $points[$pivot][0])
222 {
223 $temp = $points[$pivot];
224 $points[$pivot] = $points[$unsorted];
225 $points[$unsorted] = $points[$pivot + 1];
226 $points[$pivot + 1] = $temp;
227 $pivot++;
228 }
229 }
230 return $pivot;
231 }
232
233 // ###################################################################
234 /**
235 * Returns the unbiased statistical standard deviation of an array of
236 * values
237 *
238 * @param array Array of values
239 *
240 * @return float Standard deviation (unbiased)
241 */
242 private function _standardDeviation($vals)
243 {
244 $average = $this->_arrayAverage($vals);
245 $popVariance = array();
246
247 foreach ($vals AS $val)
248 {
249 $popVariance[] = pow($val - $average, 2);
250 }
251
252 return sqrt($this->_arrayAverage($popVariance));
253 }
254
255 // ###################################################################
256 /**
257 * Returns the stastical mean of an array of values
258 *
259 * @param array Array of values
260 *
261 * @return float Statistical mean
262 */
263 private function _arrayAverage($vals)
264 {
265 return array_sum($vals) / count($vals);
266 }
267
268 // ###################################################################
269 /**
270 * Sets the titles of the two axes
271 *
272 * @param string X-axis name
273 * @param string Y-axis name
274 */
275 public function setAxes($xaxis, $yaxis)
276 {
277 $this->axis[0] = $xaxis;
278 $this->axis[1] = $yaxis;
279 }
280 }
281
282 /*=====================================================================*
283 || ###################################################################
284 || # $HeadURL$
285 || # $Id$
286 || ###################################################################
287 \*=====================================================================*/
288 ?>