]> src.bluestatic.org Git - isso.git/blob - GraphLine.php
Fixing "method not found" for BSApp::GetType()
[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 * Number of ticks to display on the axes
66 * @var integer
67 */
68 private $ticks = 10;
69
70 // ###################################################################
71 /**
72 * Does the actual graphing and returns a byte stream of a PNG image
73 *
74 * @return string Byte stream
75 */
76 public function graph()
77 {
78 $colors = $this->_primeColors();
79 $this->_paintCanvas();
80
81 // draw the axes
82 $originx = self::PADDING + imagefontwidth(1) + self::SPACING + imagefontwidth(3) + self::PADDING;
83 $originy = $this->dimensions['height'] - (self::PADDING + imagefontheight(1) + self::SPACING + imagefontheight(3) + self::SPACING);
84 $endx = $this->dimensions['width'] - self::PADDING - 150 - self::PADDING;
85 $endy = 40;
86 $length = $endx - $originx;
87 $height = $originy - $endy;
88 imageline($this->image, $originx, $originy, $endx, $originy, $colors['grey']);
89 imageline($this->image, $originx, $originy, $originx, $endy, $colors['grey']);
90
91 // just to give us some padding
92 $this->ticks++;
93
94 // calculates the standard deviation of the two piles to determine the x and y intervals
95 $xmin = min($this->piles[0]);
96 $xmax = max($this->piles[0]);
97 $xint = round(($xmax - $xmin) / $this->ticks);
98 $xmin = ($xmin - $xint < 0 ? 0 : $xmin - $xint);
99 $xmax = $xmax + $xint;
100
101 $ymin = min($this->piles[1]);
102 $ymax = max($this->piles[1]);
103 $yint = round(($ymax - $ymin) / $this->ticks);
104 $ymin = ($ymin - $yint < 0 ? 0 : $ymin - $yint);
105 $ymax = $ymax + $yint;
106
107 // label the axes
108 imagestring($this->image, 3, $length / 2, $this->dimensions['height'] - self::SPACING - imagefontheight(3), $this->axis[0], $colors['black']);
109 imagestringup($this->image, 3, self::SPACING, $height / 2 + $endy, $this->axis[1], $colors['black']);
110
111 // score the axes
112 $count = 0;
113 for ($i = $originx; $i <= $endx; $i += ($length / $this->ticks))
114 {
115 imageline($this->image, $i - self::SPACING, $originy + self::SPACING, $i + self::SPACING, $originy - self::SPACING, $colors['grey']);
116 imagestring($this->image, 1, $i, $originy + self::PADDING, round($count), $colors['black']);
117 $count += $xint;
118 }
119 $count = 0;
120 for ($i = $originy; $i >= $endy; $i -= ($height / $this->ticks))
121 {
122 imageline($this->image, $originx, $i, $endx, $i, $colors['grey']);
123 imagestring($this->image, 1, self::SPACING + self::SPACING + self::PADDING + self::SPACING, $i - self::SPACING, round($count), $colors['black']);
124 $count += $yint;
125 }
126
127 // draw the legend
128 $legy = $endy + self::SPACING; // "cursor" y-coord for the legend
129 $legx = $endx + self::PADDING; // x-coord for the legend BORDER
130 $legex = $this->dimensions['width'] - self::PADDING; // end x-coord for the legend BORDER
131 imageline($this->image, $legx, $endy, $legex, $endy, $colors['black']); // top legend border
132
133 // go through and plot each dataset
134 foreach ($this->dataset AS $data)
135 {
136 // plot each point and connect the dots
137 $oldpoint = null;
138 foreach ($data[1] AS $points)
139 {
140 $xcord = $originx + ($points[0] * ($length / $xmax));
141 $ycord = $originy - ($points[1] * ($height / $ymax));
142 imagefilledellipse($this->image, $xcord, $ycord, 5, 5, $data[2]);
143 if ($oldpoint)
144 {
145 imageline($this->image, $xcord, $ycord, $oldpoint[0], $oldpoint[1], $data[2]);
146 }
147 $oldpoint = array($xcord, $ycord);
148 }
149
150 // draw the legend bit
151 $box = array(
152 $legx + 1 + self::SPACING, $legy, // top left
153 $legx + 1 + self::SPACING, $legy + self::PADDING, // bottom left
154 $legx + 11 + self::SPACING, $legy + self::PADDING, // bottom right
155 $legx + 11 + self::SPACING, $legy // top right
156 );
157 imagefilledpolygon($this->image, $box, 4, $data[2]);
158 imagestring($this->image, 2, $legx + 11 + self::SPACING + self::SPACING, $legy - 1, $data[0], $colors['black']);
159 $legy += self::PADDING + self::SPACING;
160 }
161
162 // finish the legend border
163 imageline($this->image, $legx, $legy, $legex, $legy, $colors['black']); // bottom
164 imageline($this->image, $legx, $endy, $legx, $legy, $colors['black']); // left
165 imageline($this->image, $legex, $endy, $legex, $legy, $colors['black']); // right
166
167 return $this->_imageFlush();
168 }
169
170 // ###################################################################
171 /**
172 * Adds a "line" with a given name and a set of datapoints in the form
173 * array(x, y)
174 *
175 * @param string The line's name
176 * @param array Array of array(x,y) as data points
177 */
178 public function addDataSet($name, $points)
179 {
180 $this->_addPoints($points);
181 $this->_sortPoints($points);
182 $this->dataset[] = array($name, $points, $this->_fetchColor());
183 }
184
185 // ###################################################################
186 /**
187 * This does the same thing as addDataSet(), except the client code
188 * can specify the color in the form of array(R, G, B)
189 *
190 * @param string The line's name
191 * @param array Array of array(x,y) as data points
192 * @param array A color in the form of 3 RGB points
193 */
194 public function addDataSetColor($name, $points, $color)
195 {
196 $this->_addPoints($points);
197 $this->_sortPoints($points);
198 $this->dataset[] = array($name, $points, imagecolorallocate($this->image, $color[0], $color[1], $color[2]));
199 }
200
201 // ###################################################################
202 /**
203 * Adds a set of points to the piles and ensures that they are all valid
204 *
205 * @param array Points to add
206 */
207 private function _addPoints($points)
208 {
209 $xpairs = array();
210 foreach ($points AS $point)
211 {
212 if (isset($xpairs["$point[0]"]))
213 {
214 trigger_error('You cannot have more than one of the same x-values for a given data set');
215 }
216 $xpairs["$point[0]"] = $point[0];
217 $this->piles[0][] = $point[0];
218 $this->piles[1][] = $point[1];
219 }
220 }
221
222 // ###################################################################
223 /**
224 * Sorts an array of points using quick sort so they're in x-increasing
225 * order
226 *
227 * @param array Array of points
228 */
229 private function _sortPoints(&$points)
230 {
231 $this->_quickSortPoints($points, 0, sizeof($points) - 1);
232 }
233
234 // ###################################################################
235 /**
236 * Quicksort function for sorting function
237 *
238 * @param array Array of points
239 * @param integer Lower bound
240 * @param integer Upper bound
241 */
242 private function _quickSortPoints(&$points, $low, $high)
243 {
244 if (($high - $low) > 1)
245 {
246 $partition = $this->_partitionPoints($points, $low, $high);
247 $this->_quickSortPoints($points, $low, $partition);
248 $this->_quickSortPoints($points, $partition + 1, $high);
249 }
250 }
251
252 // ###################################################################
253 /**
254 * Quicksort partitioner: returns the index of the pivot element where
255 * all x-coords on the left side of pivot are less than or equal to
256 * pivot, and all x-coords are higher to the right
257 *
258 * @param array Array of points
259 * @param integer Lower bound
260 * @param integer Upper bound
261 *
262 * @return integer Pivot index
263 */
264 private function _partitionPoints(&$points, $low, $high)
265 {
266 $pivot = $low;
267 for ($unsorted = $low + 1; $unsorted <= $high; $unsorted++)
268 {
269 if ($points[$unsorted][0] < $points[$pivot][0])
270 {
271 $temp = $points[$pivot];
272 $points[$pivot] = $points[$unsorted];
273 $points[$unsorted] = $points[$pivot + 1];
274 $points[$pivot + 1] = $temp;
275 $pivot++;
276 }
277 }
278 return $pivot;
279 }
280
281 // ###################################################################
282 /**
283 * Sets the titles of the two axes
284 *
285 * @param string X-axis name
286 * @param string Y-axis name
287 */
288 public function setAxes($xaxis, $yaxis)
289 {
290 $this->axis[0] = $xaxis;
291 $this->axis[1] = $yaxis;
292 }
293 }
294
295 /*=====================================================================*
296 || ###################################################################
297 || # $HeadURL$
298 || # $Id$
299 || ###################################################################
300 \*=====================================================================*/
301 ?>