source: trunk/client/modules/Elezioni/grafici/jpgraph.php@ 417

Last change on this file since 417 was 346, checked in by roby, 4 years ago

ulteriori modifiche per adeguamento a php7
Client: aggiornamento jpgraph

File size: 199.9 KB
Line 
1<?php
2//=======================================================================
3// File: JPGRAPH.PHP
4// Description: PHP Graph Plotting library. Base module.
5// Created: 2001-01-08
6// Ver: $Id: jpgraph.php 1924 2010-01-11 14:03:26Z ljp $
7//
8// Copyright (c) Asial Corporation. All rights reserved.
9//========================================================================
10
11require_once('jpg-config.inc.php');
12require_once('jpgraph_gradient.php');
13require_once('jpgraph_errhandler.inc.php');
14require_once('jpgraph_ttf.inc.php');
15require_once('jpgraph_rgb.inc.php');
16require_once('jpgraph_text.inc.php');
17require_once('jpgraph_legend.inc.php');
18require_once('jpgraph_theme.inc.php');
19require_once('gd_image.inc.php');
20
21// Version info
22define('JPG_VERSION','4.3.4');
23
24// Minimum required PHP version
25define('MIN_PHPVERSION','5.1.0');
26
27// Special file name to indicate that we only want to calc
28// the image map in the call to Graph::Stroke() used
29// internally from the GetHTMLCSIM() method.
30define('_CSIM_SPECIALFILE','_csim_special_');
31
32// HTTP GET argument that is used with image map
33// to indicate to the script to just generate the image
34// and not the full CSIM HTML page.
35define('_CSIM_DISPLAY','_jpg_csimd');
36
37// Special filename for Graph::Stroke(). If this filename is given
38// then the image will NOT be streamed to browser of file. Instead the
39// Stroke call will return the handler for the created GD image.
40define('_IMG_HANDLER','__handle');
41
42// Special filename for Graph::Stroke(). If this filename is given
43// the image will be stroked to a file with a name based on the script name.
44define('_IMG_AUTO','auto');
45
46// Tick density
47define("TICKD_DENSE",1);
48define("TICKD_NORMAL",2);
49define("TICKD_SPARSE",3);
50define("TICKD_VERYSPARSE",4);
51
52// Side for ticks and labels.
53define("SIDE_LEFT",-1);
54define("SIDE_RIGHT",1);
55define("SIDE_DOWN",-1);
56define("SIDE_BOTTOM",-1);
57define("SIDE_UP",1);
58define("SIDE_TOP",1);
59
60// Legend type stacked vertical or horizontal
61define("LEGEND_VERT",0);
62define("LEGEND_HOR",1);
63
64// Mark types for plot marks
65define("MARK_SQUARE",1);
66define("MARK_UTRIANGLE",2);
67define("MARK_DTRIANGLE",3);
68define("MARK_DIAMOND",4);
69define("MARK_CIRCLE",5);
70define("MARK_FILLEDCIRCLE",6);
71define("MARK_CROSS",7);
72define("MARK_STAR",8);
73define("MARK_X",9);
74define("MARK_LEFTTRIANGLE",10);
75define("MARK_RIGHTTRIANGLE",11);
76define("MARK_FLASH",12);
77define("MARK_IMG",13);
78define("MARK_FLAG1",14);
79define("MARK_FLAG2",15);
80define("MARK_FLAG3",16);
81define("MARK_FLAG4",17);
82
83// Builtin images
84define("MARK_IMG_PUSHPIN",50);
85define("MARK_IMG_SPUSHPIN",50);
86define("MARK_IMG_LPUSHPIN",51);
87define("MARK_IMG_DIAMOND",52);
88define("MARK_IMG_SQUARE",53);
89define("MARK_IMG_STAR",54);
90define("MARK_IMG_BALL",55);
91define("MARK_IMG_SBALL",55);
92define("MARK_IMG_MBALL",56);
93define("MARK_IMG_LBALL",57);
94define("MARK_IMG_BEVEL",58);
95
96// Inline defines
97define("INLINE_YES",1);
98define("INLINE_NO",0);
99
100// Format for background images
101define("BGIMG_FILLPLOT",1);
102define("BGIMG_FILLFRAME",2);
103define("BGIMG_COPY",3);
104define("BGIMG_CENTER",4);
105define("BGIMG_FREE",5);
106
107// Depth of objects
108define("DEPTH_BACK",0);
109define("DEPTH_FRONT",1);
110
111// Direction
112define("VERTICAL",1);
113define("HORIZONTAL",0);
114
115// Axis styles for scientific style axis
116define('AXSTYLE_SIMPLE',1);
117define('AXSTYLE_BOXIN',2);
118define('AXSTYLE_BOXOUT',3);
119define('AXSTYLE_YBOXIN',4);
120define('AXSTYLE_YBOXOUT',5);
121
122// Style for title backgrounds
123define('TITLEBKG_STYLE1',1);
124define('TITLEBKG_STYLE2',2);
125define('TITLEBKG_STYLE3',3);
126define('TITLEBKG_FRAME_NONE',0);
127define('TITLEBKG_FRAME_FULL',1);
128define('TITLEBKG_FRAME_BOTTOM',2);
129define('TITLEBKG_FRAME_BEVEL',3);
130define('TITLEBKG_FILLSTYLE_HSTRIPED',1);
131define('TITLEBKG_FILLSTYLE_VSTRIPED',2);
132define('TITLEBKG_FILLSTYLE_SOLID',3);
133
134// Styles for axis labels background
135define('LABELBKG_NONE',0);
136define('LABELBKG_XAXIS',1);
137define('LABELBKG_YAXIS',2);
138define('LABELBKG_XAXISFULL',3);
139define('LABELBKG_YAXISFULL',4);
140define('LABELBKG_XYFULL',5);
141define('LABELBKG_XY',6);
142
143
144// Style for background gradient fills
145define('BGRAD_FRAME',1);
146define('BGRAD_MARGIN',2);
147define('BGRAD_PLOT',3);
148
149// Width of tab titles
150define('TABTITLE_WIDTHFIT',0);
151define('TABTITLE_WIDTHFULL',-1);
152
153// Defines for 3D skew directions
154define('SKEW3D_UP',0);
155define('SKEW3D_DOWN',1);
156define('SKEW3D_LEFT',2);
157define('SKEW3D_RIGHT',3);
158
159// For internal use only
160define("_JPG_DEBUG",false);
161define("_FORCE_IMGTOFILE",false);
162define("_FORCE_IMGDIR",'/tmp/jpgimg/');
163
164
165//
166// Automatic settings of path for cache and font directory
167// if they have not been previously specified
168//
169if(USE_CACHE) {
170 if (!defined('CACHE_DIR')) {
171 if ( strstr( PHP_OS, 'WIN') ) {
172 if( empty($_SERVER['TEMP']) ) {
173 $t = new ErrMsgText();
174 $msg = $t->Get(11,$file,$lineno);
175 die($msg);
176 }
177 else {
178 define('CACHE_DIR', $_SERVER['TEMP'] . '/');
179 }
180 } else {
181 define('CACHE_DIR','/tmp/jpgraph_cache/');
182 }
183 }
184}
185elseif( !defined('CACHE_DIR') ) {
186 define('CACHE_DIR', '');
187}
188
189//
190// Setup path for western/latin TTF fonts
191//
192if (!defined('TTF_DIR')) {
193 if (strstr( PHP_OS, 'WIN') ) {
194 $sroot = getenv('SystemRoot');
195 if( empty($sroot) ) {
196 $t = new ErrMsgText();
197 $msg = $t->Get(12,$file,$lineno);
198 die($msg);
199 }
200 else {
201 define('TTF_DIR', $sroot.'/fonts/');
202 }
203 } else {
204 define('TTF_DIR','/usr/share/fonts/truetype/');
205 }
206}
207
208//
209// Setup path for MultiByte TTF fonts (japanese, chinese etc.)
210//
211if (!defined('MBTTF_DIR')) {
212 if (strstr( PHP_OS, 'WIN') ) {
213 $sroot = getenv('SystemRoot');
214 if( empty($sroot) ) {
215 $t = new ErrMsgText();
216 $msg = $t->Get(12,$file,$lineno);
217 die($msg);
218 }
219 else {
220 define('MBTTF_DIR', $sroot.'/fonts/');
221 }
222 } else {
223 define('MBTTF_DIR','/usr/share/fonts/truetype/');
224 }
225}
226
227//
228// Check minimum PHP version
229//
230function CheckPHPVersion($aMinVersion) {
231 list($majorC, $minorC, $editC) = preg_split('/[\/.-]/', PHP_VERSION);
232 list($majorR, $minorR, $editR) = preg_split('/[\/.-]/', $aMinVersion);
233
234 if ($majorC < $majorR) return false;
235
236 if ($majorC == $majorR) {
237 if($minorC < $minorR) return false;
238
239 if($minorC == $minorR){
240 if($editC < $editR) return false;
241 }
242 }
243
244 return true;
245}
246
247//
248// Make sure PHP version is high enough
249//
250if( !CheckPHPVersion(MIN_PHPVERSION) ) {
251 JpGraphError::RaiseL(13,PHP_VERSION,MIN_PHPVERSION);
252 die();
253}
254
255//
256// Make GD sanity check
257//
258if( !function_exists("imagetypes") || !function_exists('imagecreatefromstring') ) {
259 JpGraphError::RaiseL(25001);
260 //("This PHP installation is not configured with the GD library. Please recompile PHP with GD support to run JpGraph. (Neither function imagetypes() nor imagecreatefromstring() does exist)");
261}
262
263//
264// Setup PHP error handler
265//
266function _phpErrorHandler($errno,$errmsg,$filename, $linenum, $vars) {
267 // Respect current error level
268 if( $errno & error_reporting() ) {
269 JpGraphError::RaiseL(25003,basename($filename),$linenum,$errmsg);
270 }
271}
272
273if( INSTALL_PHP_ERR_HANDLER ) {
274 set_error_handler("_phpErrorHandler");
275}
276
277//
278// Check if there were any warnings, perhaps some wrong includes by the user. In this
279// case we raise it immediately since otherwise the image will not show and makes
280// debugging difficult. This is controlled by the user setting CATCH_PHPERRMSG
281//
282if( isset($GLOBALS['php_errormsg']) && CATCH_PHPERRMSG && !preg_match('/|Deprecated|/i', $GLOBALS['php_errormsg']) ) {
283 JpGraphError::RaiseL(25004,$GLOBALS['php_errormsg']);
284}
285
286// Useful mathematical function
287function sign($a) {return $a >= 0 ? 1 : -1;}
288
289//
290// Utility function to generate an image name based on the filename we
291// are running from and assuming we use auto detection of graphic format
292// (top level), i.e it is safe to call this function
293// from a script that uses JpGraph
294//
295function GenImgName() {
296 // Determine what format we should use when we save the images
297 $supported = imagetypes();
298 if( $supported & IMG_PNG ) $img_format="png";
299 elseif( $supported & IMG_GIF ) $img_format="gif";
300 elseif( $supported & IMG_JPG ) $img_format="jpeg";
301 elseif( $supported & IMG_WBMP ) $img_format="wbmp";
302 elseif( $supported & IMG_XPM ) $img_format="xpm";
303
304
305 if( !isset($_SERVER['PHP_SELF']) ) {
306 JpGraphError::RaiseL(25005);
307 //(" Can't access PHP_SELF, PHP global variable. You can't run PHP from command line if you want to use the 'auto' naming of cache or image files.");
308 }
309 $fname = basename($_SERVER['PHP_SELF']);
310 if( !empty($_SERVER['QUERY_STRING']) ) {
311 $q = @$_SERVER['QUERY_STRING'];
312 $fname .= '_'.preg_replace("/\W/", "_", $q).'.'.$img_format;
313 }
314 else {
315 $fname = substr($fname,0,strlen($fname)-4).'.'.$img_format;
316 }
317 return $fname;
318}
319
320//===================================================
321// CLASS JpgTimer
322// Description: General timing utility class to handle
323// time measurement of generating graphs. Multiple
324// timers can be started.
325//===================================================
326class JpgTimer {
327 private $start, $idx;
328
329 function __construct() {
330 $this->idx=0;
331 }
332
333 // Push a new timer start on stack
334 function Push() {
335 list($ms,$s)=explode(" ",microtime());
336 $this->start[$this->idx++]=floor($ms*1000) + 1000*$s;
337 }
338
339 // Pop the latest timer start and return the diff with the
340 // current time
341 function Pop() {
342 assert($this->idx>0);
343 list($ms,$s)=explode(" ",microtime());
344 $etime=floor($ms*1000) + (1000*$s);
345 $this->idx--;
346 return $etime-$this->start[$this->idx];
347 }
348} // Class
349
350//===================================================
351// CLASS DateLocale
352// Description: Hold localized text used in dates
353//===================================================
354class DateLocale {
355
356 public $iLocale = 'C'; // environmental locale be used by default
357 private $iDayAbb = null, $iShortDay = null, $iShortMonth = null, $iMonthName = null;
358
359 function __construct() {
360 settype($this->iDayAbb, 'array');
361 settype($this->iShortDay, 'array');
362 settype($this->iShortMonth, 'array');
363 settype($this->iMonthName, 'array');
364 $this->Set('C');
365 }
366
367 function Set($aLocale) {
368 if ( in_array($aLocale, array_keys($this->iDayAbb)) ){
369 $this->iLocale = $aLocale;
370 return TRUE; // already cached nothing else to do!
371 }
372
373 $pLocale = setlocale(LC_TIME, 0); // get current locale for LC_TIME
374
375 if (is_array($aLocale)) {
376 foreach ($aLocale as $loc) {
377 $res = @setlocale(LC_TIME, $loc);
378 if ( $res ) {
379 $aLocale = $loc;
380 break;
381 }
382 }
383 }
384 else {
385 $res = @setlocale(LC_TIME, $aLocale);
386 }
387
388 if ( ! $res ) {
389 JpGraphError::RaiseL(25007,$aLocale);
390 //("You are trying to use the locale ($aLocale) which your PHP installation does not support. Hint: Use '' to indicate the default locale for this geographic region.");
391 return FALSE;
392 }
393
394 $this->iLocale = $aLocale;
395 for( $i = 0, $ofs = 0 - strftime('%w'); $i < 7; $i++, $ofs++ ) {
396 $day = strftime('%a', strtotime("$ofs day"));
397 $day[0] = strtoupper($day[0]);
398 $this->iDayAbb[$aLocale][]= $day[0];
399 $this->iShortDay[$aLocale][]= $day;
400 }
401
402 for($i=1; $i<=12; ++$i) {
403 list($short ,$full) = explode('|', strftime("%b|%B",strtotime("2001-$i-01")));
404 $this->iShortMonth[$aLocale][] = ucfirst($short);
405 $this->iMonthName [$aLocale][] = ucfirst($full);
406 }
407
408 setlocale(LC_TIME, $pLocale);
409
410 return TRUE;
411 }
412
413
414 function GetDayAbb() {
415 return $this->iDayAbb[$this->iLocale];
416 }
417
418 function GetShortDay() {
419 return $this->iShortDay[$this->iLocale];
420 }
421
422 function GetShortMonth() {
423 return $this->iShortMonth[$this->iLocale];
424 }
425
426 function GetShortMonthName($aNbr) {
427 return $this->iShortMonth[$this->iLocale][$aNbr];
428 }
429
430 function GetLongMonthName($aNbr) {
431 return $this->iMonthName[$this->iLocale][$aNbr];
432 }
433
434 function GetMonth() {
435 return $this->iMonthName[$this->iLocale];
436 }
437}
438
439// Global object handlers
440$gDateLocale = new DateLocale();
441$gJpgDateLocale = new DateLocale();
442
443//=======================================================
444// CLASS Footer
445// Description: Encapsulates the footer line in the Graph
446//=======================================================
447class Footer {
448 public $iLeftMargin = 3, $iRightMargin = 3, $iBottomMargin = 3 ;
449 public $left,$center,$right;
450 private $iTimer=null, $itimerpoststring='';
451
452 function __construct() {
453 $this->left = new Text();
454 $this->left->ParagraphAlign('left');
455 $this->center = new Text();
456 $this->center->ParagraphAlign('center');
457 $this->right = new Text();
458 $this->right->ParagraphAlign('right');
459 }
460
461 function SetTimer($aTimer,$aTimerPostString='') {
462 $this->iTimer = $aTimer;
463 $this->itimerpoststring = $aTimerPostString;
464 }
465
466 function SetMargin($aLeft=3,$aRight=3,$aBottom=3) {
467 $this->iLeftMargin = $aLeft;
468 $this->iRightMargin = $aRight;
469 $this->iBottomMargin = $aBottom;
470 }
471
472 function Stroke($aImg) {
473 $y = $aImg->height - $this->iBottomMargin;
474 $x = $this->iLeftMargin;
475 $this->left->Align('left','bottom');
476 $this->left->Stroke($aImg,$x,$y);
477
478 $x = ($aImg->width - $this->iLeftMargin - $this->iRightMargin)/2;
479 $this->center->Align('center','bottom');
480 $this->center->Stroke($aImg,$x,$y);
481
482 $x = $aImg->width - $this->iRightMargin;
483 $this->right->Align('right','bottom');
484 if( $this->iTimer != null ) {
485 $this->right->Set( $this->right->t . sprintf('%.3f',$this->iTimer->Pop()/1000.0) . $this->itimerpoststring );
486 }
487 $this->right->Stroke($aImg,$x,$y);
488 }
489}
490
491
492//===================================================
493// CLASS Graph
494// Description: Main class to handle graphs
495//===================================================
496class Graph {
497 public $cache=null; // Cache object (singleton)
498 public $img=null; // Img object (singleton)
499 public $plots=array(); // Array of all plot object in the graph (for Y 1 axis)
500 public $y2plots=array(); // Array of all plot object in the graph (for Y 2 axis)
501 public $ynplots=array();
502 public $xscale=null; // X Scale object (could be instance of LinearScale or LogScale
503 public $yscale=null,$y2scale=null, $ynscale=array();
504 public $iIcons = array(); // Array of Icons to add to
505 public $cache_name; // File name to be used for the current graph in the cache directory
506 public $xgrid=null; // X Grid object (linear or logarithmic)
507 public $ygrid=null,$y2grid=null; //dito for Y
508 public $doframe,$frame_color, $frame_weight; // Frame around graph
509 public $boxed=false, $box_color='black', $box_weight=1; // Box around plot area
510 public $doshadow=false,$shadow_width=4,$shadow_color='gray@0.5'; // Shadow for graph
511 public $xaxis=null; // X-axis (instane of Axis class)
512 public $yaxis=null, $y2axis=null, $ynaxis=array(); // Y axis (instance of Axis class)
513 public $margin_color; // Margin color of graph
514 public $plotarea_color=array(255,255,255); // Plot area color
515 public $title,$subtitle,$subsubtitle; // Title and subtitle(s) text object
516 public $axtype="linlin"; // Type of axis
517 public $xtick_factor,$ytick_factor; // Factor to determine the maximum number of ticks depending on the plot width
518 public $texts=null, $y2texts=null; // Text object to ge shown in the graph
519 public $lines=null, $y2lines=null;
520 public $bands=null, $y2bands=null;
521 public $text_scale_off=0, $text_scale_abscenteroff=-1; // Text scale in fractions and for centering bars
522 public $background_image='',$background_image_type=-1,$background_image_format="png";
523 public $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
524 public $background_image_xpos=0,$background_image_ypos=0;
525 public $image_bright=0, $image_contr=0, $image_sat=0;
526 public $inline;
527 public $showcsim=0,$csimcolor="red";//debug stuff, draw the csim boundaris on the image if <>0
528 public $grid_depth=DEPTH_BACK; // Draw grid under all plots as default
529 public $iAxisStyle = AXSTYLE_SIMPLE;
530 public $iCSIMdisplay=false,$iHasStroked = false;
531 public $footer;
532 public $csimcachename = '', $csimcachetimeout = 0, $iCSIMImgAlt='';
533 public $iDoClipping = false;
534 public $y2orderback=true;
535 public $tabtitle;
536 public $bkg_gradtype=-1,$bkg_gradstyle=BGRAD_MARGIN;
537 public $bkg_gradfrom='navy', $bkg_gradto='silver';
538 public $plot_gradtype=-1,$plot_gradstyle=BGRAD_MARGIN;
539 public $plot_gradfrom='silver', $plot_gradto='navy';
540
541 public $titlebackground = false;
542 public $titlebackground_color = 'lightblue',
543 $titlebackground_style = 1,
544 $titlebackground_framecolor,
545 $titlebackground_framestyle,
546 $titlebackground_frameweight,
547 $titlebackground_bevelheight;
548 public $titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
549 public $titlebkg_scolor1='black',$titlebkg_scolor2='white';
550 public $framebevel, $framebeveldepth;
551 public $framebevelborder, $framebevelbordercolor;
552 public $framebevelcolor1, $framebevelcolor2;
553 public $background_image_mix=100;
554 public $background_cflag = '';
555 public $background_cflag_type = BGIMG_FILLPLOT;
556 public $background_cflag_mix = 100;
557 public $iImgTrans=false,
558 $iImgTransHorizon = 100,$iImgTransSkewDist=150,
559 $iImgTransDirection = 1, $iImgTransMinSize = true,
560 $iImgTransFillColor='white',$iImgTransHighQ=false,
561 $iImgTransBorder=false,$iImgTransHorizonPos=0.5;
562 public $legend;
563 public $graph_theme;
564 protected $iYAxisDeltaPos=50;
565 protected $iIconDepth=DEPTH_BACK;
566 protected $iAxisLblBgType = 0,
567 $iXAxisLblBgFillColor = 'lightgray', $iXAxisLblBgColor = 'black',
568 $iYAxisLblBgFillColor = 'lightgray', $iYAxisLblBgColor = 'black';
569 protected $iTables=NULL;
570
571 protected $isRunningClear = false;
572 protected $inputValues;
573 protected $isAfterSetScale = false;
574
575 // aWIdth Width in pixels of image
576 // aHeight Height in pixels of image
577 // aCachedName Name for image file in cache directory
578 // aTimeOut Timeout in minutes for image in cache
579 // aInline If true the image is streamed back in the call to Stroke()
580 // If false the image is just created in the cache
581 function __construct($aWidth=300,$aHeight=200,$aCachedName='',$aTimeout=0,$aInline=true) {
582
583 if( !is_numeric($aWidth) || !is_numeric($aHeight) ) {
584 JpGraphError::RaiseL(25008);//('Image width/height argument in Graph::Graph() must be numeric');
585 }
586
587 // Initialize frame and margin
588 $this->InitializeFrameAndMargin();
589
590 // Automatically generate the image file name based on the name of the script that
591 // generates the graph
592 if( $aCachedName == 'auto' ) {
593 $aCachedName=GenImgName();
594 }
595
596 // Should the image be streamed back to the browser or only to the cache?
597 $this->inline=$aInline;
598
599 $this->img = new RotImage($aWidth,$aHeight);
600 $this->cache = new ImgStreamCache();
601
602 // Window doesn't like '?' in the file name so replace it with an '_'
603 $aCachedName = str_replace("?","_",$aCachedName);
604 $this->SetupCache($aCachedName, $aTimeout);
605
606 $this->title = new Text();
607 $this->title->ParagraphAlign('center');
608 $this->title->SetFont(FF_DEFAULT,FS_NORMAL); //FF_FONT2, FS_BOLD
609 $this->title->SetMargin(5);
610 $this->title->SetAlign('center');
611
612 $this->subtitle = new Text();
613 $this->subtitle->ParagraphAlign('center');
614 $this->subtitle->SetMargin(3);
615 $this->subtitle->SetAlign('center');
616
617 $this->subsubtitle = new Text();
618 $this->subsubtitle->ParagraphAlign('center');
619 $this->subsubtitle->SetMargin(3);
620 $this->subsubtitle->SetAlign('center');
621
622 $this->legend = new Legend();
623 $this->footer = new Footer();
624
625 // If the cached version exist just read it directly from the
626 // cache, stream it back to browser and exit
627 if( $aCachedName!='' && READ_CACHE && $aInline ) {
628 if( $this->cache->GetAndStream($this->img,$aCachedName) ) {
629 exit();
630 }
631 }
632
633 $this->SetTickDensity(); // Normal density
634
635 $this->tabtitle = new GraphTabTitle();
636
637 if (!$this->isRunningClear) {
638 $this->inputValues = array();
639 $this->inputValues['aWidth'] = $aWidth;
640 $this->inputValues['aHeight'] = $aHeight;
641 $this->inputValues['aCachedName'] = $aCachedName;
642 $this->inputValues['aTimeout'] = $aTimeout;
643 $this->inputValues['aInline'] = $aInline;
644
645 $theme_class = DEFAULT_THEME_CLASS;
646 if (class_exists($theme_class)) {
647 $this->graph_theme = new $theme_class();
648 }
649 }
650 }
651
652 function InitializeFrameAndMargin() {
653 $this->doframe=true;
654 $this->frame_color='black';
655 $this->frame_weight=1;
656
657 $this->titlebackground_framecolor = 'blue';
658 $this->titlebackground_framestyle = 2;
659 $this->titlebackground_frameweight = 1;
660 $this->titlebackground_bevelheight = 3;
661 $this->titlebkg_fillstyle=TITLEBKG_FILLSTYLE_SOLID;
662 $this->titlebkg_scolor1='black';
663 $this->titlebkg_scolor2='white';
664 $this->framebevel = false;
665 $this->framebeveldepth = 2;
666 $this->framebevelborder = false;
667 $this->framebevelbordercolor='black';
668 $this->framebevelcolor1='white@0.4';
669 $this->framebevelcolor2='black@0.4';
670
671 $this->margin_color = array(250,250,250);
672 }
673
674 function SetupCache($aFilename,$aTimeout=60) {
675 $this->cache_name = $aFilename;
676 $this->cache->SetTimeOut($aTimeout);
677 }
678
679 // Enable final image perspective transformation
680 function Set3DPerspective($aDir=1,$aHorizon=100,$aSkewDist=120,$aQuality=false,$aFillColor='#FFFFFF',$aBorder=false,$aMinSize=true,$aHorizonPos=0.5) {
681 $this->iImgTrans = true;
682 $this->iImgTransHorizon = $aHorizon;
683 $this->iImgTransSkewDist= $aSkewDist;
684 $this->iImgTransDirection = $aDir;
685 $this->iImgTransMinSize = $aMinSize;
686 $this->iImgTransFillColor=$aFillColor;
687 $this->iImgTransHighQ=$aQuality;
688 $this->iImgTransBorder=$aBorder;
689 $this->iImgTransHorizonPos=$aHorizonPos;
690 }
691
692 function SetUserFont($aNormal,$aBold='',$aItalic='',$aBoldIt='') {
693 $this->img->ttf->SetUserFont($aNormal,$aBold,$aItalic,$aBoldIt);
694 }
695
696 function SetUserFont1($aNormal,$aBold='',$aItalic='',$aBoldIt='') {
697 $this->img->ttf->SetUserFont1($aNormal,$aBold,$aItalic,$aBoldIt);
698 }
699
700 function SetUserFont2($aNormal,$aBold='',$aItalic='',$aBoldIt='') {
701 $this->img->ttf->SetUserFont2($aNormal,$aBold,$aItalic,$aBoldIt);
702 }
703
704 function SetUserFont3($aNormal,$aBold='',$aItalic='',$aBoldIt='') {
705 $this->img->ttf->SetUserFont3($aNormal,$aBold,$aItalic,$aBoldIt);
706 }
707
708 // Set Image format and optional quality
709 function SetImgFormat($aFormat,$aQuality=75) {
710 $this->img->SetImgFormat($aFormat,$aQuality);
711 }
712
713 // Should the grid be in front or back of the plot?
714 function SetGridDepth($aDepth) {
715 $this->grid_depth=$aDepth;
716 }
717
718 function SetIconDepth($aDepth) {
719 $this->iIconDepth=$aDepth;
720 }
721
722 // Specify graph angle 0-360 degrees.
723 function SetAngle($aAngle) {
724 $this->img->SetAngle($aAngle);
725 }
726
727 function SetAlphaBlending($aFlg=true) {
728 $this->img->SetAlphaBlending($aFlg);
729 }
730
731 // Shortcut to image margin
732 function SetMargin($lm,$rm,$tm,$bm) {
733 $this->img->SetMargin($lm,$rm,$tm,$bm);
734 }
735
736 function SetY2OrderBack($aBack=true) {
737 $this->y2orderback = $aBack;
738 }
739
740 // Rotate the graph 90 degrees and set the margin
741 // when we have done a 90 degree rotation
742 function Set90AndMargin($lm=0,$rm=0,$tm=0,$bm=0) {
743 $adj = ($this->img->height - $this->img->width)/2;
744 $this->img->SetMargin($tm-$adj,$bm-$adj,$rm+$adj,$lm+$adj);
745 $this->img->SetCenter(floor($this->img->width/2),floor($this->img->height/2));
746 $this->SetAngle(90);
747 if( empty($this->yaxis) || empty($this->xaxis) ) {
748 JpgraphError::RaiseL(25009);//('You must specify what scale to use with a call to Graph::SetScale()');
749 }
750 $this->xaxis->SetLabelAlign('right','center');
751 $this->yaxis->SetLabelAlign('center','bottom');
752 }
753
754 function SetClipping($aFlg=true) {
755 $this->iDoClipping = $aFlg ;
756 }
757
758 // Add a plot object to the graph
759 function Add($aPlot) {
760 if( $aPlot == null ) {
761 JpGraphError::RaiseL(25010);//("Graph::Add() You tried to add a null plot to the graph.");
762 }
763 if( is_array($aPlot) && count($aPlot) > 0 ) {
764 $cl = $aPlot[0];
765 }
766 else {
767 $cl = $aPlot;
768 }
769
770 if( $cl instanceof Text ) $this->AddText($aPlot);
771 elseif( class_exists('PlotLine',false) && ($cl instanceof PlotLine) ) $this->AddLine($aPlot);
772 elseif( class_exists('PlotBand',false) && ($cl instanceof PlotBand) ) $this->AddBand($aPlot);
773 elseif( class_exists('IconPlot',false) && ($cl instanceof IconPlot) ) $this->AddIcon($aPlot);
774 elseif( class_exists('GTextTable',false) && ($cl instanceof GTextTable) ) $this->AddTable($aPlot);
775 else {
776 if( is_array($aPlot) ) {
777 $this->plots = array_merge($this->plots,$aPlot);
778 }
779 else {
780 $this->plots[] = $aPlot;
781 }
782 }
783
784 if ($this->graph_theme) {
785 $this->graph_theme->SetupPlot($aPlot);
786 }
787 }
788
789 function AddTable($aTable) {
790 if( is_array($aTable) ) {
791 for($i=0; $i < count($aTable); ++$i ) {
792 $this->iTables[]=$aTable[$i];
793 }
794 }
795 else {
796 $this->iTables[] = $aTable ;
797 }
798 }
799
800 function AddIcon($aIcon) {
801 if( is_array($aIcon) ) {
802 for($i=0; $i < count($aIcon); ++$i ) {
803 $this->iIcons[]=$aIcon[$i];
804 }
805 }
806 else {
807 $this->iIcons[] = $aIcon ;
808 }
809 }
810
811 // Add plot to second Y-scale
812 function AddY2($aPlot) {
813 if( $aPlot == null ) {
814 JpGraphError::RaiseL(25011);//("Graph::AddY2() You tried to add a null plot to the graph.");
815 }
816
817 if( is_array($aPlot) && count($aPlot) > 0 ) {
818 $cl = $aPlot[0];
819 }
820 else {
821 $cl = $aPlot;
822 }
823
824 if( $cl instanceof Text ) {
825 $this->AddText($aPlot,true);
826 }
827 elseif( class_exists('PlotLine',false) && ($cl instanceof PlotLine) ) {
828 $this->AddLine($aPlot,true);
829 }
830 elseif( class_exists('PlotBand',false) && ($cl instanceof PlotBand) ) {
831 $this->AddBand($aPlot,true);
832 }
833 else {
834 $this->y2plots[] = $aPlot;
835 }
836
837 if ($this->graph_theme) {
838 $this->graph_theme->SetupPlot($aPlot);
839 }
840 }
841
842 // Add plot to the extra Y-axises
843 function AddY($aN,$aPlot) {
844
845 if( $aPlot == null ) {
846 JpGraphError::RaiseL(25012);//("Graph::AddYN() You tried to add a null plot to the graph.");
847 }
848
849 if( is_array($aPlot) && count($aPlot) > 0 ) {
850 $cl = $aPlot[0];
851 }
852 else {
853 $cl = $aPlot;
854 }
855
856 if( ($cl instanceof Text) ||
857 (class_exists('PlotLine',false) && ($cl instanceof PlotLine)) ||
858 (class_exists('PlotBand',false) && ($cl instanceof PlotBand)) ) {
859 JpGraph::RaiseL(25013);//('You can only add standard plots to multiple Y-axis');
860 }
861 else {
862 $this->ynplots[$aN][] = $aPlot;
863 }
864
865 if ($this->graph_theme) {
866 $this->graph_theme->SetupPlot($aPlot);
867 }
868 }
869
870 // Add text object to the graph
871 function AddText($aTxt,$aToY2=false) {
872 if( $aTxt == null ) {
873 JpGraphError::RaiseL(25014);//("Graph::AddText() You tried to add a null text to the graph.");
874 }
875 if( $aToY2 ) {
876 if( is_array($aTxt) ) {
877 for($i=0; $i < count($aTxt); ++$i ) {
878 $this->y2texts[]=$aTxt[$i];
879 }
880 }
881 else {
882 $this->y2texts[] = $aTxt;
883 }
884 }
885 else {
886 if( is_array($aTxt) ) {
887 for($i=0; $i < count($aTxt); ++$i ) {
888 $this->texts[]=$aTxt[$i];
889 }
890 }
891 else {
892 $this->texts[] = $aTxt;
893 }
894 }
895 }
896
897 // Add a line object (class PlotLine) to the graph
898 function AddLine($aLine,$aToY2=false) {
899 if( $aLine == null ) {
900 JpGraphError::RaiseL(25015);//("Graph::AddLine() You tried to add a null line to the graph.");
901 }
902
903 if( $aToY2 ) {
904 if( is_array($aLine) ) {
905 for($i=0; $i < count($aLine); ++$i ) {
906 //$this->y2lines[]=$aLine[$i];
907 $this->y2plots[]=$aLine[$i];
908 }
909 }
910 else {
911 //$this->y2lines[] = $aLine;
912 $this->y2plots[]=$aLine;
913 }
914 }
915 else {
916 if( is_array($aLine) ) {
917 for($i=0; $i<count($aLine); ++$i ) {
918 //$this->lines[]=$aLine[$i];
919 $this->plots[]=$aLine[$i];
920 }
921 }
922 else {
923 //$this->lines[] = $aLine;
924 $this->plots[] = $aLine;
925 }
926 }
927 }
928
929 // Add vertical or horizontal band
930 function AddBand($aBand,$aToY2=false) {
931 if( $aBand == null ) {
932 JpGraphError::RaiseL(25016);//(" Graph::AddBand() You tried to add a null band to the graph.");
933 }
934
935 if( $aToY2 ) {
936 if( is_array($aBand) ) {
937 for($i=0; $i < count($aBand); ++$i ) {
938 $this->y2bands[] = $aBand[$i];
939 }
940 }
941 else {
942 $this->y2bands[] = $aBand;
943 }
944 }
945 else {
946 if( is_array($aBand) ) {
947 for($i=0; $i < count($aBand); ++$i ) {
948 $this->bands[] = $aBand[$i];
949 }
950 }
951 else {
952 $this->bands[] = $aBand;
953 }
954 }
955 }
956
957 function SetPlotGradient($aFrom='navy',$aTo='silver',$aGradType=2) {
958 $this->plot_gradtype=$aGradType;
959 $this->plot_gradfrom = $aFrom;
960 $this->plot_gradto = $aTo;
961 }
962
963 function SetBackgroundGradient($aFrom='navy',$aTo='silver',$aGradType=2,$aStyle=BGRAD_FRAME) {
964 $this->bkg_gradtype=$aGradType;
965 $this->bkg_gradstyle=$aStyle;
966 $this->bkg_gradfrom = $aFrom;
967 $this->bkg_gradto = $aTo;
968 }
969
970 // Set a country flag in the background
971 function SetBackgroundCFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
972 $this->background_cflag = $aName;
973 $this->background_cflag_type = $aBgType;
974 $this->background_cflag_mix = $aMix;
975 }
976
977 // Alias for the above method
978 function SetBackgroundCountryFlag($aName,$aBgType=BGIMG_FILLPLOT,$aMix=100) {
979 $this->background_cflag = $aName;
980 $this->background_cflag_type = $aBgType;
981 $this->background_cflag_mix = $aMix;
982 }
983
984
985 // Specify a background image
986 function SetBackgroundImage($aFileName,$aBgType=BGIMG_FILLPLOT,$aImgFormat='auto') {
987
988 // Get extension to determine image type
989 if( $aImgFormat == 'auto' ) {
990 $e = explode('.',$aFileName);
991 if( !$e ) {
992 JpGraphError::RaiseL(25018,$aFileName);//('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName.' Must have a valid image extension (jpg,gif,png) when using autodetection of image type');
993 }
994
995 $valid_formats = array('png', 'jpg', 'gif');
996 $aImgFormat = strtolower($e[count($e)-1]);
997 if ($aImgFormat == 'jpeg') {
998 $aImgFormat = 'jpg';
999 }
1000 elseif (!in_array($aImgFormat, $valid_formats) ) {
1001 JpGraphError::RaiseL(25019,$aImgFormat);//('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
1002 }
1003 }
1004
1005 $this->background_image = $aFileName;
1006 $this->background_image_type=$aBgType;
1007 $this->background_image_format=$aImgFormat;
1008 }
1009
1010 function SetBackgroundImageMix($aMix) {
1011 $this->background_image_mix = $aMix ;
1012 }
1013
1014 // Adjust background image position
1015 function SetBackgroundImagePos($aXpos,$aYpos) {
1016 $this->background_image_xpos = $aXpos ;
1017 $this->background_image_ypos = $aYpos ;
1018 }
1019
1020 // Specify axis style (boxed or single)
1021 function SetAxisStyle($aStyle) {
1022 $this->iAxisStyle = $aStyle ;
1023 }
1024
1025 // Set a frame around the plot area
1026 function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
1027 $this->boxed = $aDrawPlotFrame;
1028 $this->box_weight = $aPlotFrameWeight;
1029 $this->box_color = $aPlotFrameColor;
1030 }
1031
1032 // Specify color for the plotarea (not the margins)
1033 function SetColor($aColor) {
1034 $this->plotarea_color=$aColor;
1035 }
1036
1037 // Specify color for the margins (all areas outside the plotarea)
1038 function SetMarginColor($aColor) {
1039 $this->margin_color=$aColor;
1040 }
1041
1042 // Set a frame around the entire image
1043 function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
1044 $this->doframe = $aDrawImgFrame;
1045 $this->frame_color = $aImgFrameColor;
1046 $this->frame_weight = $aImgFrameWeight;
1047 }
1048
1049 function SetFrameBevel($aDepth=3,$aBorder=false,$aBorderColor='black',$aColor1='white@0.4',$aColor2='darkgray@0.4',$aFlg=true) {
1050 $this->framebevel = $aFlg ;
1051 $this->framebeveldepth = $aDepth ;
1052 $this->framebevelborder = $aBorder ;
1053 $this->framebevelbordercolor = $aBorderColor ;
1054 $this->framebevelcolor1 = $aColor1 ;
1055 $this->framebevelcolor2 = $aColor2 ;
1056
1057 $this->doshadow = false ;
1058 }
1059
1060 // Set the shadow around the whole image
1061 function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor='darkgray') {
1062 $this->doshadow = $aShowShadow;
1063 $this->shadow_color = $aShadowColor;
1064 $this->shadow_width = $aShadowWidth;
1065 $this->footer->iBottomMargin += $aShadowWidth;
1066 $this->footer->iRightMargin += $aShadowWidth;
1067 }
1068
1069 // Specify x,y scale. Note that if you manually specify the scale
1070 // you must also specify the tick distance with a call to Ticks::Set()
1071 function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
1072 $this->axtype = $aAxisType;
1073
1074 if( $aYMax < $aYMin || $aXMax < $aXMin ) {
1075 JpGraphError::RaiseL(25020);//('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
1076 }
1077
1078 $yt=substr($aAxisType,-3,3);
1079 if( $yt == 'lin' ) {
1080 $this->yscale = new LinearScale($aYMin,$aYMax);
1081 }
1082 elseif( $yt == 'int' ) {
1083 $this->yscale = new LinearScale($aYMin,$aYMax);
1084 $this->yscale->SetIntScale();
1085 }
1086 elseif( $yt == 'log' ) {
1087 $this->yscale = new LogScale($aYMin,$aYMax);
1088 }
1089 else {
1090 JpGraphError::RaiseL(25021,$aAxisType);//("Unknown scale specification for Y-scale. ($aAxisType)");
1091 }
1092
1093 $xt=substr($aAxisType,0,3);
1094 if( $xt == 'lin' || $xt == 'tex' ) {
1095 $this->xscale = new LinearScale($aXMin,$aXMax,'x');
1096 $this->xscale->textscale = ($xt == 'tex');
1097 }
1098 elseif( $xt == 'int' ) {
1099 $this->xscale = new LinearScale($aXMin,$aXMax,'x');
1100 $this->xscale->SetIntScale();
1101 }
1102 elseif( $xt == 'dat' ) {
1103 $this->xscale = new DateScale($aXMin,$aXMax,'x');
1104 }
1105 elseif( $xt == 'log' ) {
1106 $this->xscale = new LogScale($aXMin,$aXMax,'x');
1107 }
1108 else {
1109 JpGraphError::RaiseL(25022,$aAxisType);//(" Unknown scale specification for X-scale. ($aAxisType)");
1110 }
1111
1112 $this->xaxis = new Axis($this->img,$this->xscale);
1113 $this->yaxis = new Axis($this->img,$this->yscale);
1114 $this->xgrid = new Grid($this->xaxis);
1115 $this->ygrid = new Grid($this->yaxis);
1116 $this->ygrid->Show();
1117
1118
1119 if (!$this->isRunningClear) {
1120 $this->inputValues['aAxisType'] = $aAxisType;
1121 $this->inputValues['aYMin'] = $aYMin;
1122 $this->inputValues['aYMax'] = $aYMax;
1123 $this->inputValues['aXMin'] = $aXMin;
1124 $this->inputValues['aXMax'] = $aXMax;
1125
1126 if ($this->graph_theme) {
1127 $this->graph_theme->ApplyGraph($this);
1128 }
1129 }
1130
1131 $this->isAfterSetScale = true;
1132 }
1133
1134 // Specify secondary Y scale
1135 function SetY2Scale($aAxisType='lin',$aY2Min=1,$aY2Max=1) {
1136 if( $aAxisType == 'lin' ) {
1137 $this->y2scale = new LinearScale($aY2Min,$aY2Max);
1138 }
1139 elseif( $aAxisType == 'int' ) {
1140 $this->y2scale = new LinearScale($aY2Min,$aY2Max);
1141 $this->y2scale->SetIntScale();
1142 }
1143 elseif( $aAxisType == 'log' ) {
1144 $this->y2scale = new LogScale($aY2Min,$aY2Max);
1145 }
1146 else {
1147 JpGraphError::RaiseL(25023,$aAxisType);//("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)");
1148 }
1149
1150 $this->y2axis = new Axis($this->img,$this->y2scale);
1151 $this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
1152 $this->y2axis->SetLabelSide(SIDE_RIGHT);
1153 $this->y2axis->SetPos('max');
1154 $this->y2axis->SetTitleSide(SIDE_RIGHT);
1155
1156 // Deafult position is the max x-value
1157 $this->y2grid = new Grid($this->y2axis);
1158
1159 if ($this->graph_theme) {
1160 $this->graph_theme->ApplyGraph($this);
1161 }
1162 }
1163
1164 // Set the delta position (in pixels) between the multiple Y-axis
1165 function SetYDeltaDist($aDist) {
1166 $this->iYAxisDeltaPos = $aDist;
1167 }
1168
1169 // Specify secondary Y scale
1170 function SetYScale($aN,$aAxisType="lin",$aYMin=1,$aYMax=1) {
1171
1172 if( $aAxisType == 'lin' ) {
1173 $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
1174 }
1175 elseif( $aAxisType == 'int' ) {
1176 $this->ynscale[$aN] = new LinearScale($aYMin,$aYMax);
1177 $this->ynscale[$aN]->SetIntScale();
1178 }
1179 elseif( $aAxisType == 'log' ) {
1180 $this->ynscale[$aN] = new LogScale($aYMin,$aYMax);
1181 }
1182 else {
1183 JpGraphError::RaiseL(25024,$aAxisType);//("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)");
1184 }
1185
1186 $this->ynaxis[$aN] = new Axis($this->img,$this->ynscale[$aN]);
1187 $this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT);
1188 $this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT);
1189
1190 if ($this->graph_theme) {
1191 $this->graph_theme->ApplyGraph($this);
1192 }
1193 }
1194
1195 // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
1196 // The dividing factor have been determined heuristically according to my aesthetic
1197 // sense (or lack off) y.m.m.v !
1198 function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
1199 $this->xtick_factor=30;
1200 $this->ytick_factor=25;
1201 switch( $aYDensity ) {
1202 case TICKD_DENSE:
1203 $this->ytick_factor=12;
1204 break;
1205 case TICKD_NORMAL:
1206 $this->ytick_factor=25;
1207 break;
1208 case TICKD_SPARSE:
1209 $this->ytick_factor=40;
1210 break;
1211 case TICKD_VERYSPARSE:
1212 $this->ytick_factor=100;
1213 break;
1214 default:
1215 JpGraphError::RaiseL(25025,$densy);//("JpGraph: Unsupported Tick density: $densy");
1216 }
1217 switch( $aXDensity ) {
1218 case TICKD_DENSE:
1219 $this->xtick_factor=15;
1220 break;
1221 case TICKD_NORMAL:
1222 $this->xtick_factor=30;
1223 break;
1224 case TICKD_SPARSE:
1225 $this->xtick_factor=45;
1226 break;
1227 case TICKD_VERYSPARSE:
1228 $this->xtick_factor=60;
1229 break;
1230 default:
1231 JpGraphError::RaiseL(25025,$densx);//("JpGraph: Unsupported Tick density: $densx");
1232 }
1233 }
1234
1235
1236 // Get a string of all image map areas
1237 function GetCSIMareas() {
1238 if( !$this->iHasStroked ) {
1239 $this->Stroke(_CSIM_SPECIALFILE);
1240 }
1241
1242 $csim = $this->title->GetCSIMAreas();
1243 $csim .= $this->subtitle->GetCSIMAreas();
1244 $csim .= $this->subsubtitle->GetCSIMAreas();
1245 $csim .= $this->legend->GetCSIMAreas();
1246
1247 if( $this->y2axis != NULL ) {
1248 $csim .= $this->y2axis->title->GetCSIMAreas();
1249 }
1250
1251 if( $this->texts != null ) {
1252 $n = count($this->texts);
1253 for($i=0; $i < $n; ++$i ) {
1254 $csim .= $this->texts[$i]->GetCSIMAreas();
1255 }
1256 }
1257
1258 if( $this->y2texts != null && $this->y2scale != null ) {
1259 $n = count($this->y2texts);
1260 for($i=0; $i < $n; ++$i ) {
1261 $csim .= $this->y2texts[$i]->GetCSIMAreas();
1262 }
1263 }
1264
1265 if( $this->yaxis != null && $this->xaxis != null ) {
1266 $csim .= $this->yaxis->title->GetCSIMAreas();
1267 $csim .= $this->xaxis->title->GetCSIMAreas();
1268 }
1269
1270 $n = count($this->plots);
1271 for( $i=0; $i < $n; ++$i ) {
1272 $csim .= $this->plots[$i]->GetCSIMareas();
1273 }
1274
1275 $n = count($this->y2plots);
1276 for( $i=0; $i < $n; ++$i ) {
1277 $csim .= $this->y2plots[$i]->GetCSIMareas();
1278 }
1279
1280 $n = count($this->ynaxis);
1281 for( $i=0; $i < $n; ++$i ) {
1282 $m = count($this->ynplots[$i]);
1283 for($j=0; $j < $m; ++$j ) {
1284 $csim .= $this->ynplots[$i][$j]->GetCSIMareas();
1285 }
1286 }
1287
1288 if($this->iTables != null) {
1289 $n = count($this->iTables);
1290 for ($i = 0; $i < $n; ++$i) {
1291 $csim .= $this->iTables[$i]->GetCSIMareas();
1292 }
1293 }
1294
1295 return $csim;
1296 }
1297
1298 // Get a complete <MAP>..</MAP> tag for the final image map
1299 function GetHTMLImageMap($aMapName) {
1300 $im = "<map name=\"$aMapName\" id=\"$aMapName\" >\n";
1301 $im .= $this->GetCSIMareas();
1302 $im .= "</map>";
1303 return $im;
1304 }
1305
1306 function CheckCSIMCache($aCacheName,$aTimeOut=60) {
1307 global $_SERVER;
1308
1309 if( $aCacheName=='auto' ) {
1310 $aCacheName=basename($_SERVER['PHP_SELF']);
1311 }
1312
1313 $urlarg = $this->GetURLArguments();
1314 $this->csimcachename = CSIMCACHE_DIR.$aCacheName.$urlarg;
1315 $this->csimcachetimeout = $aTimeOut;
1316
1317 // First determine if we need to check for a cached version
1318 // This differs from the standard cache in the sense that the
1319 // image and CSIM map HTML file is written relative to the directory
1320 // the script executes in and not the specified cache directory.
1321 // The reason for this is that the cache directory is not necessarily
1322 // accessible from the HTTP server.
1323 if( $this->csimcachename != '' ) {
1324 $dir = dirname($this->csimcachename);
1325 $base = basename($this->csimcachename);
1326 $base = strtok($base,'.');
1327 $suffix = strtok('.');
1328 $basecsim = $dir.'/'.$base.'?'.$urlarg.'_csim_.html';
1329 $baseimg = $dir.'/'.$base.'?'.$urlarg.'.'.$this->img->img_format;
1330
1331 $timedout=false;
1332 // Does it exist at all ?
1333
1334 if( file_exists($basecsim) && file_exists($baseimg) ) {
1335 // Check that it hasn't timed out
1336 $diff=time()-filemtime($basecsim);
1337 if( $this->csimcachetimeout>0 && ($diff > $this->csimcachetimeout*60) ) {
1338 $timedout=true;
1339 @unlink($basecsim);
1340 @unlink($baseimg);
1341 }
1342 else {
1343 if ($fh = @fopen($basecsim, "r")) {
1344 fpassthru($fh);
1345 return true;
1346 }
1347 else {
1348 JpGraphError::RaiseL(25027,$basecsim);//(" Can't open cached CSIM \"$basecsim\" for reading.");
1349 }
1350 }
1351 }
1352 }
1353 return false;
1354 }
1355
1356 // Build the argument string to be used with the csim images
1357 static function GetURLArguments($aAddRecursiveBlocker=false) {
1358
1359 if( $aAddRecursiveBlocker ) {
1360 // This is a JPGRAPH internal defined that prevents
1361 // us from recursively coming here again
1362 $urlarg = _CSIM_DISPLAY.'=1';
1363 }
1364
1365 // Now reconstruct any user URL argument
1366 reset($_GET);
1367 foreach ($_GET as $key => $value) {
1368 if( is_array($value) ) {
1369 foreach ( $value as $k => $v ) {
1370 $urlarg .= '&amp;'.$key.'%5B'.$k.'%5D='.urlencode($v);
1371 }
1372 }
1373 else {
1374 $urlarg .= '&amp;'.$key.'='.urlencode($value);
1375 }
1376 }
1377
1378 // It's not ideal to convert POST argument to GET arguments
1379 // but there is little else we can do. One idea for the
1380 // future might be recreate the POST header in case.
1381 reset($_POST);
1382 foreach ($_POST as $key => $value) {
1383 if( is_array($value) ) {
1384 foreach ( $value as $k => $v ) {
1385 $urlarg .= '&amp;'.$key.'%5B'.$k.'%5D='.urlencode($v);
1386 }
1387 }
1388 else {
1389 $urlarg .= '&amp;'.$key.'='.urlencode($value);
1390 }
1391 }
1392
1393 return $urlarg;
1394 }
1395
1396 function SetCSIMImgAlt($aAlt) {
1397 $this->iCSIMImgAlt = $aAlt;
1398 }
1399
1400 function StrokeCSIM($aScriptName='auto',$aCSIMName='',$aBorder=0) {
1401 if( $aCSIMName=='' ) {
1402 // create a random map name
1403 srand ((double) microtime() * 1000000);
1404 $r = rand(0,100000);
1405 $aCSIMName='__mapname'.$r.'__';
1406 }
1407
1408 if( $aScriptName=='auto' ) {
1409 $aScriptName=basename($_SERVER['PHP_SELF']);
1410 }
1411
1412 $urlarg = $this->GetURLArguments(true);
1413
1414 if( empty($_GET[_CSIM_DISPLAY]) ) {
1415 // First determine if we need to check for a cached version
1416 // This differs from the standard cache in the sense that the
1417 // image and CSIM map HTML file is written relative to the directory
1418 // the script executes in and not the specified cache directory.
1419 // The reason for this is that the cache directory is not necessarily
1420 // accessible from the HTTP server.
1421 if( $this->csimcachename != '' ) {
1422 $dir = dirname($this->csimcachename);
1423 $base = basename($this->csimcachename);
1424 $base = strtok($base,'.');
1425 $suffix = strtok('.');
1426 $basecsim = $dir.'/'.$base.'?'.$urlarg.'_csim_.html';
1427 $baseimg = $base.'?'.$urlarg.'.'.$this->img->img_format;
1428
1429 // Check that apache can write to directory specified
1430
1431 if( file_exists($dir) && !is_writeable($dir) ) {
1432 JpgraphError::RaiseL(25028,$dir);//('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
1433 }
1434
1435 // Make sure directory exists
1436 $this->cache->MakeDirs($dir);
1437
1438 // Write the image file
1439 $this->Stroke(CSIMCACHE_DIR.$baseimg);
1440
1441 // Construct wrapper HTML and write to file and send it back to browser
1442
1443 // In the src URL we must replace the '?' with its encoding to prevent the arguments
1444 // to be converted to real arguments.
1445 $tmp = str_replace('?','%3f',$baseimg);
1446 $htmlwrap = $this->GetHTMLImageMap($aCSIMName)."\n".
1447 '<img src="'.CSIMCACHE_HTTP_DIR.$tmp.'" ismap="ismap" usemap="#'.$aCSIMName.' width="'.$this->img->width.'" height="'.$this->img->height."\" alt=\"".$this->iCSIMImgAlt."\" />\n";
1448
1449 if($fh = @fopen($basecsim,'w') ) {
1450 fwrite($fh,$htmlwrap);
1451 fclose($fh);
1452 echo $htmlwrap;
1453 }
1454 else {
1455 JpGraphError::RaiseL(25029,$basecsim);//(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
1456 }
1457 }
1458 else {
1459
1460 if( $aScriptName=='' ) {
1461 JpGraphError::RaiseL(25030);//('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().');
1462 }
1463 echo $this->GetHTMLImageMap($aCSIMName) . $this->GetCSIMImgHTML($aCSIMName, $aScriptName, $aBorder);
1464 }
1465 }
1466 else {
1467 $this->Stroke();
1468 }
1469 }
1470
1471 function StrokeCSIMImage() {
1472 if( @$_GET[_CSIM_DISPLAY] == 1 ) {
1473 $this->Stroke();
1474 }
1475 }
1476
1477 function GetCSIMImgHTML($aCSIMName, $aScriptName='auto', $aBorder=0 ) {
1478 if( $aScriptName=='auto' ) {
1479 $aScriptName=basename($_SERVER['PHP_SELF']);
1480 }
1481 $urlarg = $this->GetURLArguments(true);
1482 return "<img src=\"".$aScriptName.'?'.$urlarg."\" ismap=\"ismap\" usemap=\"#".$aCSIMName.'" height="'.$this->img->height."\" alt=\"".$this->iCSIMImgAlt."\" />\n";
1483 }
1484
1485 function GetTextsYMinMax($aY2=false) {
1486 if( $aY2 ) {
1487 $txts = $this->y2texts;
1488 }
1489 else {
1490 $txts = $this->texts;
1491 }
1492 $n = is_array($txts) ? count($txts) : 0;
1493 $min=null;
1494 $max=null;
1495 for( $i=0; $i < $n; ++$i ) {
1496 if( $txts[$i]->iScalePosY !== null && $txts[$i]->iScalePosX !== null ) {
1497 if( $min === null ) {
1498 $min = $max = $txts[$i]->iScalePosY ;
1499 }
1500 else {
1501 $min = min($min,$txts[$i]->iScalePosY);
1502 $max = max($max,$txts[$i]->iScalePosY);
1503 }
1504 }
1505 }
1506 if( $min !== null ) {
1507 return array($min,$max);
1508 }
1509 else {
1510 return null;
1511 }
1512 }
1513
1514 function GetTextsXMinMax($aY2=false) {
1515 if( $aY2 ) {
1516 $txts = $this->y2texts;
1517 }
1518 else {
1519 $txts = $this->texts;
1520 }
1521 $n = is_array($txts) ? count($txts) : 0;
1522 $min=null;
1523 $max=null;
1524 for( $i=0; $i < $n; ++$i ) {
1525 if( $txts[$i]->iScalePosY !== null && $txts[$i]->iScalePosX !== null ) {
1526 if( $min === null ) {
1527 $min = $max = $txts[$i]->iScalePosX ;
1528 }
1529 else {
1530 $min = min($min,$txts[$i]->iScalePosX);
1531 $max = max($max,$txts[$i]->iScalePosX);
1532 }
1533 }
1534 }
1535 if( $min !== null ) {
1536 return array($min,$max);
1537 }
1538 else {
1539 return null;
1540 }
1541 }
1542
1543 function GetXMinMax() {
1544
1545 list($min,$ymin) = $this->plots[0]->Min();
1546 list($max,$ymax) = $this->plots[0]->Max();
1547
1548 $i=0;
1549 // Some plots, e.g. PlotLine should not affect the scale
1550 // and will return (null,null). We should ignore those
1551 // values.
1552 while( ($min===null || $max === null) && ($i < count($this->plots)-1) ) {
1553 ++$i;
1554 list($min,$ymin) = $this->plots[$i]->Min();
1555 list($max,$ymax) = $this->plots[$i]->Max();
1556 }
1557
1558 foreach( $this->plots as $p ) {
1559 list($xmin,$ymin) = $p->Min();
1560 list($xmax,$ymax) = $p->Max();
1561
1562 if( $xmin !== null && $xmax !== null ) {
1563 $min = Min($xmin,$min);
1564 $max = Max($xmax,$max);
1565 }
1566 }
1567
1568 if( $this->y2axis != null ) {
1569 foreach( $this->y2plots as $p ) {
1570 list($xmin,$ymin) = $p->Min();
1571 list($xmax,$ymax) = $p->Max();
1572 if( $xmin !== null && $xmax !== null ) {
1573 $min = Min($xmin, $min);
1574 $max = Max($xmax, $max);
1575 }
1576 }
1577 }
1578
1579 $n = count($this->ynaxis);
1580 for( $i=0; $i < $n; ++$i ) {
1581 if( $this->ynaxis[$i] != null) {
1582 foreach( $this->ynplots[$i] as $p ) {
1583 list($xmin,$ymin) = $p->Min();
1584 list($xmax,$ymax) = $p->Max();
1585 if( $xmin !== null && $xmax !== null ) {
1586 $min = Min($xmin, $min);
1587 $max = Max($xmax, $max);
1588 }
1589 }
1590 }
1591 }
1592 return array($min,$max);
1593 }
1594
1595 function AdjustMarginsForTitles() {
1596 $totrequired =
1597 ($this->title->t != ''
1598 ? $this->title->GetTextHeight($this->img) + $this->title->margin + 5 * SUPERSAMPLING_SCALE
1599 : 0 ) +
1600 ($this->subtitle->t != ''
1601 ? $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 * SUPERSAMPLING_SCALE
1602 : 0 ) +
1603 ($this->subsubtitle->t != ''
1604 ? $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 * SUPERSAMPLING_SCALE
1605 : 0 ) ;
1606
1607 $btotrequired = 0;
1608 if($this->xaxis != null && !$this->xaxis->hide && !$this->xaxis->hide_labels ) {
1609 // Minimum bottom margin
1610 if( $this->xaxis->title->t != '' ) {
1611 if( $this->img->a == 90 ) {
1612 $btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 7 ;
1613 }
1614 else {
1615 $btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 7 ;
1616 }
1617 }
1618 else {
1619 $btotrequired = 0;
1620 }
1621
1622 if( $this->img->a == 90 ) {
1623 $this->img->SetFont($this->yaxis->font_family,$this->yaxis->font_style,
1624 $this->yaxis->font_size);
1625 $lh = $this->img->GetTextHeight('Mg',$this->yaxis->label_angle);
1626 }
1627 else {
1628 $this->img->SetFont($this->xaxis->font_family,$this->xaxis->font_style,
1629 $this->xaxis->font_size);
1630 $lh = $this->img->GetTextHeight('Mg',$this->xaxis->label_angle);
1631 }
1632
1633 $btotrequired += $lh + 6;
1634 }
1635
1636 if( $this->img->a == 90 ) {
1637 // DO Nothing. It gets too messy to do this properly for 90 deg...
1638 }
1639 else{
1640 // need more top margin
1641 if( $this->img->top_margin < $totrequired ) {
1642 $this->SetMargin(
1643 $this->img->raw_left_margin,
1644 $this->img->raw_right_margin,
1645 $totrequired / SUPERSAMPLING_SCALE,
1646 $this->img->raw_bottom_margin
1647 );
1648 }
1649
1650 // need more bottom margin
1651 if( $this->img->bottom_margin < $btotrequired ) {
1652 $this->SetMargin(
1653 $this->img->raw_left_margin,
1654 $this->img->raw_right_margin,
1655 $this->img->raw_top_margin,
1656 $btotrequired / SUPERSAMPLING_SCALE
1657 );
1658 }
1659 }
1660 }
1661
1662 function StrokeStore($aStrokeFileName) {
1663 // Get the handler to prevent the library from sending the
1664 // image to the browser
1665 $ih = $this->Stroke(_IMG_HANDLER);
1666
1667 // Stroke it to a file
1668 $this->img->Stream($aStrokeFileName);
1669
1670 // Send it back to browser
1671 $this->img->Headers();
1672 $this->img->Stream();
1673 }
1674
1675 function doAutoscaleXAxis() {
1676 //Check if we should autoscale x-axis
1677 if( !$this->xscale->IsSpecified() ) {
1678 if( substr($this->axtype,0,4) == "text" ) {
1679 $max=0;
1680 $n = count($this->plots);
1681 for($i=0; $i < $n; ++$i ) {
1682 $p = $this->plots[$i];
1683 // We need some unfortunate sub class knowledge here in order
1684 // to increase number of data points in case it is a line plot
1685 // which has the barcenter set. If not it could mean that the
1686 // last point of the data is outside the scale since the barcenter
1687 // settings means that we will shift the entire plot half a tick step
1688 // to the right in oder to align with the center of the bars.
1689 if( class_exists('BarPlot',false) ) {
1690 $cl = strtolower(get_class($p));
1691 if( (class_exists('BarPlot',false) && ($p instanceof BarPlot)) || empty($p->barcenter) ) {
1692 $max=max($max,$p->numpoints-1);
1693 }
1694 else {
1695 $max=max($max,$p->numpoints);
1696 }
1697 }
1698 else {
1699 if( empty($p->barcenter) ) {
1700 $max=max($max,$p->numpoints-1);
1701 }
1702 else {
1703 $max=max($max,$p->numpoints);
1704 }
1705 }
1706 }
1707 $min=0;
1708 if( $this->y2axis != null ) {
1709 foreach( $this->y2plots as $p ) {
1710 $max=max($max,$p->numpoints-1);
1711 }
1712 }
1713 $n = count($this->ynaxis);
1714 for( $i=0; $i < $n; ++$i ) {
1715 if( $this->ynaxis[$i] != null) {
1716 foreach( $this->ynplots[$i] as $p ) {
1717 $max=max($max,$p->numpoints-1);
1718 }
1719 }
1720 }
1721
1722 $this->xscale->Update($this->img,$min,$max);
1723 $this->xscale->ticks->Set($this->xaxis->tick_step,1);
1724 $this->xscale->ticks->SupressMinorTickMarks();
1725 }
1726 else {
1727 list($min,$max) = $this->GetXMinMax();
1728
1729 $lres = $this->GetLinesXMinMax($this->lines);
1730 if( $lres ) {
1731 list($linmin,$linmax) = $lres ;
1732 $min = min($min,$linmin);
1733 $max = max($max,$linmax);
1734 }
1735
1736 $lres = $this->GetLinesXMinMax($this->y2lines);
1737 if( $lres ) {
1738 list($linmin,$linmax) = $lres ;
1739 $min = min($min,$linmin);
1740 $max = max($max,$linmax);
1741 }
1742
1743 $tres = $this->GetTextsXMinMax();
1744 if( $tres ) {
1745 list($tmin,$tmax) = $tres ;
1746 $min = min($min,$tmin);
1747 $max = max($max,$tmax);
1748 }
1749
1750 $tres = $this->GetTextsXMinMax(true);
1751 if( $tres ) {
1752 list($tmin,$tmax) = $tres ;
1753 $min = min($min,$tmin);
1754 $max = max($max,$tmax);
1755 }
1756
1757 $this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor));
1758 }
1759
1760 //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
1761 if( !is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos) ) {
1762 $this->yaxis->SetPos($this->xscale->GetMinVal());
1763 }
1764 }
1765 elseif( $this->xscale->IsSpecified() &&
1766 ( $this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified()) ) {
1767 // The tick calculation will use the user suplied min/max values to determine
1768 // the ticks. If auto_ticks is false the exact user specifed min and max
1769 // values will be used for the scale.
1770 // If auto_ticks is true then the scale might be slightly adjusted
1771 // so that the min and max values falls on an even major step.
1772 $min = $this->xscale->scale[0];
1773 $max = $this->xscale->scale[1];
1774 $this->xscale->AutoScale($this->img,$min,$max,round($this->img->plotwidth/$this->xtick_factor),false);
1775
1776 // Now make sure we show enough precision to accurate display the
1777 // labels. If this is not done then the user might end up with
1778 // a scale that might actually start with, say 13.5, butdue to rounding
1779 // the scale label will ony show 14.
1780 if( abs(floor($min)-$min) > 0 ) {
1781
1782 // If the user has set a format then we bail out
1783 if( $this->xscale->ticks->label_formatstr == '' && $this->xscale->ticks->label_dateformatstr == '' ) {
1784 $this->xscale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1;
1785 }
1786 }
1787 }
1788
1789 // Position the optional Y2 and Yn axis to the rightmost position of the x-axis
1790 if( $this->y2axis != null ) {
1791 if( !is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos) ) {
1792 $this->y2axis->SetPos($this->xscale->GetMaxVal());
1793 }
1794 $this->y2axis->SetTitleSide(SIDE_RIGHT);
1795 }
1796
1797 $n = count($this->ynaxis);
1798 $nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0;
1799 for( $i=0; $i < $n; ++$i ) {
1800 if( $this->ynaxis[$i] != null ) {
1801 if( !is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos) ) {
1802 $this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal());
1803 $this->ynaxis[$i]->SetPosAbsDelta($i*$this->iYAxisDeltaPos + $nY2adj);
1804 }
1805 $this->ynaxis[$i]->SetTitleSide(SIDE_RIGHT);
1806 }
1807 }
1808 }
1809
1810
1811 function doAutoScaleYnAxis() {
1812
1813 if( $this->y2scale != null) {
1814 if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
1815 list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
1816
1817 $lres = $this->GetLinesYMinMax($this->y2lines);
1818 if( is_array($lres) ) {
1819 list($linmin,$linmax) = $lres ;
1820 $min = min($min,$linmin);
1821 $max = max($max,$linmax);
1822 }
1823 $tres = $this->GetTextsYMinMax(true);
1824 if( is_array($tres) ) {
1825 list($tmin,$tmax) = $tres ;
1826 $min = min($min,$tmin);
1827 $max = max($max,$tmax);
1828 }
1829 $this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
1830 }
1831 elseif( $this->y2scale->IsSpecified() && ( $this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified()) ) {
1832 // The tick calculation will use the user suplied min/max values to determine
1833 // the ticks. If auto_ticks is false the exact user specifed min and max
1834 // values will be used for the scale.
1835 // If auto_ticks is true then the scale might be slightly adjusted
1836 // so that the min and max values falls on an even major step.
1837 $min = $this->y2scale->scale[0];
1838 $max = $this->y2scale->scale[1];
1839 $this->y2scale->AutoScale($this->img,$min,$max,
1840 $this->img->plotheight/$this->ytick_factor,
1841 $this->y2scale->auto_ticks);
1842
1843 // Now make sure we show enough precision to accurate display the
1844 // labels. If this is not done then the user might end up with
1845 // a scale that might actually start with, say 13.5, butdue to rounding
1846 // the scale label will ony show 14.
1847 if( abs(floor($min)-$min) > 0 ) {
1848 // If the user has set a format then we bail out
1849 if( $this->y2scale->ticks->label_formatstr == '' && $this->y2scale->ticks->label_dateformatstr == '' ) {
1850 $this->y2scale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1;
1851 }
1852 }
1853
1854 }
1855 }
1856
1857
1858 //
1859 // Autoscale the extra Y-axises
1860 //
1861 $n = count($this->ynaxis);
1862 for( $i=0; $i < $n; ++$i ) {
1863 if( $this->ynscale[$i] != null) {
1864 if( !$this->ynscale[$i]->IsSpecified() && count($this->ynplots[$i])>0 ) {
1865 list($min,$max) = $this->GetPlotsYMinMax($this->ynplots[$i]);
1866 $this->ynscale[$i]->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
1867 }
1868 elseif( $this->ynscale[$i]->IsSpecified() && ( $this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->ticks->IsSpecified()) ) {
1869 // The tick calculation will use the user suplied min/max values to determine
1870 // the ticks. If auto_ticks is false the exact user specifed min and max
1871 // values will be used for the scale.
1872 // If auto_ticks is true then the scale might be slightly adjusted
1873 // so that the min and max values falls on an even major step.
1874 $min = $this->ynscale[$i]->scale[0];
1875 $max = $this->ynscale[$i]->scale[1];
1876 $this->ynscale[$i]->AutoScale($this->img,$min,$max,
1877 $this->img->plotheight/$this->ytick_factor,
1878 $this->ynscale[$i]->auto_ticks);
1879
1880 // Now make sure we show enough precision to accurate display the
1881 // labels. If this is not done then the user might end up with
1882 // a scale that might actually start with, say 13.5, butdue to rounding
1883 // the scale label will ony show 14.
1884 if( abs(floor($min)-$min) > 0 ) {
1885 // If the user has set a format then we bail out
1886 if( $this->ynscale[$i]->ticks->label_formatstr == '' && $this->ynscale[$i]->ticks->label_dateformatstr == '' ) {
1887 $this->ynscale[$i]->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1;
1888 }
1889 }
1890 }
1891 }
1892 }
1893 }
1894
1895 function doAutoScaleYAxis() {
1896
1897 //Check if we should autoscale y-axis
1898 if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
1899 list($min,$max) = $this->GetPlotsYMinMax($this->plots);
1900 $lres = $this->GetLinesYMinMax($this->lines);
1901 if( is_array($lres) ) {
1902 list($linmin,$linmax) = $lres ;
1903 $min = min($min,$linmin);
1904 $max = max($max,$linmax);
1905 }
1906 $tres = $this->GetTextsYMinMax();
1907 if( is_array($tres) ) {
1908 list($tmin,$tmax) = $tres ;
1909 $min = min($min,$tmin);
1910 $max = max($max,$tmax);
1911 }
1912 $this->yscale->AutoScale($this->img,$min,$max,
1913 $this->img->plotheight/$this->ytick_factor);
1914 }
1915 elseif( $this->yscale->IsSpecified() && ( $this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified()) ) {
1916 // The tick calculation will use the user suplied min/max values to determine
1917 // the ticks. If auto_ticks is false the exact user specifed min and max
1918 // values will be used for the scale.
1919 // If auto_ticks is true then the scale might be slightly adjusted
1920 // so that the min and max values falls on an even major step.
1921 $min = $this->yscale->scale[0];
1922 $max = $this->yscale->scale[1];
1923 $this->yscale->AutoScale($this->img,$min,$max,
1924 $this->img->plotheight/$this->ytick_factor,
1925 $this->yscale->auto_ticks);
1926
1927 // Now make sure we show enough precision to accurate display the
1928 // labels. If this is not done then the user might end up with
1929 // a scale that might actually start with, say 13.5, butdue to rounding
1930 // the scale label will ony show 14.
1931 if( abs(floor($min)-$min) > 0 ) {
1932
1933 // If the user has set a format then we bail out
1934 if( $this->yscale->ticks->label_formatstr == '' && $this->yscale->ticks->label_dateformatstr == '' ) {
1935 $this->yscale->ticks->precision = abs( floor(log10( abs(floor($min)-$min))) )+1;
1936 }
1937 }
1938 }
1939
1940 }
1941
1942 function InitScaleConstants() {
1943 // Setup scale constants
1944 if( $this->yscale ) $this->yscale->InitConstants($this->img);
1945 if( $this->xscale ) $this->xscale->InitConstants($this->img);
1946 if( $this->y2scale ) $this->y2scale->InitConstants($this->img);
1947
1948 $n=count($this->ynscale);
1949 for($i=0; $i < $n; ++$i) {
1950 if( $this->ynscale[$i] ) {
1951 $this->ynscale[$i]->InitConstants($this->img);
1952 }
1953 }
1954 }
1955
1956 function doPrestrokeAdjustments() {
1957
1958 // Do any pre-stroke adjustment that is needed by the different plot types
1959 // (i.e bar plots want's to add an offset to the x-labels etc)
1960 for($i=0; $i < count($this->plots) ; ++$i ) {
1961 $this->plots[$i]->PreStrokeAdjust($this);
1962 $this->plots[$i]->DoLegend($this);
1963 }
1964
1965 // Any plots on the second Y scale?
1966 if( $this->y2scale != null ) {
1967 for($i=0; $i<count($this->y2plots) ; ++$i ) {
1968 $this->y2plots[$i]->PreStrokeAdjust($this);
1969 $this->y2plots[$i]->DoLegend($this);
1970 }
1971 }
1972
1973 // Any plots on the extra Y axises?
1974 $n = count($this->ynaxis);
1975 for($i=0; $i<$n ; ++$i ) {
1976 if( $this->ynplots == null || $this->ynplots[$i] == null) {
1977 JpGraphError::RaiseL(25032,$i);//("No plots for Y-axis nbr:$i");
1978 }
1979 $m = count($this->ynplots[$i]);
1980 for($j=0; $j < $m; ++$j ) {
1981 $this->ynplots[$i][$j]->PreStrokeAdjust($this);
1982 $this->ynplots[$i][$j]->DoLegend($this);
1983 }
1984 }
1985 }
1986
1987 function StrokeBands($aDepth,$aCSIM) {
1988 // Stroke bands
1989 if( $this->bands != null && !$aCSIM) {
1990 for($i=0; $i < count($this->bands); ++$i) {
1991 // Stroke all bands that asks to be in the background
1992 if( $this->bands[$i]->depth == $aDepth ) {
1993 $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
1994 }
1995 }
1996 }
1997
1998 if( $this->y2bands != null && $this->y2scale != null && !$aCSIM ) {
1999 for($i=0; $i < count($this->y2bands); ++$i) {
2000 // Stroke all bands that asks to be in the foreground
2001 if( $this->y2bands[$i]->depth == $aDepth ) {
2002 $this->y2bands[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
2003 }
2004 }
2005 }
2006 }
2007
2008
2009 // Stroke the graph
2010 // $aStrokeFileName If != "" the image will be written to this file and NOT
2011 // streamed back to the browser
2012 function Stroke($aStrokeFileName='') {
2013 // Fist make a sanity check that user has specified a scale
2014 if( empty($this->yscale) ) {
2015 JpGraphError::RaiseL(25031);//('You must specify what scale to use with a call to Graph::SetScale().');
2016 }
2017
2018 // Start by adjusting the margin so that potential titles will fit.
2019 $this->AdjustMarginsForTitles();
2020
2021 // Give the plot a chance to do any scale adjuments the individual plots
2022 // wants to do. Right now this is only used by the contour plot to set scale
2023 // limits
2024 for($i=0; $i < count($this->plots) ; ++$i ) {
2025 $this->plots[$i]->PreScaleSetup($this);
2026 }
2027
2028 // Init scale constants that are used to calculate the transformation from
2029 // world to pixel coordinates
2030 $this->InitScaleConstants();
2031
2032 // If the filename is the predefined value = '_csim_special_'
2033 // we assume that the call to stroke only needs to do enough
2034 // to correctly generate the CSIM maps.
2035 // We use this variable to skip things we don't strictly need
2036 // to do to generate the image map to improve performance
2037 // a best we can. Therefor you will see a lot of tests !$_csim in the
2038 // code below.
2039 $_csim = ($aStrokeFileName===_CSIM_SPECIALFILE);
2040
2041 // If we are called the second time (perhaps the user has called GetHTMLImageMap()
2042 // himself then the legends have alsready been populated once in order to get the
2043 // CSIM coordinats. Since we do not want the legends to be populated a second time
2044 // we clear the legends
2045 $this->legend->Clear();
2046
2047 // We need to know if we have stroked the plot in the
2048 // GetCSIMareas. Otherwise the CSIM hasn't been generated
2049 // and in the case of GetCSIM called before stroke to generate
2050 // CSIM without storing an image to disk GetCSIM must call Stroke.
2051 $this->iHasStroked = true;
2052
2053 // Setup pre-stroked adjustments and Legends
2054 $this->doPrestrokeAdjustments();
2055
2056 if ($this->graph_theme) {
2057 $this->graph_theme->PreStrokeApply($this);
2058 }
2059
2060 // Bail out if any of the Y-axis not been specified and
2061 // has no plots. (This means it is impossible to do autoscaling and
2062 // no other scale was given so we can't possible draw anything). If you use manual
2063 // scaling you also have to supply the tick steps as well.
2064 if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
2065 ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
2066 //$e = "n=".count($this->y2plots)."\n";
2067 // $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
2068 // $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
2069 // $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
2070 JpGraphError::RaiseL(25026);
2071 }
2072
2073 // Bail out if no plots and no specified X-scale
2074 if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) ) {
2075 JpGraphError::RaiseL(25034);//("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
2076 }
2077
2078 // Autoscale the normal Y-axis
2079 $this->doAutoScaleYAxis();
2080
2081 // Autoscale all additiopnal y-axis
2082 $this->doAutoScaleYnAxis();
2083
2084 // Autoscale the regular x-axis and position the y-axis properly
2085 $this->doAutoScaleXAxis();
2086
2087 // If we have a negative values and x-axis position is at 0
2088 // we need to supress the first and possible the last tick since
2089 // they will be drawn on top of the y-axis (and possible y2 axis)
2090 // The test below might seem strange the reasone being that if
2091 // the user hasn't specified a value for position this will not
2092 // be set until we do the stroke for the axis so as of now it
2093 // is undefined.
2094 // For X-text scale we ignore all this since the tick are usually
2095 // much further in and not close to the Y-axis. Hence the test
2096 // for 'text'
2097 if( ($this->yaxis->pos==$this->xscale->GetMinVal() || (is_string($this->yaxis->pos) && $this->yaxis->pos=='min')) &&
2098 !is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 &&
2099 substr($this->axtype,0,4) != 'text' && $this->xaxis->pos != 'min' ) {
2100
2101 //$this->yscale->ticks->SupressZeroLabel(false);
2102 $this->xscale->ticks->SupressFirst();
2103 if( $this->y2axis != null ) {
2104 $this->xscale->ticks->SupressLast();
2105 }
2106 }
2107 elseif( !is_numeric($this->yaxis->pos) && $this->yaxis->pos=='max' ) {
2108 $this->xscale->ticks->SupressLast();
2109 }
2110
2111 if( !$_csim ) {
2112 $this->StrokePlotArea();
2113 if( $this->iIconDepth == DEPTH_BACK ) {
2114 $this->StrokeIcons();
2115 }
2116 }
2117 $this->StrokeAxis(false);
2118
2119 // Stroke colored bands
2120 $this->StrokeBands(DEPTH_BACK,$_csim);
2121
2122 if( $this->grid_depth == DEPTH_BACK && !$_csim) {
2123 $this->ygrid->Stroke();
2124 $this->xgrid->Stroke();
2125 }
2126
2127 // Stroke Y2-axis
2128 if( $this->y2axis != null && !$_csim) {
2129 $this->y2axis->Stroke($this->xscale);
2130 $this->y2grid->Stroke();
2131 }
2132
2133 // Stroke yn-axis
2134 $n = count($this->ynaxis);
2135 for( $i=0; $i < $n; ++$i ) {
2136 $this->ynaxis[$i]->Stroke($this->xscale);
2137 }
2138
2139 $oldoff=$this->xscale->off;
2140 if( substr($this->axtype,0,4) == 'text' ) {
2141 if( $this->text_scale_abscenteroff > -1 ) {
2142 // For a text scale the scale factor is the number of pixel per step.
2143 // Hence we can use the scale factor as a substitute for number of pixels
2144 // per major scale step and use that in order to adjust the offset so that
2145 // an object of width "abscenteroff" becomes centered.
2146 $this->xscale->off += round($this->xscale->scale_factor/2)-round($this->text_scale_abscenteroff/2);
2147 }
2148 else {
2149 $this->xscale->off += ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
2150 }
2151 }
2152
2153 if( $this->iDoClipping ) {
2154 $oldimage = $this->img->CloneCanvasH();
2155 }
2156
2157 if( ! $this->y2orderback ) {
2158 // Stroke all plots for Y1 axis
2159 for($i=0; $i < count($this->plots); ++$i) {
2160 $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
2161 $this->plots[$i]->StrokeMargin($this->img);
2162 }
2163 }
2164
2165 // Stroke all plots for Y2 axis
2166 if( $this->y2scale != null ) {
2167 for($i=0; $i< count($this->y2plots); ++$i ) {
2168 $this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
2169 }
2170 }
2171
2172 if( $this->y2orderback ) {
2173 // Stroke all plots for Y1 axis
2174 for($i=0; $i < count($this->plots); ++$i) {
2175 $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
2176 $this->plots[$i]->StrokeMargin($this->img);
2177 }
2178 }
2179
2180 $n = count($this->ynaxis);
2181 for( $i=0; $i < $n; ++$i ) {
2182 $m = count($this->ynplots[$i]);
2183 for( $j=0; $j < $m; ++$j ) {
2184 $this->ynplots[$i][$j]->Stroke($this->img,$this->xscale,$this->ynscale[$i]);
2185 $this->ynplots[$i][$j]->StrokeMargin($this->img);
2186 }
2187 }
2188
2189 if( $this->iIconDepth == DEPTH_FRONT) {
2190 $this->StrokeIcons();
2191 }
2192
2193 if( $this->iDoClipping ) {
2194 // Clipping only supports graphs at 0 and 90 degrees
2195 if( $this->img->a == 0 ) {
2196 $this->img->CopyCanvasH($oldimage,$this->img->img,
2197 $this->img->left_margin,$this->img->top_margin,
2198 $this->img->left_margin,$this->img->top_margin,
2199 $this->img->plotwidth+1,$this->img->plotheight);
2200 }
2201 elseif( $this->img->a == 90 ) {
2202 $adj = ($this->img->height - $this->img->width)/2;
2203 $this->img->CopyCanvasH($oldimage,$this->img->img,
2204 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
2205 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
2206 $this->img->plotheight+1,$this->img->plotwidth);
2207 }
2208 else {
2209 JpGraphError::RaiseL(25035,$this->img->a);//('You have enabled clipping. Cliping is only supported for graphs at 0 or 90 degrees rotation. Please adjust you current angle (='.$this->img->a.' degrees) or disable clipping.');
2210 }
2211 $this->img->Destroy();
2212 $this->img->SetCanvasH($oldimage);
2213 }
2214
2215 $this->xscale->off=$oldoff;
2216
2217 if( $this->grid_depth == DEPTH_FRONT && !$_csim ) {
2218 $this->ygrid->Stroke();
2219 $this->xgrid->Stroke();
2220 }
2221
2222 // Stroke colored bands
2223 $this->StrokeBands(DEPTH_FRONT,$_csim);
2224
2225 // Finally draw the axis again since some plots may have nagged
2226 // the axis in the edges.
2227 if( !$_csim ) {
2228 $this->StrokeAxis();
2229 }
2230
2231 if( $this->y2scale != null && !$_csim ) {
2232 $this->y2axis->Stroke($this->xscale,false);
2233 }
2234
2235 if( !$_csim ) {
2236 $this->StrokePlotBox();
2237 }
2238
2239 // The titles and legends never gets rotated so make sure
2240 // that the angle is 0 before stroking them
2241 $aa = $this->img->SetAngle(0);
2242 $this->StrokeTitles();
2243 $this->footer->Stroke($this->img);
2244 $this->legend->Stroke($this->img);
2245 $this->img->SetAngle($aa);
2246 $this->StrokeTexts();
2247 $this->StrokeTables();
2248
2249 if( !$_csim ) {
2250
2251 $this->img->SetAngle($aa);
2252
2253 // Draw an outline around the image map
2254 if(_JPG_DEBUG) {
2255 $this->DisplayClientSideaImageMapAreas();
2256 }
2257
2258 // Should we do any final image transformation
2259 if( $this->iImgTrans ) {
2260 if( !class_exists('ImgTrans',false) ) {
2261 require_once('jpgraph_imgtrans.php');
2262 //JpGraphError::Raise('In order to use image transformation you must include the file jpgraph_imgtrans.php in your script.');
2263 }
2264
2265 $tform = new ImgTrans($this->img->img);
2266 $this->img->img = $tform->Skew3D($this->iImgTransHorizon,$this->iImgTransSkewDist,
2267 $this->iImgTransDirection,$this->iImgTransHighQ,
2268 $this->iImgTransMinSize,$this->iImgTransFillColor,
2269 $this->iImgTransBorder);
2270 }
2271
2272 // If the filename is given as the special "__handle"
2273 // then the image handler is returned and the image is NOT
2274 // streamed back
2275 if( $aStrokeFileName == _IMG_HANDLER ) {
2276 return $this->img->img;
2277 }
2278 else {
2279 // Finally stream the generated picture
2280 $this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);
2281 }
2282 }
2283 }
2284
2285 function SetAxisLabelBackground($aType,$aXFColor='lightgray',$aXColor='black',$aYFColor='lightgray',$aYColor='black') {
2286 $this->iAxisLblBgType = $aType;
2287 $this->iXAxisLblBgFillColor = $aXFColor;
2288 $this->iXAxisLblBgColor = $aXColor;
2289 $this->iYAxisLblBgFillColor = $aYFColor;
2290 $this->iYAxisLblBgColor = $aYColor;
2291 }
2292
2293 function StrokeAxisLabelBackground() {
2294 // Types
2295 // 0 = No background
2296 // 1 = Only X-labels, length of axis
2297 // 2 = Only Y-labels, length of axis
2298 // 3 = As 1 but extends to width of graph
2299 // 4 = As 2 but extends to height of graph
2300 // 5 = Combination of 3 & 4
2301 // 6 = Combination of 1 & 2
2302
2303 $t = $this->iAxisLblBgType ;
2304 if( $t < 1 ) return;
2305
2306 // Stroke optional X-axis label background color
2307 if( $t == 1 || $t == 3 || $t == 5 || $t == 6 ) {
2308 $this->img->PushColor($this->iXAxisLblBgFillColor);
2309 if( $t == 1 || $t == 6 ) {
2310 $xl = $this->img->left_margin;
2311 $yu = $this->img->height - $this->img->bottom_margin + 1;
2312 $xr = $this->img->width - $this->img->right_margin ;
2313 $yl = $this->img->height-1-$this->frame_weight;
2314 }
2315 else { // t==3 || t==5
2316 $xl = $this->frame_weight;
2317 $yu = $this->img->height - $this->img->bottom_margin + 1;
2318 $xr = $this->img->width - 1 - $this->frame_weight;
2319 $yl = $this->img->height-1-$this->frame_weight;
2320 }
2321
2322 $this->img->FilledRectangle($xl,$yu,$xr,$yl);
2323 $this->img->PopColor();
2324
2325 // Check if we should add the vertical lines at left and right edge
2326 if( $this->iXAxisLblBgColor !== '' ) {
2327 // Hardcode to one pixel wide
2328 $this->img->SetLineWeight(1);
2329 $this->img->PushColor($this->iXAxisLblBgColor);
2330 if( $t == 1 || $t == 6 ) {
2331 $this->img->Line($xl,$yu,$xl,$yl);
2332 $this->img->Line($xr,$yu,$xr,$yl);
2333 }
2334 else {
2335 $xl = $this->img->width - $this->img->right_margin ;
2336 $this->img->Line($xl,$yu-1,$xr,$yu-1);
2337 }
2338 $this->img->PopColor();
2339 }
2340 }
2341
2342 if( $t == 2 || $t == 4 || $t == 5 || $t == 6 ) {
2343 $this->img->PushColor($this->iYAxisLblBgFillColor);
2344 if( $t == 2 || $t == 6 ) {
2345 $xl = $this->frame_weight;
2346 $yu = $this->frame_weight+$this->img->top_margin;
2347 $xr = $this->img->left_margin - 1;
2348 $yl = $this->img->height - $this->img->bottom_margin + 1;
2349 }
2350 else {
2351 $xl = $this->frame_weight;
2352 $yu = $this->frame_weight;
2353 $xr = $this->img->left_margin - 1;
2354 $yl = $this->img->height-1-$this->frame_weight;
2355 }
2356
2357 $this->img->FilledRectangle($xl,$yu,$xr,$yl);
2358 $this->img->PopColor();
2359
2360 // Check if we should add the vertical lines at left and right edge
2361 if( $this->iXAxisLblBgColor !== '' ) {
2362 $this->img->PushColor($this->iXAxisLblBgColor);
2363 if( $t == 2 || $t == 6 ) {
2364 $this->img->Line($xl,$yu-1,$xr,$yu-1);
2365 $this->img->Line($xl,$yl-1,$xr,$yl-1);
2366 }
2367 else {
2368 $this->img->Line($xr+1,$yu,$xr+1,$this->img->top_margin);
2369 }
2370 $this->img->PopColor();
2371 }
2372
2373 }
2374 }
2375
2376 function StrokeAxis($aStrokeLabels=true) {
2377
2378 if( $aStrokeLabels ) {
2379 $this->StrokeAxisLabelBackground();
2380 }
2381
2382 // Stroke axis
2383 if( $this->iAxisStyle != AXSTYLE_SIMPLE ) {
2384 switch( $this->iAxisStyle ) {
2385 case AXSTYLE_BOXIN :
2386 $toppos = SIDE_DOWN;
2387 $bottompos = SIDE_UP;
2388 $leftpos = SIDE_RIGHT;
2389 $rightpos = SIDE_LEFT;
2390 break;
2391 case AXSTYLE_BOXOUT :
2392 $toppos = SIDE_UP;
2393 $bottompos = SIDE_DOWN;
2394 $leftpos = SIDE_LEFT;
2395 $rightpos = SIDE_RIGHT;
2396 break;
2397 case AXSTYLE_YBOXIN:
2398 $toppos = FALSE;
2399 $bottompos = SIDE_UP;
2400 $leftpos = SIDE_RIGHT;
2401 $rightpos = SIDE_LEFT;
2402 break;
2403 case AXSTYLE_YBOXOUT:
2404 $toppos = FALSE;
2405 $bottompos = SIDE_DOWN;
2406 $leftpos = SIDE_LEFT;
2407 $rightpos = SIDE_RIGHT;
2408 break;
2409 default:
2410 JpGRaphError::RaiseL(25036,$this->iAxisStyle); //('Unknown AxisStyle() : '.$this->iAxisStyle);
2411 break;
2412 }
2413
2414 // By default we hide the first label so it doesn't cross the
2415 // Y-axis in case the positon hasn't been set by the user.
2416 // However, if we use a box we always want the first value
2417 // displayed so we make sure it will be displayed.
2418 $this->xscale->ticks->SupressFirst(false);
2419
2420 // Now draw the bottom X-axis
2421 $this->xaxis->SetPos('min');
2422 $this->xaxis->SetLabelSide(SIDE_DOWN);
2423 $this->xaxis->scale->ticks->SetSide($bottompos);
2424 $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
2425
2426 if( $toppos !== FALSE ) {
2427 // We also want a top X-axis
2428 $this->xaxis = $this->xaxis;
2429 $this->xaxis->SetPos('max');
2430 $this->xaxis->SetLabelSide(SIDE_UP);
2431 // No title for the top X-axis
2432 if( $aStrokeLabels ) {
2433 $this->xaxis->title->Set('');
2434 }
2435 $this->xaxis->scale->ticks->SetSide($toppos);
2436 $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
2437 }
2438
2439 // Stroke the left Y-axis
2440 $this->yaxis->SetPos('min');
2441 $this->yaxis->SetLabelSide(SIDE_LEFT);
2442 $this->yaxis->scale->ticks->SetSide($leftpos);
2443 $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
2444
2445 // Stroke the right Y-axis
2446 $this->yaxis->SetPos('max');
2447 // No title for the right side
2448 if( $aStrokeLabels ) {
2449 $this->yaxis->title->Set('');
2450 }
2451 $this->yaxis->SetLabelSide(SIDE_RIGHT);
2452 $this->yaxis->scale->ticks->SetSide($rightpos);
2453 $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
2454 }
2455 else {
2456 $this->xaxis->Stroke($this->yscale,$aStrokeLabels);
2457 $this->yaxis->Stroke($this->xscale,$aStrokeLabels);
2458 }
2459 }
2460
2461
2462 // Private helper function for backgound image
2463 static function LoadBkgImage($aImgFormat='',$aFile='',$aImgStr='') {
2464 if( $aImgStr != '' ) {
2465 return Image::CreateFromString($aImgStr);
2466 }
2467
2468 // Remove case sensitivity and setup appropriate function to create image
2469 // Get file extension. This should be the LAST '.' separated part of the filename
2470 $e = explode('.',$aFile);
2471 $ext = strtolower($e[count($e)-1]);
2472 if ($ext == "jpeg") {
2473 $ext = "jpg";
2474 }
2475
2476 if( trim($ext) == '' ) {
2477 $ext = 'png'; // Assume PNG if no extension specified
2478 }
2479
2480 if( $aImgFormat == '' ) {
2481 $imgtag = $ext;
2482 }
2483 else {
2484 $imgtag = $aImgFormat;
2485 }
2486
2487 $supported = imagetypes();
2488 if( ( $ext == 'jpg' && !($supported & IMG_JPG) ) ||
2489 ( $ext == 'gif' && !($supported & IMG_GIF) ) ||
2490 ( $ext == 'png' && !($supported & IMG_PNG) ) ||
2491 ( $ext == 'bmp' && !($supported & IMG_WBMP) ) ||
2492 ( $ext == 'xpm' && !($supported & IMG_XPM) ) ) {
2493
2494 JpGraphError::RaiseL(25037,$aFile);//('The image format of your background image ('.$aFile.') is not supported in your system configuration. ');
2495 }
2496
2497
2498 if( $imgtag == "jpg" || $imgtag == "jpeg") {
2499 $f = "imagecreatefromjpeg";
2500 $imgtag = "jpg";
2501 }
2502 else {
2503 $f = "imagecreatefrom".$imgtag;
2504 }
2505
2506 // Compare specified image type and file extension
2507 if( $imgtag != $ext ) {
2508 //$t = "Background image seems to be of different type (has different file extension) than specified imagetype. Specified: '".$aImgFormat."'File: '".$aFile."'";
2509 JpGraphError::RaiseL(25038, $aImgFormat, $aFile);
2510 }
2511
2512 $img = @$f($aFile);
2513 if( !$img ) {
2514 JpGraphError::RaiseL(25039,$aFile);//(" Can't read background image: '".$aFile."'");
2515 }
2516 return $img;
2517 }
2518
2519 function StrokePlotGrad() {
2520 if( $this->plot_gradtype < 0 )
2521 return;
2522
2523 $grad = new Gradient($this->img);
2524 $xl = $this->img->left_margin;
2525 $yt = $this->img->top_margin;
2526 $xr = $xl + $this->img->plotwidth+1 ;
2527 $yb = $yt + $this->img->plotheight ;
2528 $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->plot_gradfrom,$this->plot_gradto,$this->plot_gradtype);
2529
2530 }
2531
2532 function StrokeBackgroundGrad() {
2533 if( $this->bkg_gradtype < 0 )
2534 return;
2535
2536 $grad = new Gradient($this->img);
2537 if( $this->bkg_gradstyle == BGRAD_PLOT ) {
2538 $xl = $this->img->left_margin;
2539 $yt = $this->img->top_margin;
2540 $xr = $xl + $this->img->plotwidth+1 ;
2541 $yb = $yt + $this->img->plotheight ;
2542 $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
2543 }
2544 else {
2545 $xl = 0;
2546 $yt = 0;
2547 $xr = $xl + $this->img->width - 1;
2548 $yb = $yt + $this->img->height - 1 ;
2549 if( $this->doshadow ) {
2550 $xr -= $this->shadow_width;
2551 $yb -= $this->shadow_width;
2552 }
2553 if( $this->doframe ) {
2554 $yt += $this->frame_weight;
2555 $yb -= $this->frame_weight;
2556 $xl += $this->frame_weight;
2557 $xr -= $this->frame_weight;
2558 }
2559 $aa = $this->img->SetAngle(0);
2560 $grad->FilledRectangle($xl,$yt,$xr,$yb,$this->bkg_gradfrom,$this->bkg_gradto,$this->bkg_gradtype);
2561 $aa = $this->img->SetAngle($aa);
2562 }
2563 }
2564
2565 function StrokeFrameBackground() {
2566 if( $this->background_image != '' && $this->background_cflag != '' ) {
2567 JpGraphError::RaiseL(25040);//('It is not possible to specify both a background image and a background country flag.');
2568 }
2569 if( $this->background_image != '' ) {
2570 $bkgimg = $this->LoadBkgImage($this->background_image_format,$this->background_image);
2571 }
2572 elseif( $this->background_cflag != '' ) {
2573 if( ! class_exists('FlagImages',false) ) {
2574 JpGraphError::RaiseL(25041);//('In order to use Country flags as backgrounds you must include the "jpgraph_flags.php" file.');
2575 }
2576 $fobj = new FlagImages(FLAGSIZE4);
2577 $dummy='';
2578 $bkgimg = $fobj->GetImgByName($this->background_cflag,$dummy);
2579 $this->background_image_mix = $this->background_cflag_mix;
2580 $this->background_image_type = $this->background_cflag_type;
2581 }
2582 else {
2583 return ;
2584 }
2585
2586 $bw = ImageSX($bkgimg);
2587 $bh = ImageSY($bkgimg);
2588
2589 // No matter what the angle is we always stroke the image and frame
2590 // assuming it is 0 degree
2591 $aa = $this->img->SetAngle(0);
2592
2593 switch( $this->background_image_type ) {
2594 case BGIMG_FILLPLOT: // Resize to just fill the plotarea
2595 $this->FillMarginArea();
2596 $this->StrokeFrame();
2597 // Special case to hande 90 degree rotated graph corectly
2598 if( $aa == 90 ) {
2599 $this->img->SetAngle(90);
2600 $this->FillPlotArea();
2601 $aa = $this->img->SetAngle(0);
2602 $adj = ($this->img->height - $this->img->width)/2;
2603 $this->img->CopyMerge($bkgimg,
2604 $this->img->bottom_margin-$adj,$this->img->left_margin+$adj,
2605 0,0,
2606 $this->img->plotheight+1,$this->img->plotwidth,
2607 $bw,$bh,$this->background_image_mix);
2608 }
2609 else {
2610 $this->FillPlotArea();
2611 $this->img->CopyMerge($bkgimg,
2612 $this->img->left_margin,$this->img->top_margin+1,
2613 0,0,$this->img->plotwidth+1,$this->img->plotheight,
2614 $bw,$bh,$this->background_image_mix);
2615 }
2616 break;
2617 case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
2618 $hadj=0; $vadj=0;
2619 if( $this->doshadow ) {
2620 $hadj = $this->shadow_width;
2621 $vadj = $this->shadow_width;
2622 }
2623 $this->FillMarginArea();
2624 $this->FillPlotArea();
2625 $this->img->CopyMerge($bkgimg,0,0,0,0,$this->img->width-$hadj,$this->img->height-$vadj,
2626 $bw,$bh,$this->background_image_mix);
2627 $this->StrokeFrame();
2628 break;
2629 case BGIMG_COPY: // Just copy the image from left corner, no resizing
2630 $this->FillMarginArea();
2631 $this->FillPlotArea();
2632 $this->img->CopyMerge($bkgimg,0,0,0,0,$bw,$bh,
2633 $bw,$bh,$this->background_image_mix);
2634 $this->StrokeFrame();
2635 break;
2636 case BGIMG_CENTER: // Center original image in the plot area
2637 $this->FillMarginArea();
2638 $this->FillPlotArea();
2639 $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
2640 $centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
2641 $this->img->CopyMerge($bkgimg,$centerx,$centery,0,0,$bw,$bh,
2642 $bw,$bh,$this->background_image_mix);
2643 $this->StrokeFrame();
2644 break;
2645 case BGIMG_FREE: // Just copy the image to the specified location
2646 $this->img->CopyMerge($bkgimg,
2647 $this->background_image_xpos,$this->background_image_ypos,
2648 0,0,$bw,$bh,$bw,$bh,$this->background_image_mix);
2649 $this->StrokeFrame(); // New
2650 break;
2651 default:
2652 JpGraphError::RaiseL(25042);//(" Unknown background image layout");
2653 }
2654 $this->img->SetAngle($aa);
2655 }
2656
2657 // Private
2658 // Draw a frame around the image
2659 function StrokeFrame() {
2660 if( !$this->doframe ) return;
2661
2662 if( $this->background_image_type <= 1 && ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_PLOT)) ) {
2663 $c = $this->margin_color;
2664 }
2665 else {
2666 $c = false;
2667 }
2668
2669 if( $this->doshadow ) {
2670 $this->img->SetColor($this->frame_color);
2671 $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
2672 $c,$this->shadow_width,$this->shadow_color);
2673 }
2674 elseif( $this->framebevel ) {
2675 if( $c ) {
2676 $this->img->SetColor($this->margin_color);
2677 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
2678 }
2679 $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
2680 $this->framebeveldepth,
2681 $this->framebevelcolor1,$this->framebevelcolor2);
2682 if( $this->framebevelborder ) {
2683 $this->img->SetColor($this->framebevelbordercolor);
2684 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2685 }
2686 }
2687 else {
2688 $this->img->SetLineWeight($this->frame_weight);
2689 if( $c ) {
2690 $this->img->SetColor($this->margin_color);
2691 $this->img->FilledRectangle(0,0,$this->img->width-1,$this->img->height-1);
2692 }
2693 $this->img->SetColor($this->frame_color);
2694 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2695 }
2696 }
2697
2698 function FillMarginArea() {
2699 $hadj=0; $vadj=0;
2700 if( $this->doshadow ) {
2701 $hadj = $this->shadow_width;
2702 $vadj = $this->shadow_width;
2703 }
2704
2705 $this->img->SetColor($this->margin_color);
2706 $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->height-1-$vadj);
2707
2708 $this->img->FilledRectangle(0,0,$this->img->width-1-$hadj,$this->img->top_margin);
2709 $this->img->FilledRectangle(0,$this->img->top_margin,$this->img->left_margin,$this->img->height-1-$hadj);
2710 $this->img->FilledRectangle($this->img->left_margin+1,
2711 $this->img->height-$this->img->bottom_margin,
2712 $this->img->width-1-$hadj,
2713 $this->img->height-1-$hadj);
2714 $this->img->FilledRectangle($this->img->width-$this->img->right_margin,
2715 $this->img->top_margin+1,
2716 $this->img->width-1-$hadj,
2717 $this->img->height-$this->img->bottom_margin-1);
2718 }
2719
2720 function FillPlotArea() {
2721 $this->img->PushColor($this->plotarea_color);
2722 $this->img->FilledRectangle($this->img->left_margin,
2723 $this->img->top_margin,
2724 $this->img->width-$this->img->right_margin,
2725 $this->img->height-$this->img->bottom_margin);
2726 $this->img->PopColor();
2727 }
2728
2729 // Stroke the plot area with either a solid color or a background image
2730 function StrokePlotArea() {
2731 // Note: To be consistent we really should take a possible shadow
2732 // into account. However, that causes some problem for the LinearScale class
2733 // since in the current design it does not have any links to class Graph which
2734 // means it has no way of compensating for the adjusted plotarea in case of a
2735 // shadow. So, until I redesign LinearScale we can't compensate for this.
2736 // So just set the two adjustment parameters to zero for now.
2737 $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
2738 $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
2739
2740 if( $this->background_image != '' || $this->background_cflag != '' ) {
2741 $this->StrokeFrameBackground();
2742 }
2743 else {
2744 $aa = $this->img->SetAngle(0);
2745 $this->StrokeFrame();
2746 $aa = $this->img->SetAngle($aa);
2747 $this->StrokeBackgroundGrad();
2748 if( $this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle==BGRAD_MARGIN) ) {
2749 $this->FillPlotArea();
2750 }
2751 $this->StrokePlotGrad();
2752 }
2753 }
2754
2755 function StrokeIcons() {
2756 $n = count($this->iIcons);
2757 for( $i=0; $i < $n; ++$i ) {
2758 $this->iIcons[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2759 }
2760 }
2761
2762 function StrokePlotBox() {
2763 // Should we draw a box around the plot area?
2764 if( $this->boxed ) {
2765 $this->img->SetLineWeight(1);
2766 $this->img->SetLineStyle('solid');
2767 $this->img->SetColor($this->box_color);
2768 for($i=0; $i < $this->box_weight; ++$i ) {
2769 $this->img->Rectangle(
2770 $this->img->left_margin-$i,$this->img->top_margin-$i,
2771 $this->img->width-$this->img->right_margin+$i,
2772 $this->img->height-$this->img->bottom_margin+$i);
2773 }
2774 }
2775 }
2776
2777 function SetTitleBackgroundFillStyle($aStyle,$aColor1='black',$aColor2='white') {
2778 $this->titlebkg_fillstyle = $aStyle;
2779 $this->titlebkg_scolor1 = $aColor1;
2780 $this->titlebkg_scolor2 = $aColor2;
2781 }
2782
2783 function SetTitleBackground($aBackColor='gray', $aStyle=TITLEBKG_STYLE1, $aFrameStyle=TITLEBKG_FRAME_NONE, $aFrameColor='black', $aFrameWeight=1, $aBevelHeight=3, $aEnable=true) {
2784 $this->titlebackground = $aEnable;
2785 $this->titlebackground_color = $aBackColor;
2786 $this->titlebackground_style = $aStyle;
2787 $this->titlebackground_framecolor = $aFrameColor;
2788 $this->titlebackground_framestyle = $aFrameStyle;
2789 $this->titlebackground_frameweight = $aFrameWeight;
2790 $this->titlebackground_bevelheight = $aBevelHeight ;
2791 }
2792
2793
2794 function StrokeTitles() {
2795
2796 $margin=3;
2797
2798 if( $this->titlebackground ) {
2799 // Find out height
2800 $this->title->margin += 2 ;
2801 $h = $this->title->GetTextHeight($this->img)+$this->title->margin+$margin;
2802 if( $this->subtitle->t != '' && !$this->subtitle->hide ) {
2803 $h += $this->subtitle->GetTextHeight($this->img)+$margin+
2804 $this->subtitle->margin;
2805 $h += 2;
2806 }
2807 if( $this->subsubtitle->t != '' && !$this->subsubtitle->hide ) {
2808 $h += $this->subsubtitle->GetTextHeight($this->img)+$margin+
2809 $this->subsubtitle->margin;
2810 $h += 2;
2811 }
2812 $this->img->PushColor($this->titlebackground_color);
2813 if( $this->titlebackground_style === TITLEBKG_STYLE1 ) {
2814 // Inside the frame
2815 if( $this->framebevel ) {
2816 $x1 = $y1 = $this->framebeveldepth + 1 ;
2817 $x2 = $this->img->width - $this->framebeveldepth - 2 ;
2818 $this->title->margin += $this->framebeveldepth + 1 ;
2819 $h += $y1 ;
2820 $h += 2;
2821 }
2822 else {
2823 $x1 = $y1 = $this->frame_weight;
2824 $x2 = $this->img->width - $this->frame_weight-1;
2825 }
2826 }
2827 elseif( $this->titlebackground_style === TITLEBKG_STYLE2 ) {
2828 // Cover the frame as well
2829 $x1 = $y1 = 0;
2830 $x2 = $this->img->width - 1 ;
2831 }
2832 elseif( $this->titlebackground_style === TITLEBKG_STYLE3 ) {
2833 // Cover the frame as well (the difference is that
2834 // for style==3 a bevel frame border is on top
2835 // of the title background)
2836 $x1 = $y1 = 0;
2837 $x2 = $this->img->width - 1 ;
2838 $h += $this->framebeveldepth ;
2839 $this->title->margin += $this->framebeveldepth ;
2840 }
2841 else {
2842 JpGraphError::RaiseL(25043);//('Unknown title background style.');
2843 }
2844
2845 if( $this->titlebackground_framestyle === 3 ) {
2846 $h += $this->titlebackground_bevelheight*2 + 1 ;
2847 $this->title->margin += $this->titlebackground_bevelheight ;
2848 }
2849
2850 if( $this->doshadow ) {
2851 $x2 -= $this->shadow_width ;
2852 }
2853
2854 $indent=0;
2855 if( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
2856 $indent = $this->titlebackground_bevelheight;
2857 }
2858
2859 if( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_HSTRIPED ) {
2860 $this->img->FilledRectangle2($x1+$indent,$y1+$indent,$x2-$indent,$h-$indent,
2861 $this->titlebkg_scolor1,
2862 $this->titlebkg_scolor2);
2863 }
2864 elseif( $this->titlebkg_fillstyle==TITLEBKG_FILLSTYLE_VSTRIPED ) {
2865 $this->img->FilledRectangle2($x1+$indent,$y1+$indent,$x2-$indent,$h-$indent,
2866 $this->titlebkg_scolor1,
2867 $this->titlebkg_scolor2,2);
2868 }
2869 else {
2870 // Solid fill
2871 $this->img->FilledRectangle($x1,$y1,$x2,$h);
2872 }
2873 $this->img->PopColor();
2874
2875 $this->img->PushColor($this->titlebackground_framecolor);
2876 $this->img->SetLineWeight($this->titlebackground_frameweight);
2877 if( $this->titlebackground_framestyle == TITLEBKG_FRAME_FULL ) {
2878 // Frame background
2879 $this->img->Rectangle($x1,$y1,$x2,$h);
2880 }
2881 elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BOTTOM ) {
2882 // Bottom line only
2883 $this->img->Line($x1,$h,$x2,$h);
2884 }
2885 elseif( $this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL ) {
2886 $this->img->Bevel($x1,$y1,$x2,$h,$this->titlebackground_bevelheight);
2887 }
2888 $this->img->PopColor();
2889
2890 // This is clumsy. But we neeed to stroke the whole graph frame if it is
2891 // set to bevel to get the bevel shading on top of the text background
2892 if( $this->framebevel && $this->doframe && $this->titlebackground_style === 3 ) {
2893 $this->img->Bevel(1,1,$this->img->width-2,$this->img->height-2,
2894 $this->framebeveldepth,
2895 $this->framebevelcolor1,$this->framebevelcolor2);
2896 if( $this->framebevelborder ) {
2897 $this->img->SetColor($this->framebevelbordercolor);
2898 $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);
2899 }
2900 }
2901 }
2902
2903 // Stroke title
2904 $y = $this->title->margin;
2905 if( $this->title->halign == 'center' ) {
2906 $this->title->Center(0,$this->img->width,$y);
2907 }
2908 elseif( $this->title->halign == 'left' ) {
2909 $this->title->SetPos($this->title->margin+2,$y);
2910 }
2911 elseif( $this->title->halign == 'right' ) {
2912 $indent = 0;
2913 if( $this->doshadow ) {
2914 $indent = $this->shadow_width+2;
2915 }
2916 $this->title->SetPos($this->img->width-$this->title->margin-$indent,$y,'right');
2917 }
2918 $this->title->Stroke($this->img);
2919
2920 // ... and subtitle
2921 $y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
2922 if( $this->subtitle->halign == 'center' ) {
2923 $this->subtitle->Center(0,$this->img->width,$y);
2924 }
2925 elseif( $this->subtitle->halign == 'left' ) {
2926 $this->subtitle->SetPos($this->subtitle->margin+2,$y);
2927 }
2928 elseif( $this->subtitle->halign == 'right' ) {
2929 $indent = 0;
2930 if( $this->doshadow )
2931 $indent = $this->shadow_width+2;
2932 $this->subtitle->SetPos($this->img->width-$this->subtitle->margin-$indent,$y,'right');
2933 }
2934 $this->subtitle->Stroke($this->img);
2935
2936 // ... and subsubtitle
2937 $y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
2938 if( $this->subsubtitle->halign == 'center' ) {
2939 $this->subsubtitle->Center(0,$this->img->width,$y);
2940 }
2941 elseif( $this->subsubtitle->halign == 'left' ) {
2942 $this->subsubtitle->SetPos($this->subsubtitle->margin+2,$y);
2943 }
2944 elseif( $this->subsubtitle->halign == 'right' ) {
2945 $indent = 0;
2946 if( $this->doshadow )
2947 $indent = $this->shadow_width+2;
2948 $this->subsubtitle->SetPos($this->img->width-$this->subsubtitle->margin-$indent,$y,'right');
2949 }
2950 $this->subsubtitle->Stroke($this->img);
2951
2952 // ... and fancy title
2953 $this->tabtitle->Stroke($this->img);
2954
2955 }
2956
2957 function StrokeTexts() {
2958 // Stroke any user added text objects
2959 if( $this->texts != null ) {
2960 for($i=0; $i < count($this->texts); ++$i) {
2961 $this->texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2962 }
2963 }
2964
2965 if( $this->y2texts != null && $this->y2scale != null ) {
2966 for($i=0; $i < count($this->y2texts); ++$i) {
2967 $this->y2texts[$i]->StrokeWithScale($this->img,$this->xscale,$this->y2scale);
2968 }
2969 }
2970
2971 }
2972
2973 function StrokeTables() {
2974 if( $this->iTables != null ) {
2975 $n = count($this->iTables);
2976 for( $i=0; $i < $n; ++$i ) {
2977 $this->iTables[$i]->StrokeWithScale($this->img,$this->xscale,$this->yscale);
2978 }
2979 }
2980 }
2981
2982 function DisplayClientSideaImageMapAreas() {
2983 // Debug stuff - display the outline of the image map areas
2984 $csim='';
2985 foreach ($this->plots as $p) {
2986 $csim.= $p->GetCSIMareas();
2987 }
2988 $csim .= $this->legend->GetCSIMareas();
2989 if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
2990 $this->img->SetColor($this->csimcolor);
2991 $n = count($coords[0]);
2992 for ($i=0; $i < $n; $i++) {
2993 if ( $coords[1][$i] == 'poly' ) {
2994 preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
2995 $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
2996 $m = count($pts[0]);
2997 for ($j=0; $j < $m; $j++) {
2998 $this->img->LineTo($pts[1][$j],$pts[2][$j]);
2999 }
3000 } elseif ( $coords[1][$i] == 'rect' ) {
3001 $pts = preg_split('/,/', $coords[2][$i]);
3002 $this->img->SetStartPoint($pts[0],$pts[1]);
3003 $this->img->LineTo($pts[2],$pts[1]);
3004 $this->img->LineTo($pts[2],$pts[3]);
3005 $this->img->LineTo($pts[0],$pts[3]);
3006 $this->img->LineTo($pts[0],$pts[1]);
3007 }
3008 }
3009 }
3010 }
3011
3012 // Text scale offset in world coordinates
3013 function SetTextScaleOff($aOff) {
3014 $this->text_scale_off = $aOff;
3015 $this->xscale->text_scale_off = $aOff;
3016 }
3017
3018 // Text width of bar to be centered in absolute pixels
3019 function SetTextScaleAbsCenterOff($aOff) {
3020 $this->text_scale_abscenteroff = $aOff;
3021 }
3022
3023 // Get Y min and max values for added lines
3024 function GetLinesYMinMax( $aLines ) {
3025 $n = is_array($aLines) ? count($aLines) : 0;
3026 if( $n == 0 ) return false;
3027 $min = $aLines[0]->scaleposition ;
3028 $max = $min ;
3029 $flg = false;
3030 for( $i=0; $i < $n; ++$i ) {
3031 if( $aLines[$i]->direction == HORIZONTAL ) {
3032 $flg = true ;
3033 $v = $aLines[$i]->scaleposition ;
3034 if( $min > $v ) $min = $v ;
3035 if( $max < $v ) $max = $v ;
3036 }
3037 }
3038 return $flg ? array($min,$max) : false ;
3039 }
3040
3041 // Get X min and max values for added lines
3042 function GetLinesXMinMax( $aLines ) {
3043 $n = is_array($aLines) ? count($aLines) : 0;
3044 if( $n == 0 ) return false ;
3045 $min = $aLines[0]->scaleposition ;
3046 $max = $min ;
3047 $flg = false;
3048 for( $i=0; $i < $n; ++$i ) {
3049 if( $aLines[$i]->direction == VERTICAL ) {
3050 $flg = true ;
3051 $v = $aLines[$i]->scaleposition ;
3052 if( $min > $v ) $min = $v ;
3053 if( $max < $v ) $max = $v ;
3054 }
3055 }
3056 return $flg ? array($min,$max) : false ;
3057 }
3058
3059 // Get min and max values for all included plots
3060 function GetPlotsYMinMax($aPlots) {
3061 $n = count($aPlots);
3062 $i=0;
3063 do {
3064 list($xmax,$max) = $aPlots[$i]->Max();
3065 } while( ++$i < $n && !is_numeric($max) );
3066
3067 $i=0;
3068 do {
3069 list($xmin,$min) = $aPlots[$i]->Min();
3070 } while( ++$i < $n && !is_numeric($min) );
3071
3072 if( !is_numeric($min) || !is_numeric($max) ) {
3073 JpGraphError::RaiseL(25044);//('Cannot use autoscaling since it is impossible to determine a valid min/max value of the Y-axis (only null values).');
3074 }
3075
3076 for($i=0; $i < $n; ++$i ) {
3077 list($xmax,$ymax)=$aPlots[$i]->Max();
3078 list($xmin,$ymin)=$aPlots[$i]->Min();
3079 if (is_numeric($ymax)) $max=max($max,$ymax);
3080 if (is_numeric($ymin)) $min=min($min,$ymin);
3081 }
3082 if( $min == '' ) $min = 0;
3083 if( $max == '' ) $max = 0;
3084 if( $min == 0 && $max == 0 ) {
3085 // Special case if all values are 0
3086 $min=0;$max=1;
3087 }
3088 return array($min,$max);
3089 }
3090
3091 function hasLinePlotAndBarPlot() {
3092 $has_line = false;
3093 $has_bar = false;
3094
3095 foreach ($this->plots as $plot) {
3096 if ($plot instanceof LinePlot) {
3097 $has_line = true;
3098 }
3099 if ($plot instanceof BarPlot) {
3100 $has_bar = true;
3101 }
3102 }
3103
3104 if ($has_line && $has_bar) {
3105 return true;
3106 }
3107
3108 return false;
3109 }
3110
3111 function SetTheme($graph_theme) {
3112
3113 if (!($this instanceof PieGraph)) {
3114 if (!$this->isAfterSetScale) {
3115 JpGraphError::RaiseL(25133);//('Use Graph::SetTheme() after Graph::SetScale().');
3116 }
3117 }
3118
3119 if ($this->graph_theme) {
3120 $this->ClearTheme();
3121 }
3122 $this->graph_theme = $graph_theme;
3123 $this->graph_theme->ApplyGraph($this);
3124 }
3125
3126 function ClearTheme() {
3127 $this->graph_theme = null;
3128
3129 $this->isRunningClear = true;
3130
3131 $this->__construct(
3132 $this->inputValues['aWidth'],
3133 $this->inputValues['aHeight'],
3134 $this->inputValues['aCachedName'],
3135 $this->inputValues['aTimeout'],
3136 $this->inputValues['aInline']
3137 );
3138
3139 if (!($this instanceof PieGraph)) {
3140 if ($this->isAfterSetScale) {
3141 $this->SetScale(
3142 $this->inputValues['aAxisType'],
3143 $this->inputValues['aYMin'],
3144 $this->inputValues['aYMax'],
3145 $this->inputValues['aXMin'],
3146 $this->inputValues['aXMax']
3147 );
3148 }
3149 }
3150
3151 $this->isRunningClear = false;
3152 }
3153
3154 function SetSupersampling($do = false, $scale = 2) {
3155 if ($do) {
3156 define('SUPERSAMPLING_SCALE', $scale);
3157 // $this->img->scale = $scale;
3158 } else {
3159 define('SUPERSAMPLING_SCALE', 1);
3160 //$this->img->scale = 0;
3161 }
3162 }
3163
3164} // Class
3165
3166//===================================================
3167// CLASS LineProperty
3168// Description: Holds properties for a line
3169//===================================================
3170class LineProperty {
3171 public $iWeight=1, $iColor='black', $iStyle='solid', $iShow=false;
3172
3173 function __construct($aWeight=1,$aColor='black',$aStyle='solid') {
3174 $this->iWeight = $aWeight;
3175 $this->iColor = $aColor;
3176 $this->iStyle = $aStyle;
3177 }
3178
3179 function SetColor($aColor) {
3180 $this->iColor = $aColor;
3181 }
3182
3183 function SetWeight($aWeight) {
3184 $this->iWeight = $aWeight;
3185 }
3186
3187 function SetStyle($aStyle) {
3188 $this->iStyle = $aStyle;
3189 }
3190
3191 function Show($aShow=true) {
3192 $this->iShow=$aShow;
3193 }
3194
3195 function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
3196 if( $this->iShow ) {
3197 $aImg->PushColor($this->iColor);
3198 $oldls = $aImg->line_style;
3199 $oldlw = $aImg->line_weight;
3200 $aImg->SetLineWeight($this->iWeight);
3201 $aImg->SetLineStyle($this->iStyle);
3202 $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
3203 $aImg->PopColor($this->iColor);
3204 $aImg->line_style = $oldls;
3205 $aImg->line_weight = $oldlw;
3206
3207 }
3208 }
3209}
3210
3211//===================================================
3212// CLASS GraphTabTitle
3213// Description: Draw "tab" titles on top of graphs
3214//===================================================
3215class GraphTabTitle extends Text{
3216 private $corner = 6 , $posx = 7, $posy = 4;
3217 private $fillcolor='lightyellow',$bordercolor='black';
3218 private $align = 'left', $width=TABTITLE_WIDTHFIT;
3219 function __construct() {
3220 $this->t = '';
3221 $this->font_style = FS_BOLD;
3222 $this->hide = true;
3223 $this->color = 'darkred';
3224 }
3225
3226 function SetColor($aTxtColor,$aFillColor='lightyellow',$aBorderColor='black') {
3227 $this->color = $aTxtColor;
3228 $this->fillcolor = $aFillColor;
3229 $this->bordercolor = $aBorderColor;
3230 }
3231
3232 function SetFillColor($aFillColor) {
3233 $this->fillcolor = $aFillColor;
3234 }
3235
3236 function SetTabAlign($aAlign) {
3237 $this->align = $aAlign;
3238 }
3239
3240 function SetWidth($aWidth) {
3241 $this->width = $aWidth ;
3242 }
3243
3244 function Set($t) {
3245 $this->t = $t;
3246 $this->hide = false;
3247 }
3248
3249 function SetCorner($aD) {
3250 $this->corner = $aD ;
3251 }
3252
3253 function Stroke($aImg,$aDummy1=null,$aDummy2=null) {
3254 if( $this->hide )
3255 return;
3256 $this->boxed = false;
3257 $w = $this->GetWidth($aImg) + 2*$this->posx;
3258 $h = $this->GetTextHeight($aImg) + 2*$this->posy;
3259
3260 $x = $aImg->left_margin;
3261 $y = $aImg->top_margin;
3262
3263 if( $this->width === TABTITLE_WIDTHFIT ) {
3264 if( $this->align == 'left' ) {
3265 $p = array($x, $y,
3266 $x, $y-$h+$this->corner,
3267 $x + $this->corner,$y-$h,
3268 $x + $w - $this->corner, $y-$h,
3269 $x + $w, $y-$h+$this->corner,
3270 $x + $w, $y);
3271 }
3272 elseif( $this->align == 'center' ) {
3273 $x += round($aImg->plotwidth/2) - round($w/2);
3274 $p = array($x, $y,
3275 $x, $y-$h+$this->corner,
3276 $x + $this->corner, $y-$h,
3277 $x + $w - $this->corner, $y-$h,
3278 $x + $w, $y-$h+$this->corner,
3279 $x + $w, $y);
3280 }
3281 else {
3282 $x += $aImg->plotwidth -$w;
3283 $p = array($x, $y,
3284 $x, $y-$h+$this->corner,
3285 $x + $this->corner,$y-$h,
3286 $x + $w - $this->corner, $y-$h,
3287 $x + $w, $y-$h+$this->corner,
3288 $x + $w, $y);
3289 }
3290 }
3291 else {
3292 if( $this->width === TABTITLE_WIDTHFULL ) {
3293 $w = $aImg->plotwidth ;
3294 }
3295 else {
3296 $w = $this->width ;
3297 }
3298
3299 // Make the tab fit the width of the plot area
3300 $p = array($x, $y,
3301 $x, $y-$h+$this->corner,
3302 $x + $this->corner,$y-$h,
3303 $x + $w - $this->corner, $y-$h,
3304 $x + $w, $y-$h+$this->corner,
3305 $x + $w, $y);
3306
3307 }
3308 if( $this->halign == 'left' ) {
3309 $aImg->SetTextAlign('left','bottom');
3310 $x += $this->posx;
3311 $y -= $this->posy;
3312 }
3313 elseif( $this->halign == 'center' ) {
3314 $aImg->SetTextAlign('center','bottom');
3315 $x += $w/2;
3316 $y -= $this->posy;
3317 }
3318 else {
3319 $aImg->SetTextAlign('right','bottom');
3320 $x += $w - $this->posx;
3321 $y -= $this->posy;
3322 }
3323
3324 $aImg->SetColor($this->fillcolor);
3325 $aImg->FilledPolygon($p);
3326
3327 $aImg->SetColor($this->bordercolor);
3328 $aImg->Polygon($p,true);
3329
3330 $aImg->SetColor($this->color);
3331 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3332 $aImg->StrokeText($x,$y,$this->t,0,'center');
3333 }
3334
3335}
3336
3337//===================================================
3338// CLASS SuperScriptText
3339// Description: Format a superscript text
3340//===================================================
3341class SuperScriptText extends Text {
3342 private $iSuper='';
3343 private $sfont_family='',$sfont_style='',$sfont_size=8;
3344 private $iSuperMargin=2,$iVertOverlap=4,$iSuperScale=0.65;
3345 private $iSDir=0;
3346 private $iSimple=false;
3347
3348 function __construct($aTxt='',$aSuper='',$aXAbsPos=0,$aYAbsPos=0) {
3349 parent::__construct($aTxt,$aXAbsPos,$aYAbsPos);
3350 $this->iSuper = $aSuper;
3351 }
3352
3353 function FromReal($aVal,$aPrecision=2) {
3354 // Convert a floating point number to scientific notation
3355 $neg=1.0;
3356 if( $aVal < 0 ) {
3357 $neg = -1.0;
3358 $aVal = -$aVal;
3359 }
3360
3361 $l = floor(log10($aVal));
3362 $a = sprintf("%0.".$aPrecision."f",round($aVal / pow(10,$l),$aPrecision));
3363 $a *= $neg;
3364 if( $this->iSimple && ($a == 1 || $a==-1) ) $a = '';
3365
3366 if( $a != '' ) {
3367 $this->t = $a.' * 10';
3368 }
3369 else {
3370 if( $neg == 1 ) {
3371 $this->t = '10';
3372 }
3373 else {
3374 $this->t = '-10';
3375 }
3376 }
3377 $this->iSuper = $l;
3378 }
3379
3380 function Set($aTxt,$aSuper='') {
3381 $this->t = $aTxt;
3382 $this->iSuper = $aSuper;
3383 }
3384
3385 function SetSuperFont($aFontFam,$aFontStyle=FS_NORMAL,$aFontSize=8) {
3386 $this->sfont_family = $aFontFam;
3387 $this->sfont_style = $aFontStyle;
3388 $this->sfont_size = $aFontSize;
3389 }
3390
3391 // Total width of text
3392 function GetWidth($aImg) {
3393 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3394 $w = $aImg->GetTextWidth($this->t);
3395 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3396 $w += $aImg->GetTextWidth($this->iSuper);
3397 $w += $this->iSuperMargin;
3398 return $w;
3399 }
3400
3401 // Hight of font (approximate the height of the text)
3402 function GetFontHeight($aImg) {
3403 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3404 $h = $aImg->GetFontHeight();
3405 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3406 $h += $aImg->GetFontHeight();
3407 return $h;
3408 }
3409
3410 // Hight of text
3411 function GetTextHeight($aImg) {
3412 $aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
3413 $h = $aImg->GetTextHeight($this->t);
3414 $aImg->SetFont($this->sfont_family,$this->sfont_style,$this->sfont_size);
3415 $h += $aImg->GetTextHeight($this->iSuper);
3416 return $h;
3417 }
3418
3419 function Stroke($aImg,$ax=-1,$ay=-1) {
3420
3421 // To position the super script correctly we need different
3422 // cases to handle the alignmewnt specified since that will
3423 // determine how we can interpret the x,y coordinates
3424
3425 $w = parent::GetWidth($aImg);
3426 $h = parent::GetTextHeight($aImg);
3427 switch( $this->valign ) {
3428 case 'top':
3429 $sy = $this->y;
3430 break;
3431 case 'center':
3432 $sy = $this->y - $h/2;
3433 break;
3434 case 'bottom':
3435 $sy = $this->y - $h;
3436 break;
3437 default:
3438 JpGraphError::RaiseL(25052);//('PANIC: Internal error in SuperScript::Stroke(). Unknown vertical alignment for text');
3439 break;
3440 }
3441
3442 switch( $this->halign ) {
3443 case 'left':
3444 $sx = $this->x + $w;
3445 break;
3446 case 'center':
3447 $sx = $this->x + $w/2;
3448 break;
3449 case 'right':
3450 $sx = $this->x;
3451 break;
3452 default:
3453 JpGraphError::RaiseL(25053);//('PANIC: Internal error in SuperScript::Stroke(). Unknown horizontal alignment for text');
3454 break;
3455 }
3456
3457 $sx += $this->iSuperMargin;
3458 $sy += $this->iVertOverlap;
3459
3460 // Should we automatically determine the font or
3461 // has the user specified it explicetly?
3462 if( $this->sfont_family == '' ) {
3463 if( $this->font_family <= FF_FONT2 ) {
3464 if( $this->font_family == FF_FONT0 ) {
3465 $sff = FF_FONT0;
3466 }
3467 elseif( $this->font_family == FF_FONT1 ) {
3468 if( $this->font_style == FS_NORMAL ) {
3469 $sff = FF_FONT0;
3470 }
3471 else {
3472 $sff = FF_FONT1;
3473 }
3474 }
3475 else {
3476 $sff = FF_FONT1;
3477 }
3478 $sfs = $this->font_style;
3479 $sfz = $this->font_size;
3480 }
3481 else {
3482 // TTF fonts
3483 $sff = $this->font_family;
3484 $sfs = $this->font_style;
3485 $sfz = floor($this->font_size*$this->iSuperScale);
3486 if( $sfz < 8 ) $sfz = 8;
3487 }
3488 $this->sfont_family = $sff;
3489 $this->sfont_style = $sfs;
3490 $this->sfont_size = $sfz;
3491 }
3492 else {
3493 $sff = $this->sfont_family;
3494 $sfs = $this->sfont_style;
3495 $sfz = $this->sfont_size;
3496 }
3497
3498 parent::Stroke($aImg,$ax,$ay);
3499
3500 // For the builtin fonts we need to reduce the margins
3501 // since the bounding bx reported for the builtin fonts
3502 // are much larger than for the TTF fonts.
3503 if( $sff <= FF_FONT2 ) {
3504 $sx -= 2;
3505 $sy += 3;
3506 }
3507
3508 $aImg->SetTextAlign('left','bottom');
3509 $aImg->SetFont($sff,$sfs,$sfz);
3510 $aImg->PushColor($this->color);
3511 $aImg->StrokeText($sx,$sy,$this->iSuper,$this->iSDir,'left');
3512 $aImg->PopColor();
3513 }
3514}
3515
3516
3517//===================================================
3518// CLASS Grid
3519// Description: responsible for drawing grid lines in graph
3520//===================================================
3521class Grid {
3522 protected $img;
3523 protected $scale;
3524 protected $majorcolor='#CCCCCC',$minorcolor='#DDDDDD';
3525 protected $majortype='solid',$minortype='solid';
3526 protected $show=false, $showMinor=false,$majorweight=1,$minorweight=1;
3527 protected $fill=false,$fillcolor=array('#EFEFEF','#BBCCFF');
3528
3529 function __construct($aAxis) {
3530 $this->scale = $aAxis->scale;
3531 $this->img = $aAxis->img;
3532 }
3533
3534 function SetColor($aMajColor,$aMinColor=false) {
3535 $this->majorcolor=$aMajColor;
3536 if( $aMinColor === false ) {
3537 $aMinColor = $aMajColor ;
3538 }
3539 $this->minorcolor = $aMinColor;
3540 }
3541
3542 function SetWeight($aMajorWeight,$aMinorWeight=1) {
3543 $this->majorweight=$aMajorWeight;
3544 $this->minorweight=$aMinorWeight;
3545 }
3546
3547 // Specify if grid should be dashed, dotted or solid
3548 function SetLineStyle($aMajorType,$aMinorType='solid') {
3549 $this->majortype = $aMajorType;
3550 $this->minortype = $aMinorType;
3551 }
3552
3553 function SetStyle($aMajorType,$aMinorType='solid') {
3554 $this->SetLineStyle($aMajorType,$aMinorType);
3555 }
3556
3557 // Decide if both major and minor grid should be displayed
3558 function Show($aShowMajor=true,$aShowMinor=false) {
3559 $this->show=$aShowMajor;
3560 $this->showMinor=$aShowMinor;
3561 }
3562
3563 function SetFill($aFlg=true,$aColor1='lightgray',$aColor2='lightblue') {
3564 $this->fill = $aFlg;
3565 $this->fillcolor = array( $aColor1, $aColor2 );
3566 }
3567
3568 // Display the grid
3569 function Stroke() {
3570 if( $this->showMinor && !$this->scale->textscale ) {
3571 $this->DoStroke($this->scale->ticks->ticks_pos,$this->minortype,$this->minorcolor,$this->minorweight);
3572 $this->DoStroke($this->scale->ticks->maj_ticks_pos,$this->majortype,$this->majorcolor,$this->majorweight);
3573 }
3574 else {
3575 $this->DoStroke($this->scale->ticks->maj_ticks_pos,$this->majortype,$this->majorcolor,$this->majorweight);
3576 }
3577 }
3578
3579 //--------------
3580 // Private methods
3581 // Draw the grid
3582 function DoStroke($aTicksPos,$aType,$aColor,$aWeight) {
3583 if( !$this->show ) return;
3584 $nbrgrids = count($aTicksPos);
3585
3586 if( $this->scale->type == 'y' ) {
3587 $xl=$this->img->left_margin;
3588 $xr=$this->img->width-$this->img->right_margin;
3589
3590 if( $this->fill ) {
3591 // Draw filled areas
3592 $y2 = !empty($aTicksPos) ? $aTicksPos[0] : null;
3593 $i=1;
3594 while( $i < $nbrgrids ) {
3595 $y1 = $y2;
3596 $y2 = $aTicksPos[$i++];
3597 $this->img->SetColor($this->fillcolor[$i & 1]);
3598 $this->img->FilledRectangle($xl,$y1,$xr,$y2);
3599 }
3600 }
3601
3602 $this->img->SetColor($aColor);
3603 $this->img->SetLineWeight($aWeight);
3604
3605 // Draw grid lines
3606 switch( $aType ) {
3607 case 'solid': $style = LINESTYLE_SOLID; break;
3608 case 'dotted': $style = LINESTYLE_DOTTED; break;
3609 case 'dashed': $style = LINESTYLE_DASHED; break;
3610 case 'longdashed': $style = LINESTYLE_LONGDASH; break;
3611 default:
3612 $style = LINESTYLE_SOLID; break;
3613 }
3614
3615 for($i=0; $i < $nbrgrids; ++$i) {
3616 $y=$aTicksPos[$i];
3617 $this->img->StyleLine($xl,$y,$xr,$y,$style,true);
3618 }
3619 }
3620 elseif( $this->scale->type == 'x' ) {
3621 $yu=$this->img->top_margin;
3622 $yl=$this->img->height-$this->img->bottom_margin;
3623 $limit=$this->img->width-$this->img->right_margin;
3624
3625 if( $this->fill ) {
3626 // Draw filled areas
3627 $x2 = $aTicksPos[0];
3628 $i=1;
3629 while( $i < $nbrgrids ) {
3630 $x1 = $x2;
3631 $x2 = min($aTicksPos[$i++],$limit) ;
3632 $this->img->SetColor($this->fillcolor[$i & 1]);
3633 $this->img->FilledRectangle($x1,$yu,$x2,$yl);
3634 }
3635 }
3636
3637 $this->img->SetColor($aColor);
3638 $this->img->SetLineWeight($aWeight);
3639
3640 // We must also test for limit since we might have
3641 // an offset and the number of ticks is calculated with
3642 // assumption offset==0 so we might end up drawing one
3643 // to many gridlines
3644 $i=0;
3645 while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) {
3646 if ( $aType == 'solid' ) $this->img->Line($x,$yl,$x,$yu);
3647 elseif( $aType == 'dotted' ) $this->img->DashedLineForGrid($x,$yl,$x,$yu,1,6);
3648 elseif( $aType == 'dashed' ) $this->img->DashedLineForGrid($x,$yl,$x,$yu,2,4);
3649 elseif( $aType == 'longdashed' ) $this->img->DashedLineForGrid($x,$yl,$x,$yu,8,6);
3650 ++$i;
3651 }
3652 }
3653 else {
3654 JpGraphError::RaiseL(25054,$this->scale->type);//('Internal error: Unknown grid axis ['.$this->scale->type.']');
3655 }
3656 return true;
3657 }
3658} // Class
3659
3660//===================================================
3661// CLASS Axis
3662// Description: Defines X and Y axis. Notes that at the
3663// moment the code is not really good since the axis on
3664// several occasion must know wheter it's an X or Y axis.
3665// This was a design decision to make the code easier to
3666// follow.
3667//===================================================
3668class AxisPrototype {
3669 public $scale=null;
3670 public $img=null;
3671 public $hide=false,$hide_labels=false;
3672 public $title=null;
3673 public $font_family=FF_DEFAULT,$font_style=FS_NORMAL,$font_size=8,$label_angle=0;
3674 public $tick_step=1;
3675 public $pos = false;
3676 public $ticks_label = array();
3677
3678 protected $weight=1;
3679 protected $color=array(0,0,0),$label_color=array(0,0,0);
3680 protected $ticks_label_colors=null;
3681 protected $show_first_label=true,$show_last_label=true;
3682 protected $label_step=1; // Used by a text axis to specify what multiple of major steps
3683 // should be labeled.
3684 protected $labelPos=0; // Which side of the axis should the labels be?
3685 protected $title_adjust,$title_margin,$title_side=SIDE_LEFT;
3686 protected $tick_label_margin=5;
3687 protected $label_halign = '',$label_valign = '', $label_para_align='left';
3688 protected $hide_line=false;
3689 protected $iDeltaAbsPos=0;
3690
3691 function __construct($img,$aScale,$color = array(0,0,0)) {
3692 $this->img = $img;
3693 $this->scale = $aScale;
3694 $this->color = $color;
3695 $this->title=new Text('');
3696
3697 if( $aScale->type == 'y' ) {
3698 $this->title_margin = 25;
3699 $this->title_adjust = 'middle';
3700 $this->title->SetOrientation(90);
3701 $this->tick_label_margin=7;
3702 $this->labelPos=SIDE_LEFT;
3703 }
3704 else {
3705 $this->title_margin = 5;
3706 $this->title_adjust = 'high';
3707 $this->title->SetOrientation(0);
3708 $this->tick_label_margin=5;
3709 $this->labelPos=SIDE_DOWN;
3710 $this->title_side=SIDE_DOWN;
3711 }
3712 }
3713
3714 function SetLabelFormat($aFormStr) {
3715 $this->scale->ticks->SetLabelFormat($aFormStr);
3716 }
3717
3718 function SetLabelFormatString($aFormStr,$aDate=false) {
3719 $this->scale->ticks->SetLabelFormat($aFormStr,$aDate);
3720 }
3721
3722 function SetLabelFormatCallback($aFuncName) {
3723 $this->scale->ticks->SetFormatCallback($aFuncName);
3724 }
3725
3726 function SetLabelAlign($aHAlign,$aVAlign='top',$aParagraphAlign='left') {
3727 $this->label_halign = $aHAlign;
3728 $this->label_valign = $aVAlign;
3729 $this->label_para_align = $aParagraphAlign;
3730 }
3731
3732 // Don't display the first label
3733 function HideFirstTickLabel($aShow=false) {
3734 $this->show_first_label=$aShow;
3735 }
3736
3737 function HideLastTickLabel($aShow=false) {
3738 $this->show_last_label=$aShow;
3739 }
3740
3741 // Manually specify the major and (optional) minor tick position and labels
3742 function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
3743 $this->scale->ticks->SetTickPositions($aMajPos,$aMinPos,$aLabels);
3744 }
3745
3746 // Manually specify major tick positions and optional labels
3747 function SetMajTickPositions($aMajPos,$aLabels=NULL) {
3748 $this->scale->ticks->SetTickPositions($aMajPos,NULL,$aLabels);
3749 }
3750
3751 // Hide minor or major tick marks
3752 function HideTicks($aHideMinor=true,$aHideMajor=true) {
3753 $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
3754 $this->scale->ticks->SupressTickMarks($aHideMajor);
3755 }
3756
3757 // Hide zero label
3758 function HideZeroLabel($aFlag=true) {
3759 $this->scale->ticks->SupressZeroLabel();
3760 }
3761
3762 function HideFirstLastLabel() {
3763 // The two first calls to ticks method will supress
3764 // automatically generated scale values. However, that
3765 // will not affect manually specified value, e.g text-scales.
3766 // therefor we also make a kludge here to supress manually
3767 // specified scale labels.
3768 $this->scale->ticks->SupressLast();
3769 $this->scale->ticks->SupressFirst();
3770 $this->show_first_label = false;
3771 $this->show_last_label = false;
3772 }
3773
3774 // Hide the axis
3775 function Hide($aHide=true) {
3776 $this->hide=$aHide;
3777 }
3778
3779 // Hide the actual axis-line, but still print the labels
3780 function HideLine($aHide=true) {
3781 $this->hide_line = $aHide;
3782 }
3783
3784 function HideLabels($aHide=true) {
3785 $this->hide_labels = $aHide;
3786 }
3787
3788 // Weight of axis
3789 function SetWeight($aWeight) {
3790 $this->weight = $aWeight;
3791 }
3792
3793 // Axis color
3794 function SetColor($aColor,$aLabelColor=false) {
3795 $this->color = $aColor;
3796 if( !$aLabelColor ) $this->label_color = $aColor;
3797 else $this->label_color = $aLabelColor;
3798 }
3799
3800 // Title on axis
3801 function SetTitle($aTitle,$aAdjustAlign='high') {
3802 $this->title->Set($aTitle);
3803 $this->title_adjust=$aAdjustAlign;
3804 }
3805
3806 // Specify distance from the axis
3807 function SetTitleMargin($aMargin) {
3808 $this->title_margin=$aMargin;
3809 }
3810
3811 // Which side of the axis should the axis title be?
3812 function SetTitleSide($aSideOfAxis) {
3813 $this->title_side = $aSideOfAxis;
3814 }
3815
3816 function SetTickSide($aDir) {
3817 $this->scale->ticks->SetSide($aDir);
3818 }
3819
3820 function SetTickSize($aMajSize,$aMinSize=3) {
3821 $this->scale->ticks->SetSize($aMajSize,$aMinSize=3);
3822 }
3823
3824 // Specify text labels for the ticks. One label for each data point
3825 function SetTickLabels($aLabelArray,$aLabelColorArray=null) {
3826 $this->ticks_label = $aLabelArray;
3827 $this->ticks_label_colors = $aLabelColorArray;
3828 }
3829
3830 function SetLabelMargin($aMargin) {
3831 $this->tick_label_margin=$aMargin;
3832 }
3833
3834 // Specify that every $step of the ticks should be displayed starting
3835 // at $start
3836 function SetTextTickInterval($aStep,$aStart=0) {
3837 $this->scale->ticks->SetTextLabelStart($aStart);
3838 $this->tick_step=$aStep;
3839 }
3840
3841 // Specify that every $step tick mark should have a label
3842 // should be displayed starting
3843 function SetTextLabelInterval($aStep) {
3844 if( $aStep < 1 ) {
3845 JpGraphError::RaiseL(25058);//(" Text label interval must be specified >= 1.");
3846 }
3847 $this->label_step=$aStep;
3848 }
3849
3850 function SetLabelSide($aSidePos) {
3851 $this->labelPos=$aSidePos;
3852 }
3853
3854 // Set the font
3855 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
3856 $this->font_family = $aFamily;
3857 $this->font_style = $aStyle;
3858 $this->font_size = $aSize;
3859 }
3860
3861 // Position for axis line on the "other" scale
3862 function SetPos($aPosOnOtherScale) {
3863 $this->pos=$aPosOnOtherScale;
3864 }
3865
3866 // Set the position of the axis to be X-pixels delta to the right
3867 // of the max X-position (used to position the multiple Y-axis)
3868 function SetPosAbsDelta($aDelta) {
3869 $this->iDeltaAbsPos=$aDelta;
3870 }
3871
3872 // Specify the angle for the tick labels
3873 function SetLabelAngle($aAngle) {
3874 $this->label_angle = $aAngle;
3875 }
3876
3877} // Class
3878
3879
3880//===================================================
3881// CLASS Axis
3882// Description: Defines X and Y axis. Notes that at the
3883// moment the code is not really good since the axis on
3884// several occasion must know wheter it's an X or Y axis.
3885// This was a design decision to make the code easier to
3886// follow.
3887//===================================================
3888class Axis extends AxisPrototype {
3889
3890 function __construct($img,$aScale,$color='black') {
3891 parent::__construct($img,$aScale,$color);
3892 }
3893
3894 // Stroke the axis.
3895 function Stroke($aOtherAxisScale,$aStrokeLabels=true) {
3896 if( $this->hide )
3897 return;
3898 if( is_numeric($this->pos) ) {
3899 $pos=$aOtherAxisScale->Translate($this->pos);
3900 }
3901 else { // Default to minimum of other scale if pos not set
3902 if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos == 'min' ) {
3903 $pos = $aOtherAxisScale->scale_abs[0];
3904 }
3905 elseif($this->pos == "max") {
3906 $pos = $aOtherAxisScale->scale_abs[1];
3907 }
3908 else { // If negative set x-axis at 0
3909 $this->pos=0;
3910 $pos=$aOtherAxisScale->Translate(0);
3911 }
3912 }
3913
3914 $pos += $this->iDeltaAbsPos;
3915 $this->img->SetLineWeight($this->weight);
3916 $this->img->SetColor($this->color);
3917 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
3918
3919 if( $this->scale->type == "x" ) {
3920 if( !$this->hide_line ) {
3921 // Stroke X-axis
3922 $this->img->FilledRectangle(
3923 $this->img->left_margin,
3924 $pos,
3925 $this->img->width - $this->img->right_margin,
3926 $pos + $this->weight-1
3927 );
3928 }
3929 if( $this->title_side == SIDE_DOWN ) {
3930 $y = $pos + $this->img->GetFontHeight() + $this->title_margin + $this->title->margin;
3931 $yalign = 'top';
3932 }
3933 else {
3934 $y = $pos - $this->img->GetFontHeight() - $this->title_margin - $this->title->margin;
3935 $yalign = 'bottom';
3936 }
3937
3938 if( $this->title_adjust=='high' ) {
3939 $this->title->SetPos($this->img->width-$this->img->right_margin,$y,'right',$yalign);
3940 }
3941 elseif( $this->title_adjust=='middle' || $this->title_adjust=='center' ) {
3942 $this->title->SetPos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,'center',$yalign);
3943 }
3944 elseif($this->title_adjust=='low') {
3945 $this->title->SetPos($this->img->left_margin,$y,'left',$yalign);
3946 }
3947 else {
3948 JpGraphError::RaiseL(25060,$this->title_adjust);//('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
3949 }
3950 }
3951 elseif( $this->scale->type == "y" ) {
3952 // Add line weight to the height of the axis since
3953 // the x-axis could have a width>1 and we want the axis to fit nicely together.
3954 if( !$this->hide_line ) {
3955 // Stroke Y-axis
3956 $this->img->FilledRectangle(
3957 $pos - $this->weight + 1,
3958 $this->img->top_margin,
3959 $pos,
3960 $this->img->height - $this->img->bottom_margin + $this->weight - 1
3961 );
3962 }
3963
3964 $x=$pos ;
3965 if( $this->title_side == SIDE_LEFT ) {
3966 $x -= $this->title_margin;
3967 $x -= $this->title->margin;
3968 $halign = 'right';
3969 }
3970 else {
3971 $x += $this->title_margin;
3972 $x += $this->title->margin;
3973 $halign = 'left';
3974 }
3975 // If the user has manually specified an hor. align
3976 // then we override the automatic settings with this
3977 // specifed setting. Since default is 'left' we compare
3978 // with that. (This means a manually set 'left' align
3979 // will have no effect.)
3980 if( $this->title->halign != 'left' ) {
3981 $halign = $this->title->halign;
3982 }
3983 if( $this->title_adjust == 'high' ) {
3984 $this->title->SetPos($x,$this->img->top_margin,$halign,'top');
3985 }
3986 elseif($this->title_adjust=='middle' || $this->title_adjust=='center') {
3987 $this->title->SetPos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
3988 }
3989 elseif($this->title_adjust=='low') {
3990 $this->title->SetPos($x,$this->img->height-$this->img->bottom_margin,$halign,'bottom');
3991 }
3992 else {
3993 JpGraphError::RaiseL(25061,$this->title_adjust);//('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
3994 }
3995 }
3996 $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
3997 if( $aStrokeLabels ) {
3998 if( !$this->hide_labels ) {
3999 $this->StrokeLabels($pos);
4000 }
4001 $this->title->Stroke($this->img);
4002 }
4003 }
4004
4005 //---------------
4006 // PRIVATE METHODS
4007 // Draw all the tick labels on major tick marks
4008 function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
4009
4010 if( is_array($this->label_color) && count($this->label_color) > 3 ) {
4011 $this->ticks_label_colors = $this->label_color;
4012 $this->img->SetColor($this->label_color[0]);
4013 }
4014 else {
4015 $this->img->SetColor($this->label_color);
4016 }
4017 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
4018 $yoff=$this->img->GetFontHeight()/2;
4019
4020 // Only draw labels at major tick marks
4021 $nbr = count($this->scale->ticks->maj_ticks_label);
4022
4023 // We have the option to not-display the very first mark
4024 // (Usefull when the first label might interfere with another
4025 // axis.)
4026 $i = $this->show_first_label ? 0 : 1 ;
4027 if( !$this->show_last_label ) {
4028 --$nbr;
4029 }
4030 // Now run through all labels making sure we don't overshoot the end
4031 // of the scale.
4032 $ncolor=0;
4033 if( isset($this->ticks_label_colors) ) {
4034 $ncolor=count($this->ticks_label_colors);
4035 }
4036 while( $i < $nbr ) {
4037 // $tpos holds the absolute text position for the label
4038 $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
4039
4040 // Note. the $limit is only used for the x axis since we
4041 // might otherwise overshoot if the scale has been centered
4042 // This is due to us "loosing" the last tick mark if we center.
4043 if( $this->scale->type == 'x' && $tpos > $this->img->width-$this->img->right_margin+1 ) {
4044 return;
4045 }
4046 // we only draw every $label_step label
4047 if( ($i % $this->label_step)==0 ) {
4048
4049 // Set specific label color if specified
4050 if( $ncolor > 0 ) {
4051 $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
4052 }
4053
4054 // If the label has been specified use that and in other case
4055 // just label the mark with the actual scale value
4056 $m=$this->scale->ticks->GetMajor();
4057
4058 // ticks_label has an entry for each data point and is the array
4059 // that holds the labels set by the user. If the user hasn't
4060 // specified any values we use whats in the automatically asigned
4061 // labels in the maj_ticks_label
4062 if( isset($this->ticks_label[$i*$m]) ) {
4063 $label=$this->ticks_label[$i*$m];
4064 }
4065 else {
4066 if( $aAbsLabel ) {
4067 $label=abs($this->scale->ticks->maj_ticks_label[$i]);
4068 }
4069 else {
4070 $label=$this->scale->ticks->maj_ticks_label[$i];
4071 }
4072
4073 // We number the scale from 1 and not from 0 so increase by one
4074 if( $this->scale->textscale &&
4075 $this->scale->ticks->label_formfunc == '' &&
4076 ! $this->scale->ticks->HaveManualLabels() ) {
4077
4078 ++$label;
4079
4080 }
4081 }
4082
4083 if( $this->scale->type == "x" ) {
4084 if( $this->labelPos == SIDE_DOWN ) {
4085 if( $this->label_angle==0 || $this->label_angle==90 ) {
4086 if( $this->label_halign=='' && $this->label_valign=='') {
4087 $this->img->SetTextAlign('center','top');
4088 }
4089 else {
4090 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4091 }
4092
4093 }
4094 else {
4095 if( $this->label_halign=='' && $this->label_valign=='') {
4096 $this->img->SetTextAlign("right","top");
4097 }
4098 else {
4099 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4100 }
4101 }
4102 $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
4103 $this->label_angle,$this->label_para_align);
4104 }
4105 else {
4106 if( $this->label_angle==0 || $this->label_angle==90 ) {
4107 if( $this->label_halign=='' && $this->label_valign=='') {
4108 $this->img->SetTextAlign("center","bottom");
4109 }
4110 else {
4111 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4112 }
4113 }
4114 else {
4115 if( $this->label_halign=='' && $this->label_valign=='') {
4116 $this->img->SetTextAlign("right","bottom");
4117 }
4118 else {
4119 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4120 }
4121 }
4122 $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin-1,$label,
4123 $this->label_angle,$this->label_para_align);
4124 }
4125 }
4126 else {
4127 // scale->type == "y"
4128 //if( $this->label_angle!=0 )
4129 //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
4130 if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis
4131 if( $this->label_halign=='' && $this->label_valign=='') {
4132 $this->img->SetTextAlign("right","center");
4133 }
4134 else {
4135 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4136 }
4137 $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
4138 }
4139 else { // To the right of the y-axis
4140 if( $this->label_halign=='' && $this->label_valign=='') {
4141 $this->img->SetTextAlign("left","center");
4142 }
4143 else {
4144 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4145 }
4146 $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
4147 }
4148 }
4149 }
4150 ++$i;
4151 }
4152 }
4153
4154}
4155
4156
4157//===================================================
4158// CLASS Ticks
4159// Description: Abstract base class for drawing linear and logarithmic
4160// tick marks on axis
4161//===================================================
4162class Ticks {
4163 public $label_formatstr=''; // C-style format string to use for labels
4164 public $label_formfunc='';
4165 public $label_dateformatstr='';
4166 public $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)
4167 public $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
4168 public $maj_ticks_pos = array(), $maj_ticklabels_pos = array(),
4169 $ticks_pos = array(), $maj_ticks_label = array();
4170 public $precision;
4171
4172 protected $minor_abs_size=3, $major_abs_size=5;
4173 protected $scale;
4174 protected $is_set=false;
4175 protected $supress_zerolabel=false,$supress_first=false;
4176 protected $mincolor='',$majcolor='';
4177 protected $weight=1;
4178 protected $label_usedateformat=FALSE;
4179
4180 function __construct($aScale) {
4181 $this->scale=$aScale;
4182 $this->precision = -1;
4183 }
4184
4185 // Set format string for automatic labels
4186 function SetLabelFormat($aFormatString,$aDate=FALSE) {
4187 $this->label_formatstr=$aFormatString;
4188 $this->label_usedateformat=$aDate;
4189 }
4190
4191 function SetLabelDateFormat($aFormatString) {
4192 $this->label_dateformatstr=$aFormatString;
4193 }
4194
4195 function SetFormatCallback($aCallbackFuncName) {
4196 $this->label_formfunc = $aCallbackFuncName;
4197 }
4198
4199 // Don't display the first zero label
4200 function SupressZeroLabel($aFlag=true) {
4201 $this->supress_zerolabel=$aFlag;
4202 }
4203
4204 // Don't display minor tick marks
4205 function SupressMinorTickMarks($aHide=true) {
4206 $this->supress_minor_tickmarks=$aHide;
4207 }
4208
4209 // Don't display major tick marks
4210 function SupressTickMarks($aHide=true) {
4211 $this->supress_tickmarks=$aHide;
4212 }
4213
4214 // Hide the first tick mark
4215 function SupressFirst($aHide=true) {
4216 $this->supress_first=$aHide;
4217 }
4218
4219 // Hide the last tick mark
4220 function SupressLast($aHide=true) {
4221 $this->supress_last=$aHide;
4222 }
4223
4224 // Size (in pixels) of minor tick marks
4225 function GetMinTickAbsSize() {
4226 return $this->minor_abs_size;
4227 }
4228
4229 // Size (in pixels) of major tick marks
4230 function GetMajTickAbsSize() {
4231 return $this->major_abs_size;
4232 }
4233
4234 function SetSize($aMajSize,$aMinSize=3) {
4235 $this->major_abs_size = $aMajSize;
4236 $this->minor_abs_size = $aMinSize;
4237 }
4238
4239 // Have the ticks been specified
4240 function IsSpecified() {
4241 return $this->is_set;
4242 }
4243
4244 function SetSide($aSide) {
4245 $this->direction=$aSide;
4246 }
4247
4248 // Which side of the axis should the ticks be on
4249 function SetDirection($aSide=SIDE_RIGHT) {
4250 $this->direction=$aSide;
4251 }
4252
4253 // Set colors for major and minor tick marks
4254 function SetMarkColor($aMajorColor,$aMinorColor='') {
4255 $this->SetColor($aMajorColor,$aMinorColor);
4256 }
4257
4258 function SetColor($aMajorColor,$aMinorColor='') {
4259 $this->majcolor=$aMajorColor;
4260
4261 // If not specified use same as major
4262 if( $aMinorColor == '' ) {
4263 $this->mincolor=$aMajorColor;
4264 }
4265 else {
4266 $this->mincolor=$aMinorColor;
4267 }
4268 }
4269
4270 function SetWeight($aWeight) {
4271 $this->weight=$aWeight;
4272 }
4273
4274} // Class
4275
4276//===================================================
4277// CLASS LinearTicks
4278// Description: Draw linear ticks on axis
4279//===================================================
4280class LinearTicks extends Ticks {
4281 public $minor_step=1, $major_step=2;
4282 public $xlabel_offset=0,$xtick_offset=0;
4283 private $label_offset=0; // What offset should the displayed label have
4284 // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
4285 private $text_label_start=0;
4286 private $iManualTickPos = NULL, $iManualMinTickPos = NULL, $iManualTickLabels = NULL;
4287 private $iAdjustForDST = false; // If a date falls within the DST period add one hour to the diaplyed time
4288
4289 function __construct() {
4290 $this->precision = -1;
4291 }
4292
4293 // Return major step size in world coordinates
4294 function GetMajor() {
4295 return $this->major_step;
4296 }
4297
4298 // Return minor step size in world coordinates
4299 function GetMinor() {
4300 return $this->minor_step;
4301 }
4302
4303 // Set Minor and Major ticks (in world coordinates)
4304 function Set($aMajStep,$aMinStep=false) {
4305 if( $aMinStep==false ) {
4306 $aMinStep=$aMajStep;
4307 }
4308
4309 if( $aMajStep <= 0 || $aMinStep <= 0 ) {
4310 JpGraphError::RaiseL(25064);
4311 //(" Minor or major step size is 0. Check that you haven't got an accidental SetTextTicks(0) in your code. If this is not the case you might have stumbled upon a bug in JpGraph. Please report this and if possible include the data that caused the problem.");
4312 }
4313
4314 $this->major_step=$aMajStep;
4315 $this->minor_step=$aMinStep;
4316 $this->is_set = true;
4317 }
4318
4319 function SetMajTickPositions($aMajPos,$aLabels=NULL) {
4320 $this->SetTickPositions($aMajPos,NULL,$aLabels);
4321 }
4322
4323 function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
4324 if( !is_array($aMajPos) || ($aMinPos!==NULL && !is_array($aMinPos)) ) {
4325 JpGraphError::RaiseL(25065);//('Tick positions must be specifued as an array()');
4326 return;
4327 }
4328 $n=count($aMajPos);
4329 if( is_array($aLabels) && (count($aLabels) != $n) ) {
4330 JpGraphError::RaiseL(25066);//('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.');
4331 }
4332 $this->iManualTickPos = $aMajPos;
4333 $this->iManualMinTickPos = $aMinPos;
4334 $this->iManualTickLabels = $aLabels;
4335 }
4336
4337 function HaveManualLabels() {
4338 return is_array($this->iManualTickLabels) ? count($this->iManualTickLabels) > 0 : false;
4339 }
4340
4341 // Specify all the tick positions manually and possible also the exact labels
4342 function _doManualTickPos($aScale) {
4343 $n=count($this->iManualTickPos);
4344 $m= is_array($this->iManualMinTickPos) ? count($this->iManualMinTickPos) : 0;
4345 $doLbl= is_array($this->iManualTickLabels) ? count($this->iManualTickLabels) > 0 : false;
4346
4347 $this->maj_ticks_pos = array();
4348 $this->maj_ticklabels_pos = array();
4349 $this->ticks_pos = array();
4350
4351 // Now loop through the supplied positions and translate them to screen coordinates
4352 // and store them in the maj_label_positions
4353 $minScale = $aScale->scale[0];
4354 $maxScale = $aScale->scale[1];
4355 $j=0;
4356 for($i=0; $i < $n ; ++$i ) {
4357 // First make sure that the first tick is not lower than the lower scale value
4358 if( !isset($this->iManualTickPos[$i]) || $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) {
4359 continue;
4360 }
4361
4362 $this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]);
4363 $this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j];
4364
4365 // Set the minor tick marks the same as major if not specified
4366 if( $m <= 0 ) {
4367 $this->ticks_pos[$j] = $this->maj_ticks_pos[$j];
4368 }
4369 if( $doLbl ) {
4370 $this->maj_ticks_label[$j] = $this->iManualTickLabels[$i];
4371 }
4372 else {
4373 $this->maj_ticks_label[$j]=$this->_doLabelFormat($this->iManualTickPos[$i],$i,$n);
4374 }
4375 ++$j;
4376 }
4377
4378 // Some sanity check
4379 if( count($this->maj_ticks_pos) < 2 ) {
4380 JpGraphError::RaiseL(25067);//('Your manually specified scale and ticks is not correct. The scale seems to be too small to hold any of the specified tickl marks.');
4381 }
4382
4383 // Setup the minor tick marks
4384 $j=0;
4385 for($i=0; $i < $m; ++$i ) {
4386 if( empty($this->iManualMinTickPos[$i]) || $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale) {
4387 continue;
4388 }
4389 $this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]);
4390 ++$j;
4391 }
4392 }
4393
4394 function _doAutoTickPos($aScale) {
4395 $maj_step_abs = $aScale->scale_factor*$this->major_step;
4396 $min_step_abs = $aScale->scale_factor*$this->minor_step;
4397
4398 if( $min_step_abs==0 || $maj_step_abs==0 ) {
4399 JpGraphError::RaiseL(25068);//("A plot has an illegal scale. This could for example be that you are trying to use text autoscaling to draw a line plot with only one point or that the plot area is too small. It could also be that no input data value is numeric (perhaps only '-' or 'x')");
4400 }
4401 // We need to make this an int since comparing it below
4402 // with the result from round() can give wrong result, such that
4403 // (40 < 40) == TRUE !!!
4404 $limit = (int)$aScale->scale_abs[1];
4405
4406 if( $aScale->textscale ) {
4407 // This can only be true for a X-scale (horizontal)
4408 // Define ticks for a text scale. This is slightly different from a
4409 // normal linear type of scale since the position might be adjusted
4410 // and the labels start at on
4411 $label = (float)$aScale->GetMinVal()+$this->text_label_start+$this->label_offset;
4412 $start_abs=$aScale->scale_factor*$this->text_label_start;
4413 $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4414
4415 $x = $aScale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs;
4416 for( $i=0; $label <= $aScale->GetMaxVal()+$this->label_offset; ++$i ) {
4417 // Apply format to label
4418 $this->maj_ticks_label[$i]=$this->_doLabelFormat($label,$i,$nbrmajticks);
4419 $label+=$this->major_step;
4420
4421 // The x-position of the tick marks can be different from the labels.
4422 // Note that we record the tick position (not the label) so that the grid
4423 // happen upon tick marks and not labels.
4424 $xtick=$aScale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs;
4425 $this->maj_ticks_pos[$i]=$xtick;
4426 $this->maj_ticklabels_pos[$i] = round($x);
4427 $x += $maj_step_abs;
4428 }
4429 }
4430 else {
4431 $label = $aScale->GetMinVal();
4432 $abs_pos = $aScale->scale_abs[0];
4433 $j=0; $i=0;
4434 $step = round($maj_step_abs/$min_step_abs);
4435 if( $aScale->type == "x" ) {
4436 // For a normal linear type of scale the major ticks will always be multiples
4437 // of the minor ticks. In order to avoid any rounding issues the major ticks are
4438 // defined as every "step" minor ticks and not calculated separately
4439 $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4440 while( round($abs_pos) <= $limit ) {
4441 $this->ticks_pos[] = round($abs_pos);
4442 $this->ticks_label[] = $label;
4443 if( $step== 0 || $i % $step == 0 && $j < $nbrmajticks ) {
4444 $this->maj_ticks_pos[$j] = round($abs_pos);
4445 $this->maj_ticklabels_pos[$j] = round($abs_pos);
4446 $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
4447 ++$j;
4448 }
4449 ++$i;
4450 $abs_pos += $min_step_abs;
4451 $label+=$this->minor_step;
4452 }
4453 }
4454 elseif( $aScale->type == "y" ) {
4455 //@todo s=2:20,12 s=1:50,6 $this->major_step:$nbr
4456 // abs_point,limit s=1:270,80 s=2:540,160
4457 // $this->major_step = 50;
4458 $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal())/$this->major_step)+1;
4459// $step = 5;
4460 while( round($abs_pos) >= $limit ) {
4461 $this->ticks_pos[$i] = round($abs_pos);
4462 $this->ticks_label[$i]=$label;
4463 if( $step== 0 || $i % $step == 0 && $j < $nbrmajticks) {
4464 $this->maj_ticks_pos[$j] = round($abs_pos);
4465 $this->maj_ticklabels_pos[$j] = round($abs_pos);
4466 $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
4467 ++$j;
4468 }
4469 ++$i;
4470 $abs_pos += $min_step_abs;
4471 $label += $this->minor_step;
4472 }
4473 }
4474 }
4475 }
4476
4477 function AdjustForDST($aFlg=true) {
4478 $this->iAdjustForDST = $aFlg;
4479 }
4480
4481
4482 function _doLabelFormat($aVal,$aIdx,$aNbrTicks) {
4483
4484 // If precision hasn't been specified set it to a sensible value
4485 if( $this->precision==-1 ) {
4486 $t = log10($this->minor_step);
4487 if( $t > 0 || $t === 0.0) {
4488 $precision = 0;
4489 }
4490 else {
4491 $precision = -floor($t);
4492 }
4493 }
4494 else {
4495 $precision = $this->precision;
4496 }
4497
4498 if( $this->label_formfunc != '' ) {
4499 $f=$this->label_formfunc;
4500 if( $this->label_formatstr == '' ) {
4501 $l = call_user_func($f,$aVal);
4502 }
4503 else {
4504 $l = sprintf($this->label_formatstr, call_user_func($f,$aVal));
4505 }
4506 }
4507 elseif( $this->label_formatstr != '' || $this->label_dateformatstr != '' ) {
4508 if( $this->label_usedateformat ) {
4509 // Adjust the value to take daylight savings into account
4510 if (date("I",$aVal)==1 && $this->iAdjustForDST ) {
4511 // DST
4512 $aVal+=3600;
4513 }
4514
4515 $l = date($this->label_formatstr,$aVal);
4516 if( $this->label_formatstr == 'W' ) {
4517 // If we use week formatting then add a single 'w' in front of the
4518 // week number to differentiate it from dates
4519 $l = 'w'.$l;
4520 }
4521 }
4522 else {
4523 if( $this->label_dateformatstr !== '' ) {
4524 // Adjust the value to take daylight savings into account
4525 if (date("I",$aVal)==1 && $this->iAdjustForDST ) {
4526 // DST
4527 $aVal+=3600;
4528 }
4529
4530 $l = date($this->label_dateformatstr,$aVal);
4531 if( $this->label_formatstr == 'W' ) {
4532 // If we use week formatting then add a single 'w' in front of the
4533 // week number to differentiate it from dates
4534 $l = 'w'.$l;
4535 }
4536 }
4537 else {
4538 $l = sprintf($this->label_formatstr,$aVal);
4539 }
4540 }
4541 }
4542 else {
4543 $l = sprintf('%01.'.$precision.'f',round($aVal,$precision));
4544 }
4545
4546 if( ($this->supress_zerolabel && $l==0) || ($this->supress_first && $aIdx==0) || ($this->supress_last && $aIdx==$aNbrTicks-1) ) {
4547 $l='';
4548 }
4549 return $l;
4550 }
4551
4552 // Stroke ticks on either X or Y axis
4553 function _StrokeTicks($aImg,$aScale,$aPos) {
4554 $hor = $aScale->type == 'x';
4555 $aImg->SetLineWeight($this->weight);
4556
4557 // We need to make this an int since comparing it below
4558 // with the result from round() can give wrong result, such that
4559 // (40 < 40) == TRUE !!!
4560 $limit = (int)$aScale->scale_abs[1];
4561
4562 // A text scale doesn't have any minor ticks
4563 if( !$aScale->textscale ) {
4564 // Stroke minor ticks
4565 $yu = $aPos - $this->direction*$this->GetMinTickAbsSize();
4566 $xr = $aPos + $this->direction*$this->GetMinTickAbsSize();
4567 $n = count($this->ticks_pos);
4568 for($i=0; $i < $n; ++$i ) {
4569 if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
4570 if( $this->mincolor != '') {
4571 $aImg->PushColor($this->mincolor);
4572 }
4573 if( $hor ) {
4574 //if( $this->ticks_pos[$i] <= $limit )
4575 $aImg->Line($this->ticks_pos[$i],$aPos,$this->ticks_pos[$i],$yu);
4576 }
4577 else {
4578 //if( $this->ticks_pos[$i] >= $limit )
4579 $aImg->Line($aPos,$this->ticks_pos[$i],$xr,$this->ticks_pos[$i]);
4580 }
4581 if( $this->mincolor != '' ) {
4582 $aImg->PopColor();
4583 }
4584 }
4585 }
4586 }
4587
4588 // Stroke major ticks
4589 $yu = $aPos - $this->direction*$this->GetMajTickAbsSize();
4590 $xr = $aPos + $this->direction*$this->GetMajTickAbsSize();
4591 $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4592 $n = count($this->maj_ticks_pos);
4593 for($i=0; $i < $n ; ++$i ) {
4594 if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
4595 if( $this->majcolor != '') {
4596 $aImg->PushColor($this->majcolor);
4597 }
4598 if( $hor ) {
4599 //if( $this->maj_ticks_pos[$i] <= $limit )
4600 $aImg->Line($this->maj_ticks_pos[$i],$aPos,$this->maj_ticks_pos[$i],$yu);
4601 }
4602 else {
4603 //if( $this->maj_ticks_pos[$i] >= $limit )
4604 $aImg->Line($aPos,$this->maj_ticks_pos[$i],$xr,$this->maj_ticks_pos[$i]);
4605 }
4606 if( $this->majcolor != '') {
4607 $aImg->PopColor();
4608 }
4609 }
4610 }
4611
4612 }
4613
4614 // Draw linear ticks
4615 function Stroke($aImg,$aScale,$aPos) {
4616 if( $this->iManualTickPos != NULL ) {
4617 $this->_doManualTickPos($aScale);
4618 }
4619 else {
4620 $this->_doAutoTickPos($aScale);
4621 }
4622 $this->_StrokeTicks($aImg,$aScale,$aPos, $aScale->type == 'x' );
4623 }
4624
4625 //---------------
4626 // PRIVATE METHODS
4627 // Spoecify the offset of the displayed tick mark with the tick "space"
4628 // Legal values for $o is [0,1] used to adjust where the tick marks and label
4629 // should be positioned within the major tick-size
4630 // $lo specifies the label offset and $to specifies the tick offset
4631 // this comes in handy for example in bar graphs where we wont no offset for the
4632 // tick but have the labels displayed halfway under the bars.
4633 function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
4634 $this->xlabel_offset=$aLabelOff;
4635 if( $aTickOff==-1 ) {
4636 // Same as label offset
4637 $this->xtick_offset=$aLabelOff;
4638 }
4639 else {
4640 $this->xtick_offset=$aTickOff;
4641 }
4642 if( $aLabelOff>0 ) {
4643 $this->SupressLast(); // The last tick wont fit
4644 }
4645 }
4646
4647 // Which tick label should we start with?
4648 function SetTextLabelStart($aTextLabelOff) {
4649 $this->text_label_start=$aTextLabelOff;
4650 }
4651
4652} // Class
4653
4654//===================================================
4655// CLASS LinearScale
4656// Description: Handle linear scaling between screen and world
4657//===================================================
4658class LinearScale {
4659 public $textscale=false; // Just a flag to let the Plot class find out if
4660 // we are a textscale or not. This is a cludge since
4661 // this information is available in Graph::axtype but
4662 // we don't have access to the graph object in the Plots
4663 // stroke method. So we let graph store the status here
4664 // when the linear scale is created. A real cludge...
4665 public $type; // is this x or y scale ?
4666 public $ticks=null; // Store ticks
4667 public $text_scale_off = 0;
4668 public $scale_abs=array(0,0);
4669 public $scale_factor; // Scale factor between world and screen
4670 public $off; // Offset between image edge and plot area
4671 public $scale=array(0,0);
4672 public $name = 'lin';
4673 public $auto_ticks=false; // When using manual scale should the ticks be automatically set?
4674 public $world_abs_size; // Plot area size in pixels (Needed public in jpgraph_radar.php)
4675 public $intscale=false; // Restrict autoscale to integers
4676 protected $autoscale_min=false; // Forced minimum value, auto determine max
4677 protected $autoscale_max=false; // Forced maximum value, auto determine min
4678 private $gracetop=0,$gracebottom=0;
4679
4680 private $_world_size; // Plot area size in world coordinates
4681
4682 function __construct($aMin=0,$aMax=0,$aType='y') {
4683 assert($aType=='x' || $aType=='y' );
4684 assert($aMin<=$aMax);
4685
4686 $this->type=$aType;
4687 $this->scale=array($aMin,$aMax);
4688 $this->world_size=$aMax-$aMin;
4689 $this->ticks = new LinearTicks();
4690 }
4691
4692 // Check if scale is set or if we should autoscale
4693 // We should do this is either scale or ticks has not been set
4694 function IsSpecified() {
4695 if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set
4696 return false;
4697 }
4698 return true;
4699 }
4700
4701 // Set the minimum data value when the autoscaling is used.
4702 // Usefull if you want a fix minimum (like 0) but have an
4703 // automatic maximum
4704 function SetAutoMin($aMin) {
4705 $this->autoscale_min=$aMin;
4706 }
4707
4708 // Set the minimum data value when the autoscaling is used.
4709 // Usefull if you want a fix minimum (like 0) but have an
4710 // automatic maximum
4711 function SetAutoMax($aMax) {
4712 $this->autoscale_max=$aMax;
4713 }
4714
4715 // If the user manually specifies a scale should the ticks
4716 // still be set automatically?
4717 function SetAutoTicks($aFlag=true) {
4718 $this->auto_ticks = $aFlag;
4719 }
4720
4721 // Specify scale "grace" value (top and bottom)
4722 function SetGrace($aGraceTop,$aGraceBottom=0) {
4723 if( $aGraceTop<0 || $aGraceBottom < 0 ) {
4724 JpGraphError::RaiseL(25069);//(" Grace must be larger then 0");
4725 }
4726 $this->gracetop=$aGraceTop;
4727 $this->gracebottom=$aGraceBottom;
4728 }
4729
4730 // Get the minimum value in the scale
4731 function GetMinVal() {
4732 return $this->scale[0];
4733 }
4734
4735 // get maximum value for scale
4736 function GetMaxVal() {
4737 return $this->scale[1];
4738 }
4739
4740 // Specify a new min/max value for sclae
4741 function Update($aImg,$aMin,$aMax) {
4742 $this->scale=array($aMin,$aMax);
4743 $this->world_size=$aMax-$aMin;
4744 $this->InitConstants($aImg);
4745 }
4746
4747 // Translate between world and screen
4748 function Translate($aCoord) {
4749 if( !is_numeric($aCoord) ) {
4750 if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) {
4751 JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
4752 }
4753 return 0;
4754 }
4755 else {
4756 return round($this->off+($aCoord - $this->scale[0]) * $this->scale_factor);
4757 }
4758 }
4759
4760 // Relative translate (don't include offset) usefull when we just want
4761 // to know the relative position (in pixels) on the axis
4762 function RelTranslate($aCoord) {
4763 if( !is_numeric($aCoord) ) {
4764 if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) {
4765 JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
4766 }
4767 return 0;
4768 }
4769 else {
4770 return ($aCoord - $this->scale[0]) * $this->scale_factor;
4771 }
4772 }
4773
4774 // Restrict autoscaling to only use integers
4775 function SetIntScale($aIntScale=true) {
4776 $this->intscale=$aIntScale;
4777 }
4778
4779 // Calculate an integer autoscale
4780 function IntAutoScale($img,$min,$max,$maxsteps,$majend=true) {
4781 // Make sure limits are integers
4782 $min=floor($min);
4783 $max=ceil($max);
4784 if( abs($min-$max)==0 ) {
4785 --$min; ++$max;
4786 }
4787 $maxsteps = floor($maxsteps);
4788
4789 $gracetop=round(($this->gracetop/100.0)*abs($max-$min));
4790 $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min));
4791 if( is_numeric($this->autoscale_min) ) {
4792 $min = ceil($this->autoscale_min);
4793 if( $min >= $max ) {
4794 JpGraphError::RaiseL(25071);//('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
4795 }
4796 }
4797
4798 if( is_numeric($this->autoscale_max) ) {
4799 $max = ceil($this->autoscale_max);
4800 if( $min >= $max ) {
4801 JpGraphError::RaiseL(25072);//('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
4802 }
4803 }
4804
4805 if( abs($min-$max ) == 0 ) {
4806 ++$max;
4807 --$min;
4808 }
4809
4810 $min -= $gracebottom;
4811 $max += $gracetop;
4812
4813 // First get tickmarks as multiples of 1, 10, ...
4814 if( $majend ) {
4815 list($num1steps,$adj1min,$adj1max,$maj1step) = $this->IntCalcTicks($maxsteps,$min,$max,1);
4816 }
4817 else {
4818 $adj1min = $min;
4819 $adj1max = $max;
4820 list($num1steps,$maj1step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,1);
4821 }
4822
4823 if( abs($min-$max) > 2 ) {
4824 // Then get tick marks as 2:s 2, 20, ...
4825 if( $majend ) {
4826 list($num2steps,$adj2min,$adj2max,$maj2step) = $this->IntCalcTicks($maxsteps,$min,$max,5);
4827 }
4828 else {
4829 $adj2min = $min;
4830 $adj2max = $max;
4831 list($num2steps,$maj2step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,5);
4832 }
4833 }
4834 else {
4835 $num2steps = 10000; // Dummy high value so we don't choose this
4836 }
4837
4838 if( abs($min-$max) > 5 ) {
4839 // Then get tickmarks as 5:s 5, 50, 500, ...
4840 if( $majend ) {
4841 list($num5steps,$adj5min,$adj5max,$maj5step) = $this->IntCalcTicks($maxsteps,$min,$max,2);
4842 }
4843 else {
4844 $adj5min = $min;
4845 $adj5max = $max;
4846 list($num5steps,$maj5step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,2);
4847 }
4848 }
4849 else {
4850 $num5steps = 10000; // Dummy high value so we don't choose this
4851 }
4852
4853 // Check to see whichof 1:s, 2:s or 5:s fit better with
4854 // the requested number of major ticks
4855 $match1=abs($num1steps-$maxsteps);
4856 $match2=abs($num2steps-$maxsteps);
4857 if( !empty($maj5step) && $maj5step > 1 ) {
4858 $match5=abs($num5steps-$maxsteps);
4859 }
4860 else {
4861 $match5=10000; // Dummy high value
4862 }
4863
4864 // Compare these three values and see which is the closest match
4865 // We use a 0.6 weight to gravitate towards multiple of 5:s
4866 if( $match1 < $match2 ) {
4867 if( $match1 < $match5 ) $r=1;
4868 else $r=3;
4869 }
4870 else {
4871 if( $match2 < $match5 ) $r=2;
4872 else $r=3;
4873 }
4874 // Minsteps are always the same as maxsteps for integer scale
4875 switch( $r ) {
4876 case 1:
4877 $this->ticks->Set($maj1step,$maj1step);
4878 $this->Update($img,$adj1min,$adj1max);
4879 break;
4880 case 2:
4881 $this->ticks->Set($maj2step,$maj2step);
4882 $this->Update($img,$adj2min,$adj2max);
4883 break;
4884 case 3:
4885 $this->ticks->Set($maj5step,$maj5step);
4886 $this->Update($img,$adj5min,$adj5max);
4887 break;
4888 default:
4889 JpGraphError::RaiseL(25073,$r);//('Internal error. Integer scale algorithm comparison out of bound (r=$r)');
4890 }
4891 }
4892
4893
4894 // Calculate autoscale. Used if user hasn't given a scale and ticks
4895 // $maxsteps is the maximum number of major tickmarks allowed.
4896 function AutoScale($img,$min,$max,$maxsteps,$majend=true) {
4897
4898 if( !is_numeric($min) || !is_numeric($max) ) {
4899 JpGraphError::Raise(25044);
4900 }
4901
4902 if( $this->intscale ) {
4903 $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
4904 return;
4905 }
4906 if( abs($min-$max) < 0.00001 ) {
4907 // We need some difference to be able to autoscale
4908 // make it 5% above and 5% below value
4909 if( $min==0 && $max==0 ) { // Special case
4910 $min=-1; $max=1;
4911 }
4912 else {
4913 $delta = (abs($max)+abs($min))*0.005;
4914 $min -= $delta;
4915 $max += $delta;
4916 }
4917 }
4918
4919 $gracetop=($this->gracetop/100.0)*abs($max-$min);
4920 $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
4921 if( is_numeric($this->autoscale_min) ) {
4922 $min = $this->autoscale_min;
4923 if( $min >= $max ) {
4924 JpGraphError::RaiseL(25071);//('You have specified a min value with SetAutoMin() which is larger than the maximum value used for the scale. This is not possible.');
4925 }
4926 if( abs($min-$max ) < 0.001 ) {
4927 $max *= 1.2;
4928 }
4929 }
4930
4931 if( is_numeric($this->autoscale_max) ) {
4932 $max = $this->autoscale_max;
4933 if( $min >= $max ) {
4934 JpGraphError::RaiseL(25072);//('You have specified a max value with SetAutoMax() which is smaller than the miminum value used for the scale. This is not possible.');
4935 }
4936 if( abs($min-$max ) < 0.001 ) {
4937 $min *= 0.8;
4938 }
4939 }
4940
4941 $min -= $gracebottom;
4942 $max += $gracetop;
4943
4944 // First get tickmarks as multiples of 0.1, 1, 10, ...
4945 if( $majend ) {
4946 list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) = $this->CalcTicks($maxsteps,$min,$max,1,2);
4947 }
4948 else {
4949 $adj1min=$min;
4950 $adj1max=$max;
4951 list($num1steps,$min1step,$maj1step) = $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false);
4952 }
4953
4954 // Then get tick marks as 2:s 0.2, 2, 20, ...
4955 if( $majend ) {
4956 list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) = $this->CalcTicks($maxsteps,$min,$max,5,2);
4957 }
4958 else {
4959 $adj2min=$min;
4960 $adj2max=$max;
4961 list($num2steps,$min2step,$maj2step) = $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false);
4962 }
4963
4964 // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
4965 if( $majend ) {
4966 list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) = $this->CalcTicks($maxsteps,$min,$max,2,5);
4967 }
4968 else {
4969 $adj5min=$min;
4970 $adj5max=$max;
4971 list($num5steps,$min5step,$maj5step) = $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false);
4972 }
4973
4974 // Check to see whichof 1:s, 2:s or 5:s fit better with
4975 // the requested number of major ticks
4976 $match1=abs($num1steps-$maxsteps);
4977 $match2=abs($num2steps-$maxsteps);
4978 $match5=abs($num5steps-$maxsteps);
4979
4980 // Compare these three values and see which is the closest match
4981 // We use a 0.8 weight to gravitate towards multiple of 5:s
4982 $r=$this->MatchMin3($match1,$match2,$match5,0.8);
4983 switch( $r ) {
4984 case 1:
4985 $this->Update($img,$adj1min,$adj1max);
4986 $this->ticks->Set($maj1step,$min1step);
4987 break;
4988 case 2:
4989 $this->Update($img,$adj2min,$adj2max);
4990 $this->ticks->Set($maj2step,$min2step);
4991 break;
4992 case 3:
4993 $this->Update($img,$adj5min,$adj5max);
4994 $this->ticks->Set($maj5step,$min5step);
4995 break;
4996 }
4997 }
4998
4999 //---------------
5000 // PRIVATE METHODS
5001
5002 // This method recalculates all constants that are depending on the
5003 // margins in the image. If the margins in the image are changed
5004 // this method should be called for every scale that is registred with
5005 // that image. Should really be installed as an observer of that image.
5006 function InitConstants($img) {
5007 if( $this->type=='x' ) {
5008 $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
5009 $this->off=$img->left_margin;
5010 $this->scale_factor = 0;
5011 if( $this->world_size > 0 ) {
5012 $this->scale_factor=$this->world_abs_size/($this->world_size*0.999999);
5013 }
5014 }
5015 else { // y scale
5016 $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
5017 $this->off=$img->top_margin+$this->world_abs_size;
5018 $this->scale_factor = 0;
5019 if( $this->world_size > 0 ) {
5020 $this->scale_factor=-$this->world_abs_size/($this->world_size*0.999999);
5021 }
5022 }
5023 $size = $this->world_size * $this->scale_factor;
5024 $this->scale_abs=array($this->off,$this->off + $size);
5025 }
5026
5027 // Initialize the conversion constants for this scale
5028 // This tries to pre-calculate as much as possible to speed up the
5029 // actual conversion (with Translate()) later on
5030 // $start =scale start in absolute pixels (for x-scale this is an y-position
5031 // and for an y-scale this is an x-position
5032 // $len =absolute length in pixels of scale
5033 function SetConstants($aStart,$aLen) {
5034 $this->world_abs_size=$aLen;
5035 $this->off=$aStart;
5036
5037 if( $this->world_size<=0 ) {
5038 // This should never ever happen !!
5039 JpGraphError::RaiseL(25074);
5040 //("You have unfortunately stumbled upon a bug in JpGraph. It seems like the scale range is ".$this->world_size." [for ".$this->type." scale] <br> Please report Bug #01 to info@jpgraph.net and include the script that gave this error. This problem could potentially be caused by trying to use \"illegal\" values in the input data arrays (like trying to send in strings or only NULL values) which causes the autoscaling to fail.");
5041 }
5042
5043 // scale_factor = number of pixels per world unit
5044 $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
5045
5046 // scale_abs = start and end points of scale in absolute pixels
5047 $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);
5048 }
5049
5050
5051 // Calculate number of ticks steps with a specific division
5052 // $a is the divisor of 10**x to generate the first maj tick intervall
5053 // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
5054 // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
5055 // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
5056 // We return a vector of
5057 // [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
5058 // If $majend==true then the first and last marks on the axis will be major
5059 // labeled tick marks otherwise it will be adjusted to the closest min tick mark
5060 function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
5061 $diff=$max-$min;
5062 if( $diff==0 ) {
5063 $ld=0;
5064 }
5065 else {
5066 $ld=floor(log10($diff));
5067 }
5068
5069 // Gravitate min towards zero if we are close
5070 if( $min>0 && $min < pow(10,$ld) ) $min=0;
5071
5072 //$majstep=pow(10,$ld-1)/$a;
5073 $majstep=pow(10,$ld)/$a;
5074 $minstep=$majstep/$b;
5075
5076 $adjmax=ceil($max/$minstep)*$minstep;
5077 $adjmin=floor($min/$minstep)*$minstep;
5078 $adjdiff = $adjmax-$adjmin;
5079 $numsteps=$adjdiff/$majstep;
5080
5081 while( $numsteps>$maxsteps ) {
5082 $majstep=pow(10,$ld)/$a;
5083 $numsteps=$adjdiff/$majstep;
5084 ++$ld;
5085 }
5086
5087 $minstep=$majstep/$b;
5088 $adjmin=floor($min/$minstep)*$minstep;
5089 $adjdiff = $adjmax-$adjmin;
5090 if( $majend ) {
5091 $adjmin = floor($min/$majstep)*$majstep;
5092 $adjdiff = $adjmax-$adjmin;
5093 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
5094 }
5095 else {
5096 $adjmax=ceil($max/$minstep)*$minstep;
5097 }
5098
5099 return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
5100 }
5101
5102 function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) {
5103 // Same as CalcTicks but don't adjust min/max values
5104 $diff=$max-$min;
5105 if( $diff==0 ) {
5106 $ld=0;
5107 }
5108 else {
5109 $ld=floor(log10($diff));
5110 }
5111
5112 //$majstep=pow(10,$ld-1)/$a;
5113 $majstep=pow(10,$ld)/$a;
5114 $minstep=$majstep/$b;
5115 $numsteps=floor($diff/$majstep);
5116
5117 while( $numsteps > $maxsteps ) {
5118 $majstep=pow(10,$ld)/$a;
5119 $numsteps=floor($diff/$majstep);
5120 ++$ld;
5121 }
5122 $minstep=$majstep/$b;
5123 return array($numsteps,$minstep,$majstep);
5124 }
5125
5126
5127 function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
5128 $diff=$max-$min;
5129 if( $diff==0 ) {
5130 JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
5131 }
5132 else {
5133 $ld=floor(log10($diff));
5134 }
5135
5136 // Gravitate min towards zero if we are close
5137 if( $min>0 && $min < pow(10,$ld) ) {
5138 $min=0;
5139 }
5140 if( $ld == 0 ) {
5141 $ld=1;
5142 }
5143 if( $a == 1 ) {
5144 $majstep = 1;
5145 }
5146 else {
5147 $majstep=pow(10,$ld)/$a;
5148 }
5149 $adjmax=ceil($max/$majstep)*$majstep;
5150
5151 $adjmin=floor($min/$majstep)*$majstep;
5152 $adjdiff = $adjmax-$adjmin;
5153 $numsteps=$adjdiff/$majstep;
5154 while( $numsteps>$maxsteps ) {
5155 $majstep=pow(10,$ld)/$a;
5156 $numsteps=$adjdiff/$majstep;
5157 ++$ld;
5158 }
5159
5160 $adjmin=floor($min/$majstep)*$majstep;
5161 $adjdiff = $adjmax-$adjmin;
5162 if( $majend ) {
5163 $adjmin = floor($min/$majstep)*$majstep;
5164 $adjdiff = $adjmax-$adjmin;
5165 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
5166 }
5167 else {
5168 $adjmax=ceil($max/$majstep)*$majstep;
5169 }
5170
5171 return array($numsteps,$adjmin,$adjmax,$majstep);
5172 }
5173
5174
5175 function IntCalcTicksFreeze($maxsteps,$min,$max,$a) {
5176 // Same as IntCalcTick but don't change min/max values
5177 $diff=$max-$min;
5178 if( $diff==0 ) {
5179 JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
5180 }
5181 else {
5182 $ld=floor(log10($diff));
5183 }
5184 if( $ld == 0 ) {
5185 $ld=1;
5186 }
5187 if( $a == 1 ) {
5188 $majstep = 1;
5189 }
5190 else {
5191 $majstep=pow(10,$ld)/$a;
5192 }
5193
5194 $numsteps=floor($diff/$majstep);
5195 while( $numsteps > $maxsteps ) {
5196 $majstep=pow(10,$ld)/$a;
5197 $numsteps=floor($diff/$majstep);
5198 ++$ld;
5199 }
5200
5201 return array($numsteps,$majstep);
5202 }
5203
5204 // Determine the minimum of three values witha weight for last value
5205 function MatchMin3($a,$b,$c,$weight) {
5206 if( $a < $b ) {
5207 if( $a < ($c*$weight) ) {
5208 return 1; // $a smallest
5209 }
5210 else {
5211 return 3; // $c smallest
5212 }
5213 }
5214 elseif( $b < ($c*$weight) ) {
5215 return 2; // $b smallest
5216 }
5217 return 3; // $c smallest
5218 }
5219
5220 function __get($name) {
5221 $variable_name = '_' . $name;
5222
5223 if (isset($this->$variable_name)) {
5224 return $this->$variable_name * SUPERSAMPLING_SCALE;
5225 } else {
5226 JpGraphError::RaiseL('25132', $name);
5227 }
5228 }
5229
5230 function __set($name, $value) {
5231 $this->{'_'.$name} = $value;
5232 }
5233} // Class
5234
5235
5236//===================================================
5237// CLASS DisplayValue
5238// Description: Used to print data values at data points
5239//===================================================
5240class DisplayValue {
5241 public $margin=5;
5242 public $show=false;
5243 public $valign='',$halign='center';
5244 public $format='%.1f',$negformat='';
5245 private $ff=FF_DEFAULT,$fs=FS_NORMAL,$fsize=8;
5246 private $iFormCallback='';
5247 private $angle=0;
5248 private $color='navy',$negcolor='';
5249 private $iHideZero=false;
5250 public $txt=null;
5251
5252 function __construct() {
5253 $this->txt = new Text();
5254 }
5255
5256 function Show($aFlag=true) {
5257 $this->show=$aFlag;
5258 }
5259
5260 function SetColor($aColor,$aNegcolor='') {
5261 $this->color = $aColor;
5262 $this->negcolor = $aNegcolor;
5263 }
5264
5265 function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=8) {
5266 $this->ff=$aFontFamily;
5267 $this->fs=$aFontStyle;
5268 $this->fsize=$aFontSize;
5269 }
5270
5271 function ApplyFont($aImg) {
5272 $aImg->SetFont($this->ff,$this->fs,$this->fsize);
5273 }
5274
5275 function SetMargin($aMargin) {
5276 $this->margin = $aMargin;
5277 }
5278
5279 function SetAngle($aAngle) {
5280 $this->angle = $aAngle;
5281 }
5282
5283 function SetAlign($aHAlign,$aVAlign='') {
5284 $this->halign = $aHAlign;
5285 $this->valign = $aVAlign;
5286 }
5287
5288 function SetFormat($aFormat,$aNegFormat='') {
5289 $this->format= $aFormat;
5290 $this->negformat= $aNegFormat;
5291 }
5292
5293 function SetFormatCallback($aFunc) {
5294 $this->iFormCallback = $aFunc;
5295 }
5296
5297 function HideZero($aFlag=true) {
5298 $this->iHideZero=$aFlag;
5299 }
5300
5301 function Stroke($img,$aVal,$x,$y) {
5302
5303 if( $this->show )
5304 {
5305 if( $this->negformat=='' ) {
5306 $this->negformat=$this->format;
5307 }
5308 if( $this->negcolor=='' ) {
5309 $this->negcolor=$this->color;
5310 }
5311
5312 if( $aVal===NULL || (is_string($aVal) && ($aVal=='' || $aVal=='-' || $aVal=='x' ) ) ) {
5313 return;
5314 }
5315
5316 if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) {
5317 return;
5318 }
5319
5320 // Since the value is used in different cirumstances we need to check what
5321 // kind of formatting we shall use. For example, to display values in a line
5322 // graph we simply display the formatted value, but in the case where the user
5323 // has already specified a text string we don't fo anything.
5324 if( $this->iFormCallback != '' ) {
5325 $f = $this->iFormCallback;
5326 $sval = call_user_func($f,$aVal);
5327 }
5328 elseif( is_numeric($aVal) ) {
5329 if( $aVal >= 0 ) {
5330 $sval=sprintf($this->format,$aVal);
5331 }
5332 else {
5333 $sval=sprintf($this->negformat,$aVal);
5334 }
5335 }
5336 else {
5337 $sval=$aVal;
5338 }
5339
5340 $y = $y-sign($aVal)*$this->margin;
5341
5342 $this->txt->Set($sval);
5343 $this->txt->SetPos($x,$y);
5344 $this->txt->SetFont($this->ff,$this->fs,$this->fsize);
5345 if( $this->valign == '' ) {
5346 if( $aVal >= 0 ) {
5347 $valign = "bottom";
5348 }
5349 else {
5350 $valign = "top";
5351 }
5352 }
5353 else {
5354 $valign = $this->valign;
5355 }
5356 $this->txt->Align($this->halign,$valign);
5357
5358 $this->txt->SetOrientation($this->angle);
5359 if( $aVal > 0 ) {
5360 $this->txt->SetColor($this->color);
5361 }
5362 else {
5363 $this->txt->SetColor($this->negcolor);
5364 }
5365 $this->txt->Stroke($img);
5366 }
5367 }
5368}
5369
5370//===================================================
5371// CLASS Plot
5372// Description: Abstract base class for all concrete plot classes
5373//===================================================
5374class Plot {
5375 public $numpoints=0;
5376 public $value;
5377 public $legend='';
5378 public $coords=array();
5379 public $color='black';
5380 public $hidelegend=false;
5381 public $line_weight=1;
5382 public $csimtargets=array(),$csimwintargets=array(); // Array of targets for CSIM
5383 public $csimareas=''; // Resultant CSIM area tags
5384 public $csimalts=null; // ALT:s for corresponding target
5385 public $legendcsimtarget='',$legendcsimwintarget='';
5386 public $legendcsimalt='';
5387 protected $weight=1;
5388 protected $center=false;
5389
5390 protected $inputValues;
5391 protected $isRunningClear = false;
5392
5393 function __construct($aDatay,$aDatax=false) {
5394 $this->numpoints = count($aDatay);
5395 if( $this->numpoints==0 ) {
5396 JpGraphError::RaiseL(25121);//("Empty input data array specified for plot. Must have at least one data point.");
5397 }
5398
5399 if (!$this->isRunningClear) {
5400 $this->inputValues = array();
5401 $this->inputValues['aDatay'] = $aDatay;
5402 $this->inputValues['aDatax'] = $aDatax;
5403 }
5404
5405 $this->coords[0]=$aDatay;
5406 if( is_array($aDatax) ) {
5407 $this->coords[1]=$aDatax;
5408 $n = count($aDatax);
5409 for( $i=0; $i < $n; ++$i ) {
5410 if( !is_numeric($aDatax[$i]) ) {
5411 JpGraphError::RaiseL(25070);
5412 }
5413 }
5414 }
5415 $this->value = new DisplayValue();
5416 }
5417
5418 // Stroke the plot
5419 // "virtual" function which must be implemented by
5420 // the subclasses
5421 function Stroke($aImg,$aXScale,$aYScale) {
5422 JpGraphError::RaiseL(25122);//("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
5423 }
5424
5425 function HideLegend($f=true) {
5426 $this->hidelegend = $f;
5427 }
5428
5429 function DoLegend($graph) {
5430 if( !$this->hidelegend )
5431 $this->Legend($graph);
5432 }
5433
5434 function StrokeDataValue($img,$aVal,$x,$y) {
5435 $this->value->Stroke($img,$aVal,$x,$y);
5436 }
5437
5438 // Set href targets for CSIM
5439 function SetCSIMTargets($aTargets,$aAlts='',$aWinTargets='') {
5440 $this->csimtargets=$aTargets;
5441 $this->csimwintargets=$aWinTargets;
5442 $this->csimalts=$aAlts;
5443 }
5444
5445 // Get all created areas
5446 function GetCSIMareas() {
5447 return $this->csimareas;
5448 }
5449
5450 // "Virtual" function which gets called before any scale
5451 // or axis are stroked used to do any plot specific adjustment
5452 function PreStrokeAdjust($aGraph) {
5453 if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) ) {
5454 JpGraphError::RaiseL(25123);//("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
5455 }
5456 return true;
5457 }
5458
5459 // Virtual function to the the concrete plot class to make any changes to the graph
5460 // and scale before the stroke process begins
5461 function PreScaleSetup($aGraph) {
5462 // Empty
5463 }
5464
5465 // Get minimum values in plot
5466 function Min() {
5467 if( isset($this->coords[1]) ) {
5468 $x=$this->coords[1];
5469 }
5470 else {
5471 $x='';
5472 }
5473 if( $x != '' && count($x) > 0 ) {
5474 $xm=min($x);
5475 }
5476 else {
5477 $xm=0;
5478 }
5479 $y=$this->coords[0];
5480 $cnt = count($y);
5481 if( $cnt > 0 ) {
5482 $i=0;
5483 while( $i<$cnt && !is_numeric($ym=$y[$i]) ) {
5484 $i++;
5485 }
5486 while( $i < $cnt) {
5487 if( is_numeric($y[$i]) ) {
5488 $ym=min($ym,$y[$i]);
5489 }
5490 ++$i;
5491 }
5492 }
5493 else {
5494 $ym='';
5495 }
5496 return array($xm,$ym);
5497 }
5498
5499 // Get maximum value in plot
5500 function Max() {
5501 if( isset($this->coords[1]) ) {
5502 $x=$this->coords[1];
5503 }
5504 else {
5505 $x='';
5506 }
5507
5508 if( $x!='' && count($x) > 0 ) {
5509 $xm=max($x);
5510 }
5511 else {
5512 $xm = $this->numpoints-1;
5513 }
5514 $y=$this->coords[0];
5515 if( count($y) > 0 ) {
5516 $cnt = count($y);
5517 $i=0;
5518 while( $i<$cnt && !is_numeric($ym=$y[$i]) ) {
5519 $i++;
5520 }
5521 while( $i < $cnt ) {
5522 if( is_numeric($y[$i]) ) {
5523 $ym=max($ym,$y[$i]);
5524 }
5525 ++$i;
5526 }
5527 }
5528 else {
5529 $ym='';
5530 }
5531 return array($xm,$ym);
5532 }
5533
5534 function SetColor($aColor) {
5535 $this->color=$aColor;
5536 }
5537
5538 function SetLegend($aLegend,$aCSIM='',$aCSIMAlt='',$aCSIMWinTarget='') {
5539 $this->legend = $aLegend;
5540 $this->legendcsimtarget = $aCSIM;
5541 $this->legendcsimwintarget = $aCSIMWinTarget;
5542 $this->legendcsimalt = $aCSIMAlt;
5543 }
5544
5545 function SetWeight($aWeight) {
5546 $this->weight=$aWeight;
5547 }
5548
5549 function SetLineWeight($aWeight=1) {
5550 $this->line_weight=$aWeight;
5551 }
5552
5553 function SetCenter($aCenter=true) {
5554 $this->center = $aCenter;
5555 }
5556
5557 // This method gets called by Graph class to plot anything that should go
5558 // into the margin after the margin color has been set.
5559 function StrokeMargin($aImg) {
5560 return true;
5561 }
5562
5563 // Framework function the chance for each plot class to set a legend
5564 function Legend($aGraph) {
5565 if( $this->legend != '' ) {
5566 $aGraph->legend->Add($this->legend,$this->color,'',0,$this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget);
5567 }
5568 }
5569
5570 function Clear() {
5571 $this->isRunningClear = true;
5572 Plot::__construct($this->inputValues['aDatay'], $this->inputValues['aDatax']);
5573 $this->isRunningClear = false;
5574 }
5575
5576} // Class
5577
5578
5579// Provide a deterministic list of new colors whenever the getColor() method
5580// is called. Used to automatically set colors of plots.
5581class ColorFactory {
5582
5583 static private $iIdx = 0;
5584 static private $iColorList = array(
5585 'black',
5586 'blue',
5587 'orange',
5588 'darkgreen',
5589 'red',
5590 'AntiqueWhite3',
5591 'aquamarine3',
5592 'azure4',
5593 'brown',
5594 'cadetblue3',
5595 'chartreuse4',
5596 'chocolate',
5597 'darkblue',
5598 'darkgoldenrod3',
5599 'darkorchid3',
5600 'darksalmon',
5601 'darkseagreen4',
5602 'deepskyblue2',
5603 'dodgerblue4',
5604 'gold3',
5605 'hotpink',
5606 'lawngreen',
5607 'lightcoral',
5608 'lightpink3',
5609 'lightseagreen',
5610 'lightslateblue',
5611 'mediumpurple',
5612 'olivedrab',
5613 'orangered1',
5614 'peru',
5615 'slategray',
5616 'yellow4',
5617 'springgreen2');
5618 static private $iNum = 33;
5619
5620 static function getColor() {
5621 if( ColorFactory::$iIdx >= ColorFactory::$iNum )
5622 ColorFactory::$iIdx = 0;
5623 return ColorFactory::$iColorList[ColorFactory::$iIdx++];
5624 }
5625
5626}
5627
5628// <EOF>
5629?>
Note: See TracBrowser for help on using the repository browser.