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

Last change on this file since 284 was 284, checked in by roby, 6 years ago
File size: 200.0 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.2.6');
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 $x=$aTicksPos[$i];
3646 while( $i<count($aTicksPos) && ($x=$aTicksPos[$i]) <= $limit ) {
3647 if ( $aType == 'solid' ) $this->img->Line($x,$yl,$x,$yu);
3648 elseif( $aType == 'dotted' ) $this->img->DashedLineForGrid($x,$yl,$x,$yu,1,6);
3649 elseif( $aType == 'dashed' ) $this->img->DashedLineForGrid($x,$yl,$x,$yu,2,4);
3650 elseif( $aType == 'longdashed' ) $this->img->DashedLineForGrid($x,$yl,$x,$yu,8,6);
3651 ++$i;
3652 }
3653 }
3654 else {
3655 JpGraphError::RaiseL(25054,$this->scale->type);//('Internal error: Unknown grid axis ['.$this->scale->type.']');
3656 }
3657 return true;
3658 }
3659} // Class
3660
3661//===================================================
3662// CLASS Axis
3663// Description: Defines X and Y axis. Notes that at the
3664// moment the code is not really good since the axis on
3665// several occasion must know wheter it's an X or Y axis.
3666// This was a design decision to make the code easier to
3667// follow.
3668//===================================================
3669class AxisPrototype {
3670 public $scale=null;
3671 public $img=null;
3672 public $hide=false,$hide_labels=false;
3673 public $title=null;
3674 public $font_family=FF_DEFAULT,$font_style=FS_NORMAL,$font_size=8,$label_angle=0;
3675 public $tick_step=1;
3676 public $pos = false;
3677 public $ticks_label = array();
3678
3679 protected $weight=1;
3680 protected $color=array(0,0,0),$label_color=array(0,0,0);
3681 protected $ticks_label_colors=null;
3682 protected $show_first_label=true,$show_last_label=true;
3683 protected $label_step=1; // Used by a text axis to specify what multiple of major steps
3684 // should be labeled.
3685 protected $labelPos=0; // Which side of the axis should the labels be?
3686 protected $title_adjust,$title_margin,$title_side=SIDE_LEFT;
3687 protected $tick_label_margin=5;
3688 protected $label_halign = '',$label_valign = '', $label_para_align='left';
3689 protected $hide_line=false;
3690 protected $iDeltaAbsPos=0;
3691
3692 function __construct($img,$aScale,$color = array(0,0,0)) {
3693 $this->img = $img;
3694 $this->scale = $aScale;
3695 $this->color = $color;
3696 $this->title=new Text('');
3697
3698 if( $aScale->type == 'y' ) {
3699 $this->title_margin = 25;
3700 $this->title_adjust = 'middle';
3701 $this->title->SetOrientation(90);
3702 $this->tick_label_margin=7;
3703 $this->labelPos=SIDE_LEFT;
3704 }
3705 else {
3706 $this->title_margin = 5;
3707 $this->title_adjust = 'high';
3708 $this->title->SetOrientation(0);
3709 $this->tick_label_margin=5;
3710 $this->labelPos=SIDE_DOWN;
3711 $this->title_side=SIDE_DOWN;
3712 }
3713 }
3714
3715 function SetLabelFormat($aFormStr) {
3716 $this->scale->ticks->SetLabelFormat($aFormStr);
3717 }
3718
3719 function SetLabelFormatString($aFormStr,$aDate=false) {
3720 $this->scale->ticks->SetLabelFormat($aFormStr,$aDate);
3721 }
3722
3723 function SetLabelFormatCallback($aFuncName) {
3724 $this->scale->ticks->SetFormatCallback($aFuncName);
3725 }
3726
3727 function SetLabelAlign($aHAlign,$aVAlign='top',$aParagraphAlign='left') {
3728 $this->label_halign = $aHAlign;
3729 $this->label_valign = $aVAlign;
3730 $this->label_para_align = $aParagraphAlign;
3731 }
3732
3733 // Don't display the first label
3734 function HideFirstTickLabel($aShow=false) {
3735 $this->show_first_label=$aShow;
3736 }
3737
3738 function HideLastTickLabel($aShow=false) {
3739 $this->show_last_label=$aShow;
3740 }
3741
3742 // Manually specify the major and (optional) minor tick position and labels
3743 function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
3744 $this->scale->ticks->SetTickPositions($aMajPos,$aMinPos,$aLabels);
3745 }
3746
3747 // Manually specify major tick positions and optional labels
3748 function SetMajTickPositions($aMajPos,$aLabels=NULL) {
3749 $this->scale->ticks->SetTickPositions($aMajPos,NULL,$aLabels);
3750 }
3751
3752 // Hide minor or major tick marks
3753 function HideTicks($aHideMinor=true,$aHideMajor=true) {
3754 $this->scale->ticks->SupressMinorTickMarks($aHideMinor);
3755 $this->scale->ticks->SupressTickMarks($aHideMajor);
3756 }
3757
3758 // Hide zero label
3759 function HideZeroLabel($aFlag=true) {
3760 $this->scale->ticks->SupressZeroLabel();
3761 }
3762
3763 function HideFirstLastLabel() {
3764 // The two first calls to ticks method will supress
3765 // automatically generated scale values. However, that
3766 // will not affect manually specified value, e.g text-scales.
3767 // therefor we also make a kludge here to supress manually
3768 // specified scale labels.
3769 $this->scale->ticks->SupressLast();
3770 $this->scale->ticks->SupressFirst();
3771 $this->show_first_label = false;
3772 $this->show_last_label = false;
3773 }
3774
3775 // Hide the axis
3776 function Hide($aHide=true) {
3777 $this->hide=$aHide;
3778 }
3779
3780 // Hide the actual axis-line, but still print the labels
3781 function HideLine($aHide=true) {
3782 $this->hide_line = $aHide;
3783 }
3784
3785 function HideLabels($aHide=true) {
3786 $this->hide_labels = $aHide;
3787 }
3788
3789 // Weight of axis
3790 function SetWeight($aWeight) {
3791 $this->weight = $aWeight;
3792 }
3793
3794 // Axis color
3795 function SetColor($aColor,$aLabelColor=false) {
3796 $this->color = $aColor;
3797 if( !$aLabelColor ) $this->label_color = $aColor;
3798 else $this->label_color = $aLabelColor;
3799 }
3800
3801 // Title on axis
3802 function SetTitle($aTitle,$aAdjustAlign='high') {
3803 $this->title->Set($aTitle);
3804 $this->title_adjust=$aAdjustAlign;
3805 }
3806
3807 // Specify distance from the axis
3808 function SetTitleMargin($aMargin) {
3809 $this->title_margin=$aMargin;
3810 }
3811
3812 // Which side of the axis should the axis title be?
3813 function SetTitleSide($aSideOfAxis) {
3814 $this->title_side = $aSideOfAxis;
3815 }
3816
3817 function SetTickSide($aDir) {
3818 $this->scale->ticks->SetSide($aDir);
3819 }
3820
3821 function SetTickSize($aMajSize,$aMinSize=3) {
3822 $this->scale->ticks->SetSize($aMajSize,$aMinSize=3);
3823 }
3824
3825 // Specify text labels for the ticks. One label for each data point
3826 function SetTickLabels($aLabelArray,$aLabelColorArray=null) {
3827 $this->ticks_label = $aLabelArray;
3828 $this->ticks_label_colors = $aLabelColorArray;
3829 }
3830
3831 function SetLabelMargin($aMargin) {
3832 $this->tick_label_margin=$aMargin;
3833 }
3834
3835 // Specify that every $step of the ticks should be displayed starting
3836 // at $start
3837 function SetTextTickInterval($aStep,$aStart=0) {
3838 $this->scale->ticks->SetTextLabelStart($aStart);
3839 $this->tick_step=$aStep;
3840 }
3841
3842 // Specify that every $step tick mark should have a label
3843 // should be displayed starting
3844 function SetTextLabelInterval($aStep) {
3845 if( $aStep < 1 ) {
3846 JpGraphError::RaiseL(25058);//(" Text label interval must be specified >= 1.");
3847 }
3848 $this->label_step=$aStep;
3849 }
3850
3851 function SetLabelSide($aSidePos) {
3852 $this->labelPos=$aSidePos;
3853 }
3854
3855 // Set the font
3856 function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
3857 $this->font_family = $aFamily;
3858 $this->font_style = $aStyle;
3859 $this->font_size = $aSize;
3860 }
3861
3862 // Position for axis line on the "other" scale
3863 function SetPos($aPosOnOtherScale) {
3864 $this->pos=$aPosOnOtherScale;
3865 }
3866
3867 // Set the position of the axis to be X-pixels delta to the right
3868 // of the max X-position (used to position the multiple Y-axis)
3869 function SetPosAbsDelta($aDelta) {
3870 $this->iDeltaAbsPos=$aDelta;
3871 }
3872
3873 // Specify the angle for the tick labels
3874 function SetLabelAngle($aAngle) {
3875 $this->label_angle = $aAngle;
3876 }
3877
3878} // Class
3879
3880
3881//===================================================
3882// CLASS Axis
3883// Description: Defines X and Y axis. Notes that at the
3884// moment the code is not really good since the axis on
3885// several occasion must know wheter it's an X or Y axis.
3886// This was a design decision to make the code easier to
3887// follow.
3888//===================================================
3889class Axis extends AxisPrototype {
3890
3891 function __construct($img,$aScale,$color='black') {
3892 parent::__construct($img,$aScale,$color);
3893 }
3894
3895 // Stroke the axis.
3896 function Stroke($aOtherAxisScale,$aStrokeLabels=true) {
3897 if( $this->hide )
3898 return;
3899 if( is_numeric($this->pos) ) {
3900 $pos=$aOtherAxisScale->Translate($this->pos);
3901 }
3902 else { // Default to minimum of other scale if pos not set
3903 if( ($aOtherAxisScale->GetMinVal() >= 0 && $this->pos==false) || $this->pos == 'min' ) {
3904 $pos = $aOtherAxisScale->scale_abs[0];
3905 }
3906 elseif($this->pos == "max") {
3907 $pos = $aOtherAxisScale->scale_abs[1];
3908 }
3909 else { // If negative set x-axis at 0
3910 $this->pos=0;
3911 $pos=$aOtherAxisScale->Translate(0);
3912 }
3913 }
3914
3915 $pos += $this->iDeltaAbsPos;
3916 $this->img->SetLineWeight($this->weight);
3917 $this->img->SetColor($this->color);
3918 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
3919
3920 if( $this->scale->type == "x" ) {
3921 if( !$this->hide_line ) {
3922 // Stroke X-axis
3923 $this->img->FilledRectangle(
3924 $this->img->left_margin,
3925 $pos,
3926 $this->img->width - $this->img->right_margin,
3927 $pos + $this->weight-1
3928 );
3929 }
3930 if( $this->title_side == SIDE_DOWN ) {
3931 $y = $pos + $this->img->GetFontHeight() + $this->title_margin + $this->title->margin;
3932 $yalign = 'top';
3933 }
3934 else {
3935 $y = $pos - $this->img->GetFontHeight() - $this->title_margin - $this->title->margin;
3936 $yalign = 'bottom';
3937 }
3938
3939 if( $this->title_adjust=='high' ) {
3940 $this->title->SetPos($this->img->width-$this->img->right_margin,$y,'right',$yalign);
3941 }
3942 elseif( $this->title_adjust=='middle' || $this->title_adjust=='center' ) {
3943 $this->title->SetPos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,'center',$yalign);
3944 }
3945 elseif($this->title_adjust=='low') {
3946 $this->title->SetPos($this->img->left_margin,$y,'left',$yalign);
3947 }
3948 else {
3949 JpGraphError::RaiseL(25060,$this->title_adjust);//('Unknown alignment specified for X-axis title. ('.$this->title_adjust.')');
3950 }
3951 }
3952 elseif( $this->scale->type == "y" ) {
3953 // Add line weight to the height of the axis since
3954 // the x-axis could have a width>1 and we want the axis to fit nicely together.
3955 if( !$this->hide_line ) {
3956 // Stroke Y-axis
3957 $this->img->FilledRectangle(
3958 $pos - $this->weight + 1,
3959 $this->img->top_margin,
3960 $pos,
3961 $this->img->height - $this->img->bottom_margin + $this->weight - 1
3962 );
3963 }
3964
3965 $x=$pos ;
3966 if( $this->title_side == SIDE_LEFT ) {
3967 $x -= $this->title_margin;
3968 $x -= $this->title->margin;
3969 $halign = 'right';
3970 }
3971 else {
3972 $x += $this->title_margin;
3973 $x += $this->title->margin;
3974 $halign = 'left';
3975 }
3976 // If the user has manually specified an hor. align
3977 // then we override the automatic settings with this
3978 // specifed setting. Since default is 'left' we compare
3979 // with that. (This means a manually set 'left' align
3980 // will have no effect.)
3981 if( $this->title->halign != 'left' ) {
3982 $halign = $this->title->halign;
3983 }
3984 if( $this->title_adjust == 'high' ) {
3985 $this->title->SetPos($x,$this->img->top_margin,$halign,'top');
3986 }
3987 elseif($this->title_adjust=='middle' || $this->title_adjust=='center') {
3988 $this->title->SetPos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
3989 }
3990 elseif($this->title_adjust=='low') {
3991 $this->title->SetPos($x,$this->img->height-$this->img->bottom_margin,$halign,'bottom');
3992 }
3993 else {
3994 JpGraphError::RaiseL(25061,$this->title_adjust);//('Unknown alignment specified for Y-axis title. ('.$this->title_adjust.')');
3995 }
3996 }
3997 $this->scale->ticks->Stroke($this->img,$this->scale,$pos);
3998 if( $aStrokeLabels ) {
3999 if( !$this->hide_labels ) {
4000 $this->StrokeLabels($pos);
4001 }
4002 $this->title->Stroke($this->img);
4003 }
4004 }
4005
4006 //---------------
4007 // PRIVATE METHODS
4008 // Draw all the tick labels on major tick marks
4009 function StrokeLabels($aPos,$aMinor=false,$aAbsLabel=false) {
4010
4011 if( is_array($this->label_color) && count($this->label_color) > 3 ) {
4012 $this->ticks_label_colors = $this->label_color;
4013 $this->img->SetColor($this->label_color[0]);
4014 }
4015 else {
4016 $this->img->SetColor($this->label_color);
4017 }
4018 $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
4019 $yoff=$this->img->GetFontHeight()/2;
4020
4021 // Only draw labels at major tick marks
4022 $nbr = count($this->scale->ticks->maj_ticks_label);
4023
4024 // We have the option to not-display the very first mark
4025 // (Usefull when the first label might interfere with another
4026 // axis.)
4027 $i = $this->show_first_label ? 0 : 1 ;
4028 if( !$this->show_last_label ) {
4029 --$nbr;
4030 }
4031 // Now run through all labels making sure we don't overshoot the end
4032 // of the scale.
4033 $ncolor=0;
4034 if( isset($this->ticks_label_colors) ) {
4035 $ncolor=count($this->ticks_label_colors);
4036 }
4037 while( $i < $nbr ) {
4038 // $tpos holds the absolute text position for the label
4039 $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];
4040
4041 // Note. the $limit is only used for the x axis since we
4042 // might otherwise overshoot if the scale has been centered
4043 // This is due to us "loosing" the last tick mark if we center.
4044 if( $this->scale->type == 'x' && $tpos > $this->img->width-$this->img->right_margin+1 ) {
4045 return;
4046 }
4047 // we only draw every $label_step label
4048 if( ($i % $this->label_step)==0 ) {
4049
4050 // Set specific label color if specified
4051 if( $ncolor > 0 ) {
4052 $this->img->SetColor($this->ticks_label_colors[$i % $ncolor]);
4053 }
4054
4055 // If the label has been specified use that and in other case
4056 // just label the mark with the actual scale value
4057 $m=$this->scale->ticks->GetMajor();
4058
4059 // ticks_label has an entry for each data point and is the array
4060 // that holds the labels set by the user. If the user hasn't
4061 // specified any values we use whats in the automatically asigned
4062 // labels in the maj_ticks_label
4063 if( isset($this->ticks_label[$i*$m]) ) {
4064 $label=$this->ticks_label[$i*$m];
4065 }
4066 else {
4067 if( $aAbsLabel ) {
4068 $label=abs($this->scale->ticks->maj_ticks_label[$i]);
4069 }
4070 else {
4071 $label=$this->scale->ticks->maj_ticks_label[$i];
4072 }
4073
4074 // We number the scale from 1 and not from 0 so increase by one
4075 if( $this->scale->textscale &&
4076 $this->scale->ticks->label_formfunc == '' &&
4077 ! $this->scale->ticks->HaveManualLabels() ) {
4078
4079 ++$label;
4080
4081 }
4082 }
4083
4084 if( $this->scale->type == "x" ) {
4085 if( $this->labelPos == SIDE_DOWN ) {
4086 if( $this->label_angle==0 || $this->label_angle==90 ) {
4087 if( $this->label_halign=='' && $this->label_valign=='') {
4088 $this->img->SetTextAlign('center','top');
4089 }
4090 else {
4091 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4092 }
4093
4094 }
4095 else {
4096 if( $this->label_halign=='' && $this->label_valign=='') {
4097 $this->img->SetTextAlign("right","top");
4098 }
4099 else {
4100 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4101 }
4102 }
4103 $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,
4104 $this->label_angle,$this->label_para_align);
4105 }
4106 else {
4107 if( $this->label_angle==0 || $this->label_angle==90 ) {
4108 if( $this->label_halign=='' && $this->label_valign=='') {
4109 $this->img->SetTextAlign("center","bottom");
4110 }
4111 else {
4112 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4113 }
4114 }
4115 else {
4116 if( $this->label_halign=='' && $this->label_valign=='') {
4117 $this->img->SetTextAlign("right","bottom");
4118 }
4119 else {
4120 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4121 }
4122 }
4123 $this->img->StrokeText($tpos,$aPos-$this->tick_label_margin-1,$label,
4124 $this->label_angle,$this->label_para_align);
4125 }
4126 }
4127 else {
4128 // scale->type == "y"
4129 //if( $this->label_angle!=0 )
4130 //JpGraphError::Raise(" Labels at an angle are not supported on Y-axis");
4131 if( $this->labelPos == SIDE_LEFT ) { // To the left of y-axis
4132 if( $this->label_halign=='' && $this->label_valign=='') {
4133 $this->img->SetTextAlign("right","center");
4134 }
4135 else {
4136 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4137 }
4138 $this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
4139 }
4140 else { // To the right of the y-axis
4141 if( $this->label_halign=='' && $this->label_valign=='') {
4142 $this->img->SetTextAlign("left","center");
4143 }
4144 else {
4145 $this->img->SetTextAlign($this->label_halign,$this->label_valign);
4146 }
4147 $this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label,$this->label_angle,$this->label_para_align);
4148 }
4149 }
4150 }
4151 ++$i;
4152 }
4153 }
4154
4155}
4156
4157
4158//===================================================
4159// CLASS Ticks
4160// Description: Abstract base class for drawing linear and logarithmic
4161// tick marks on axis
4162//===================================================
4163class Ticks {
4164 public $label_formatstr=''; // C-style format string to use for labels
4165 public $label_formfunc='';
4166 public $label_dateformatstr='';
4167 public $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)
4168 public $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
4169 public $maj_ticks_pos = array(), $maj_ticklabels_pos = array(),
4170 $ticks_pos = array(), $maj_ticks_label = array();
4171 public $precision;
4172
4173 protected $minor_abs_size=3, $major_abs_size=5;
4174 protected $scale;
4175 protected $is_set=false;
4176 protected $supress_zerolabel=false,$supress_first=false;
4177 protected $mincolor='',$majcolor='';
4178 protected $weight=1;
4179 protected $label_usedateformat=FALSE;
4180
4181 function __construct($aScale) {
4182 $this->scale=$aScale;
4183 $this->precision = -1;
4184 }
4185
4186 // Set format string for automatic labels
4187 function SetLabelFormat($aFormatString,$aDate=FALSE) {
4188 $this->label_formatstr=$aFormatString;
4189 $this->label_usedateformat=$aDate;
4190 }
4191
4192 function SetLabelDateFormat($aFormatString) {
4193 $this->label_dateformatstr=$aFormatString;
4194 }
4195
4196 function SetFormatCallback($aCallbackFuncName) {
4197 $this->label_formfunc = $aCallbackFuncName;
4198 }
4199
4200 // Don't display the first zero label
4201 function SupressZeroLabel($aFlag=true) {
4202 $this->supress_zerolabel=$aFlag;
4203 }
4204
4205 // Don't display minor tick marks
4206 function SupressMinorTickMarks($aHide=true) {
4207 $this->supress_minor_tickmarks=$aHide;
4208 }
4209
4210 // Don't display major tick marks
4211 function SupressTickMarks($aHide=true) {
4212 $this->supress_tickmarks=$aHide;
4213 }
4214
4215 // Hide the first tick mark
4216 function SupressFirst($aHide=true) {
4217 $this->supress_first=$aHide;
4218 }
4219
4220 // Hide the last tick mark
4221 function SupressLast($aHide=true) {
4222 $this->supress_last=$aHide;
4223 }
4224
4225 // Size (in pixels) of minor tick marks
4226 function GetMinTickAbsSize() {
4227 return $this->minor_abs_size;
4228 }
4229
4230 // Size (in pixels) of major tick marks
4231 function GetMajTickAbsSize() {
4232 return $this->major_abs_size;
4233 }
4234
4235 function SetSize($aMajSize,$aMinSize=3) {
4236 $this->major_abs_size = $aMajSize;
4237 $this->minor_abs_size = $aMinSize;
4238 }
4239
4240 // Have the ticks been specified
4241 function IsSpecified() {
4242 return $this->is_set;
4243 }
4244
4245 function SetSide($aSide) {
4246 $this->direction=$aSide;
4247 }
4248
4249 // Which side of the axis should the ticks be on
4250 function SetDirection($aSide=SIDE_RIGHT) {
4251 $this->direction=$aSide;
4252 }
4253
4254 // Set colors for major and minor tick marks
4255 function SetMarkColor($aMajorColor,$aMinorColor='') {
4256 $this->SetColor($aMajorColor,$aMinorColor);
4257 }
4258
4259 function SetColor($aMajorColor,$aMinorColor='') {
4260 $this->majcolor=$aMajorColor;
4261
4262 // If not specified use same as major
4263 if( $aMinorColor == '' ) {
4264 $this->mincolor=$aMajorColor;
4265 }
4266 else {
4267 $this->mincolor=$aMinorColor;
4268 }
4269 }
4270
4271 function SetWeight($aWeight) {
4272 $this->weight=$aWeight;
4273 }
4274
4275} // Class
4276
4277//===================================================
4278// CLASS LinearTicks
4279// Description: Draw linear ticks on axis
4280//===================================================
4281class LinearTicks extends Ticks {
4282 public $minor_step=1, $major_step=2;
4283 public $xlabel_offset=0,$xtick_offset=0;
4284 private $label_offset=0; // What offset should the displayed label have
4285 // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
4286 private $text_label_start=0;
4287 private $iManualTickPos = NULL, $iManualMinTickPos = NULL, $iManualTickLabels = NULL;
4288 private $iAdjustForDST = false; // If a date falls within the DST period add one hour to the diaplyed time
4289
4290 function __construct() {
4291 $this->precision = -1;
4292 }
4293
4294 // Return major step size in world coordinates
4295 function GetMajor() {
4296 return $this->major_step;
4297 }
4298
4299 // Return minor step size in world coordinates
4300 function GetMinor() {
4301 return $this->minor_step;
4302 }
4303
4304 // Set Minor and Major ticks (in world coordinates)
4305 function Set($aMajStep,$aMinStep=false) {
4306 if( $aMinStep==false ) {
4307 $aMinStep=$aMajStep;
4308 }
4309
4310 if( $aMajStep <= 0 || $aMinStep <= 0 ) {
4311 JpGraphError::RaiseL(25064);
4312 //(" 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.");
4313 }
4314
4315 $this->major_step=$aMajStep;
4316 $this->minor_step=$aMinStep;
4317 $this->is_set = true;
4318 }
4319
4320 function SetMajTickPositions($aMajPos,$aLabels=NULL) {
4321 $this->SetTickPositions($aMajPos,NULL,$aLabels);
4322 }
4323
4324 function SetTickPositions($aMajPos,$aMinPos=NULL,$aLabels=NULL) {
4325 if( !is_array($aMajPos) || ($aMinPos!==NULL && !is_array($aMinPos)) ) {
4326 JpGraphError::RaiseL(25065);//('Tick positions must be specifued as an array()');
4327 return;
4328 }
4329 $n=count($aMajPos);
4330 if( is_array($aLabels) && (count($aLabels) != $n) ) {
4331 JpGraphError::RaiseL(25066);//('When manually specifying tick positions and labels the number of labels must be the same as the number of specified ticks.');
4332 }
4333 $this->iManualTickPos = $aMajPos;
4334 $this->iManualMinTickPos = $aMinPos;
4335 $this->iManualTickLabels = $aLabels;
4336 }
4337
4338 function HaveManualLabels() {
4339 return is_array($this->iManualTickLabels) ? count($this->iManualTickLabels) > 0 : false;
4340 }
4341
4342 // Specify all the tick positions manually and possible also the exact labels
4343 function _doManualTickPos($aScale) {
4344 $n=count($this->iManualTickPos);
4345 $m= is_array($this->iManualMinTickPos) ? count($this->iManualMinTickPos) : 0;
4346 $doLbl= is_array($this->iManualTickLabels) ? count($this->iManualTickLabels) > 0 : false;
4347
4348 $this->maj_ticks_pos = array();
4349 $this->maj_ticklabels_pos = array();
4350 $this->ticks_pos = array();
4351
4352 // Now loop through the supplied positions and translate them to screen coordinates
4353 // and store them in the maj_label_positions
4354 $minScale = $aScale->scale[0];
4355 $maxScale = $aScale->scale[1];
4356 $j=0;
4357 for($i=0; $i < $n ; ++$i ) {
4358 // First make sure that the first tick is not lower than the lower scale value
4359 if( !isset($this->iManualTickPos[$i]) || $this->iManualTickPos[$i] < $minScale || $this->iManualTickPos[$i] > $maxScale) {
4360 continue;
4361 }
4362
4363 $this->maj_ticks_pos[$j] = $aScale->Translate($this->iManualTickPos[$i]);
4364 $this->maj_ticklabels_pos[$j] = $this->maj_ticks_pos[$j];
4365
4366 // Set the minor tick marks the same as major if not specified
4367 if( $m <= 0 ) {
4368 $this->ticks_pos[$j] = $this->maj_ticks_pos[$j];
4369 }
4370 if( $doLbl ) {
4371 $this->maj_ticks_label[$j] = $this->iManualTickLabels[$i];
4372 }
4373 else {
4374 $this->maj_ticks_label[$j]=$this->_doLabelFormat($this->iManualTickPos[$i],$i,$n);
4375 }
4376 ++$j;
4377 }
4378
4379 // Some sanity check
4380 if( count($this->maj_ticks_pos) < 2 ) {
4381 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.');
4382 }
4383
4384 // Setup the minor tick marks
4385 $j=0;
4386 for($i=0; $i < $m; ++$i ) {
4387 if( empty($this->iManualMinTickPos[$i]) || $this->iManualMinTickPos[$i] < $minScale || $this->iManualMinTickPos[$i] > $maxScale) {
4388 continue;
4389 }
4390 $this->ticks_pos[$j] = $aScale->Translate($this->iManualMinTickPos[$i]);
4391 ++$j;
4392 }
4393 }
4394
4395 function _doAutoTickPos($aScale) {
4396 $maj_step_abs = $aScale->scale_factor*$this->major_step;
4397 $min_step_abs = $aScale->scale_factor*$this->minor_step;
4398
4399 if( $min_step_abs==0 || $maj_step_abs==0 ) {
4400 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')");
4401 }
4402 // We need to make this an int since comparing it below
4403 // with the result from round() can give wrong result, such that
4404 // (40 < 40) == TRUE !!!
4405 $limit = (int)$aScale->scale_abs[1];
4406
4407 if( $aScale->textscale ) {
4408 // This can only be true for a X-scale (horizontal)
4409 // Define ticks for a text scale. This is slightly different from a
4410 // normal linear type of scale since the position might be adjusted
4411 // and the labels start at on
4412 $label = (float)$aScale->GetMinVal()+$this->text_label_start+$this->label_offset;
4413 $start_abs=$aScale->scale_factor*$this->text_label_start;
4414 $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4415
4416 $x = $aScale->scale_abs[0]+$start_abs+$this->xlabel_offset*$min_step_abs;
4417 for( $i=0; $label <= $aScale->GetMaxVal()+$this->label_offset; ++$i ) {
4418 // Apply format to label
4419 $this->maj_ticks_label[$i]=$this->_doLabelFormat($label,$i,$nbrmajticks);
4420 $label+=$this->major_step;
4421
4422 // The x-position of the tick marks can be different from the labels.
4423 // Note that we record the tick position (not the label) so that the grid
4424 // happen upon tick marks and not labels.
4425 $xtick=$aScale->scale_abs[0]+$start_abs+$this->xtick_offset*$min_step_abs+$i*$maj_step_abs;
4426 $this->maj_ticks_pos[$i]=$xtick;
4427 $this->maj_ticklabels_pos[$i] = round($x);
4428 $x += $maj_step_abs;
4429 }
4430 }
4431 else {
4432 $label = $aScale->GetMinVal();
4433 $abs_pos = $aScale->scale_abs[0];
4434 $j=0; $i=0;
4435 $step = round($maj_step_abs/$min_step_abs);
4436 if( $aScale->type == "x" ) {
4437 // For a normal linear type of scale the major ticks will always be multiples
4438 // of the minor ticks. In order to avoid any rounding issues the major ticks are
4439 // defined as every "step" minor ticks and not calculated separately
4440 $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4441 while( round($abs_pos) <= $limit ) {
4442 $this->ticks_pos[] = round($abs_pos);
4443 $this->ticks_label[] = $label;
4444 if( $step== 0 || $i % $step == 0 && $j < $nbrmajticks ) {
4445 $this->maj_ticks_pos[$j] = round($abs_pos);
4446 $this->maj_ticklabels_pos[$j] = round($abs_pos);
4447 $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
4448 ++$j;
4449 }
4450 ++$i;
4451 $abs_pos += $min_step_abs;
4452 $label+=$this->minor_step;
4453 }
4454 }
4455 elseif( $aScale->type == "y" ) {
4456 //@todo s=2:20,12 s=1:50,6 $this->major_step:$nbr
4457 // abs_point,limit s=1:270,80 s=2:540,160
4458 // $this->major_step = 50;
4459 $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal())/$this->major_step)+1;
4460// $step = 5;
4461 while( round($abs_pos) >= $limit ) {
4462 $this->ticks_pos[$i] = round($abs_pos);
4463 $this->ticks_label[$i]=$label;
4464 if( $step== 0 || $i % $step == 0 && $j < $nbrmajticks) {
4465 $this->maj_ticks_pos[$j] = round($abs_pos);
4466 $this->maj_ticklabels_pos[$j] = round($abs_pos);
4467 $this->maj_ticks_label[$j]=$this->_doLabelFormat($label,$j,$nbrmajticks);
4468 ++$j;
4469 }
4470 ++$i;
4471 $abs_pos += $min_step_abs;
4472 $label += $this->minor_step;
4473 }
4474 }
4475 }
4476 }
4477
4478 function AdjustForDST($aFlg=true) {
4479 $this->iAdjustForDST = $aFlg;
4480 }
4481
4482
4483 function _doLabelFormat($aVal,$aIdx,$aNbrTicks) {
4484
4485 // If precision hasn't been specified set it to a sensible value
4486 if( $this->precision==-1 ) {
4487 $t = log10($this->minor_step);
4488 if( $t > 0 || $t === 0.0) {
4489 $precision = 0;
4490 }
4491 else {
4492 $precision = -floor($t);
4493 }
4494 }
4495 else {
4496 $precision = $this->precision;
4497 }
4498
4499 if( $this->label_formfunc != '' ) {
4500 $f=$this->label_formfunc;
4501 if( $this->label_formatstr == '' ) {
4502 $l = call_user_func($f,$aVal);
4503 }
4504 else {
4505 $l = sprintf($this->label_formatstr, call_user_func($f,$aVal));
4506 }
4507 }
4508 elseif( $this->label_formatstr != '' || $this->label_dateformatstr != '' ) {
4509 if( $this->label_usedateformat ) {
4510 // Adjust the value to take daylight savings into account
4511 if (date("I",$aVal)==1 && $this->iAdjustForDST ) {
4512 // DST
4513 $aVal+=3600;
4514 }
4515
4516 $l = date($this->label_formatstr,$aVal);
4517 if( $this->label_formatstr == 'W' ) {
4518 // If we use week formatting then add a single 'w' in front of the
4519 // week number to differentiate it from dates
4520 $l = 'w'.$l;
4521 }
4522 }
4523 else {
4524 if( $this->label_dateformatstr !== '' ) {
4525 // Adjust the value to take daylight savings into account
4526 if (date("I",$aVal)==1 && $this->iAdjustForDST ) {
4527 // DST
4528 $aVal+=3600;
4529 }
4530
4531 $l = date($this->label_dateformatstr,$aVal);
4532 if( $this->label_formatstr == 'W' ) {
4533 // If we use week formatting then add a single 'w' in front of the
4534 // week number to differentiate it from dates
4535 $l = 'w'.$l;
4536 }
4537 }
4538 else {
4539 $l = sprintf($this->label_formatstr,$aVal);
4540 }
4541 }
4542 }
4543 else {
4544 $l = sprintf('%01.'.$precision.'f',round($aVal,$precision));
4545 }
4546
4547 if( ($this->supress_zerolabel && $l==0) || ($this->supress_first && $aIdx==0) || ($this->supress_last && $aIdx==$aNbrTicks-1) ) {
4548 $l='';
4549 }
4550 return $l;
4551 }
4552
4553 // Stroke ticks on either X or Y axis
4554 function _StrokeTicks($aImg,$aScale,$aPos) {
4555 $hor = $aScale->type == 'x';
4556 $aImg->SetLineWeight($this->weight);
4557
4558 // We need to make this an int since comparing it below
4559 // with the result from round() can give wrong result, such that
4560 // (40 < 40) == TRUE !!!
4561 $limit = (int)$aScale->scale_abs[1];
4562
4563 // A text scale doesn't have any minor ticks
4564 if( !$aScale->textscale ) {
4565 // Stroke minor ticks
4566 $yu = $aPos - $this->direction*$this->GetMinTickAbsSize();
4567 $xr = $aPos + $this->direction*$this->GetMinTickAbsSize();
4568 $n = count($this->ticks_pos);
4569 for($i=0; $i < $n; ++$i ) {
4570 if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks) {
4571 if( $this->mincolor != '') {
4572 $aImg->PushColor($this->mincolor);
4573 }
4574 if( $hor ) {
4575 //if( $this->ticks_pos[$i] <= $limit )
4576 $aImg->Line($this->ticks_pos[$i],$aPos,$this->ticks_pos[$i],$yu);
4577 }
4578 else {
4579 //if( $this->ticks_pos[$i] >= $limit )
4580 $aImg->Line($aPos,$this->ticks_pos[$i],$xr,$this->ticks_pos[$i]);
4581 }
4582 if( $this->mincolor != '' ) {
4583 $aImg->PopColor();
4584 }
4585 }
4586 }
4587 }
4588
4589 // Stroke major ticks
4590 $yu = $aPos - $this->direction*$this->GetMajTickAbsSize();
4591 $xr = $aPos + $this->direction*$this->GetMajTickAbsSize();
4592 $nbrmajticks=round(($aScale->GetMaxVal()-$aScale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;
4593 $n = count($this->maj_ticks_pos);
4594 for($i=0; $i < $n ; ++$i ) {
4595 if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
4596 if( $this->majcolor != '') {
4597 $aImg->PushColor($this->majcolor);
4598 }
4599 if( $hor ) {
4600 //if( $this->maj_ticks_pos[$i] <= $limit )
4601 $aImg->Line($this->maj_ticks_pos[$i],$aPos,$this->maj_ticks_pos[$i],$yu);
4602 }
4603 else {
4604 //if( $this->maj_ticks_pos[$i] >= $limit )
4605 $aImg->Line($aPos,$this->maj_ticks_pos[$i],$xr,$this->maj_ticks_pos[$i]);
4606 }
4607 if( $this->majcolor != '') {
4608 $aImg->PopColor();
4609 }
4610 }
4611 }
4612
4613 }
4614
4615 // Draw linear ticks
4616 function Stroke($aImg,$aScale,$aPos) {
4617 if( $this->iManualTickPos != NULL ) {
4618 $this->_doManualTickPos($aScale);
4619 }
4620 else {
4621 $this->_doAutoTickPos($aScale);
4622 }
4623 $this->_StrokeTicks($aImg,$aScale,$aPos, $aScale->type == 'x' );
4624 }
4625
4626 //---------------
4627 // PRIVATE METHODS
4628 // Spoecify the offset of the displayed tick mark with the tick "space"
4629 // Legal values for $o is [0,1] used to adjust where the tick marks and label
4630 // should be positioned within the major tick-size
4631 // $lo specifies the label offset and $to specifies the tick offset
4632 // this comes in handy for example in bar graphs where we wont no offset for the
4633 // tick but have the labels displayed halfway under the bars.
4634 function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
4635 $this->xlabel_offset=$aLabelOff;
4636 if( $aTickOff==-1 ) {
4637 // Same as label offset
4638 $this->xtick_offset=$aLabelOff;
4639 }
4640 else {
4641 $this->xtick_offset=$aTickOff;
4642 }
4643 if( $aLabelOff>0 ) {
4644 $this->SupressLast(); // The last tick wont fit
4645 }
4646 }
4647
4648 // Which tick label should we start with?
4649 function SetTextLabelStart($aTextLabelOff) {
4650 $this->text_label_start=$aTextLabelOff;
4651 }
4652
4653} // Class
4654
4655//===================================================
4656// CLASS LinearScale
4657// Description: Handle linear scaling between screen and world
4658//===================================================
4659class LinearScale {
4660 public $textscale=false; // Just a flag to let the Plot class find out if
4661 // we are a textscale or not. This is a cludge since
4662 // this information is available in Graph::axtype but
4663 // we don't have access to the graph object in the Plots
4664 // stroke method. So we let graph store the status here
4665 // when the linear scale is created. A real cludge...
4666 public $type; // is this x or y scale ?
4667 public $ticks=null; // Store ticks
4668 public $text_scale_off = 0;
4669 public $scale_abs=array(0,0);
4670 public $scale_factor; // Scale factor between world and screen
4671 public $off; // Offset between image edge and plot area
4672 public $scale=array(0,0);
4673 public $name = 'lin';
4674 public $auto_ticks=false; // When using manual scale should the ticks be automatically set?
4675 public $world_abs_size; // Plot area size in pixels (Needed public in jpgraph_radar.php)
4676 public $intscale=false; // Restrict autoscale to integers
4677 protected $autoscale_min=false; // Forced minimum value, auto determine max
4678 protected $autoscale_max=false; // Forced maximum value, auto determine min
4679 private $gracetop=0,$gracebottom=0;
4680
4681 private $_world_size; // Plot area size in world coordinates
4682
4683 function __construct($aMin=0,$aMax=0,$aType='y') {
4684 assert($aType=='x' || $aType=='y' );
4685 assert($aMin<=$aMax);
4686
4687 $this->type=$aType;
4688 $this->scale=array($aMin,$aMax);
4689 $this->world_size=$aMax-$aMin;
4690 $this->ticks = new LinearTicks();
4691 }
4692
4693 // Check if scale is set or if we should autoscale
4694 // We should do this is either scale or ticks has not been set
4695 function IsSpecified() {
4696 if( $this->GetMinVal()==$this->GetMaxVal() ) { // Scale not set
4697 return false;
4698 }
4699 return true;
4700 }
4701
4702 // Set the minimum data value when the autoscaling is used.
4703 // Usefull if you want a fix minimum (like 0) but have an
4704 // automatic maximum
4705 function SetAutoMin($aMin) {
4706 $this->autoscale_min=$aMin;
4707 }
4708
4709 // Set the minimum data value when the autoscaling is used.
4710 // Usefull if you want a fix minimum (like 0) but have an
4711 // automatic maximum
4712 function SetAutoMax($aMax) {
4713 $this->autoscale_max=$aMax;
4714 }
4715
4716 // If the user manually specifies a scale should the ticks
4717 // still be set automatically?
4718 function SetAutoTicks($aFlag=true) {
4719 $this->auto_ticks = $aFlag;
4720 }
4721
4722 // Specify scale "grace" value (top and bottom)
4723 function SetGrace($aGraceTop,$aGraceBottom=0) {
4724 if( $aGraceTop<0 || $aGraceBottom < 0 ) {
4725 JpGraphError::RaiseL(25069);//(" Grace must be larger then 0");
4726 }
4727 $this->gracetop=$aGraceTop;
4728 $this->gracebottom=$aGraceBottom;
4729 }
4730
4731 // Get the minimum value in the scale
4732 function GetMinVal() {
4733 return $this->scale[0];
4734 }
4735
4736 // get maximum value for scale
4737 function GetMaxVal() {
4738 return $this->scale[1];
4739 }
4740
4741 // Specify a new min/max value for sclae
4742 function Update($aImg,$aMin,$aMax) {
4743 $this->scale=array($aMin,$aMax);
4744 $this->world_size=$aMax-$aMin;
4745 $this->InitConstants($aImg);
4746 }
4747
4748 // Translate between world and screen
4749 function Translate($aCoord) {
4750 if( !is_numeric($aCoord) ) {
4751 if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) {
4752 JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
4753 }
4754 return 0;
4755 }
4756 else {
4757 return round($this->off+($aCoord - $this->scale[0]) * $this->scale_factor);
4758 }
4759 }
4760
4761 // Relative translate (don't include offset) usefull when we just want
4762 // to know the relative position (in pixels) on the axis
4763 function RelTranslate($aCoord) {
4764 if( !is_numeric($aCoord) ) {
4765 if( $aCoord != '' && $aCoord != '-' && $aCoord != 'x' ) {
4766 JpGraphError::RaiseL(25070);//('Your data contains non-numeric values.');
4767 }
4768 return 0;
4769 }
4770 else {
4771 return ($aCoord - $this->scale[0]) * $this->scale_factor;
4772 }
4773 }
4774
4775 // Restrict autoscaling to only use integers
4776 function SetIntScale($aIntScale=true) {
4777 $this->intscale=$aIntScale;
4778 }
4779
4780 // Calculate an integer autoscale
4781 function IntAutoScale($img,$min,$max,$maxsteps,$majend=true) {
4782 // Make sure limits are integers
4783 $min=floor($min);
4784 $max=ceil($max);
4785 if( abs($min-$max)==0 ) {
4786 --$min; ++$max;
4787 }
4788 $maxsteps = floor($maxsteps);
4789
4790 $gracetop=round(($this->gracetop/100.0)*abs($max-$min));
4791 $gracebottom=round(($this->gracebottom/100.0)*abs($max-$min));
4792 if( is_numeric($this->autoscale_min) ) {
4793 $min = ceil($this->autoscale_min);
4794 if( $min >= $max ) {
4795 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.');
4796 }
4797 }
4798
4799 if( is_numeric($this->autoscale_max) ) {
4800 $max = ceil($this->autoscale_max);
4801 if( $min >= $max ) {
4802 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.');
4803 }
4804 }
4805
4806 if( abs($min-$max ) == 0 ) {
4807 ++$max;
4808 --$min;
4809 }
4810
4811 $min -= $gracebottom;
4812 $max += $gracetop;
4813
4814 // First get tickmarks as multiples of 1, 10, ...
4815 if( $majend ) {
4816 list($num1steps,$adj1min,$adj1max,$maj1step) = $this->IntCalcTicks($maxsteps,$min,$max,1);
4817 }
4818 else {
4819 $adj1min = $min;
4820 $adj1max = $max;
4821 list($num1steps,$maj1step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,1);
4822 }
4823
4824 if( abs($min-$max) > 2 ) {
4825 // Then get tick marks as 2:s 2, 20, ...
4826 if( $majend ) {
4827 list($num2steps,$adj2min,$adj2max,$maj2step) = $this->IntCalcTicks($maxsteps,$min,$max,5);
4828 }
4829 else {
4830 $adj2min = $min;
4831 $adj2max = $max;
4832 list($num2steps,$maj2step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,5);
4833 }
4834 }
4835 else {
4836 $num2steps = 10000; // Dummy high value so we don't choose this
4837 }
4838
4839 if( abs($min-$max) > 5 ) {
4840 // Then get tickmarks as 5:s 5, 50, 500, ...
4841 if( $majend ) {
4842 list($num5steps,$adj5min,$adj5max,$maj5step) = $this->IntCalcTicks($maxsteps,$min,$max,2);
4843 }
4844 else {
4845 $adj5min = $min;
4846 $adj5max = $max;
4847 list($num5steps,$maj5step) = $this->IntCalcTicksFreeze($maxsteps,$min,$max,2);
4848 }
4849 }
4850 else {
4851 $num5steps = 10000; // Dummy high value so we don't choose this
4852 }
4853
4854 // Check to see whichof 1:s, 2:s or 5:s fit better with
4855 // the requested number of major ticks
4856 $match1=abs($num1steps-$maxsteps);
4857 $match2=abs($num2steps-$maxsteps);
4858 if( !empty($maj5step) && $maj5step > 1 ) {
4859 $match5=abs($num5steps-$maxsteps);
4860 }
4861 else {
4862 $match5=10000; // Dummy high value
4863 }
4864
4865 // Compare these three values and see which is the closest match
4866 // We use a 0.6 weight to gravitate towards multiple of 5:s
4867 if( $match1 < $match2 ) {
4868 if( $match1 < $match5 ) $r=1;
4869 else $r=3;
4870 }
4871 else {
4872 if( $match2 < $match5 ) $r=2;
4873 else $r=3;
4874 }
4875 // Minsteps are always the same as maxsteps for integer scale
4876 switch( $r ) {
4877 case 1:
4878 $this->ticks->Set($maj1step,$maj1step);
4879 $this->Update($img,$adj1min,$adj1max);
4880 break;
4881 case 2:
4882 $this->ticks->Set($maj2step,$maj2step);
4883 $this->Update($img,$adj2min,$adj2max);
4884 break;
4885 case 3:
4886 $this->ticks->Set($maj5step,$maj5step);
4887 $this->Update($img,$adj5min,$adj5max);
4888 break;
4889 default:
4890 JpGraphError::RaiseL(25073,$r);//('Internal error. Integer scale algorithm comparison out of bound (r=$r)');
4891 }
4892 }
4893
4894
4895 // Calculate autoscale. Used if user hasn't given a scale and ticks
4896 // $maxsteps is the maximum number of major tickmarks allowed.
4897 function AutoScale($img,$min,$max,$maxsteps,$majend=true) {
4898
4899 if( !is_numeric($min) || !is_numeric($max) ) {
4900 JpGraphError::Raise(25044);
4901 }
4902
4903 if( $this->intscale ) {
4904 $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
4905 return;
4906 }
4907 if( abs($min-$max) < 0.00001 ) {
4908 // We need some difference to be able to autoscale
4909 // make it 5% above and 5% below value
4910 if( $min==0 && $max==0 ) { // Special case
4911 $min=-1; $max=1;
4912 }
4913 else {
4914 $delta = (abs($max)+abs($min))*0.005;
4915 $min -= $delta;
4916 $max += $delta;
4917 }
4918 }
4919
4920 $gracetop=($this->gracetop/100.0)*abs($max-$min);
4921 $gracebottom=($this->gracebottom/100.0)*abs($max-$min);
4922 if( is_numeric($this->autoscale_min) ) {
4923 $min = $this->autoscale_min;
4924 if( $min >= $max ) {
4925 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.');
4926 }
4927 if( abs($min-$max ) < 0.001 ) {
4928 $max *= 1.2;
4929 }
4930 }
4931
4932 if( is_numeric($this->autoscale_max) ) {
4933 $max = $this->autoscale_max;
4934 if( $min >= $max ) {
4935 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.');
4936 }
4937 if( abs($min-$max ) < 0.001 ) {
4938 $min *= 0.8;
4939 }
4940 }
4941
4942 $min -= $gracebottom;
4943 $max += $gracetop;
4944
4945 // First get tickmarks as multiples of 0.1, 1, 10, ...
4946 if( $majend ) {
4947 list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) = $this->CalcTicks($maxsteps,$min,$max,1,2);
4948 }
4949 else {
4950 $adj1min=$min;
4951 $adj1max=$max;
4952 list($num1steps,$min1step,$maj1step) = $this->CalcTicksFreeze($maxsteps,$min,$max,1,2,false);
4953 }
4954
4955 // Then get tick marks as 2:s 0.2, 2, 20, ...
4956 if( $majend ) {
4957 list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) = $this->CalcTicks($maxsteps,$min,$max,5,2);
4958 }
4959 else {
4960 $adj2min=$min;
4961 $adj2max=$max;
4962 list($num2steps,$min2step,$maj2step) = $this->CalcTicksFreeze($maxsteps,$min,$max,5,2,false);
4963 }
4964
4965 // Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
4966 if( $majend ) {
4967 list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) = $this->CalcTicks($maxsteps,$min,$max,2,5);
4968 }
4969 else {
4970 $adj5min=$min;
4971 $adj5max=$max;
4972 list($num5steps,$min5step,$maj5step) = $this->CalcTicksFreeze($maxsteps,$min,$max,2,5,false);
4973 }
4974
4975 // Check to see whichof 1:s, 2:s or 5:s fit better with
4976 // the requested number of major ticks
4977 $match1=abs($num1steps-$maxsteps);
4978 $match2=abs($num2steps-$maxsteps);
4979 $match5=abs($num5steps-$maxsteps);
4980
4981 // Compare these three values and see which is the closest match
4982 // We use a 0.8 weight to gravitate towards multiple of 5:s
4983 $r=$this->MatchMin3($match1,$match2,$match5,0.8);
4984 switch( $r ) {
4985 case 1:
4986 $this->Update($img,$adj1min,$adj1max);
4987 $this->ticks->Set($maj1step,$min1step);
4988 break;
4989 case 2:
4990 $this->Update($img,$adj2min,$adj2max);
4991 $this->ticks->Set($maj2step,$min2step);
4992 break;
4993 case 3:
4994 $this->Update($img,$adj5min,$adj5max);
4995 $this->ticks->Set($maj5step,$min5step);
4996 break;
4997 }
4998 }
4999
5000 //---------------
5001 // PRIVATE METHODS
5002
5003 // This method recalculates all constants that are depending on the
5004 // margins in the image. If the margins in the image are changed
5005 // this method should be called for every scale that is registred with
5006 // that image. Should really be installed as an observer of that image.
5007 function InitConstants($img) {
5008 if( $this->type=='x' ) {
5009 $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
5010 $this->off=$img->left_margin;
5011 $this->scale_factor = 0;
5012 if( $this->world_size > 0 ) {
5013 $this->scale_factor=$this->world_abs_size/($this->world_size*0.999999);
5014 }
5015 }
5016 else { // y scale
5017 $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin;
5018 $this->off=$img->top_margin+$this->world_abs_size;
5019 $this->scale_factor = 0;
5020 if( $this->world_size > 0 ) {
5021 $this->scale_factor=-$this->world_abs_size/($this->world_size*0.999999);
5022 }
5023 }
5024 $size = $this->world_size * $this->scale_factor;
5025 $this->scale_abs=array($this->off,$this->off + $size);
5026 }
5027
5028 // Initialize the conversion constants for this scale
5029 // This tries to pre-calculate as much as possible to speed up the
5030 // actual conversion (with Translate()) later on
5031 // $start =scale start in absolute pixels (for x-scale this is an y-position
5032 // and for an y-scale this is an x-position
5033 // $len =absolute length in pixels of scale
5034 function SetConstants($aStart,$aLen) {
5035 $this->world_abs_size=$aLen;
5036 $this->off=$aStart;
5037
5038 if( $this->world_size<=0 ) {
5039 // This should never ever happen !!
5040 JpGraphError::RaiseL(25074);
5041 //("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.");
5042 }
5043
5044 // scale_factor = number of pixels per world unit
5045 $this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
5046
5047 // scale_abs = start and end points of scale in absolute pixels
5048 $this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);
5049 }
5050
5051
5052 // Calculate number of ticks steps with a specific division
5053 // $a is the divisor of 10**x to generate the first maj tick intervall
5054 // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
5055 // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
5056 // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
5057 // We return a vector of
5058 // [$numsteps,$adjmin,$adjmax,$minstep,$majstep]
5059 // If $majend==true then the first and last marks on the axis will be major
5060 // labeled tick marks otherwise it will be adjusted to the closest min tick mark
5061 function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
5062 $diff=$max-$min;
5063 if( $diff==0 ) {
5064 $ld=0;
5065 }
5066 else {
5067 $ld=floor(log10($diff));
5068 }
5069
5070 // Gravitate min towards zero if we are close
5071 if( $min>0 && $min < pow(10,$ld) ) $min=0;
5072
5073 //$majstep=pow(10,$ld-1)/$a;
5074 $majstep=pow(10,$ld)/$a;
5075 $minstep=$majstep/$b;
5076
5077 $adjmax=ceil($max/$minstep)*$minstep;
5078 $adjmin=floor($min/$minstep)*$minstep;
5079 $adjdiff = $adjmax-$adjmin;
5080 $numsteps=$adjdiff/$majstep;
5081
5082 while( $numsteps>$maxsteps ) {
5083 $majstep=pow(10,$ld)/$a;
5084 $numsteps=$adjdiff/$majstep;
5085 ++$ld;
5086 }
5087
5088 $minstep=$majstep/$b;
5089 $adjmin=floor($min/$minstep)*$minstep;
5090 $adjdiff = $adjmax-$adjmin;
5091 if( $majend ) {
5092 $adjmin = floor($min/$majstep)*$majstep;
5093 $adjdiff = $adjmax-$adjmin;
5094 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
5095 }
5096 else {
5097 $adjmax=ceil($max/$minstep)*$minstep;
5098 }
5099
5100 return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
5101 }
5102
5103 function CalcTicksFreeze($maxsteps,$min,$max,$a,$b) {
5104 // Same as CalcTicks but don't adjust min/max values
5105 $diff=$max-$min;
5106 if( $diff==0 ) {
5107 $ld=0;
5108 }
5109 else {
5110 $ld=floor(log10($diff));
5111 }
5112
5113 //$majstep=pow(10,$ld-1)/$a;
5114 $majstep=pow(10,$ld)/$a;
5115 $minstep=$majstep/$b;
5116 $numsteps=floor($diff/$majstep);
5117
5118 while( $numsteps > $maxsteps ) {
5119 $majstep=pow(10,$ld)/$a;
5120 $numsteps=floor($diff/$majstep);
5121 ++$ld;
5122 }
5123 $minstep=$majstep/$b;
5124 return array($numsteps,$minstep,$majstep);
5125 }
5126
5127
5128 function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
5129 $diff=$max-$min;
5130 if( $diff==0 ) {
5131 JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
5132 }
5133 else {
5134 $ld=floor(log10($diff));
5135 }
5136
5137 // Gravitate min towards zero if we are close
5138 if( $min>0 && $min < pow(10,$ld) ) {
5139 $min=0;
5140 }
5141 if( $ld == 0 ) {
5142 $ld=1;
5143 }
5144 if( $a == 1 ) {
5145 $majstep = 1;
5146 }
5147 else {
5148 $majstep=pow(10,$ld)/$a;
5149 }
5150 $adjmax=ceil($max/$majstep)*$majstep;
5151
5152 $adjmin=floor($min/$majstep)*$majstep;
5153 $adjdiff = $adjmax-$adjmin;
5154 $numsteps=$adjdiff/$majstep;
5155 while( $numsteps>$maxsteps ) {
5156 $majstep=pow(10,$ld)/$a;
5157 $numsteps=$adjdiff/$majstep;
5158 ++$ld;
5159 }
5160
5161 $adjmin=floor($min/$majstep)*$majstep;
5162 $adjdiff = $adjmax-$adjmin;
5163 if( $majend ) {
5164 $adjmin = floor($min/$majstep)*$majstep;
5165 $adjdiff = $adjmax-$adjmin;
5166 $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
5167 }
5168 else {
5169 $adjmax=ceil($max/$majstep)*$majstep;
5170 }
5171
5172 return array($numsteps,$adjmin,$adjmax,$majstep);
5173 }
5174
5175
5176 function IntCalcTicksFreeze($maxsteps,$min,$max,$a) {
5177 // Same as IntCalcTick but don't change min/max values
5178 $diff=$max-$min;
5179 if( $diff==0 ) {
5180 JpGraphError::RaiseL(25075);//('Can\'t automatically determine ticks since min==max.');
5181 }
5182 else {
5183 $ld=floor(log10($diff));
5184 }
5185 if( $ld == 0 ) {
5186 $ld=1;
5187 }
5188 if( $a == 1 ) {
5189 $majstep = 1;
5190 }
5191 else {
5192 $majstep=pow(10,$ld)/$a;
5193 }
5194
5195 $numsteps=floor($diff/$majstep);
5196 while( $numsteps > $maxsteps ) {
5197 $majstep=pow(10,$ld)/$a;
5198 $numsteps=floor($diff/$majstep);
5199 ++$ld;
5200 }
5201
5202 return array($numsteps,$majstep);
5203 }
5204
5205 // Determine the minimum of three values witha weight for last value
5206 function MatchMin3($a,$b,$c,$weight) {
5207 if( $a < $b ) {
5208 if( $a < ($c*$weight) ) {
5209 return 1; // $a smallest
5210 }
5211 else {
5212 return 3; // $c smallest
5213 }
5214 }
5215 elseif( $b < ($c*$weight) ) {
5216 return 2; // $b smallest
5217 }
5218 return 3; // $c smallest
5219 }
5220
5221 function __get($name) {
5222 $variable_name = '_' . $name;
5223
5224 if (isset($this->$variable_name)) {
5225 return $this->$variable_name * SUPERSAMPLING_SCALE;
5226 } else {
5227 JpGraphError::RaiseL('25132', $name);
5228 }
5229 }
5230
5231 function __set($name, $value) {
5232 $this->{'_'.$name} = $value;
5233 }
5234} // Class
5235
5236
5237//===================================================
5238// CLASS DisplayValue
5239// Description: Used to print data values at data points
5240//===================================================
5241class DisplayValue {
5242 public $margin=5;
5243 public $show=false;
5244 public $valign='',$halign='center';
5245 public $format='%.1f',$negformat='';
5246 private $ff=FF_DEFAULT,$fs=FS_NORMAL,$fsize=8;
5247 private $iFormCallback='';
5248 private $angle=0;
5249 private $color='navy',$negcolor='';
5250 private $iHideZero=false;
5251 public $txt=null;
5252
5253 function __construct() {
5254 $this->txt = new Text();
5255 }
5256
5257 function Show($aFlag=true) {
5258 $this->show=$aFlag;
5259 }
5260
5261 function SetColor($aColor,$aNegcolor='') {
5262 $this->color = $aColor;
5263 $this->negcolor = $aNegcolor;
5264 }
5265
5266 function SetFont($aFontFamily,$aFontStyle=FS_NORMAL,$aFontSize=8) {
5267 $this->ff=$aFontFamily;
5268 $this->fs=$aFontStyle;
5269 $this->fsize=$aFontSize;
5270 }
5271
5272 function ApplyFont($aImg) {
5273 $aImg->SetFont($this->ff,$this->fs,$this->fsize);
5274 }
5275
5276 function SetMargin($aMargin) {
5277 $this->margin = $aMargin;
5278 }
5279
5280 function SetAngle($aAngle) {
5281 $this->angle = $aAngle;
5282 }
5283
5284 function SetAlign($aHAlign,$aVAlign='') {
5285 $this->halign = $aHAlign;
5286 $this->valign = $aVAlign;
5287 }
5288
5289 function SetFormat($aFormat,$aNegFormat='') {
5290 $this->format= $aFormat;
5291 $this->negformat= $aNegFormat;
5292 }
5293
5294 function SetFormatCallback($aFunc) {
5295 $this->iFormCallback = $aFunc;
5296 }
5297
5298 function HideZero($aFlag=true) {
5299 $this->iHideZero=$aFlag;
5300 }
5301
5302 function Stroke($img,$aVal,$x,$y) {
5303
5304 if( $this->show )
5305 {
5306 if( $this->negformat=='' ) {
5307 $this->negformat=$this->format;
5308 }
5309 if( $this->negcolor=='' ) {
5310 $this->negcolor=$this->color;
5311 }
5312
5313 if( $aVal===NULL || (is_string($aVal) && ($aVal=='' || $aVal=='-' || $aVal=='x' ) ) ) {
5314 return;
5315 }
5316
5317 if( is_numeric($aVal) && $aVal==0 && $this->iHideZero ) {
5318 return;
5319 }
5320
5321 // Since the value is used in different cirumstances we need to check what
5322 // kind of formatting we shall use. For example, to display values in a line
5323 // graph we simply display the formatted value, but in the case where the user
5324 // has already specified a text string we don't fo anything.
5325 if( $this->iFormCallback != '' ) {
5326 $f = $this->iFormCallback;
5327 $sval = call_user_func($f,$aVal);
5328 }
5329 elseif( is_numeric($aVal) ) {
5330 if( $aVal >= 0 ) {
5331 $sval=sprintf($this->format,$aVal);
5332 }
5333 else {
5334 $sval=sprintf($this->negformat,$aVal);
5335 }
5336 }
5337 else {
5338 $sval=$aVal;
5339 }
5340
5341 $y = $y-sign($aVal)*$this->margin;
5342
5343 $this->txt->Set($sval);
5344 $this->txt->SetPos($x,$y);
5345 $this->txt->SetFont($this->ff,$this->fs,$this->fsize);
5346 if( $this->valign == '' ) {
5347 if( $aVal >= 0 ) {
5348 $valign = "bottom";
5349 }
5350 else {
5351 $valign = "top";
5352 }
5353 }
5354 else {
5355 $valign = $this->valign;
5356 }
5357 $this->txt->Align($this->halign,$valign);
5358
5359 $this->txt->SetOrientation($this->angle);
5360 if( $aVal > 0 ) {
5361 $this->txt->SetColor($this->color);
5362 }
5363 else {
5364 $this->txt->SetColor($this->negcolor);
5365 }
5366 $this->txt->Stroke($img);
5367 }
5368 }
5369}
5370
5371//===================================================
5372// CLASS Plot
5373// Description: Abstract base class for all concrete plot classes
5374//===================================================
5375class Plot {
5376 public $numpoints=0;
5377 public $value;
5378 public $legend='';
5379 public $coords=array();
5380 public $color='black';
5381 public $hidelegend=false;
5382 public $line_weight=1;
5383 public $csimtargets=array(),$csimwintargets=array(); // Array of targets for CSIM
5384 public $csimareas=''; // Resultant CSIM area tags
5385 public $csimalts=null; // ALT:s for corresponding target
5386 public $legendcsimtarget='',$legendcsimwintarget='';
5387 public $legendcsimalt='';
5388 protected $weight=1;
5389 protected $center=false;
5390
5391 protected $inputValues;
5392 protected $isRunningClear = false;
5393
5394 function __construct($aDatay,$aDatax=false) {
5395 $this->numpoints = count($aDatay);
5396 if( $this->numpoints==0 ) {
5397 JpGraphError::RaiseL(25121);//("Empty input data array specified for plot. Must have at least one data point.");
5398 }
5399
5400 if (!$this->isRunningClear) {
5401 $this->inputValues = array();
5402 $this->inputValues['aDatay'] = $aDatay;
5403 $this->inputValues['aDatax'] = $aDatax;
5404 }
5405
5406 $this->coords[0]=$aDatay;
5407 if( is_array($aDatax) ) {
5408 $this->coords[1]=$aDatax;
5409 $n = count($aDatax);
5410 for( $i=0; $i < $n; ++$i ) {
5411 if( !is_numeric($aDatax[$i]) ) {
5412 JpGraphError::RaiseL(25070);
5413 }
5414 }
5415 }
5416 $this->value = new DisplayValue();
5417 }
5418
5419 // Stroke the plot
5420 // "virtual" function which must be implemented by
5421 // the subclasses
5422 function Stroke($aImg,$aXScale,$aYScale) {
5423 JpGraphError::RaiseL(25122);//("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
5424 }
5425
5426 function HideLegend($f=true) {
5427 $this->hidelegend = $f;
5428 }
5429
5430 function DoLegend($graph) {
5431 if( !$this->hidelegend )
5432 $this->Legend($graph);
5433 }
5434
5435 function StrokeDataValue($img,$aVal,$x,$y) {
5436 $this->value->Stroke($img,$aVal,$x,$y);
5437 }
5438
5439 // Set href targets for CSIM
5440 function SetCSIMTargets($aTargets,$aAlts='',$aWinTargets='') {
5441 $this->csimtargets=$aTargets;
5442 $this->csimwintargets=$aWinTargets;
5443 $this->csimalts=$aAlts;
5444 }
5445
5446 // Get all created areas
5447 function GetCSIMareas() {
5448 return $this->csimareas;
5449 }
5450
5451 // "Virtual" function which gets called before any scale
5452 // or axis are stroked used to do any plot specific adjustment
5453 function PreStrokeAdjust($aGraph) {
5454 if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) ) {
5455 JpGraphError::RaiseL(25123);//("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
5456 }
5457 return true;
5458 }
5459
5460 // Virtual function to the the concrete plot class to make any changes to the graph
5461 // and scale before the stroke process begins
5462 function PreScaleSetup($aGraph) {
5463 // Empty
5464 }
5465
5466 // Get minimum values in plot
5467 function Min() {
5468 if( isset($this->coords[1]) ) {
5469 $x=$this->coords[1];
5470 }
5471 else {
5472 $x='';
5473 }
5474 if( $x != '' && count($x) > 0 ) {
5475 $xm=min($x);
5476 }
5477 else {
5478 $xm=0;
5479 }
5480 $y=$this->coords[0];
5481 $cnt = count($y);
5482 if( $cnt > 0 ) {
5483 $i=0;
5484 while( $i<$cnt && !is_numeric($ym=$y[$i]) ) {
5485 $i++;
5486 }
5487 while( $i < $cnt) {
5488 if( is_numeric($y[$i]) ) {
5489 $ym=min($ym,$y[$i]);
5490 }
5491 ++$i;
5492 }
5493 }
5494 else {
5495 $ym='';
5496 }
5497 return array($xm,$ym);
5498 }
5499
5500 // Get maximum value in plot
5501 function Max() {
5502 if( isset($this->coords[1]) ) {
5503 $x=$this->coords[1];
5504 }
5505 else {
5506 $x='';
5507 }
5508
5509 if( $x!='' && count($x) > 0 ) {
5510 $xm=max($x);
5511 }
5512 else {
5513 $xm = $this->numpoints-1;
5514 }
5515 $y=$this->coords[0];
5516 if( count($y) > 0 ) {
5517 $cnt = count($y);
5518 $i=0;
5519 while( $i<$cnt && !is_numeric($ym=$y[$i]) ) {
5520 $i++;
5521 }
5522 while( $i < $cnt ) {
5523 if( is_numeric($y[$i]) ) {
5524 $ym=max($ym,$y[$i]);
5525 }
5526 ++$i;
5527 }
5528 }
5529 else {
5530 $ym='';
5531 }
5532 return array($xm,$ym);
5533 }
5534
5535 function SetColor($aColor) {
5536 $this->color=$aColor;
5537 }
5538
5539 function SetLegend($aLegend,$aCSIM='',$aCSIMAlt='',$aCSIMWinTarget='') {
5540 $this->legend = $aLegend;
5541 $this->legendcsimtarget = $aCSIM;
5542 $this->legendcsimwintarget = $aCSIMWinTarget;
5543 $this->legendcsimalt = $aCSIMAlt;
5544 }
5545
5546 function SetWeight($aWeight) {
5547 $this->weight=$aWeight;
5548 }
5549
5550 function SetLineWeight($aWeight=1) {
5551 $this->line_weight=$aWeight;
5552 }
5553
5554 function SetCenter($aCenter=true) {
5555 $this->center = $aCenter;
5556 }
5557
5558 // This method gets called by Graph class to plot anything that should go
5559 // into the margin after the margin color has been set.
5560 function StrokeMargin($aImg) {
5561 return true;
5562 }
5563
5564 // Framework function the chance for each plot class to set a legend
5565 function Legend($aGraph) {
5566 if( $this->legend != '' ) {
5567 $aGraph->legend->Add($this->legend,$this->color,'',0,$this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget);
5568 }
5569 }
5570
5571 function Clear() {
5572 $this->isRunningClear = true;
5573 $this->__construct($this->inputValues['aDatay'], $this->inputValues['aDatax']);
5574 $this->isRunningClear = false;
5575 }
5576
5577} // Class
5578
5579
5580// Provide a deterministic list of new colors whenever the getColor() method
5581// is called. Used to automatically set colors of plots.
5582class ColorFactory {
5583
5584 static private $iIdx = 0;
5585 static private $iColorList = array(
5586 'black',
5587 'blue',
5588 'orange',
5589 'darkgreen',
5590 'red',
5591 'AntiqueWhite3',
5592 'aquamarine3',
5593 'azure4',
5594 'brown',
5595 'cadetblue3',
5596 'chartreuse4',
5597 'chocolate',
5598 'darkblue',
5599 'darkgoldenrod3',
5600 'darkorchid3',
5601 'darksalmon',
5602 'darkseagreen4',
5603 'deepskyblue2',
5604 'dodgerblue4',
5605 'gold3',
5606 'hotpink',
5607 'lawngreen',
5608 'lightcoral',
5609 'lightpink3',
5610 'lightseagreen',
5611 'lightslateblue',
5612 'mediumpurple',
5613 'olivedrab',
5614 'orangered1',
5615 'peru',
5616 'slategray',
5617 'yellow4',
5618 'springgreen2');
5619 static private $iNum = 33;
5620
5621 static function getColor() {
5622 if( ColorFactory::$iIdx >= ColorFactory::$iNum )
5623 ColorFactory::$iIdx = 0;
5624 return ColorFactory::$iColorList[ColorFactory::$iIdx++];
5625 }
5626
5627}
5628
5629// <EOF>
5630?>
Note: See TracBrowser for help on using the repository browser.