source: trunk/xgraph/jpgraph/jpgraph.php@ 42

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