66 |
aurelien |
1 |
<?php
|
|
|
2 |
/*=======================================================================
|
|
|
3 |
// File: JPGRAPH_UTILS.INC
|
|
|
4 |
// Description: Collection of non-essential "nice to have" utilities
|
|
|
5 |
// Created: 2005-11-20
|
|
|
6 |
// Ver: $Id: jpgraph_utils.inc.php 1777 2009-08-23 17:34:36Z ljp $
|
|
|
7 |
//
|
|
|
8 |
// Copyright (c) Aditus Consulting. All rights reserved.
|
|
|
9 |
//========================================================================
|
|
|
10 |
*/
|
|
|
11 |
|
|
|
12 |
//===================================================
|
|
|
13 |
// CLASS FuncGenerator
|
|
|
14 |
// Description: Utility class to help generate data for function plots.
|
|
|
15 |
// The class supports both parametric and regular functions.
|
|
|
16 |
//===================================================
|
|
|
17 |
class FuncGenerator {
|
|
|
18 |
private $iFunc='',$iXFunc='',$iMin,$iMax,$iStepSize;
|
|
|
19 |
|
|
|
20 |
function __construct($aFunc,$aXFunc='') {
|
|
|
21 |
$this->iFunc = $aFunc;
|
|
|
22 |
$this->iXFunc = $aXFunc;
|
|
|
23 |
}
|
|
|
24 |
|
|
|
25 |
function E($aXMin,$aXMax,$aSteps=50) {
|
|
|
26 |
$this->iMin = $aXMin;
|
|
|
27 |
$this->iMax = $aXMax;
|
|
|
28 |
$this->iStepSize = ($aXMax-$aXMin)/$aSteps;
|
|
|
29 |
|
|
|
30 |
if( $this->iXFunc != '' )
|
|
|
31 |
$t = 'for($i='.$aXMin.'; $i<='.$aXMax.'; $i += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]='.$this->iXFunc.';}';
|
|
|
32 |
elseif( $this->iFunc != '' )
|
|
|
33 |
$t = 'for($x='.$aXMin.'; $x<='.$aXMax.'; $x += '.$this->iStepSize.') {$ya[]='.$this->iFunc.';$xa[]=$x;} $x='.$aXMax.';$ya[]='.$this->iFunc.';$xa[]=$x;';
|
|
|
34 |
else
|
|
|
35 |
JpGraphError::RaiseL(24001);//('FuncGenerator : No function specified. ');
|
|
|
36 |
|
|
|
37 |
@eval($t);
|
|
|
38 |
|
|
|
39 |
// If there is an error in the function specifcation this is the only
|
|
|
40 |
// way we can discover that.
|
|
|
41 |
if( empty($xa) || empty($ya) )
|
|
|
42 |
JpGraphError::RaiseL(24002);//('FuncGenerator : Syntax error in function specification ');
|
|
|
43 |
|
|
|
44 |
return array($xa,$ya);
|
|
|
45 |
}
|
|
|
46 |
}
|
|
|
47 |
|
|
|
48 |
|
|
|
49 |
//=============================================================================
|
|
|
50 |
// CLASS DateScaleUtils
|
|
|
51 |
// Description: Help to create a manual date scale
|
|
|
52 |
//=============================================================================
|
|
|
53 |
define('DSUTILS_MONTH',1); // Major and minor ticks on a monthly basis
|
|
|
54 |
define('DSUTILS_MONTH1',1); // Major and minor ticks on a monthly basis
|
|
|
55 |
define('DSUTILS_MONTH2',2); // Major ticks on a bi-monthly basis
|
|
|
56 |
define('DSUTILS_MONTH3',3); // Major icks on a tri-monthly basis
|
|
|
57 |
define('DSUTILS_MONTH6',4); // Major on a six-monthly basis
|
|
|
58 |
define('DSUTILS_WEEK1',5); // Major ticks on a weekly basis
|
|
|
59 |
define('DSUTILS_WEEK2',6); // Major ticks on a bi-weekly basis
|
|
|
60 |
define('DSUTILS_WEEK4',7); // Major ticks on a quod-weekly basis
|
|
|
61 |
define('DSUTILS_DAY1',8); // Major ticks on a daily basis
|
|
|
62 |
define('DSUTILS_DAY2',9); // Major ticks on a bi-daily basis
|
|
|
63 |
define('DSUTILS_DAY4',10); // Major ticks on a qoud-daily basis
|
|
|
64 |
define('DSUTILS_YEAR1',11); // Major ticks on a yearly basis
|
|
|
65 |
define('DSUTILS_YEAR2',12); // Major ticks on a bi-yearly basis
|
|
|
66 |
define('DSUTILS_YEAR5',13); // Major ticks on a five-yearly basis
|
|
|
67 |
|
|
|
68 |
|
|
|
69 |
class DateScaleUtils {
|
|
|
70 |
public static $iMin=0, $iMax=0;
|
|
|
71 |
|
|
|
72 |
private static $starthour,$startmonth, $startday, $startyear;
|
|
|
73 |
private static $endmonth, $endyear, $endday;
|
|
|
74 |
private static $tickPositions=array(),$minTickPositions=array();
|
|
|
75 |
private static $iUseWeeks = true;
|
|
|
76 |
|
|
|
77 |
static function UseWeekFormat($aFlg) {
|
|
|
78 |
self::$iUseWeeks = $aFlg;
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
static function doYearly($aType,$aMinor=false) {
|
|
|
82 |
$i=0; $j=0;
|
|
|
83 |
$m = self::$startmonth;
|
|
|
84 |
$y = self::$startyear;
|
|
|
85 |
|
|
|
86 |
if( self::$startday == 1 ) {
|
|
|
87 |
self::$tickPositions[$i++] = mktime(0,0,0,$m,1,$y);
|
|
|
88 |
}
|
|
|
89 |
++$m;
|
|
|
90 |
|
|
|
91 |
|
|
|
92 |
switch( $aType ) {
|
|
|
93 |
case DSUTILS_YEAR1:
|
|
|
94 |
for($y=self::$startyear; $y <= self::$endyear; ++$y ) {
|
|
|
95 |
if( $aMinor ) {
|
|
|
96 |
while( $m <= 12 ) {
|
|
|
97 |
if( !($y == self::$endyear && $m > self::$endmonth) ) {
|
|
|
98 |
self::$minTickPositions[$j++] = mktime(0,0,0,$m,1,$y);
|
|
|
99 |
}
|
|
|
100 |
++$m;
|
|
|
101 |
}
|
|
|
102 |
$m=1;
|
|
|
103 |
}
|
|
|
104 |
self::$tickPositions[$i++] = mktime(0,0,0,1,1,$y);
|
|
|
105 |
}
|
|
|
106 |
break;
|
|
|
107 |
case DSUTILS_YEAR2:
|
|
|
108 |
$y=self::$startyear;
|
|
|
109 |
while( $y <= self::$endyear ) {
|
|
|
110 |
self::$tickPositions[$i++] = mktime(0,0,0,1,1,$y);
|
|
|
111 |
for($k=0; $k < 1; ++$k ) {
|
|
|
112 |
++$y;
|
|
|
113 |
if( $aMinor ) {
|
|
|
114 |
self::$minTickPositions[$j++] = mktime(0,0,0,1,1,$y);
|
|
|
115 |
}
|
|
|
116 |
}
|
|
|
117 |
++$y;
|
|
|
118 |
}
|
|
|
119 |
break;
|
|
|
120 |
case DSUTILS_YEAR5:
|
|
|
121 |
$y=self::$startyear;
|
|
|
122 |
while( $y <= self::$endyear ) {
|
|
|
123 |
self::$tickPositions[$i++] = mktime(0,0,0,1,1,$y);
|
|
|
124 |
for($k=0; $k < 4; ++$k ) {
|
|
|
125 |
++$y;
|
|
|
126 |
if( $aMinor ) {
|
|
|
127 |
self::$minTickPositions[$j++] = mktime(0,0,0,1,1,$y);
|
|
|
128 |
}
|
|
|
129 |
}
|
|
|
130 |
++$y;
|
|
|
131 |
}
|
|
|
132 |
break;
|
|
|
133 |
}
|
|
|
134 |
}
|
|
|
135 |
|
|
|
136 |
static function doDaily($aType,$aMinor=false) {
|
|
|
137 |
$m = self::$startmonth;
|
|
|
138 |
$y = self::$startyear;
|
|
|
139 |
$d = self::$startday;
|
|
|
140 |
$h = self::$starthour;
|
|
|
141 |
$i=0;$j=0;
|
|
|
142 |
|
|
|
143 |
if( $h == 0 ) {
|
|
|
144 |
self::$tickPositions[$i++] = mktime(0,0,0,$m,$d,$y);
|
|
|
145 |
}
|
|
|
146 |
$t = mktime(0,0,0,$m,$d,$y);
|
|
|
147 |
|
|
|
148 |
switch($aType) {
|
|
|
149 |
case DSUTILS_DAY1:
|
|
|
150 |
while( $t <= self::$iMax ) {
|
|
|
151 |
$t = strtotime('+1 day',$t);
|
|
|
152 |
self::$tickPositions[$i++] = $t;
|
|
|
153 |
if( $aMinor ) {
|
|
|
154 |
self::$minTickPositions[$j++] = strtotime('+12 hours',$t);
|
|
|
155 |
}
|
|
|
156 |
}
|
|
|
157 |
break;
|
|
|
158 |
case DSUTILS_DAY2:
|
|
|
159 |
while( $t <= self::$iMax ) {
|
|
|
160 |
$t = strtotime('+1 day',$t);
|
|
|
161 |
if( $aMinor ) {
|
|
|
162 |
self::$minTickPositions[$j++] = $t;
|
|
|
163 |
}
|
|
|
164 |
$t = strtotime('+1 day',$t);
|
|
|
165 |
self::$tickPositions[$i++] = $t;
|
|
|
166 |
}
|
|
|
167 |
break;
|
|
|
168 |
case DSUTILS_DAY4:
|
|
|
169 |
while( $t <= self::$iMax ) {
|
|
|
170 |
for($k=0; $k < 3; ++$k ) {
|
|
|
171 |
$t = strtotime('+1 day',$t);
|
|
|
172 |
if( $aMinor ) {
|
|
|
173 |
self::$minTickPositions[$j++] = $t;
|
|
|
174 |
}
|
|
|
175 |
}
|
|
|
176 |
$t = strtotime('+1 day',$t);
|
|
|
177 |
self::$tickPositions[$i++] = $t;
|
|
|
178 |
}
|
|
|
179 |
break;
|
|
|
180 |
}
|
|
|
181 |
}
|
|
|
182 |
|
|
|
183 |
static function doWeekly($aType,$aMinor=false) {
|
|
|
184 |
$hpd = 3600*24;
|
|
|
185 |
$hpw = 3600*24*7;
|
|
|
186 |
// Find out week number of min date
|
|
|
187 |
$thursday = self::$iMin + $hpd * (3 - (date('w', self::$iMin) + 6) % 7);
|
|
|
188 |
$week = 1 + (date('z', $thursday) - (11 - date('w', mktime(0, 0, 0, 1, 1, date('Y', $thursday)))) % 7) / 7;
|
|
|
189 |
$daynumber = date('w',self::$iMin);
|
|
|
190 |
if( $daynumber == 0 ) $daynumber = 7;
|
|
|
191 |
$m = self::$startmonth;
|
|
|
192 |
$y = self::$startyear;
|
|
|
193 |
$d = self::$startday;
|
|
|
194 |
$i=0;$j=0;
|
|
|
195 |
// The assumption is that the weeks start on Monday. If the first day
|
|
|
196 |
// is later in the week then the first week tick has to be on the following
|
|
|
197 |
// week.
|
|
|
198 |
if( $daynumber == 1 ) {
|
|
|
199 |
self::$tickPositions[$i++] = mktime(0,0,0,$m,$d,$y);
|
|
|
200 |
$t = mktime(0,0,0,$m,$d,$y) + $hpw;
|
|
|
201 |
}
|
|
|
202 |
else {
|
|
|
203 |
$t = mktime(0,0,0,$m,$d,$y) + $hpd*(8-$daynumber);
|
|
|
204 |
}
|
|
|
205 |
|
|
|
206 |
switch($aType) {
|
|
|
207 |
case DSUTILS_WEEK1:
|
|
|
208 |
$cnt=0;
|
|
|
209 |
break;
|
|
|
210 |
case DSUTILS_WEEK2:
|
|
|
211 |
$cnt=1;
|
|
|
212 |
break;
|
|
|
213 |
case DSUTILS_WEEK4:
|
|
|
214 |
$cnt=3;
|
|
|
215 |
break;
|
|
|
216 |
}
|
|
|
217 |
while( $t <= self::$iMax ) {
|
|
|
218 |
self::$tickPositions[$i++] = $t;
|
|
|
219 |
for($k=0; $k < $cnt; ++$k ) {
|
|
|
220 |
$t += $hpw;
|
|
|
221 |
if( $aMinor ) {
|
|
|
222 |
self::$minTickPositions[$j++] = $t;
|
|
|
223 |
}
|
|
|
224 |
}
|
|
|
225 |
$t += $hpw;
|
|
|
226 |
}
|
|
|
227 |
}
|
|
|
228 |
|
|
|
229 |
static function doMonthly($aType,$aMinor=false) {
|
|
|
230 |
$monthcount=0;
|
|
|
231 |
$m = self::$startmonth;
|
|
|
232 |
$y = self::$startyear;
|
|
|
233 |
$i=0; $j=0;
|
|
|
234 |
|
|
|
235 |
// Skip the first month label if it is before the startdate
|
|
|
236 |
if( self::$startday == 1 ) {
|
|
|
237 |
self::$tickPositions[$i++] = mktime(0,0,0,$m,1,$y);
|
|
|
238 |
$monthcount=1;
|
|
|
239 |
}
|
|
|
240 |
if( $aType == 1 ) {
|
|
|
241 |
if( self::$startday < 15 ) {
|
|
|
242 |
self::$minTickPositions[$j++] = mktime(0,0,0,$m,15,$y);
|
|
|
243 |
}
|
|
|
244 |
}
|
|
|
245 |
++$m;
|
|
|
246 |
|
|
|
247 |
// Loop through all the years included in the scale
|
|
|
248 |
for($y=self::$startyear; $y <= self::$endyear; ++$y ) {
|
|
|
249 |
// Loop through all the months. There are three cases to consider:
|
|
|
250 |
// 1. We are in the first year and must start with the startmonth
|
|
|
251 |
// 2. We are in the end year and we must stop at last month of the scale
|
|
|
252 |
// 3. A year in between where we run through all the 12 months
|
|
|
253 |
$stopmonth = $y == self::$endyear ? self::$endmonth : 12;
|
|
|
254 |
while( $m <= $stopmonth ) {
|
|
|
255 |
switch( $aType ) {
|
|
|
256 |
case DSUTILS_MONTH1:
|
|
|
257 |
// Set minor tick at the middle of the month
|
|
|
258 |
if( $aMinor ) {
|
|
|
259 |
if( $m <= $stopmonth ) {
|
|
|
260 |
if( !($y==self::$endyear && $m==$stopmonth && self::$endday < 15) )
|
|
|
261 |
self::$minTickPositions[$j++] = mktime(0,0,0,$m,15,$y);
|
|
|
262 |
}
|
|
|
263 |
}
|
|
|
264 |
// Major at month
|
|
|
265 |
// Get timestamp of first hour of first day in each month
|
|
|
266 |
self::$tickPositions[$i++] = mktime(0,0,0,$m,1,$y);
|
|
|
267 |
|
|
|
268 |
break;
|
|
|
269 |
case DSUTILS_MONTH2:
|
|
|
270 |
if( $aMinor ) {
|
|
|
271 |
// Set minor tick at start of each month
|
|
|
272 |
self::$minTickPositions[$j++] = mktime(0,0,0,$m,1,$y);
|
|
|
273 |
}
|
|
|
274 |
|
|
|
275 |
// Major at every second month
|
|
|
276 |
// Get timestamp of first hour of first day in each month
|
|
|
277 |
if( $monthcount % 2 == 0 ) {
|
|
|
278 |
self::$tickPositions[$i++] = mktime(0,0,0,$m,1,$y);
|
|
|
279 |
}
|
|
|
280 |
break;
|
|
|
281 |
case DSUTILS_MONTH3:
|
|
|
282 |
if( $aMinor ) {
|
|
|
283 |
// Set minor tick at start of each month
|
|
|
284 |
self::$minTickPositions[$j++] = mktime(0,0,0,$m,1,$y);
|
|
|
285 |
}
|
|
|
286 |
// Major at every third month
|
|
|
287 |
// Get timestamp of first hour of first day in each month
|
|
|
288 |
if( $monthcount % 3 == 0 ) {
|
|
|
289 |
self::$tickPositions[$i++] = mktime(0,0,0,$m,1,$y);
|
|
|
290 |
}
|
|
|
291 |
break;
|
|
|
292 |
case DSUTILS_MONTH6:
|
|
|
293 |
if( $aMinor ) {
|
|
|
294 |
// Set minor tick at start of each month
|
|
|
295 |
self::$minTickPositions[$j++] = mktime(0,0,0,$m,1,$y);
|
|
|
296 |
}
|
|
|
297 |
// Major at every third month
|
|
|
298 |
// Get timestamp of first hour of first day in each month
|
|
|
299 |
if( $monthcount % 6 == 0 ) {
|
|
|
300 |
self::$tickPositions[$i++] = mktime(0,0,0,$m,1,$y);
|
|
|
301 |
}
|
|
|
302 |
break;
|
|
|
303 |
}
|
|
|
304 |
++$m;
|
|
|
305 |
++$monthcount;
|
|
|
306 |
}
|
|
|
307 |
$m=1;
|
|
|
308 |
}
|
|
|
309 |
|
|
|
310 |
// For the case where all dates are within the same month
|
|
|
311 |
// we want to make sure we have at least two ticks on the scale
|
|
|
312 |
// since the scale want work properly otherwise
|
|
|
313 |
if(self::$startmonth == self::$endmonth && self::$startyear == self::$endyear && $aType==1 ) {
|
|
|
314 |
self::$tickPositions[$i++] = mktime(0 ,0 ,0, self::$startmonth + 1, 1, self::$startyear);
|
|
|
315 |
}
|
|
|
316 |
|
|
|
317 |
return array(self::$tickPositions,self::$minTickPositions);
|
|
|
318 |
}
|
|
|
319 |
|
|
|
320 |
static function GetTicks($aData,$aType=1,$aMinor=false,$aEndPoints=false) {
|
|
|
321 |
$n = count($aData);
|
|
|
322 |
return self::GetTicksFromMinMax($aData[0],$aData[$n-1],$aType,$aMinor,$aEndPoints);
|
|
|
323 |
}
|
|
|
324 |
|
|
|
325 |
static function GetAutoTicks($aMin,$aMax,$aMaxTicks=10,$aMinor=false) {
|
|
|
326 |
$diff = $aMax - $aMin;
|
|
|
327 |
$spd = 3600*24;
|
|
|
328 |
$spw = $spd*7;
|
|
|
329 |
$spm = $spd*30;
|
|
|
330 |
$spy = $spd*352;
|
|
|
331 |
|
|
|
332 |
if( self::$iUseWeeks )
|
|
|
333 |
$w = 'W';
|
|
|
334 |
else
|
|
|
335 |
$w = 'd M';
|
|
|
336 |
|
|
|
337 |
// Decision table for suitable scales
|
|
|
338 |
// First value: Main decision point
|
|
|
339 |
// Second value: Array of formatting depending on divisor for wanted max number of ticks. <divisor><formatting><format-string>,..
|
|
|
340 |
$tt = array(
|
|
|
341 |
array($spw, array(1,DSUTILS_DAY1,'d M',2,DSUTILS_DAY2,'d M',-1,DSUTILS_DAY4,'d M')),
|
|
|
342 |
array($spm, array(1,DSUTILS_DAY1,'d M',2,DSUTILS_DAY2,'d M',4,DSUTILS_DAY4,'d M',7,DSUTILS_WEEK1,$w,-1,DSUTILS_WEEK2,$w)),
|
|
|
343 |
array($spy, array(1,DSUTILS_DAY1,'d M',2,DSUTILS_DAY2,'d M',4,DSUTILS_DAY4,'d M',7,DSUTILS_WEEK1,$w,14,DSUTILS_WEEK2,$w,30,DSUTILS_MONTH1,'M',60,DSUTILS_MONTH2,'M',-1,DSUTILS_MONTH3,'M')),
|
|
|
344 |
array(-1, array(30,DSUTILS_MONTH1,'M-Y',60,DSUTILS_MONTH2,'M-Y',90,DSUTILS_MONTH3,'M-Y',180,DSUTILS_MONTH6,'M-Y',352,DSUTILS_YEAR1,'Y',704,DSUTILS_YEAR2,'Y',-1,DSUTILS_YEAR5,'Y')));
|
|
|
345 |
|
|
|
346 |
$ntt = count($tt);
|
|
|
347 |
$nd = floor($diff/$spd);
|
|
|
348 |
for($i=0; $i < $ntt; ++$i ) {
|
|
|
349 |
if( $diff <= $tt[$i][0] || $i==$ntt-1) {
|
|
|
350 |
$t = $tt[$i][1];
|
|
|
351 |
$n = count($t)/3;
|
|
|
352 |
for( $j=0; $j < $n; ++$j ) {
|
|
|
353 |
if( $nd/$t[3*$j] <= $aMaxTicks || $j==$n-1) {
|
|
|
354 |
$type = $t[3*$j+1];
|
|
|
355 |
$fs = $t[3*$j+2];
|
|
|
356 |
list($tickPositions,$minTickPositions) = self::GetTicksFromMinMax($aMin,$aMax,$type,$aMinor);
|
|
|
357 |
return array($fs,$tickPositions,$minTickPositions,$type);
|
|
|
358 |
}
|
|
|
359 |
}
|
|
|
360 |
}
|
|
|
361 |
}
|
|
|
362 |
}
|
|
|
363 |
|
|
|
364 |
static function GetTicksFromMinMax($aMin,$aMax,$aType,$aMinor=false,$aEndPoints=false) {
|
|
|
365 |
self::$starthour = date('G',$aMin);
|
|
|
366 |
self::$startmonth = date('n',$aMin);
|
|
|
367 |
self::$startday = date('j',$aMin);
|
|
|
368 |
self::$startyear = date('Y',$aMin);
|
|
|
369 |
self::$endmonth = date('n',$aMax);
|
|
|
370 |
self::$endyear = date('Y',$aMax);
|
|
|
371 |
self::$endday = date('j',$aMax);
|
|
|
372 |
self::$iMin = $aMin;
|
|
|
373 |
self::$iMax = $aMax;
|
|
|
374 |
|
|
|
375 |
if( $aType <= DSUTILS_MONTH6 ) {
|
|
|
376 |
self::doMonthly($aType,$aMinor);
|
|
|
377 |
}
|
|
|
378 |
elseif( $aType <= DSUTILS_WEEK4 ) {
|
|
|
379 |
self::doWeekly($aType,$aMinor);
|
|
|
380 |
}
|
|
|
381 |
elseif( $aType <= DSUTILS_DAY4 ) {
|
|
|
382 |
self::doDaily($aType,$aMinor);
|
|
|
383 |
}
|
|
|
384 |
elseif( $aType <= DSUTILS_YEAR5 ) {
|
|
|
385 |
self::doYearly($aType,$aMinor);
|
|
|
386 |
}
|
|
|
387 |
else {
|
|
|
388 |
JpGraphError::RaiseL(24003);
|
|
|
389 |
}
|
|
|
390 |
// put a label at the very left data pos
|
|
|
391 |
if( $aEndPoints ) {
|
|
|
392 |
$tickPositions[$i++] = $aData[0];
|
|
|
393 |
}
|
|
|
394 |
|
|
|
395 |
// put a label at the very right data pos
|
|
|
396 |
if( $aEndPoints ) {
|
|
|
397 |
$tickPositions[$i] = $aData[$n-1];
|
|
|
398 |
}
|
|
|
399 |
|
|
|
400 |
return array(self::$tickPositions,self::$minTickPositions);
|
|
|
401 |
}
|
|
|
402 |
}
|
|
|
403 |
|
|
|
404 |
//=============================================================================
|
|
|
405 |
// Class ReadFileData
|
|
|
406 |
//=============================================================================
|
|
|
407 |
Class ReadFileData {
|
|
|
408 |
//----------------------------------------------------------------------------
|
|
|
409 |
// Desciption:
|
|
|
410 |
// Read numeric data from a file.
|
|
|
411 |
// Each value should be separated by either a new line or by a specified
|
|
|
412 |
// separator character (default is ',').
|
|
|
413 |
// Before returning the data each value is converted to a proper float
|
|
|
414 |
// value. The routine is robust in the sense that non numeric data in the
|
|
|
415 |
// file will be discarded.
|
|
|
416 |
//
|
|
|
417 |
// Returns:
|
|
|
418 |
// The number of data values read on success, FALSE on failure
|
|
|
419 |
//----------------------------------------------------------------------------
|
|
|
420 |
static function FromCSV($aFile,&$aData,$aSepChar=',',$aMaxLineLength=1024) {
|
|
|
421 |
$rh = @fopen($aFile,'r');
|
|
|
422 |
if( $rh === false ) {
|
|
|
423 |
return false;
|
|
|
424 |
}
|
|
|
425 |
$tmp = array();
|
|
|
426 |
$lineofdata = fgetcsv($rh, 1000, ',');
|
|
|
427 |
while ( $lineofdata !== FALSE) {
|
|
|
428 |
$tmp = array_merge($tmp,$lineofdata);
|
|
|
429 |
$lineofdata = fgetcsv($rh, $aMaxLineLength, $aSepChar);
|
|
|
430 |
}
|
|
|
431 |
fclose($rh);
|
|
|
432 |
|
|
|
433 |
// Now make sure that all data is numeric. By default
|
|
|
434 |
// all data is read as strings
|
|
|
435 |
$n = count($tmp);
|
|
|
436 |
$aData = array();
|
|
|
437 |
$cnt=0;
|
|
|
438 |
for($i=0; $i < $n; ++$i) {
|
|
|
439 |
if( $tmp[$i] !== "" ) {
|
|
|
440 |
$aData[$cnt++] = floatval($tmp[$i]);
|
|
|
441 |
}
|
|
|
442 |
}
|
|
|
443 |
return $cnt;
|
|
|
444 |
}
|
|
|
445 |
|
|
|
446 |
//----------------------------------------------------------------------------
|
|
|
447 |
// Desciption:
|
|
|
448 |
// Read numeric data from a file.
|
|
|
449 |
// Each value should be separated by either a new line or by a specified
|
|
|
450 |
// separator character (default is ',').
|
|
|
451 |
// Before returning the data each value is converted to a proper float
|
|
|
452 |
// value. The routine is robust in the sense that non numeric data in the
|
|
|
453 |
// file will be discarded.
|
|
|
454 |
//
|
|
|
455 |
// Options:
|
|
|
456 |
// 'separator' => ',',
|
|
|
457 |
// 'enclosure' => '"',
|
|
|
458 |
// 'readlength' => 1024,
|
|
|
459 |
// 'ignore_first' => false,
|
|
|
460 |
// 'first_as_key' => false
|
|
|
461 |
// 'escape' => '\', # PHP >= 5.3 only
|
|
|
462 |
//
|
|
|
463 |
// Returns:
|
|
|
464 |
// The number of lines read on success, FALSE on failure
|
|
|
465 |
//----------------------------------------------------------------------------
|
|
|
466 |
static function FromCSV2($aFile, &$aData, $aOptions = array()) {
|
|
|
467 |
$aDefaults = array(
|
|
|
468 |
'separator' => ',',
|
|
|
469 |
'enclosure' => chr(34),
|
|
|
470 |
'escape' => chr(92),
|
|
|
471 |
'readlength' => 1024,
|
|
|
472 |
'ignore_first' => false,
|
|
|
473 |
'first_as_key' => false
|
|
|
474 |
);
|
|
|
475 |
|
|
|
476 |
$aOptions = array_merge(
|
|
|
477 |
$aDefaults, is_array($aOptions) ? $aOptions : array());
|
|
|
478 |
|
|
|
479 |
if( $aOptions['first_as_key'] ) {
|
|
|
480 |
$aOptions['ignore_first'] = true;
|
|
|
481 |
}
|
|
|
482 |
|
|
|
483 |
$rh = @fopen($aFile, 'r');
|
|
|
484 |
|
|
|
485 |
if( $rh === false ) {
|
|
|
486 |
return false;
|
|
|
487 |
}
|
|
|
488 |
|
|
|
489 |
$aData = array();
|
|
|
490 |
$aLine = fgetcsv($rh,
|
|
|
491 |
$aOptions['readlength'],
|
|
|
492 |
$aOptions['separator'],
|
|
|
493 |
$aOptions['enclosure']
|
|
|
494 |
/*, $aOptions['escape'] # PHP >= 5.3 only */
|
|
|
495 |
);
|
|
|
496 |
|
|
|
497 |
// Use numeric array keys for the columns by default
|
|
|
498 |
// If specified use first lines values as assoc keys instead
|
|
|
499 |
$keys = array_keys($aLine);
|
|
|
500 |
if( $aOptions['first_as_key'] ) {
|
|
|
501 |
$keys = array_values($aLine);
|
|
|
502 |
}
|
|
|
503 |
|
|
|
504 |
$num_lines = 0;
|
|
|
505 |
$num_cols = count($aLine);
|
|
|
506 |
|
|
|
507 |
while ($aLine !== false) {
|
|
|
508 |
if( is_array($aLine) && count($aLine) != $num_cols ) {
|
|
|
509 |
JpGraphError::RaiseL(24004);
|
|
|
510 |
// 'ReadCSV2: Column count mismatch in %s line %d'
|
|
|
511 |
}
|
|
|
512 |
|
|
|
513 |
// fgetcsv returns NULL for empty lines
|
|
|
514 |
if( !is_null($aLine) ) {
|
|
|
515 |
$num_lines++;
|
|
|
516 |
|
|
|
517 |
if( !($aOptions['ignore_first'] && $num_lines == 1) && is_numeric($aLine[0]) ) {
|
|
|
518 |
for( $i = 0; $i < $num_cols; $i++ ) {
|
|
|
519 |
$aData[ $keys[$i] ][] = floatval($aLine[$i]);
|
|
|
520 |
}
|
|
|
521 |
}
|
|
|
522 |
}
|
|
|
523 |
|
|
|
524 |
$aLine = fgetcsv($rh,
|
|
|
525 |
$aOptions['readlength'],
|
|
|
526 |
$aOptions['separator'],
|
|
|
527 |
$aOptions['enclosure']
|
|
|
528 |
/*, $aOptions['escape'] # PHP >= 5.3 only*/
|
|
|
529 |
);
|
|
|
530 |
}
|
|
|
531 |
|
|
|
532 |
fclose($rh);
|
|
|
533 |
|
|
|
534 |
if( $aOptions['ignore_first'] ) {
|
|
|
535 |
$num_lines--;
|
|
|
536 |
}
|
|
|
537 |
|
|
|
538 |
return $num_lines;
|
|
|
539 |
}
|
|
|
540 |
|
|
|
541 |
// Read data from two columns in a plain text file
|
|
|
542 |
static function From2Col($aFile, $aCol1, $aCol2, $aSepChar=' ') {
|
|
|
543 |
$lines = @file($aFile,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
|
|
|
544 |
if( $lines === false ) {
|
|
|
545 |
return false;
|
|
|
546 |
}
|
|
|
547 |
$s = '/[\s]+/';
|
|
|
548 |
if( $aSepChar == ',' ) {
|
|
|
549 |
$s = '/[\s]*,[\s]*/';
|
|
|
550 |
}
|
|
|
551 |
elseif( $aSepChar == ';' ) {
|
|
|
552 |
$s = '/[\s]*;[\s]*/';
|
|
|
553 |
}
|
|
|
554 |
foreach( $lines as $line => $datarow ) {
|
|
|
555 |
$split = preg_split($s,$datarow);
|
|
|
556 |
$aCol1[] = floatval(trim($split[0]));
|
|
|
557 |
$aCol2[] = floatval(trim($split[1]));
|
|
|
558 |
}
|
|
|
559 |
|
|
|
560 |
return count($lines);
|
|
|
561 |
}
|
|
|
562 |
|
|
|
563 |
// Read data from one columns in a plain text file
|
|
|
564 |
static function From1Col($aFile, $aCol1) {
|
|
|
565 |
$lines = @file($aFile,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
|
|
|
566 |
if( $lines === false ) {
|
|
|
567 |
return false;
|
|
|
568 |
}
|
|
|
569 |
foreach( $lines as $line => $datarow ) {
|
|
|
570 |
$aCol1[] = floatval(trim($datarow));
|
|
|
571 |
}
|
|
|
572 |
|
|
|
573 |
return count($lines);
|
|
|
574 |
}
|
|
|
575 |
|
|
|
576 |
static function FromMatrix($aFile,$aSepChar=' ') {
|
|
|
577 |
$lines = @file($aFile,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
|
|
|
578 |
if( $lines === false ) {
|
|
|
579 |
return false;
|
|
|
580 |
}
|
|
|
581 |
$mat = array();
|
|
|
582 |
$reg = '/'.$aSepChar.'/';
|
|
|
583 |
foreach( $lines as $line => $datarow ) {
|
|
|
584 |
$row = preg_split($reg,trim($datarow));
|
|
|
585 |
foreach ($row as $key => $cell ) {
|
|
|
586 |
$row[$key] = floatval(trim($cell));
|
|
|
587 |
}
|
|
|
588 |
$mat[] = $row;
|
|
|
589 |
}
|
|
|
590 |
return $mat;
|
|
|
591 |
}
|
|
|
592 |
|
|
|
593 |
|
|
|
594 |
}
|
|
|
595 |
|
|
|
596 |
define('__LR_EPSILON', 1.0e-8);
|
|
|
597 |
//=============================================================================
|
|
|
598 |
// Class LinearRegression
|
|
|
599 |
//=============================================================================
|
|
|
600 |
class LinearRegression {
|
|
|
601 |
private $ix=array(),$iy=array();
|
|
|
602 |
private $ib=0, $ia=0;
|
|
|
603 |
private $icalculated=false;
|
|
|
604 |
public $iDet=0, $iCorr=0, $iStdErr=0;
|
|
|
605 |
|
|
|
606 |
public function __construct($aDataX,$aDataY) {
|
|
|
607 |
if( count($aDataX) !== count($aDataY) ) {
|
|
|
608 |
JpGraph::Raise('LinearRegression: X and Y data array must be of equal length.');
|
|
|
609 |
}
|
|
|
610 |
$this->ix = $aDataX;
|
|
|
611 |
$this->iy = $aDataY;
|
|
|
612 |
}
|
|
|
613 |
|
|
|
614 |
public function Calc() {
|
|
|
615 |
|
|
|
616 |
$this->icalculated = true;
|
|
|
617 |
|
|
|
618 |
$n = count($this->ix);
|
|
|
619 |
$sx2 = 0 ;
|
|
|
620 |
$sy2 = 0 ;
|
|
|
621 |
$sxy = 0 ;
|
|
|
622 |
$sx = 0 ;
|
|
|
623 |
$sy = 0 ;
|
|
|
624 |
|
|
|
625 |
for( $i=0; $i < $n; ++$i ) {
|
|
|
626 |
$sx2 += $this->ix[$i] * $this->ix[$i];
|
|
|
627 |
$sy2 += $this->iy[$i] * $this->iy[$i];
|
|
|
628 |
$sxy += $this->ix[$i] * $this->iy[$i];
|
|
|
629 |
$sx += $this->ix[$i];
|
|
|
630 |
$sy += $this->iy[$i];
|
|
|
631 |
}
|
|
|
632 |
|
|
|
633 |
if( $n*$sx2 - $sx*$sx > __LR_EPSILON ) {
|
|
|
634 |
$this->ib = ($n*$sxy - $sx*$sy) / ( $n*$sx2 - $sx*$sx );
|
|
|
635 |
$this->ia = ( $sy - $this->ib*$sx ) / $n;
|
|
|
636 |
|
|
|
637 |
$sx = $this->ib * ( $sxy - $sx*$sy/$n );
|
|
|
638 |
$sy2 = $sy2 - $sy*$sy/$n;
|
|
|
639 |
$sy = $sy2 - $sx;
|
|
|
640 |
|
|
|
641 |
$this->iDet = $sx / $sy2;
|
|
|
642 |
$this->iCorr = sqrt($this->iDet);
|
|
|
643 |
if( $n > 2 ) {
|
|
|
644 |
$this->iStdErr = sqrt( $sy / ($n-2) );
|
|
|
645 |
}
|
|
|
646 |
else {
|
|
|
647 |
$this->iStdErr = NAN ;
|
|
|
648 |
}
|
|
|
649 |
}
|
|
|
650 |
else {
|
|
|
651 |
$this->ib = 0;
|
|
|
652 |
$this->ia = 0;
|
|
|
653 |
}
|
|
|
654 |
|
|
|
655 |
}
|
|
|
656 |
|
|
|
657 |
public function GetAB() {
|
|
|
658 |
if( $this->icalculated == false )
|
|
|
659 |
$this->Calc();
|
|
|
660 |
return array($this->ia, $this->ib);
|
|
|
661 |
}
|
|
|
662 |
|
|
|
663 |
public function GetStat() {
|
|
|
664 |
if( $this->icalculated == false )
|
|
|
665 |
$this->Calc();
|
|
|
666 |
return array($this->iStdErr, $this->iCorr, $this->iDet);
|
|
|
667 |
}
|
|
|
668 |
|
|
|
669 |
public function GetY($aMinX, $aMaxX, $aStep=1) {
|
|
|
670 |
if( $this->icalculated == false )
|
|
|
671 |
$this->Calc();
|
|
|
672 |
|
|
|
673 |
$yy = array();
|
|
|
674 |
$i = 0;
|
|
|
675 |
for( $x=$aMinX; $x <= $aMaxX; $x += $aStep ) {
|
|
|
676 |
$xx[$i ] = $x;
|
|
|
677 |
$yy[$i++] = $this->ia + $this->ib * $x;
|
|
|
678 |
}
|
|
|
679 |
|
|
|
680 |
return array($xx,$yy);
|
|
|
681 |
}
|
|
|
682 |
|
|
|
683 |
}
|
|
|
684 |
|
|
|
685 |
?>
|